diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..02bbfd96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +Binaries +DerivedDataCache +Intermediate +Saved +Windows +.vscode +.vs +*.VC.db +*.opensdf +*.opendb +*.sdf +*.sln +*.suo +*.xcodeproj +*.xcworkspace +*.idea \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e84276bf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Plugins/JsonAsAsset"] + path = Plugins/JsonAsAsset + url = https://github.com/Tectors/JsonAsAsset diff --git a/.vsconfig b/.vsconfig new file mode 100644 index 00000000..b9be5257 --- /dev/null +++ b/.vsconfig @@ -0,0 +1,17 @@ +{ + "version": "1.0", + "components": [ + "Microsoft.Net.Component.4.6.2.TargetingPack", + "Microsoft.VisualStudio.Component.VC.14.33.17.3.ARM64", + "Microsoft.VisualStudio.Component.VC.14.33.17.3.x86.x64", + "Microsoft.VisualStudio.Component.VC.Tools.ARM64", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "Microsoft.VisualStudio.Component.Windows10SDK", + "Microsoft.VisualStudio.Workload.CoreEditor", + "Microsoft.VisualStudio.Workload.ManagedDesktop", + "Microsoft.VisualStudio.Workload.NativeCrossPlat", + "Microsoft.VisualStudio.Workload.NativeDesktop", + "Microsoft.VisualStudio.Workload.NativeGame", + "Microsoft.VisualStudio.Workload.Universal" + ] +} diff --git a/Config/DefaultCrypto.ini b/Config/DefaultCrypto.ini new file mode 100644 index 00000000..78dff835 --- /dev/null +++ b/Config/DefaultCrypto.ini @@ -0,0 +1,11 @@ +[/Script/CryptoKeys.CryptoKeysSettings] +EncryptionKey= +bEncryptPakIniFiles=False +bEncryptPakIndex=False +bEncryptUAssetFiles=False +bEncryptAllAssetFiles=False +SigningPublicExponent=AQAB +SigningModulus=yV+GbdTlYWf1R2Pwk/NdRCKT+b8V3ICsil6IVmA3QDoGr/Q9iEaJ28oKk7LulsG5MGcLj0VAJyKtuDZSyIQoeNpSSP5axfMJGVQod1aIRk6w2vtTcFSJRUqTYUrzEKhGj9YsvnfqFd5A4jn2CZQ4tkfcFmOrYWxup/c3B91wwlKhywknYZMHXZOGNt1kfO0G644ZhE6RNKNlGxLZf0jH3FXkQyZ5WkCvr2EMWBP4OVoG8CVJqyRcYZlFlFgawpj9ZJTXMK4XPq2Xz6Z3+xYgrg13uGWw2TpRS+kh0GVkzcFJa7yKknownFjnAucnSOwRl2gyzbaQiuxO4AIEuzlBSStyaI03UcJ1/N5MLboL5vQw/YeYgChoZs/BWAne2jEI+cAtOTifIc4f8V1fVI5xDK7uu4FhWwXJQFDexeEuzW1IdWk7e/jFCsEFYMhw41lh6KR2S/DmzpnoYbxNrrYwVsXftIyA91hjpDGbqN+M2Y2SPYNAlbLg3WGuCV19uOiawIaB1E+0ezwAWxJDMqvKN3c3HdIGFGQkPC8kEk6dxNaX26pUYVL7j4E6vQ59eKyeiaWJLSltIfi6nS4CmtzVz2lqX5B1Eu8ImqhchFWMVpX++e3wokJ8J47yy7OMbRGsauQgKFWFo8MRIw7FzHUhlawzovEPcrpUrxdWrpKTK5g= +SigningPrivateExponent=scdZJBIQCa/JnU/dw+Pj/qNoq/QUyp+JxdlUxLKk9519yqSREPjv7MBSAUZzgSu7Fzy8RTzR70JaLkiJ3iNXCOWi+2jru5SyZH3EjcHlTtHhvL8VXFQjHvbGK5WHb2ktCsFge/KHTkxzOUGq2ECRRnGDh0UwqKAWs+glcsVLIZmH1ClS6E4qBgR2rNmEWrS3Mo9Q+p+kiSu6eadu/0RMlPzvghh9Ilor18QpVQDmqwC3a65x/ghbbpiwV2QBv91WPmuRsGGL/VCQL14NoYZfkHqcIspd2HUA+l0RXzuyssJjwP58vD1C7fptmTI894NFmSLR0A0sZNu9NX5hFUjrd5I4reKYevRrmwDVU9/UJKAXtnGHWtNinmFLm0AcAglKpYcOwVEDJPVExgw65q4fHROWxpU2LFEdkIpmyw4lg1bHb1N6g2hRJRPlxpOikkWvqkBeLu+XHzExTFAChX6bt+i3pOBwFacAAWQvUKZtpZamq07jVt37dbLoL0rcmKiw18ikuRCWFpyYPm52UOx+rDJf3CZ/T0ByOLogRaDbA1mliotrbI7IyUiiJemU+cj9sWVs2/7mVzb6XhGC13TG3bYbiHVXY9dXnWjWYMvirdxoN2KV/ZkT5vXFwjPzTwVlYQehtTw1Cz+lmUO6X8IPYdVUE4TP+mDJffoouNLW/3w= +bEnablePakSigning=True + diff --git a/Config/DefaultEditor.ini b/Config/DefaultEditor.ini new file mode 100644 index 00000000..8ab8a81c --- /dev/null +++ b/Config/DefaultEditor.ini @@ -0,0 +1,20 @@ +[/Script/Localization.LocalizationSettings] +-EngineTargetsSettings=(Name="Engine",Guid=33482D004789784C9DA695A682ACCA1B,TargetDependencies=,AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=True,SearchDirectories=((Path="Source/Runtime/"),(Path="Source/Developer/"),(Path="Config/")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*"),(Pattern="Source/Developer/NoRedist/UnrealEngineLauncherServices/*"),(Pattern="Source/Developer/NoRedist/BuildPatchServices/*")),FileExtensions=((Pattern="cpp"),(Pattern="h"),(Pattern="c"),(Pattern="inl"),(Pattern="mm"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=True,IncludePathWildcards=((Pattern="Content/*")),ExcludePathWildcards=((Pattern="Content/Editor/*"),(Pattern="Content/Tutorial/*"),(Pattern="Content/Developers/*"),(Pattern="Content/TestPackages/*"),(Pattern="Content/QA_Assets/*"),(Pattern="Content/Maps/Automation/*"),(Pattern="Content/EngineSounds/*")),FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=True),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-EngineTargetsSettings=(Name="Editor",Guid=AC8BFD2A41A2FB2893BB8EA0AF903E6D,TargetDependencies=(33482D004789784C9DA695A682ACCA1B),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=True,SearchDirectories=((Path="Source/Editor/")),ExcludePathWildcards=,FileExtensions=((Pattern="cpp"),(Pattern="h"),(Pattern="c"),(Pattern="inl"),(Pattern="mm"))),GatherFromPackages=(IsEnabled=True,IncludePathWildcards=((Pattern="Content/Editor/*"),(Pattern="Content/Editor*")),ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=True),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-EngineTargetsSettings=(Name="EditorTutorials",Guid=00F8E3AD47F0A73D50D46881C14DF28F,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=("IntroTutorials"),GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=True,IncludePathWildcards=((Pattern="Content/Tutorial/*")),ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=True),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-EngineTargetsSettings=(Name="PropertyNames",Guid=E391A8B149980E8154E056AF2DA49479,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=True),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="DisplayName"),TextNamespace="UObjectDisplayNames",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-EngineTargetsSettings=(Name="ToolTips",Guid=0F116534468918AEA432DD8C77703BA8,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=True),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="ToolTip"),TextNamespace="UObjectToolTips",TextKeyPattern=(Pattern="{FieldPath}")),(MetaDataKey=(Name="ShortToolTip"),TextNamespace="UObjectShortToolTips",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-EngineTargetsSettings=(Name="Keywords",Guid=AE89AECB47475F420D0D69A5547515DC,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=True),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="Keywords"),TextNamespace="UObjectKeywords",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-EngineTargetsSettings=(Name="Category",Guid=14B8DEE642A6A7AFEB5A28B959EC373A,TargetDependencies=,AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=False),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="Category"),TextNamespace="UObjectCategory",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="Engine",Guid=33482D004789784C9DA695A682ACCA1B,TargetDependencies=,AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=True,SearchDirectories=((Path="Source/Runtime/"),(Path="Source/Developer/"),(Path="Config/")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*"),(Pattern="Source/Developer/NoRedist/UnrealEngineLauncherServices/*"),(Pattern="Source/Developer/NoRedist/BuildPatchServices/*")),FileExtensions=((Pattern="cpp"),(Pattern="h"),(Pattern="c"),(Pattern="inl"),(Pattern="mm"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=True,IncludePathWildcards=((Pattern="Content/*")),ExcludePathWildcards=((Pattern="Content/Editor/*"),(Pattern="Content/Tutorial/*"),(Pattern="Content/Developers/*"),(Pattern="Content/TestPackages/*"),(Pattern="Content/QA_Assets/*"),(Pattern="Content/Maps/Automation/*"),(Pattern="Content/EngineSounds/*")),FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=True,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="Editor",Guid=AC8BFD2A41A2FB2893BB8EA0AF903E6D,TargetDependencies=(33482D004789784C9DA695A682ACCA1B),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=True,SearchDirectories=((Path="Source/Editor/")),ExcludePathWildcards=,FileExtensions=((Pattern="cpp"),(Pattern="h"),(Pattern="c"),(Pattern="inl"),(Pattern="mm")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=True,IncludePathWildcards=((Pattern="Content/Editor/*"),(Pattern="Content/Editor*")),ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=True,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="EditorTutorials",Guid=00F8E3AD47F0A73D50D46881C14DF28F,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=("IntroTutorials"),GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=True,IncludePathWildcards=((Pattern="Content/Tutorial/*")),ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=True,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="PropertyNames",Guid=E391A8B149980E8154E056AF2DA49479,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=True,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="DisplayName"),TextNamespace="UObjectDisplayNames",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="ToolTips",Guid=0F116534468918AEA432DD8C77703BA8,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=True,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="ToolTip"),TextNamespace="UObjectToolTips",TextKeyPattern=(Pattern="{FieldPath}")),(MetaDataKey=(Name="ShortToolTip"),TextNamespace="UObjectShortToolTips",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="Keywords",Guid=AE89AECB47475F420D0D69A5547515DC,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=True,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="Keywords"),TextNamespace="UObjectKeywords",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) ++EngineTargetsSettings=(Name="Category",Guid=14B8DEE642A6A7AFEB5A28B959EC373A,TargetDependencies=,AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=False,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=True,IncludePathWildcards=((Pattern="Source/Editor/*"),(Pattern="Source/Runtime/*"),(Pattern="Source/Developer/*")),ExcludePathWildcards=((Pattern="Source/Developer/NoRedist/CommunityPortalServices/*")),KeySpecifications=((MetaDataKey=(Name="Category"),TextNamespace="UObjectCategory",TextKeyPattern=(Pattern="{FieldPath}"))),ShouldGatherFromEditorOnlyData=True),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=0,SupportedCulturesStatistics=((CultureName="en"),(CultureName="es"),(CultureName="ja"),(CultureName="ko"),(CultureName="pt-BR"),(CultureName="zh-CN"))) +-GameTargetsSettings=(Name="Game",Guid=AE0EA34A45461A25BA65A391026F19F8,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini"))),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),ShouldGatherFromEditorOnlyData=False),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=False),NativeCultureIndex=-1,SupportedCulturesStatistics=((CultureName="en"))) ++GameTargetsSettings=(Name="Game",Guid=AE0EA34A45461A25BA65A391026F19F8,TargetDependencies=(33482D004789784C9DA695A682ACCA1B,AC8BFD2A41A2FB2893BB8EA0AF903E6D),AdditionalManifestDependencies=,RequiredModuleNames=,GatherFromTextFiles=(IsEnabled=False,SearchDirectories=,ExcludePathWildcards=,FileExtensions=((Pattern="h"),(Pattern="cpp"),(Pattern="ini")),ShouldGatherFromEditorOnlyData=False),GatherFromPackages=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,FileExtensions=((Pattern="umap"),(Pattern="uasset")),Collections=,ExcludeClasses=,ShouldExcludeDerivedClasses=False,ShouldGatherFromEditorOnlyData=False,SkipGatherCache=False),GatherFromMetaData=(IsEnabled=False,IncludePathWildcards=,ExcludePathWildcards=,KeySpecifications=,ShouldGatherFromEditorOnlyData=False),ExportSettings=(CollapseMode=IdenticalTextIdAndSource,ShouldPersistCommentsOnExport=False,ShouldAddSourceLocationsAsComments=True),CompileSettings=(SkipSourceCheck=False,ValidateFormatPatterns=True,ValidateSafeWhitespace=False),ImportDialogueSettings=(RawAudioPath=(Path=""),ImportedDialogueFolder="ImportedDialogue",bImportNativeAsSource=False),NativeCultureIndex=-1,SupportedCulturesStatistics=((CultureName="en"))) + +[/Script/AdvancedPreviewScene.SharedProfiles] + diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini new file mode 100644 index 00000000..50136872 --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,340 @@ +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/EngineSettings.GameMapsSettings] +EditorStartupMap=/Game/Athena/Athena_Terrain.Athena_Terrain +GameDefaultMap=/Game/Athena/Athena_Terrain.Athena_Terrain + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/FortniteGame") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/FortniteGame") ++ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="FortniteGameGameModeBase") + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +bUseAudioStreamCaching=True + +[AssetRegistry] +bSerializeDependencies=false +bUseAssetRegistryTagsWhitelistInsteadOfBlacklist=true +bFilterAssetDataWithNoTags=true +bFilterDependenciesWithNoTags=true + + + + +[/Script/Engine.PhysicsSettings] +DefaultGravityZ=-2800.000000 +DefaultTerminalVelocity=4000.000000 +DefaultFluidFriction=0.300000 +SimulateScratchMemorySize=262144 +RagdollAggregateThreshold=4 +TriangleMeshTriangleMinAreaThreshold=5.000000 +bEnableShapeSharing=False +bEnablePCM=False +bEnableStabilization=False +bWarnMissingLocks=True +bEnable2DPhysics=False +PhysicErrorCorrection=(PingExtrapolation=0.300000,PingLimit=100.000000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=0.010000,MaxRestoredStateError=2.000000,MaxLinearHardSnapDistance=400.000000,PositionLerp=0.000000,AngleLerp=0.020000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=20.000000,ErrorAccumulationSeconds=3.000000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) +DefaultDegreesOfFreedom=Full3D +BounceThresholdVelocity=200.000000 +FrictionCombineMode=Average +RestitutionCombineMode=Average +MaxAngularVelocity=3600.000000 +MaxDepenetrationVelocity=0.000000 +ContactOffsetMultiplier=0.010000 +MinContactOffset=0.000100 +MaxContactOffset=1.000000 +bSimulateSkeletalMeshOnDedicatedServer=False +DefaultShapeComplexity=CTF_UseSimpleAndComplex +bSuppressFaceRemapTable=True +bSupportUVFromHitResults=False +bDisableActiveActors=False +bDisableKinematicStaticPairs=False +bDisableKinematicKinematicPairs=False +bDisableCCD=False +bEnableEnhancedDeterminism=False +AnimPhysicsMinDeltaTime=0.000100 +bSimulateAnimPhysicsAfterReset=True +MaxPhysicsDeltaTime=0.050000 +bSubstepping=False +bSubsteppingAsync=False +MaxSubstepDeltaTime=0.050000 +MaxSubsteps=3 +SyncSceneSmoothingFactor=0.000000 +InitialAverageFrameRate=0.016667 +PhysXTreeRebuildRate=100 ++PhysicalSurfaces=(Type=SurfaceType1,Name="Wood") ++PhysicalSurfaces=(Type=SurfaceType2,Name="Stone") ++PhysicalSurfaces=(Type=SurfaceType3,Name="Metal") ++PhysicalSurfaces=(Type=SurfaceType4,Name="HumanFlesh") ++PhysicalSurfaces=(Type=SurfaceType5,Name="HumanOppositionFlesh") ++PhysicalSurfaces=(Type=SurfaceType6,Name="HuskFlesh") ++PhysicalSurfaces=(Type=SurfaceType7,Name="PurpleFlesh") ++PhysicalSurfaces=(Type=SurfaceType8,Name="Water") ++PhysicalSurfaces=(Type=SurfaceType9,Name="Grass") ++PhysicalSurfaces=(Type=SurfaceType10,Name="Dirt") ++PhysicalSurfaces=(Type=SurfaceType11,Name="Explosive") ++PhysicalSurfaces=(Type=SurfaceType12,Name="WeakSpot") ++PhysicalSurfaces=(Type=SurfaceType13,Name="Objective") ++PhysicalSurfaces=(Type=SurfaceType14,Name="WeakSpot_Wood") ++PhysicalSurfaces=(Type=SurfaceType15,Name="WeakSpot_Stone") ++PhysicalSurfaces=(Type=SurfaceType16,Name="WeakSpot_Metal") ++PhysicalSurfaces=(Type=SurfaceType17,Name="TakerFlesh") ++PhysicalSurfaces=(Type=SurfaceType18,Name="Glass") ++PhysicalSurfaces=(Type=SurfaceType19,Name="HuskFleshMetal") ++PhysicalSurfaces=(Type=SurfaceType20,Name="HuskFleshIce") ++PhysicalSurfaces=(Type=SurfaceType21,Name="HuskFleshElectric") ++PhysicalSurfaces=(Type=SurfaceType22,Name="HuskFleshFire") ++PhysicalSurfaces=(Type=SurfaceType23,Name="Snow") ++PhysicalSurfaces=(Type=SurfaceType24,Name="Ice") ++PhysicalSurfaces=(Type=SurfaceType25,Name="Lava") +DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,bUseMBPOuterBounds=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPOuterBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2) +ChaosSettings=(DefaultThreadingModel=TaskGraph,DedicatedThreadTickMode=VariableCappedWithTarget,DedicatedThreadBufferMode=Double) + +[/Script/UnrealEd.CookerSettings] +cook.AllowCookedDataInEditorBuilds=True +s.AllowUnversionedContentInEditor=1 +bCookOnTheFlyForLaunchOn=True + + + +[Audio] +UseAudioMixer=True + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Scalable +AppliedDefaultGraphicsPerformance=Scalable + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +!D3D12TargetedShaderFormats=ClearArray ++D3D12TargetedShaderFormats=PCD3D_SM6 + +[/Script/Engine.RendererSettings] +r.Mobile.ShadingPath=0 +r.Mobile.SupportGPUScene=False +r.Mobile.AntiAliasing=1 +r.Mobile.FloatPrecisionMode=0 +r.Mobile.AllowDitheredLODTransition=False +r.Mobile.VirtualTextures=False +r.DiscardUnusedQuality=False +r.Shaders.CompressionFormat=2 +r.AllowOcclusionQueries=True +r.MinScreenRadiusForLights=0.030000 +r.MinScreenRadiusForDepthPrepass=0.030000 +r.MinScreenRadiusForCSMDepth=0.010000 +r.PrecomputedVisibilityWarning=False +r.TextureStreaming=True +Compat.UseDXT5NormalMaps=False +r.VirtualTextures=False +r.VT.EnableAutoImport=True +r.VirtualTexturedLightmaps=False +r.VT.AnisotropicFiltering=False +bEnableVirtualTextureOpacityMask=False +r.VT.TileSize=128 +r.VT.TileBorderSize=4 +r.vt.FeedbackFactor=16 +WorkingColorSpaceChoice=sRGB +RedChromaticityCoordinate=(X=0.640000,Y=0.330000) +GreenChromaticityCoordinate=(X=0.300000,Y=0.600000) +BlueChromaticityCoordinate=(X=0.150000,Y=0.060000) +WhiteChromaticityCoordinate=(X=0.312700,Y=0.329000) +r.ClearCoatNormal=False +r.DynamicGlobalIlluminationMethod=1 +r.ReflectionMethod=1 +r.ReflectionCaptureResolution=128 +r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True +r.Lumen.HardwareRayTracing=False +r.Lumen.HardwareRayTracing.LightingMode=0 +r.Lumen.TranslucencyReflections.FrontLayer.EnableForProject=False +r.Lumen.TraceMeshSDFs=1 +r.Shadow.Virtual.Enable=1 +r.RayTracing=False +r.RayTracing.Shadows=False +r.RayTracing.Skylight=False +r.RayTracing.UseTextureLod=False +r.PathTracing=True +r.GenerateMeshDistanceFields=True +r.DistanceFields.DefaultVoxelDensity=0.200000 +r.Nanite.ProjectEnabled=True +r.AllowStaticLighting=True +r.NormalMapsForStaticLighting=False +r.ForwardShading=False +r.VertexFoggingForOpaque=True +r.SeparateTranslucency=True +r.TranslucentSortPolicy=0 +TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) +vr.VRS.HMDFixedFoveationLevel=0 +vr.VRS.HMDFixedFoveationDynamic=False +r.CustomDepth=1 +r.CustomDepthTemporalAAJitter=True +r.PostProcessing.PropagateAlpha=0 +r.DefaultFeature.Bloom=True +r.DefaultFeature.AmbientOcclusion=True +r.DefaultFeature.AmbientOcclusionStaticFraction=True +r.DefaultFeature.AutoExposure=False +r.DefaultFeature.AutoExposure.Method=0 +r.DefaultFeature.AutoExposure.Bias=1.000000 +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True +r.DefaultFeature.MotionBlur=False +r.DefaultFeature.LensFlare=False +r.TemporalAA.Upsampling=True +r.AntiAliasingMethod=0 +r.MSAACount=4 +r.DefaultFeature.LightUnits=1 +r.DefaultBackBufferPixelFormat=4 +r.Shadow.UnbuiltPreviewInGame=True +r.StencilForLODDither=False +r.EarlyZPass=3 +r.EarlyZPassOnlyMaterialMasking=False +r.Shadow.CSMCaching=False +r.DBuffer=True +r.ClearSceneMethod=1 +r.VelocityOutputPass=1 +r.Velocity.EnableVertexDeformation=2 +r.SelectiveBasePassOutputs=False +bDefaultParticleCutouts=False +fx.GPUSimulationTextureSizeX=1024 +fx.GPUSimulationTextureSizeY=1024 +r.AllowGlobalClipPlane=False +r.GBufferFormat=1 +r.MorphTarget.Mode=True +r.GPUCrashDebugging=False +vr.InstancedStereo=False +r.MobileHDR=True +vr.MobileMultiView=False +r.Mobile.UseHWsRGBEncoding=False +vr.RoundRobinOcclusion=False +r.MeshStreaming=False +r.HeterogeneousVolumes=False +r.WireframeCullThreshold=5.000000 +r.SupportStationarySkylight=True +r.SupportLowQualityLightmaps=True +r.SupportPointLightWholeSceneShadows=True +r.SupportSkyAtmosphere=True +r.SupportSkyAtmosphereAffectsHeightFog=True +r.SupportCloudShadowOnForwardLitTranslucent=False +r.Shadow.TranslucentPerObject.ProjectEnabled=False +r.Water.SingleLayerWater.SupportCloudShadow=False +r.Strata=False +r.Strata.BytesPerPixel=80 +r.Strata.OpaqueMaterialRoughRefraction=False +r.Strata.Debug.AdvancedVisualizationShaders=False +r.Material.RoughDiffuse=False +r.Material.EnergyConservation=False +r.OIT.SortedPixels=False +r.SkinCache.CompileShaders=False +r.SkinCache.SkipCompilingGPUSkinVF=False +r.SkinCache.DefaultBehavior=1 +r.SkinCache.SceneMemoryLimitInMB=128.000000 +r.Mobile.EnableStaticAndCSMShadowReceivers=True +r.Mobile.EnableMovableLightCSMShaderCulling=True +r.Mobile.Forward.EnableLocalLights=True +r.Mobile.Forward.EnableClusteredReflections=False +r.Mobile.EnableNoPrecomputedLightingCSMShader=False +r.Mobile.AllowDistanceFieldShadows=True +r.Mobile.AllowMovableDirectionalLights=True +r.Mobile.EnableMovableSpotlightsShadow=False +r.GPUSkin.Support16BitBoneIndex=False +r.GPUSkin.Limit2BoneInfluences=False +r.SupportDepthOnlyIndexBuffers=True +r.SupportReversedIndexBuffers=True +r.Mobile.AmbientOcclusion=False +r.GPUSkin.UnlimitedBoneInfluences=False +r.GPUSkin.UnlimitedBoneInfluencesThreshold=8 +MaxSkinBones=(Default=65536,PerPlatform=(("Mobile", 256))) +r.Mobile.PlanarReflectionMode=0 +r.Mobile.SupportsGen4TAA=True +bStreamSkeletalMeshLODs=(Default=False,PerPlatform=()) +bDiscardSkeletalMeshOptionalLODs=(Default=False,PerPlatform=()) +VisualizeCalibrationColorMaterialPath=/Engine/EngineMaterials/PPM_DefaultCalibrationColor.PPM_DefaultCalibrationColor +VisualizeCalibrationCustomMaterialPath=None +VisualizeCalibrationGrayscaleMaterialPath=/Engine/EngineMaterials/PPM_DefaultCalibrationGrayscale.PPM_DefaultCalibrationGrayscale + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=DA129A2A4F844A2C352B918E9FDCD27C +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + +[ConsoleVariables] + +r.StaticMesh.StripMinLodDataDuringCooking=1 +r.SkeletalMesh.StripMinLodDataDuringCooking=1 + +r.StaticMesh.KeepMobileMinLODSettingOnDesktop=1 +r.SkeletalMesh.KeepMobileMinLODSettingOnDesktop=1 + +r.SkinCache.AllowDupedVertsForRecomputeTangents=1 +r.DeformerGraph.Enable=0 +r.MeshDeformerMaxLOD=0 + +[SystemSettings] +r.VT.EnableCompressZlib=1 +r.VT.SyncProduceLockedTiles=0 + +r.AllowStaticLighting=False +r.Shadow.CacheWPOPrimitives=1 +r.Shadow.TransitionScale=1000 +r.Shadow.PerObjectDirectionalDepthBias=10 +r.Shadow.PerObjectDirectionalSlopeDepthBias=8 +r.Shadow.TexelsPerPixel=4 +r.Shadow.PerObjectCastDistanceMin = 4500.0 + + +r.CapsuleIndirectShadowMinVisibility=.5 +r.CapsuleIndirectConeAngle=.2 +r.AOSpecularOcclusionMode=0 +p.BatchPhysXTasksSize=8 +r.DoLazyStaticMeshUpdate=0 +FX.BatchAsync=1 +FX.BatchAsyncBatchSize=32 +fx.Niagara.RenderTarget.ReleaseResourceOnRemove=0 +fx.ParticleDefaultLightInverseExposureBlend=1.0 +grass.TickInterval=10 +r.Mobile.TonemapperFilm=1 + +r.DistanceFields.MaxPerMeshResolution=128 +r.DistanceFields.DefaultVoxelDensity=.1 + +r.Bloom.ApplyLocalExposure=true + +r.TSR.ShadingRejection.Flickering.Period=2 + +r.setres=1280x720 +r.MinYResolutionFor3DView=270 +D3D12.EnablePSOCache=0 + +r.D3D12.DXR.MinimumWindowsBuildVersion=18362 + +r.D3D12.DXR.MinimumDriverVersionNVIDIA=45000 + +r.StencilForLODDither=1 + +r.HLOD.ForceDisableCastDynamicShadow=1 + +r.gpucrash.collectionenable=1 +r.gpucrash.datadepth=5 + +[/Script/EngineSettings.GameMapsSettings] +EditorStartupMap=/Game/NewMap.NewMap +GameDefaultMap=/Game/NewMap.NewMap + + + diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini new file mode 100644 index 00000000..ae9f6074 --- /dev/null +++ b/Config/DefaultGame.ini @@ -0,0 +1,43 @@ + + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=0D60AEF640166774DADBD19E5F806578 +ProjectName=Fortnite +ProjectDisplayedTitle=NSLOCTEXT("", "FortniteMainWindowTitle", "Fortnite") +ProjectVersion=1.0.0 +CompanyName=Epic Games, Inc. +CompanyDistinguishedName="CN=Epic Games, O=Epic Games, L=Cary, S=North Carolina, C=US" +CopyrightNotice=Copyright Epic Games, Inc. All Rights Reserved. +bUseBorderlessWindow=True + + + +[/Script/Engine.AssetManagerSettings] +-PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown)) +-PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass=/Script/Engine.PrimaryAssetLabel,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass="/Script/Engine.World",bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass="/Script/Engine.PrimaryAssetLabel",bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="AthenaCharacter",AssetBaseClass="/Script/FortniteGame.AthenaCharacterItemDefinition",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Athena/Items/Cosmetics/Characters")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="Hero",AssetBaseClass="/Script/FortniteGame.FortHeroType",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Athena/Heroes")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="FortHeroSpecialization",AssetBaseClass="/Script/FortniteGame.FortHeroSpecialization",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=,SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="CustomCharacterPart",AssetBaseClass="/Script/FortniteGame.CustomCharacterPart",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Athena/Heroes/Meshes/Bodies"),(Path="/Game/Characters/CharacterParts/FaceAccessories"),(Path="/Game/Characters/CharacterParts/Female/Medium/Heads"),(Path="/Game/Characters/CharacterParts/Backpacks"),(Path="/Game/Characters/CharacterParts/Male/Medium/Bodies"),(Path="/Game/Characters/CharacterParts/Male/Medium/Heads"),(Path="/Game/Characters/CharacterParts/Hats"),(Path="/Game/Characters/CharacterParts/Male/Small/Bodies")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="AthenaMusicPack",AssetBaseClass="/Script/FortniteGame.AthenaMusicPackItemDefinition",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Athena/Items/Cosmetics/MusicPacks")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="AthenaDance",AssetBaseClass="/Script/FortniteGame.AthenaDanceItemDefinition",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Athena/Items/Cosmetics/Dances"),(Path="/Game/Zylox/Animations/Emotes/Items"),(Path="/Game/Athena/Items/Cosmetics/Toys"),(Path="/Game/Athena/Items/Cosmetics/Sprays")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="GameDataCosmetics",AssetBaseClass="/Script/FortniteGame.GameDataCosmetics",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Balance")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="SkeletalMesh",AssetBaseClass="/Script/Engine.SkeletalMesh",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=,SpecificAssets=(None),Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="GiftBox",AssetBaseClass="/Script/FortniteGame.FortGiftBoxItemDefinition",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Items/GiftBoxes")),SpecificAssets=,Rules=(Priority=20,ChunkId=10,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="ItemAccessToken",AssetBaseClass="/Script/FortniteGame.FortItemAccessTokenType",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Athena/Items/ItemAccessTokens")),SpecificAssets=,Rules=(Priority=20,ChunkId=10,bApplyRecursively=True,CookRule=AlwaysCook)) ++PrimaryAssetTypesToScan=(PrimaryAssetType="Weapon",AssetBaseClass="/Script/FortniteGame.FortWeaponItemDefinition",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Items/Weapons"),(Path="/Game/Abilities/Player"),(Path="/Game/Athena/Items/Weapons"),(Path="/Game/Athena/Items/Consumables"),(Path="/Game/Playgrounds/Items"),(Path="/Game/Athena/Items/Gameplay"),(Path="/Game/Athena/Items/HeldObjects")),SpecificAssets=,Rules=(Priority=20,ChunkId=10,bApplyRecursively=True,CookRule=AlwaysCook)) +bOnlyCookProductionAssets=False +bShouldManagerDetermineTypeAndName=False +bShouldGuessTypeAndNameInEditor=True +bShouldAcquireMissingChunksOnLoad=False +bShouldWarnAboutInvalidAssets=True +MetaDataTagsForAssetRegistry=() + +[/Script/UnrealEd.ProjectPackagingSettings] +bSharedMaterialNativeLibraries=False +bShareMaterialShaderCode=False +bGenerateChunks=True +bCookAll=True + diff --git a/Config/DefaultGameplayTags.ini b/Config/DefaultGameplayTags.ini new file mode 100644 index 00000000..1adc8f68 --- /dev/null +++ b/Config/DefaultGameplayTags.ini @@ -0,0 +1,14358 @@ +[/Script/GameplayTags.GameplayTagsSettings] +ImportTagsFromConfig=True +WarnOnInvalidTags=True +ClearInvalidTags=False +AllowEditorTagUnloading=True +AllowGameTagUnloading=False +FastReplication=True +InvalidTagCharacters="\"\'," ++CategoryRemapping=(BaseCategory="CurieElement",RemapCategories=("Curie.Element")) ++CategoryRemapping=(BaseCategory="CurieMaterial",RemapCategories=("Curie.Material")) ++CategoryRemapping=(BaseCategory="CurieState",RemapCategories=("Curie.State")) ++CategoryRemapping=(BaseCategory="CurieMaterialProperty",RemapCategories=("Curie.Property.Material")) ++CategoryRemapping=(BaseCategory="CurieDisable",RemapCategories=("Curie.Disable")) ++GameplayTagTableList=/Game/Balance/DataTables/GameplayTagTable.GameplayTagTable ++GameplayTagTableList=/Game/Balance/GameplayTags/ActionTags.ActionTags ++GameplayTagTableList=/Game/Balance/GameplayTags/TrapTags.TrapTags ++GameplayTagTableList=/Game/Balance/GameplayTags/NinjaTags.NinjaTags ++GameplayTagTableList=/Game/Balance/GameplayTags/CommandoTags.CommandoTags ++GameplayTagTableList=/Game/Balance/GameplayTags/ConstructorTags.ConstructorTags ++GameplayTagTableList=/Game/Balance/GameplayTags/HomeBaseTags.HomeBaseTags ++GameplayTagTableList=/Game/Balance/GameplayTags/OutlanderTags.OutlanderTags ++GameplayTagTableList=/Game/Balance/GameplayTags/StatusTags.StatusTags ++GameplayTagTableList=/Game/Balance/GameplayTags/NPCTags.NPCTags ++GameplayTagTableList=/Game/Balance/GameplayTags/SpecialEventTags.SpecialEventTags ++GameplayTagTableList=/Game/Balance/GameplayTags/GameplayCueTags.GameplayCueTags ++GameplayTagTableList=/Game/Balance/GameplayTags/HardcodedTags.HardcodedTags ++GameplayTagTableList=/Game/Balance/GameplayTags/PlayerCombatTags.PlayerCombatTags ++GameplayTagTableList=/Game/Balance/GameplayTags/WeaponTags.WeaponTags ++GameplayTagTableList=/Game/Balance/GameplayTags/GenericGadgetTags.GenericGadgetTags ++GameplayTagTableList=/Game/Balance/GameplayTags/UnlockTags.UnlockTags ++GameplayTagTableList=/Game/Balance/GameplayTags/ActivitiesTags.ActivitiesTags ++GameplayTagTableList=/Game/Balance/GameplayTags/PvPStrategicBuildingTags.PvPStrategicBuildingTags ++GameplayTagTableList=/Game/Balance/GameplayTags/RewardTags.RewardTags ++GameplayTagTableList=/Game/Balance/GameplayTags/AlterationTags.AlterationTags ++GameplayTagTableList=/Game/Balance/GameplayTags/TooltipTags.TooltipTags ++GameplayTagTableList=/Game/Balance/GameplayTags/StatTags.StatTags ++GameplayTagTableList=/Game/Balance/GameplayTags/QuestTags.QuestTags ++GameplayTagTableList=/Game/Balance/GameplayTags/PvPTags.PvPTags ++GameplayTagTableList=/Game/Balance/GameplayTags/GameplayModifierTags.GameplayModifierTags ++GameplayTagTableList=/Game/Balance/GameplayTags/EvolutionTags.EvolutionTags ++GameplayTagTableList=/Game/Balance/GameplayTags/ExpeditionTags.ExpeditionTags ++GameplayTagTableList=/Game/Balance/GameplayTags/BuildingTags.BuildingTags ++GameplayTagTableList=/Game/Balance/GameplayTags/HUDTags.HUDTags ++GameplayTagTableList=/Game/Balance/GameplayTags/StWHUDTags.StWHUDTags ++GameplayTagTableList=/Game/Balance/GameplayTags/PlaygroundTags.PlaygroundTags ++GameplayTagTableList=/Game/Athena/AthenaTags.AthenaTags ++GameplayTagTableList=/Game/Athena/HUD/AthenaTagsHUD.AthenaTagsHUD ++GameplayTagTableList=/Game/Balance/GameplayTags/LeaderboardGameplayTags.LeaderboardGameplayTags ++GameplayTagTableList=/Game/Balance/GameplayTags/MissionAlertVisualTags.MissionAlertVisualTags ++GameplayTagTableList=/Game/Balance/GameplayTags/AthenaLocationTags.AthenaLocationTags ++GameplayTagTableList=/Game/Athena/Prototype/Blueprints/Underground/ElementalAfflictionTags_Underground.ElementalAfflictionTags_Underground ++GameplayTagTableList=/Game/Balance/GameplayTags/VFXTags.VFXTags ++GameplayTagTableList=/Game/Conversation/ConversationTags.ConversationTags ++GameplayTagRedirects=(OldTagName="Missions.Placement.Floating",NewTagName="Activities.Placement.Floating") ++GameplayTagRedirects=(OldTagName="Missions.Placement.GateSimple",NewTagName="Activities.Placement.GateSimple") ++GameplayTagRedirects=(OldTagName="Missions.Placement.GateComplex",NewTagName="Activities.Placement.GateComplex") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Generic",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.HighElevation",NewTagName="Activities.Placement.HighElevation") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Inside",NewTagName="Activities.Placement.Indoor") ++GameplayTagRedirects=(OldTagName="Missions.Placement.LowElevation",NewTagName="Activities.Placement.LowElevation") ++GameplayTagRedirects=(OldTagName="Missions.Placement.LowEleveation",NewTagName="Activities.Placement.LowElevation") ++GameplayTagRedirects=(OldTagName="Missions.Placement.OnGround",NewTagName="Activities.Placement.OnGround") ++GameplayTagRedirects=(OldTagName="Missions.Placement.OnNonDestructible",NewTagName="Activities.Placement.OnNonDestructible") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Outside",NewTagName="Activities.Placement.Outside") ++GameplayTagRedirects=(OldTagName="Missions.Placement.PlayerSpawn",NewTagName="PlacementActor.PlayerSpawn") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.BUILD",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.CrateReturn",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.CrateStart",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.EnemySpawner",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.Powersource",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.RadarTower",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.Recover",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.Relic",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.Supply",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="Missions.Placement.Specific.TrappedHuman",NewTagName="Activities.Placement.Generic") ++GameplayTagRedirects=(OldTagName="building.container",NewTagName="Building.Type.Container") ++GameplayTagRedirects=(OldTagName="building.deco",NewTagName="Building.Type.Deco") ++GameplayTagRedirects=(OldTagName="building.prop",NewTagName="Building.Type.Prop") ++GameplayTagRedirects=(OldTagName="Building.wall",NewTagName="Building.Resource") ++GameplayTagRedirects=(OldTagName="Building.wall.metal",NewTagName="Building.Resource.Metal") ++GameplayTagRedirects=(OldTagName="Building.wall.stone",NewTagName="Building.Resource.Stone") ++GameplayTagRedirects=(OldTagName="Building.wall.wood",NewTagName="Building.Resource.Wood") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Bite",NewTagName="SoundLibrary.Irwin.Action.Bite") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.BodyFall",NewTagName="SoundLibrary.Irwin.Action.BodyFall") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Charge",NewTagName="SoundLibrary.Irwin.Action.Charge") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Charge.Recovery",NewTagName="SoundLibrary.Irwin.Action.Charge.Recovery") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Idle",NewTagName="SoundLibrary.Irwin.Action.Idle") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Idle.Break",NewTagName="SoundLibrary.Irwin.Action.Idle.Break") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Jump",NewTagName="SoundLibrary.Irwin.Action.Jump") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Land",NewTagName="SoundLibrary.Irwin.Action.Land") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Run",NewTagName="SoundLibrary.Irwin.Action.Run") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Run.Front",NewTagName="SoundLibrary.Irwin.Action.Run.Front") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Run.Rear",NewTagName="SoundLibrary.Irwin.Action.Run.Rear") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Scuff",NewTagName="SoundLibrary.Irwin.Action.Scuff") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Shake",NewTagName="SoundLibrary.Irwin.Action.Shake") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.ShakeDouble",NewTagName="SoundLibrary.Irwin.Action.ShakeDouble") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.ShakeFull",NewTagName="SoundLibrary.Irwin.Action.ShakeFull") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.ShakeSingle",NewTagName="SoundLibrary.Irwin.Action.ShakeSingle") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Sprint.Front",NewTagName="SoundLibrary.Irwin.Action.Sprint.Front") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Sprint.Rear",NewTagName="SoundLibrary.Irwin.Action.Sprint.Rear") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Step",NewTagName="SoundLibrary.Irwin.Action.Step") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Step.Front",NewTagName="SoundLibrary.Irwin.Action.Step.Front") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Step.Rear",NewTagName="SoundLibrary.Irwin.Action.Step.Rear") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Step.Slide",NewTagName="SoundLibrary.Irwin.Action.Step.Slide") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Swim",NewTagName="SoundLibrary.Irwin.Action.Swim") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Swim.Idle",NewTagName="SoundLibrary.Irwin.Action.Swim.Idle") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Swim.Jump",NewTagName="SoundLibrary.Irwin.Action.Swim.Jump") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Swim.Land",NewTagName="SoundLibrary.Irwin.Action.Swim.Land") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Swim.Sprint",NewTagName="SoundLibrary.Irwin.Action.Swim.Sprint") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Swim.TapSurface",NewTagName="SoundLibrary.Irwin.Action.Swim.TapSurface") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.Action.Turn",NewTagName="SoundLibrary.Irwin.Action.Turn") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Alert",NewTagName="SoundLibrary.Irwin.VO.Alert") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Attack.Bite",NewTagName="SoundLibrary.Irwin.VO.Attack.Bite") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Attack.Lunge",NewTagName="SoundLibrary.Irwin.VO.Attack.Lunge") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Bark",NewTagName="SoundLibrary.Irwin.VO.Bark") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Breath.Run",NewTagName="SoundLibrary.Irwin.VO.Breath.Run") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Breath.Sprint",NewTagName="SoundLibrary.Irwin.VO.Breath.Sprint") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Breath.Swim",NewTagName="SoundLibrary.Irwin.VO.Breath.Swim") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Breath.Walk",NewTagName="SoundLibrary.Irwin.VO.Breath.Walk") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Charge",NewTagName="SoundLibrary.Irwin.VO.Charge") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Chase",NewTagName="SoundLibrary.Irwin.VO.Chase") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Death",NewTagName="SoundLibrary.Irwin.VO.Death") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Eat",NewTagName="SoundLibrary.Irwin.VO.Eat") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Eat.Short",NewTagName="SoundLibrary.Irwin.VO.Eat.Short") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Howl",NewTagName="SoundLibrary.Irwin.VO.Howl") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Idle",NewTagName="SoundLibrary.Irwin.VO.Idle") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Idle.Ambient",NewTagName="SoundLibrary.Irwin.VO.Idle.Ambient") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Idle.Breath",NewTagName="SoundLibrary.Irwin.VO.Idle.Breath") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Idle.Content",NewTagName="SoundLibrary.Irwin.VO.Idle.Content") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Idle.short",NewTagName="SoundLibrary.Irwin.VO.Idle.short") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Idle_Break",NewTagName="SoundLibrary.Irwin.VO.Idle_Break") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Jump",NewTagName="SoundLibrary.Irwin.VO.Jump") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Land",NewTagName="SoundLibrary.Irwin.VO.Land") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Pain",NewTagName="SoundLibrary.Irwin.VO.Pain") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Pain.Heavy",NewTagName="SoundLibrary.Irwin.VO.Pain.Heavy") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Pain.Weak",NewTagName="SoundLibrary.Irwin.VO.Pain.Weak") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.PickUp",NewTagName="SoundLibrary.Irwin.VO.PickUp") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Shake",NewTagName="SoundLibrary.Irwin.VO.Shake") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Snort",NewTagName="SoundLibrary.Irwin.VO.Snort") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Spawn",NewTagName="SoundLibrary.Irwin.VO.Spawn") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Tamed",NewTagName="SoundLibrary.Irwin.VO.Tamed") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Thrown",NewTagName="SoundLibrary.Irwin.VO.Thrown") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Turn.180",NewTagName="SoundLibrary.Irwin.VO.Turn.180") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Turn.90",NewTagName="SoundLibrary.Irwin.VO.Turn.90") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Turn.Swim_90",NewTagName="SoundLibrary.Irwin.VO.Turn.Swim_90") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Turn.Swim_180",NewTagName="SoundLibrary.Irwin.VO.Turn.Swim_180") ++GameplayTagRedirects=(OldTagName="FoleySound.Fauna.VO.Worry",NewTagName="SoundLibrary.Irwin.VO.Worry") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Building",NewTagName="Stat.Mission.BluGloPylon.Building") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Health",NewTagName="Stat.Mission.BluGloPylon.Health") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Shield",NewTagName="Stat.Mission.BluGloPylon.Shield") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Speed",NewTagName="Stat.Mission.BluGloPylon.Speed") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Stamina",NewTagName="Stat.Mission.BluGloPylon.Stamina") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Difficulty",NewTagName="Stat.Mission.BluGloPylon.Difficulty") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Combat",NewTagName="Stat.Mission.BluGloPylon.Combat") ++GameplayTagRedirects=(OldTagName="Stat.Mission.Secondary.BluGloPylon_Harvest",NewTagName="Stat.Mission.BluGloPylon.Harvest") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Axe",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Club",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Harvest",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Scythe",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Spear",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Sword",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="Gameplay.Melee.EnableHeavyAttack.Tool",NewTagName="Gameplay.Melee.EnableHeavyAttack") ++GameplayTagRedirects=(OldTagName="NPC.Ability.Attack.Ranged.FlyingPrimary",NewTagName="NPC.Ability.Attack.Ranged.CheapFlyingPrimary") ++GameplayTagRedirects=(OldTagName="Cosmetics.MetaTags.CarabusFullFivea",NewTagName="Cosmetics.MetaTags.CarabusFullFive") ++GameplayTagRedirects=(OldTagName="Cosmetics.MetaTags.CarabusFullFoura",NewTagName="Cosmetics.MetaTags.CarabusFullFour") ++GameplayTagRedirects=(OldTagName="Cosmetics.MetaTags.CarabusFullSixa",NewTagName="Cosmetics.MetaTags.CarabusFullSix") ++GameplayTagRedirects=(OldTagName="Quests.S20.IOCard.Deliver",NewTagName="Athena.Quests.S20.IOCard.Deliver") ++GameplayTagRedirects=(OldTagName="Quests.S20.IOCard.Pickup",NewTagName="Athena.Quests.S20.IOCard.Pickup") ++GameplayTagRedirects=(OldTagName="Athena.Quests.VehicleDistanceWater",NewTagName="Athena.Quests.VehicleDistanceWaterOnLand") ++GameplayTagRedirects=(OldTagName="Gameplay.Status.Sand.Burrowed",NewTagName="Gameplay.Status.Blob.BlobState") ++GameplayTagRedirects=(OldTagName="Athena.ContextualTutorial.HiredNPC",NewTagName="Athena.ContextualTutorial.SquadComs") +NumBitsForContainerSize=7 +NetIndexFirstBitSegment=16 ++GameplayTagList=(Tag="Abilities.ArsenicCore.Cooldowns.Scream",DevComment="") ++GameplayTagList=(Tag="Abilities.AthenaSmokeBomb.Expired",DevComment="") ++GameplayTagList=(Tag="Abilities.AthenaSmokeBomb.Passive",DevComment="") ++GameplayTagList=(Tag="Abilities.Cobalt.Rift.Teleport",DevComment="") ++GameplayTagList=(Tag="Abilities.Commando.Perks.LeftyAndRighty.AbsolutelyStunning",DevComment="") ++GameplayTagList=(Tag="Abilities.Commando.Perks.LeftyAndRighty.HandCannons",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.Cooldowns.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.Cooldowns.MountedTurret",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.DamageResistance",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.Deactivate",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.Explosion",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.Hammer.Damage",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.ShieldBash.AnimationTrigger",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.ShieldBash.Damage",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.ShieldBlock",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.ShieldRaised",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.GoinConstructor.Weapon",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.OnCooldown.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.Perks.Decoy.Freeze",DevComment="Brain Freeze perk") ++GameplayTagList=(Tag="Abilities.Constructor.Perks.GetReadyForASurprise.T01",DevComment="") ++GameplayTagList=(Tag="Abilities.Constructor.Perks.GetReadyForASurprise.T02",DevComment="") ++GameplayTagList=(Tag="Abilities.Cooldowns.TpRiftTeleport",DevComment="Applied to units that have recently been teleported through a TpRift - IronCity specific") ++GameplayTagList=(Tag="Abilities.Creative.Dash.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Enrage.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Invisibility.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Invisibility.Active",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Invisibility.Ammo",DevComment="Tag corresponding with the ammo item for invisibility") ++GameplayTagList=(Tag="Abilities.Creative.Leap.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Rush.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.ShieldBubble.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Transform.Effect",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Transform.Invalid",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Transform.Invisible",DevComment="") ++GameplayTagList=(Tag="Abilities.Creative.Transform.Skin",DevComment="") ++GameplayTagList=(Tag="Abilities.DoubleJump.Active",DevComment="Tag given to the owner when double jumping with an ability") ++GameplayTagList=(Tag="Abilities.Drops.AddToActorLimit.CuddleFish",DevComment="") ++GameplayTagList=(Tag="Abilities.Drops.AddToActorLimit.ProxMine",DevComment="") ++GameplayTagList=(Tag="Abilities.Drops.AddToActorLimit.SneakySnowman",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.Escalation.Threshold.01",DevComment="Used to send Gameplay Events to players for escalation") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.Escalation.Threshold.DmgBuffApplied",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.Fatigue",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.Rage.Cooldown",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.Rage.HasRage",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperConstructor.Trap",DevComment="Tags constructor made traps so enemy resistance mode ignores them") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperHusks.Melee",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperHusks.Ranged",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperHusks.Ranged.Blaster",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperHusks.Tank",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperOutlander.AbilityDmgBuff.Active",DevComment="") ++GameplayTagList=(Tag="Abilities.GameplayModifiers.Phoenix.SuperOutlander.Cooldown",DevComment="Used to manage cool down for Outlander give fragment ability") ++GameplayTagList=(Tag="Abilities.Generic.ActiveAbility.ActivateSecondary",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.ActiveAbility.EndAbility",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.ActiveAbility.InputReleased",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Athena.Bush.Destroyed",DevComment="Fires when the bush\'s damage exceeds its max health") ++GameplayTagList=(Tag="Abilities.Generic.Athena.SpookyMist.Enter",DevComment="Event Tag - Spooky Mist effect entered by user.") ++GameplayTagList=(Tag="Abilities.Generic.Athena.SpookyMist.Exit.Forced",DevComment="Event Tag - Spooky Mist effect intentionally ended by user.") ++GameplayTagList=(Tag="Abilities.Generic.Athena.SpookyMist.Exit.TimeOut",DevComment="Event Tag- Spooky Mist effect duration expired") ++GameplayTagList=(Tag="Abilities.Generic.Athena.ZipLine.Enter",DevComment="Triggers when a player enters a zip line") ++GameplayTagList=(Tag="Abilities.Generic.Athena.ZipLine.Exit",DevComment="Triggers when player exits the zip line") ++GameplayTagList=(Tag="Abilities.Generic.Athena.ZipLine.TriggerA",DevComment="Trigger ZipLine Movement to point A") ++GameplayTagList=(Tag="Abilities.Generic.Athena.ZipLine.TriggerB",DevComment="Trigger movement to Zip Line point B") ++GameplayTagList=(Tag="Abilities.Generic.Athena.ZipLine.ZiplineSmashImmune",DevComment="Do not smash/break structures with this tag that might be just a bit too close (for LDs)") ++GameplayTagList=(Tag="Abilities.Generic.BadNews.Immune",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Boombox.Activate",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Boombox.Immune",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.BotTurret.Reload",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.CallBackup",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Damage.Immune",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.DangerGrape",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.DangerGrape.Spawned",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.DangerGrape.Unspawned",DevComment="") ++GameplayTagList=(Tag="Abilities.generic.GenericProjectile.Explode.Additional",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Indicator.Marked",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Indicator.Marked.FlareGun",DevComment="Applied to marked targets by the Flare Gun.") ++GameplayTagList=(Tag="Abilities.Generic.Rift.NoSound.Trigger",DevComment="Trigger tag to activate Rift Ability without sound.") ++GameplayTagList=(Tag="Abilities.Generic.Rift.Trigger",DevComment="Trigger tag to activate Rift Ability.") ++GameplayTagList=(Tag="Abilities.Generic.Snowman",DevComment="Tag to track number of active Snowmen") ++GameplayTagList=(Tag="Abilities.Generic.Snowman.Block",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Snowman.Hide",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Snowman.Steal",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.TargetDummy.Spawn",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Vent.Enter",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Vent.Exit",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Vent.RecentlyLaunched",DevComment="Tag applied to an actor when it was recently launched by a vent. Used to prevent vents from re-launching an actor immediately after it has already been launched (Early relaunching can easily lead to a massive amount of force being added causing an actor to go out of bounds).") ++GameplayTagList=(Tag="Abilities.Generic.Vent.Update",DevComment="") ++GameplayTagList=(Tag="Abilities.Generic.Vortex.Enter",DevComment="This is applied when players enter the vortex.") ++GameplayTagList=(Tag="Abilities.Generic.Vortex.Exit",DevComment="Applied when leaving the vortex") ++GameplayTagList=(Tag="Abilities.Generic.Vortex.Update",DevComment="") ++GameplayTagList=(Tag="Abilities.Grapple.Hit",DevComment="") ++GameplayTagList=(Tag="Abilities.Grapple.HoverboardMontage",DevComment="") ++GameplayTagList=(Tag="Abilities.Grapple.NoShoot",DevComment="") ++GameplayTagList=(Tag="Abilities.Grapple.Return",DevComment="") ++GameplayTagList=(Tag="Abilities.Grapple.RopeActive",DevComment="") ++GameplayTagList=(Tag="Abilities.GungamePack.AmmoPassive",DevComment="") ++GameplayTagList=(Tag="Abilities.GungamePack.StackCountTag",DevComment="") ++GameplayTagList=(Tag="Abilities.Irwin.StartupDebuff",DevComment="") ++GameplayTagList=(Tag="Abilities.Irwin.Wolf.AoE",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.KunaiStorm.KunaiBarrageTargeting",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.KunaiStorm.KunaiProjectileTargeting",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.Perks.DebilitatingBite.Cooldown",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.Perks.DebilitatingBite.Mark",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.Perks.DebilitatingBite.T02",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.Perks.DebilitatingBite.Trigger",DevComment="") ++GameplayTagList=(Tag="Abilities.Ninja.Perks.DebilitatingBite.Vulnerability",DevComment="") ++GameplayTagList=(Tag="Abilities.Oddball.Tagged",DevComment="") ++GameplayTagList=(Tag="Abilities.Outlander.GroundWave.Target",DevComment="") ++GameplayTagList=(Tag="Abilities.Outlander.Perks.VitalBond.Pet",DevComment="") ++GameplayTagList=(Tag="Abilities.Outlander.Perks.VitalBond.T01",DevComment="") ++GameplayTagList=(Tag="Abilities.Outlander.Perks.VitalBond.T02",DevComment="") ++GameplayTagList=(Tag="Abilities.PhoneBooth.Cooldown",DevComment="") ++GameplayTagList=(Tag="Abilities.PhoneBooth.RemoveDisguise",DevComment="") ++GameplayTagList=(Tag="Abilities.s",DevComment="") ++GameplayTagList=(Tag="Abilities.Traps.Floor.Tar.Snare",DevComment="") ++GameplayTagList=(Tag="Abilities.Vendetta.Cooldowns.MarkTarget",DevComment="") ++GameplayTagList=(Tag="Abilities.Vendetta.DeactivatePuck",DevComment="Ability Tag for the vendetta bounty puck ability that deactivates the puck") ++GameplayTagList=(Tag="Abilities.Vendetta.MarkTarget",DevComment="") ++GameplayTagList=(Tag="Abilities.Vendetta.MarkTarget.Active",DevComment="") ++GameplayTagList=(Tag="Abilities.Weapons.Melee.Combo.Sword.Crescentstrike.Available",DevComment="") ++GameplayTagList=(Tag="Abilities.Weapons.Ranged.HomingProjectile.FinalLock",DevComment="") ++GameplayTagList=(Tag="Abilities.Weapons.Ranged.HomingProjectile.InitialLock",DevComment="") ++GameplayTagList=(Tag="Ability.AIMovement.Allow",DevComment="") ++GameplayTagList=(Tag="Ability.AIMovement.Leap",DevComment="Use for motion warping leap to know when we touch the ground") ++GameplayTagList=(Tag="Ability.Ashton.Chicago.DeepDish.Defense.Defending",DevComment="") ++GameplayTagList=(Tag="Ability.Ashton.Chicago.DeepDish.Out",DevComment="") ++GameplayTagList=(Tag="Ability.Ashton.Indigo.Equipped",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.Crafting.PlayerCrafting",DevComment="Crafting tag for player crating Ability. Needs to be in the main game so that everything has access to the tag.") ++GameplayTagList=(Tag="Ability.Athena.HopRocks.Jump",DevComment="Appleis to Low Grav ability triggers on jump event") ++GameplayTagList=(Tag="Ability.Athena.Item.Ashton.Hippo.Altfire",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.Item.Ashton.Turbo.Slam",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.Item.DogSweater.AllowDamage",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.Item.DogSweater.BlockDamage",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.Item.DogSweater.DeliveryBucket",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.Item.ShadowBomb",DevComment="") ++GameplayTagList=(Tag="Ability.Athena.ZeroPoint.AbilityActive",DevComment="") ++GameplayTagList=(Tag="Ability.BlingPack.Equipped",DevComment="") ++GameplayTagList=(Tag="Ability.CancelOnImpulse",DevComment="") ++GameplayTagList=(Tag="Ability.Commando.FragGrenade",DevComment="") ++GameplayTagList=(Tag="Ability.Commando.GoinCommando",DevComment="") ++GameplayTagList=(Tag="Ability.Commando.GoinCommando.Weapon",DevComment="") ++GameplayTagList=(Tag="Ability.Commando.LeftyAndRighty",DevComment="") ++GameplayTagList=(Tag="Ability.Commando.Shockwave",DevComment="") ++GameplayTagList=(Tag="Ability.Commando.WarCry",DevComment="") ++GameplayTagList=(Tag="Ability.Constructor.BASE",DevComment="") ++GameplayTagList=(Tag="Ability.Constructor.BullRush",DevComment="") ++GameplayTagList=(Tag="Ability.Constructor.Decoy",DevComment="") ++GameplayTagList=(Tag="Ability.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Ability.Constructor.MountedTurret",DevComment="Constructor ROSIE ability") ++GameplayTagList=(Tag="Ability.Constructor.MountedTurret.KnockBack",DevComment="") ++GameplayTagList=(Tag="Ability.Constructor.PlasmaPulse",DevComment="") ++GameplayTagList=(Tag="Ability.Converted.Teleport",DevComment="") ++GameplayTagList=(Tag="Ability.Creative.PowerTrip.Equipped",DevComment="") ++GameplayTagList=(Tag="Ability.Creative.ScoreManager.HUD",DevComment="") ++GameplayTagList=(Tag="Ability.DamageSource.AOE.Line",DevComment="Indicates that the ability deals damage in a Line") ++GameplayTagList=(Tag="Ability.DamageSource.AOE.PointBlank",DevComment="Indicates that the ability deals damage in the area directly surrounding the user.") ++GameplayTagList=(Tag="Ability.DamageSource.AOE.Shape",DevComment="Indicates that the ability deals damage in a shape, such as a sphere or a triangle.") ++GameplayTagList=(Tag="Ability.DamageSource.Melee",DevComment="Indicates that damage from the Ability is considered Melee") ++GameplayTagList=(Tag="Ability.DamageSource.Ranged",DevComment="Indicates that damage from the Ability is considered Ranged.") ++GameplayTagList=(Tag="Ability.DirectionalLaunch.Applied",DevComment="") ++GameplayTagList=(Tag="Ability.DirectionalLaunch.AppliedTemp",DevComment="") ++GameplayTagList=(Tag="Ability.DMR.Fire",DevComment="") ++GameplayTagList=(Tag="Ability.FollowTheLeader",DevComment="") ++GameplayTagList=(Tag="Ability.FriendBot.DisallowListLoot",DevComment="") ++GameplayTagList=(Tag="Ability.Galileo.Lobster.Dodge",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Axe",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Club",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Hardware",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Impact",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Scythe",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Spear",DevComment="") ++GameplayTagList=(Tag="Ability.HeavyAttack.Melee.Sword",DevComment="") ++GameplayTagList=(Tag="Ability.InputAction.Jump",DevComment="") ++GameplayTagList=(Tag="Ability.InputAction.Sprint",DevComment="") ++GameplayTagList=(Tag="Ability.Interrogation.Reaction.Stop",DevComment="") ++GameplayTagList=(Tag="Ability.Interrogation.Release",DevComment="") ++GameplayTagList=(Tag="Ability.Interrogation.Reveal.Immunity",DevComment="Gives a pawn immunity to being revealed to an enemy when a teammate is interrogated by an enemy.") ++GameplayTagList=(Tag="Ability.Interrogation.Reveal.Trigger",DevComment="") ++GameplayTagList=(Tag="Ability.Interrogation.Start",DevComment="") ++GameplayTagList=(Tag="Ability.Interrogation.Stop",DevComment="") ++GameplayTagList=(Tag="Ability.Interrupt.Hightower.MangoCosmetic",DevComment="") ++GameplayTagList=(Tag="Ability.Jetpack.HasMaxReserveFuel",DevComment="") ++GameplayTagList=(Tag="Ability.Mantis",DevComment="") ++GameplayTagList=(Tag="Ability.Mayday.VehicleHealthRegen",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.Basic",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.Block",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.BlockBreak",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.Dodge",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.InAirAttack",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.Parry",DevComment="") ++GameplayTagList=(Tag="Ability.Melee.PreventBlock",DevComment="Add Tag to Prevent Players from blocking") ++GameplayTagList=(Tag="Ability.Melee.PreventSecondary",DevComment="Add tag tp prevent players from charging or using their secondary") ++GameplayTagList=(Tag="Ability.MeleeMantis",DevComment="") ++GameplayTagList=(Tag="Ability.Ninja.CrescentKick",DevComment="") ++GameplayTagList=(Tag="Ability.Ninja.DragonSlash",DevComment="") ++GameplayTagList=(Tag="Ability.Ninja.KunaiStorm",DevComment="") ++GameplayTagList=(Tag="Ability.Ninja.MantisLeap",DevComment="Is the Mantis Leap Ability") ++GameplayTagList=(Tag="Ability.Ninja.SmokeBomb",DevComment="") ++GameplayTagList=(Tag="Ability.Ninja.ThrowingStars",DevComment="") ++GameplayTagList=(Tag="Ability.Ostrich.KickStomp",DevComment="") ++GameplayTagList=(Tag="Ability.Outlander.Bear",DevComment="") ++GameplayTagList=(Tag="Ability.Outlander.GroundWave",DevComment="") ++GameplayTagList=(Tag="Ability.Outlander.GroundWave.Wave",DevComment="") ++GameplayTagList=(Tag="Ability.Outlander.PhaseShift",DevComment="") ++GameplayTagList=(Tag="Ability.Outlander.ShockTower",DevComment="") ++GameplayTagList=(Tag="Ability.Passive.SurfaceChange",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.AllTogetherNow",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Assassination",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Bearricade",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.BearTogether",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.BlakebeardsStash.Cannonball.Activate",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.BlakebeardsStash.Cannonball.Deactivate",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.BouquetOfROSIES",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.BringinDownTheHouse",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Buckle",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.CantStop",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Cascade",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ClusterBomb",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Commando.StayFrosty",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Commando.SuppressiveFire",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Constructor.KineticOverload",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.CoolCustomer",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.CorrosiveStars",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.CorrosiveStrikes",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.CrystalDaggers",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.DangerDanger",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.DevastatingDuet",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.DonatingPlasma",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.EggsplosiveEntrance",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Eggstermination",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ElectricAttraction",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.EmergencyOverride",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.EnduringMachine",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.EscapeArtist",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ExplosiveRounds",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.FanOfStars",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.FireOfTheDragon",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.FourOnTheFloor",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.FromTheDepths.T01",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.FromTheDepths.T02",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Fumblerooski",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.GetReadyForASurprise",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.GrenadeGeneration",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.HangTime",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.HeartyStrikes",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.HolidayCheer",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.HuntersInstinct",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.IcyShot",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.InAndOutlander",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.KeepOut",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.KickEmWhenTheyreDown",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.KunaiCollection",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Kunaihilation",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.LoneDragon",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.LonelyBear",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.LowOrdinance",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.MadTidings",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.MakeItRain",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.MonsterSmash.T01",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.MonsterSmash.T02",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.MoralBoost",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.NeverTellMeTheOdds",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.NightStalker",DevComment="The Night Stalker Ability") ++GameplayTagList=(Tag="Ability.Perk.Ninja.MantisLeap",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Ninja.ShadowStance",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.OneTwoPunch",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Outlander.AntiMaterialCharge",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Outlander.InTheZone",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Outlander.InTheZone.KeenEyes",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.PartingGift",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.PhaseForward",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.PlasmaArc",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.PokingHoles",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.RogueWave",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.RunningWithTheBulls",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SaurianClaws",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SaurianFocus",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SaurianHide",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SaurianMight",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ScytheToMeetYou",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SecondWind",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SetPhasersToKill",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShiftingGears",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShockinglyAlone",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShootyMcShootsAlot",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShortFuse",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShortFuse.Reset",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShortFuse.SCRRemoval",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ShredTheGnar",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SlowReleaseBombs",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SlowYourRoll",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SnowinCommando",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SoloImpact",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SoloStarPower",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SpaceTechnology",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Stormborne",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.StormyWeather",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.SuperchargedTraps",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Swash",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.TailOfTheDragon",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.Twinblast",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.UneggspectedDeparture",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.UpInSmoke",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ViolentInspiration",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.WheresLefty",DevComment="") ++GameplayTagList=(Tag="Ability.Perk.ZipAndZap",DevComment="") ++GameplayTagList=(Tag="Ability.Player.Dudebro.BlockAbilities",DevComment="Blocks Abilities associated with being close to the Dude Bro boss") ++GameplayTagList=(Tag="Ability.Player.Slide",DevComment="") ++GameplayTagList=(Tag="Ability.Quest.SignalBooster",DevComment="") ++GameplayTagList=(Tag="Ability.SeizePack.Equipped",DevComment="") ++GameplayTagList=(Tag="Ability.Trigger.Hightower.MangoCosmetic",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Charge",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Dash",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Dash.End",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Dash.PredictEnd",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Dash.Start",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Float",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Float.Falling",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Float.FallWarning",DevComment="") ++GameplayTagList=(Tag="Ability.Uncle.Passive",DevComment="") ++GameplayTagList=(Tag="AbilityWeapon",DevComment="Tag to indicate a weapon was granted by an ability and is not a traditional weapon.") ++GameplayTagList=(Tag="AbilityWeapon.Commando.GoinCommando",DevComment="") ++GameplayTagList=(Tag="AbilityWeapon.Commando.LeftyAndRighty",DevComment="") ++GameplayTagList=(Tag="AbilityWeapon.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="AbilityWeapon.Constructor.MountedTurret",DevComment="Defines the mounted Turret Ability Weapon") ++GameplayTagList=(Tag="AbilityWeapon.Melee",DevComment="Tag to identify ability weapons as melee.") ++GameplayTagList=(Tag="AbilityWeapon.Ranged",DevComment="Tag to identify ability weapons as ranged.") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.AssaultExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.ExplosiveExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.FirstHealedToFull",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.MiniCoinCombo",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.PickaxeExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.PickaxeSpecialist",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.PistolExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.Scavenger.Gold",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.Scavenger.Silver",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.ShotgunExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.SMGExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.AdditionalStatEvent.SniperExpert",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.Bodyguard.Elim.VIP",DevComment="LTM Bodyguard Eliminate a VIP XP") ++GameplayTagList=(Tag="Accolade.Metadata.Bodyguard.Elim.VIPMulti",DevComment="LTM Bodyguard Team XP bonus for 7 VIP Eliminations during the single match") ++GameplayTagList=(Tag="Accolade.Metadata.Bodyguard.Revive.Bodyguard",DevComment="LTM Bodyguard XP for when a VIP revives a Bodyguard") ++GameplayTagList=(Tag="Accolade.Metadata.Bodyguard.Revive.VIP",DevComment="LTM Bodyguard XP given when a Bodyguard revives the VIP") ++GameplayTagList=(Tag="Accolade.Metadata.Bodyguard.VIP.Survive",DevComment="LTM bodyguard given to the VIP who survives to the end of the match") ++GameplayTagList=(Tag="Accolade.Metadata.Daybreak.DestroyChoppa",DevComment="LTM Daybreak XP Accoalde given when a Player Destroys an Escaping Choppa") ++GameplayTagList=(Tag="Accolade.Metadata.Daybreak.Escape",DevComment="LTM Daybreak XP Accolade given when the Player Escapes the Island in a Choppa (Can only be earned once per game session.)") ++GameplayTagList=(Tag="Accolade.Metadata.Daybreak.Party",DevComment="LTM Daybreak XP given when the Player Partys up with another Player for the first time each game.") ++GameplayTagList=(Tag="Accolade.Metadata.Daybreak.Quest",DevComment="LTM Daybreak XP Accolades given when a ") ++GameplayTagList=(Tag="Accolade.Metadata.Daybreak.Repair",DevComment="LTM Daybreak XP Accolade for when a player installes 1 part of the Choppa.") ++GameplayTagList=(Tag="Accolade.Metadata.Goose.Elim.Plane",DevComment="Goose LTM XP given to team when eliminate enemy plane") ++GameplayTagList=(Tag="Accolade.Metadata.Goose.Elim.Player",DevComment="LTM Goose XP given to team when enemy player is eliminated") ++GameplayTagList=(Tag="Accolade.Metadata.Nitrogen.1StarDelivery",DevComment="The Player completes a 1 Star Delivery in the Nitrogen LTM") ++GameplayTagList=(Tag="Accolade.Metadata.Nitrogen.2StarDelivery",DevComment="The Player completes a 2 Star Delivery in the Nitrogen LTM") ++GameplayTagList=(Tag="Accolade.Metadata.Nitrogen.3StarDelivery",DevComment="The Player completes a 3 Star Delivery in the Nitrogen LTM") ++GameplayTagList=(Tag="Accolade.Metadata.Nitrogen.4StarDelivery",DevComment="The Player completes a 4 Star Delivery in the Nitrogen LTM") ++GameplayTagList=(Tag="Accolade.Metadata.Nitrogen.5StarDelivery",DevComment="The Player completes a 5 Star Delivery in the Nitrogen LTM") ++GameplayTagList=(Tag="Accolade.Metadata.Nitrogen.StarCoinPickUp",DevComment="The Player Picked Up a Star Coin in the Nitrogen LTM") ++GameplayTagList=(Tag="Accolade.Metadata.SecondaryXp.Faction",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.Survival.Silver",DevComment="") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Assassin",DevComment="LTM Vendetta Eliminate someone who has you as their Bounty Target") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Early",DevComment="LTM Vendetta Eliminate Target in the Early part of the game (Usually First Storm Phase)") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Hunter",DevComment="LTM Vendetta Eliminate Hunter Bot") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Hunter.Multi",DevComment="LTM Vendetta Eliminate X Hunter Bots") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Leader",DevComment="LTM Vendetta Eliminate the Top Player") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Mando",DevComment="LTM Vendetta Eliminate The Mandalorian Bot") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Target",DevComment="LTM Vendetta Eliminate Target") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Target.Max.Bounty",DevComment="LTM Vendetta Eliminate a Target that is at Maximum Bounty Bonus") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Target.Multi",DevComment="LTM Vendetta Eliminate X Targets") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Elim.Top.Three",DevComment="LTM Vendetta Eliminate a Top 3 Player") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Hunter.Weapon.Pick.Up",DevComment="LTM Vendetta Pick Up a Hunter Bot\'s Weapon") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Leader",DevComment="LTM Vendetta Earn This XP Accolade if you are number 1 when the Mandalorian Bot arrives") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Puck.Target.Friendly",DevComment="LTM Vendetta Pick up a Bounty Puck that has you or an ally as the target") ++GameplayTagList=(Tag="Accolade.Metadata.Vendetta.Puck.Target.Hunter",DevComment="LTM Vendetta Have a Hunter Bot as a Bounty Target") ++GameplayTagList=(Tag="Activities.Actor.DefenseObject",DevComment="") ++GameplayTagList=(Tag="Activities.ConfigData.TwineMythic",DevComment="") ++GameplayTagList=(Tag="Activities.DynamicPlacement.Actor.DropPoints",DevComment="") ++GameplayTagList=(Tag="Activities.DynamicPlacement.Actor.EndlessBluGloDispenser",DevComment="") ++GameplayTagList=(Tag="Activities.DynamicPlacement.Actor.EndlessHeater",DevComment="") ++GameplayTagList=(Tag="Activities.DynamicPlacement.Actor.IceBreaker",DevComment="") ++GameplayTagList=(Tag="Activities.DynamicPlacement.Actor.PlayerHoldingArea",DevComment="") ++GameplayTagList=(Tag="Activities.ObjectiveHandle.Generic.DoorAndKey",DevComment="") ++GameplayTagList=(Tag="Activities.ObjectiveHandle.Generic.MissionComplete",DevComment="") ++GameplayTagList=(Tag="Activities.ObjectiveHandle.Generic.ProgressBar",DevComment="") ++GameplayTagList=(Tag="Activities.ObjectiveHandle.IronCity.TextDisplay",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.Demeter.Navigation",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.IronCity.NearDefensePoint",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.Phoenix.VendingMachine",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.Phoenix.Workbench",DevComment="Used to place Phoenix Workbenches") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations",DevComment="Specific locations for quest objectives - used with season and QuestGenericAlias tags") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.01",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.02",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.03",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.04",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.05",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.06",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.07",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.QuestLocations.08",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.Starlight.BossCoil",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.Starlight.IncubationChamber",DevComment="") ++GameplayTagList=(Tag="Activities.Placement.TeleportTarget",DevComment="") ++GameplayTagList=(Tag="Activities.QuestGenericAlias",DevComment="Used with season tag to specify a quest") ++GameplayTagList=(Tag="Activities.QuestGenericAlias.QuestA",DevComment="") ++GameplayTagList=(Tag="Activities.QuestGenericAlias.QuestB",DevComment="") ++GameplayTagList=(Tag="Activities.QuestGenericAlias.QuestC",DevComment="") ++GameplayTagList=(Tag="Activities.QuestGenericAlias.QuestD",DevComment="") ++GameplayTagList=(Tag="Activities.QuestGenericAlias.QuestE",DevComment="") ++GameplayTagList=(Tag="Activities.QuestGenericAlias.QuestF",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Encounters.SidewaysEncounter",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Encounters.SidewaysEncounterInstant",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.AwardThanks",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.HB",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Silkwalker",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.SilkWalkerAcolyteLonely",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BasicCar.Base",DevComment="Spawner Data Tag for Valet Basic Car ") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BasicCar.IO",DevComment="LWM SpawnerData Tag for IO Sedan") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BasicCar.Taxi",DevComment="Spawner Data Tag for Valet Taxi") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BasicCar.Upgraded",DevComment="Spawner Data tag for Upgraded Valet Basic Car") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BasicTruck.Base",DevComment="Spawner Data tag for Basic Truck Valet Vehicle") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BasicTruck.Upgraded",DevComment="Spawner Data tag for Upgraded Valet Basic Truck Vehicle") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BigRig.Base",DevComment="Spawner Data Tag for Base Big Rig Valet Vehicle") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BigRig.IO",DevComment="LWM Spawn Actor Data Tag for IO BigRig") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.BigRig.Upgraded",DevComment="Spawner Data tag for Upgraded Valet Big Rig") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.SportsCar.Base",DevComment="Spawner Data tag for Sports Car Valet Vehicle") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Vehicle.Valet.SportsCar.Upgraded",DevComment="Spawner Data Tag for Upgraded Valet Sports Car") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Weather.Lightning",DevComment="") ++GameplayTagList=(Tag="ActorSpawnerData.Type.Weather.Tornado",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Category.Water",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.AwardThanks",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Cosmos",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Emblem",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Freaky",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Habitat",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.HabitatCreature",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Inner",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.MotherGround",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Nightmare",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Outter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Realm",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Ruckus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Slone",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.SloneClone",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.SloneResolve",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.SmallBag",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.SuperCosmos",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.TameFoot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.ThunderMeow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.ThunderMeowLight",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.TinyBalloon",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Boss.Willow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Buddy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ButterCake",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ButterCakeA",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ButterCakeB",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ButterCakeC",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ButterCakeD",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ButterCakeS",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.DangerGrape.Base",DevComment="Tag used as descriptor tag for AISpawnerData_DangerGrape") ++GameplayTagList=(Tag="AISpawnerData.Type.DangerGrape.NoStartingWeapon",DevComment="Tag used as descriptor tag for AISpawnerData_DangerGrape_NoStartingWeapons") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Brute.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Brute.Exploding",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Brute.Major",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Brute.Mega",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Fiend.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Fiend.Major",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Fiend.Poison",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Deimos.Fiend.Ranged",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Events.VB.B01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Events.VB.B02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Events.VB.B03",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Events.VB.Faction",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.FiringRange",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Follower.H",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.FriendBot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.Date",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateAlpha",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateAR",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateLonely",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateLonelyGhost",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateShotgun",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateSMG",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateSniper",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.DateVG",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.Generic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.MangAlpha",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.MangAR",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.MangCrossbow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.MangLauncher",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.MangShotgun",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.MangSMG",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.Tomato",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.TomatoAlpha",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gasket.Wasabi",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gibson.Elite",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gibson.Grunt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gibson.Heavy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Gladiator",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.G",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.G_RE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.I",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.Random",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.S",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.S_RE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HB.T",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HBISolo",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.HBV",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.Burt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.BurtH",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.Grandma",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.GrandmaH",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.Nug",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.NugH",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Hitchhiker.Smackie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Airfield",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.B",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Base",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.DrillF",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter.AR.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter.AR.RedDotBurst",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter.Pistol.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter.Shotgun.Burst",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter.Shotgun.DPS",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Encounter.SMG.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.FieldTeam",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.FieldTeam.D",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Grunt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Hollow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.LG",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Light",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.LS",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Patrol.Encounter.AR.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.Patrol.Encounter.AR.RedDotBurst",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.IO.SpicySake",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Avian.Crow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Avian.Crow.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Avian.Crow.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Avian.Crow.PassivePhase",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.LootVariation01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.LootVariation02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Mandatory",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.PassivePhase",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.SA",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Variation01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Variation02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Variation03",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Variation04",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Burt.Variation05",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Dev.Dev1",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Dev.Dev2",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Dev.Dev3",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Dev.Dev4",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Dev.Dev5",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Dev.Dev6",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.LootVariation01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.LootVariation02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.LootVariation03",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.PassivePhase",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.SA",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Variation01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Variation02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Variation03",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Variation04",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Grandma.Variation05",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.LootVariation01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.LootVariation02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.PassivePhase",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.PassivePhaseLoot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Variation01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Variation02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Variation03",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Variation04",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Nug.Variation05",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.NugS",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.NugSE1",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.NugSE2",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.NugZ",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Robert",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Robert.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Robert.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Smackie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Smackie.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Smackie.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Smackie.PassivePhase",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Irwin.Smackie.SA",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Islander.BunkerJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Islander.Generic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Labrador",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Lonely.G",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Lonely.GNoInv",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Lonely.S",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Lonely.SNoInv",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.LoneWolf",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.LowTower.Hardy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.LowTower.Laurel",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.MonkeyGrave",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.MonkeyGraveVehicle",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Monster.Caretaker",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Monster.CaretakerPrelude",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Monster.CaretakerSmash",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.MotherWall.Grunt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.MotherWall.Lonely",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.NSS.LW",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Army",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Beach",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.CAN",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Cat",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.ChickenOwner",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Clown",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Dino",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Driver",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Faction01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Faction02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Farmer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Fox",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Garagist",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.GenericAI",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Prefab.Default",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Prefab.SP",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Prefab.WF",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Robber",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.SignatureDance",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Slurp",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Template.00",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Template.01",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Template.02",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Template.03",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Template.04",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.TNT",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.WF",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RE.Yellow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.RiftGate",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter.AR.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter.Pistol.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter.Shotgun.Burst",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter.Shotgun.DPS",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter.SMG.Core",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.Guard.Encounter.SMG.Recoil",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Seven.GuardS21",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.SilkWalker",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.SilkWalkerAcolytes",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.SilkWalkerAcolytes_Lonely",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.SilkWalkerAcolytesVehicle",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.SipTea",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Specialist.Heavy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Specialist.Medic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Specialist.Melee",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Specialist.Scout",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Specialist.Supply",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.SpicySake.PassivePhase",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Avian.Crow.Basic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Avian.Crow.Loot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Burt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Grandma",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Nug",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.NugZ",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Robert",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.STW.Smackie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Abstrakt",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Abstrakt.asd",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AbstraktGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AgentJones",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AgentJonesGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AlienSummer",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AlienSummerGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AlienTrooper",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AlienTrooperGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AlpineAceCAN",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Antique",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.AntiqueGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ArcticSniper",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Aura",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BabaYaga",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BabaYagaGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bandolette",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BandoletteGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bandolier",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BaoBros",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BaoBrosGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BattleSuit",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BeachBomber",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BeachBrutus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BeefBoss",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BeefBossGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Believer",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BelieverGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bicycle",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BigChuggus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BigChuggusGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bigfoot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BigMouth",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BigMouthGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Binary",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BinaryTwin",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BirthdayRamirez",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BirthdayRamirezGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BistroAstronaut",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BistroAstronautGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Blackheart",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BlackheartGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BlackWidowRogue",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Blaze",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BlazeGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bloom",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BloomRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BoardwalkRuby",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BoneWasp",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BountyHunter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Brainiac",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BrainiacGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Brat",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BratGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BriteBomber",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Broccoli",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Brutus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BuffLlama",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BuffLlamaGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bullseye",DevComment="") ++GameplayTagList=(Tag="AiSpawnerData.Type.Tandem.Bulma",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BunkerJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BunkerJonesyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BunkerJonesyRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Burnout",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BurnoutGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Bushranger",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BushrangerGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.BushrangerRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cabbie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CabbieGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cashier",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CastawayJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CastawayJonesyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Catalyst",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CatBurglar",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cattus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CavernArmored",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CerealBox",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CerealBoxGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ChickenWarrior",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ChickenWarriorGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ChillCat",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ChromePunk",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cobb",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CobbGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cole",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ColeGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Columbus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Columbus.1",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Columbus.2",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Columbus.3",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cowgirl",DevComment="Tied to old outfit challenges") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CritterManiac",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CritterManiacGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CritterRaven",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CritterRavenGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Crush",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Crustina",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CrustinaGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cryptic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CubePaintRedKnight",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CubeQueen",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CubeThief",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cuddlepool",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CuddlepoolGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CuddleTeamLeader",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CuddleTeamLeaderGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CupidDark",DevComment="Tied to M_CupidDark old outfit challenges") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CupidDarkF",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.CurdleScreamLeader",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Cutiepie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DarkBomber",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DarkJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DarkJonesyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DarkViking",DevComment="Tied to M_DarkViking old outfit challenge") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Deadfire",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DeadfireGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DerbyDominator",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DiamondDiva",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DinoHunter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DinoHunterGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Dire",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DireGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DireRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Division",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DivisionGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DJBop",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Doggo",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Dolph",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DolphRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DragonNinja",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Dreamflower",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DreamflowerGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Drift",DevComment="Tied to M_Drift old outfit challenges") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Dummy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DummyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Dusk",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.DuskGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.EightBall",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.EightBallGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Ember",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.EmberGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Emperor",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.EmperorGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Enforcer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.EnforcerRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Evie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.EvilSuit",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ExampleNeutral",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ExampleRag",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ExoSuit",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ExoSuitGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FabioSparklemane",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FarmerSteel",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FarmerSteelGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Faux",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FauxGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Firecracker",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Fishstick",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FishstickRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FNM.HayMan",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FNM.Mincemeat",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FNM.Nitehare",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FNM.Willow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Foreshadowing",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FrozenFishstick",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FrozenRedKnight",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FurnaceFace",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.FutureSamurai",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Galactico",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GhostHunter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GhostHunterGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Giggle",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GiggleGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Gladiator",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GlobeShaker",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GoldenSkeleton",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Goldie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GrillSaergentGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GrillSeargent",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Grimbles",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GrimFable",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GrimFableGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Grind",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Guaco",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GuacoGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Guide",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Guild",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GutBomb",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.GutBombGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Halloween",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Hayseed",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HayseedGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HeadbandK",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HeadbandKGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HenchmanSummer",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HenchmanSummerGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Hollowhead",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HollowheadGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HonorGuard",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HonorGuardRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Hush",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.HushGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Hyde",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.IceKing",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.IceQueen",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Inferno",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Innovator",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.InnovatorGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Insight",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Invader",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.InvaderGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.IslandNomad",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.IslandNomadGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JadeRacer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Jailbird",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Jekyll",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JonesUnchained",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JonesyTheFirst",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JonesyTheFirstGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JonesyTheFirstRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Jules",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JulesGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JulesRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.JumpShot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Kit",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Kitbash",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.KitbashGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.KitbashRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Kyle",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.KyleGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LilWhip",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LilWhipGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LilWhipRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LollipopTrickster",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LoneWolf",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LoneWolfGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Longshot",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Loveless",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LtEvergreen",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Ludwig",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.LudwigGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Madcap",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MadcapGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MadcapRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Majesty",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Malcore",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MalcoreGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Mancake",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MancakeGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Mandalorian",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Marigold",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MarigoldGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MaryJane",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Maven",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MavenGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Maximillian",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MechaPop",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MechaPopRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MechaStrikeCommander",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Meowscles",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MeowSkulls",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MetalMouth",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MetalTeamLeader",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MetalTeamLeaderGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Mitosis",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MogulMaster",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MogulMasterRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MoistyMerman",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MoistyMermanGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MoonHawk",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MrDappermint",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MrDappermintRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MulletMarauder",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MulletMarauderGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.MunitionsMajor",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Mystic",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Mystify",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Neoncat",DevComment="Tied to F_Neoncat old outfit challenges") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.NeonLines",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Nitehare",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.NitehareGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Nitrojerry",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Noir",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.NoSweat",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Obsidian",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ObsidianGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.OmegaKnight",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Oro",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Outcast",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.OutcastGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.OutcastRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Outlaw",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Painter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Panther",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Peekaboo",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Peely",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PeelyMech",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PeelyRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Penny",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PennyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Phantom",DevComment="Tied to old outfit challenge bundle") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PinkTrooper",DevComment="Tied to old outfit challenge bundle") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PinkTrooperMale",DevComment="Tied to old outfit challenge bundle") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PirateProgressive",DevComment="Tied to old outfit challenge bundle quests") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Pitstop",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PitstopGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Pizza",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PlumRetro",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Polygon",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PowerChord",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PowerChordGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PrincessFelicityFish",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Prisoner",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PrisonerGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PumpkinPunkGlitch",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PunkKoi",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.PunkKoiGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Quackling",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.QuacklingGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Ragnarok",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RagnarokGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Ragsy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RagsyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RainbowRacer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RainbowRacerGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rapscallion",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Raptor",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RaptorianTheBrave",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Raven",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RavenGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rebel",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RedOasisGooseberry",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RelaxedFitJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Remedy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RemedyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RenegadeShadow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Revolt",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rex",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RexGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rickus",DevComment="S17") ++GameplayTagList=(Tag="aispawnerdata.type.tandem.RickusGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Riot",DevComment="S17") ++GameplayTagList=(Tag="aispawnerdata.type.tandem.RiotGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rippley",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rook",DevComment="S17") ++GameplayTagList=(Tag="aispawnerdata.type.tandem.RookGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Ruckus",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Rustler",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RustLord",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.RustLordGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sabina",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SashSeargent",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SashSeargentGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sayara",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SayaraRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ScrapknightJules",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ScubaCrystal",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ScubaJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ScubaJonesyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sentinel",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SevenSentryNPC.Female",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SevenSentryNPC.Male",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SgtGreenClover",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SgtGreenCloverRE",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Shade",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ShadeGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ShadowOps",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ShadowOpsGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Shapeshifter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SharpFang",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Shiny",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ShotStopper",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Silencer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SkullBrite",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sledgehammer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SledgehammerGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sleuth",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SlurpBandolette",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SlurpJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SlurpJonesyGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SlurpLeviathan",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SmallBag",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SmallBagGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Snomando",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Snowheart",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SnowSniper",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SnowSniperGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sorana",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SoranaGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SpaceBunny",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SpaceChimp",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SpaceChimpGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SpaceFighter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sparkplug",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SparkplugGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SpecialForces",DevComment="S17") ++GameplayTagList=(Tag="aispawnerdata.type.tandem.SpecialForcesGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Splode",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SplodeGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.StageSlayer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.StageSlayerGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Stashd",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sterling",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.StoneViper",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.StormRacer",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.StreetCuddles",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.StreetFashionEclipse",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SummerDrift",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sunbird",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Sunflower",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SunrisePalace",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SunrisePalaceGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SuntanJonesy",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SuntanSpecialistGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SurrrBurger",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.SwampStalker",DevComment="S17") ++GameplayTagList=(Tag="aispawnerdata.type.tandem.SwampStalkerGeneric",DevComment="S17") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TameFoot",DevComment="S20NPC DefaultGameplayTags") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TandemTemplate",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TandemTest",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TandemTestA",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TandemTestB",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TandemTestC",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TandemTestServices",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Temple",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TenderDefender",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TeriyakiFishAssassin",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TeriyakiFishToon",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TeriyakiFishToonGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Tess",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TessGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Test",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TestBrawnySmasher",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TextilePup",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheFoundation",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheOrder",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheOrigin",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheParadigm",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheReaper",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheReaperGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheScientist",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheScientistGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheUnderwriter",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheVisitor",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TheVisitorGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TNTina",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TomatoHead",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TomatoHeadGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TriceraOps",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Triggerfish",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TriggerFishGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TripleScoop",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Troops",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Tsuki",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TsukiGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Turk",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.TurkGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Unpeely",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Vi",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Vigilante",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Viking",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WeaponsExpert",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Werewolf",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Whiteout",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WickedDuck",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WildCard",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Willow",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WillowGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Wrath",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WrathGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WreckRaider",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.WreckRaiderGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.York",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.YorkGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Zadie",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.Zenith",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Tandem.ZenithGeneric",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.TandemTemplate",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.TandemTestServices",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.Umami.Base",DevComment="") ++GameplayTagList=(Tag="AISpawnerData.Type.ZigZag",DevComment="") ++GameplayTagList=(Tag="Alteration.Dance.Stack",DevComment="") ++GameplayTagList=(Tag="Alteration.Freeze.Stack",DevComment="") ++GameplayTagList=(Tag="Alteration.HomingProjectile.Target",DevComment="") ++GameplayTagList=(Tag="Ammo.Ability.Commando.FragGrenade",DevComment="") ++GameplayTagList=(Tag="Ammo.Ability.Outlander.PhaseShift",DevComment="") ++GameplayTagList=(Tag="Ammo.Husk",DevComment="") ++GameplayTagList=(Tag="AmmoType.Heavy",DevComment="") ++GameplayTagList=(Tag="AmmoType.Light",DevComment="") ++GameplayTagList=(Tag="AmmoType.Medium",DevComment="") ++GameplayTagList=(Tag="AmmoType.Shells",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Aircraft.Helicopter",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Automobile",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Campfire",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.NPC.Bigfoot",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.NPC.Shark",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.NPC.Tandem",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Safe",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.TreasureChest.Common",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.TreasureChest.Rare",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Watercraft.Boat",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Wildlife.All",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Wildlife.Burt",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Wildlife.Grandma",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Wildlife.Nug",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Wildlife.Robert",DevComment="") ++GameplayTagList=(Tag="Analytics.Intel.Wildlife.Smackie",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Apple",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Banana",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Cabbage",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.CampFire",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Coconut",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Corn",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Mushroom",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slaps.Barrel",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Barrel",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Drain",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Mushroom",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Puddle",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Silo",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Tanker",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Slurp.Vat",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Env.Tomato",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Trap.CampFire",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Weapon.Ranged.Mustache",DevComment="") ++GameplayTagList=(Tag="Analytics.Item.Weapon.Ranged.SlurpBazooka",DevComment="") ++GameplayTagList=(Tag="Analytics.Vehicle.Meatball.Damage.DriveOnLand",DevComment="Analytics tag used for tracking damage boat takes from driving on land.") ++GameplayTagList=(Tag="Analytics.Vehicle.Valet.Damage.RimScrape",DevComment="Analytics tag to track rim scrape damage") ++GameplayTagList=(Tag="Animation.CharacterParts",DevComment="") ++GameplayTagList=(Tag="Animation.CharacterParts.HideFX",DevComment="Should be set when equipping an item requires certain FX attached to CharacterParts be hidden") ++GameplayTagList=(Tag="Animation.CharacterParts.HideFX.Hands",DevComment="") ++GameplayTagList=(Tag="Animation.CharacterParts.Swapping",DevComment="Should be set when character parts are being swapped during gameplay, to prevent t-posing during the swap") ++GameplayTagList=(Tag="Animation.FrontEnd.HeroLoadout",DevComment="Hero Loadout related tags used for animation montages.") ++GameplayTagList=(Tag="Animation.FrontEnd.HeroLoadout.IsCommander",DevComment="") ++GameplayTagList=(Tag="Animation.FrontEnd.HeroLoadout.IsCrewLeader",DevComment="") ++GameplayTagList=(Tag="Animation.FrontEnd.HeroLoadout.IsCrewMember",DevComment="") ++GameplayTagList=(Tag="animation.frontend.heroloadout.IsSlotFocused",DevComment="") ++GameplayTagList=(Tag="Animation.Notify.AbilityEnd",DevComment="") ++GameplayTagList=(Tag="Animation.Notify.AI.Interruptible",DevComment="") ++GameplayTagList=(Tag="Animation.Notify.Mantis.Execution",DevComment="") ++GameplayTagList=(Tag="Animation.Notify.Mantis.Execution.End",DevComment="") ++GameplayTagList=(Tag="Animation.Notify.Mantis.Execution.Start",DevComment="") ++GameplayTagList=(Tag="Animation.Notify.PlayFX.01",DevComment="") ++GameplayTagList=(Tag="antishield",DevComment="") ++GameplayTagList=(Tag="arenafloor",DevComment="") ++GameplayTagList=(Tag="Ashton.Milo.Player",DevComment="") ++GameplayTagList=(Tag="Ashton.Player.Milo",DevComment="") ++GameplayTagList=(Tag="Asset.Abilities.Generic.Teleporter.ArmTimeUpgrade",DevComment="Reduces the Arm Time of Teleporter") ++GameplayTagList=(Tag="Asset.Abilities.Generic.Teleporter.EnemyProjectile",DevComment="Allows Enemy Projectiles to go through Teleporter") ++GameplayTagList=(Tag="Asset.Abilities.Generic.Teleporter.FriendlyProjectile",DevComment="Allows Friendly Projectiles to go through Teleporter") ++GameplayTagList=(Tag="Asset.Abilities.Generic.Teleporter.InteractionUpgrade",DevComment="Improves the Interaction speed of the Teleporter") ++GameplayTagList=(Tag="Asset.Abilities.Outlander.Groundwave.Cooldowns",DevComment="") ++GameplayTagList=(Tag="Asset.Ability.Constructor.MountedTurret",DevComment="") ++GameplayTagList=(Tag="Asset.Ability.Constructor.MountedTurret.BlockActivation",DevComment="Prevent Mounted Turret Activation") ++GameplayTagList=(Tag="Asset.AbilityEffect.BotShield.CastBlocker",DevComment="") ++GameplayTagList=(Tag="asset.abilityeffect.BotShield.DamageResistanceBuff",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.FragGrenade.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.GoinCommando.Active",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.GoinCommando.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.GoinCommando.SelfSnare",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.LeftyAndRighty.Active",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.LeftyAndRighty.Duration",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.Shockwave.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.WarCry.Buff",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Commando.WarCry.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Constructor.BullRush.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Constructor.GoinConstructor.ArmorUp",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Constructor.GoinConstructor.Duration",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Constructor.GoinConstructor.ShieldBlock.BonusArmor",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Constructor.PlasmaPulse.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Ninja.CrescentKick.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Ninja.DragonSlash.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Ninja.KunaiStorm.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Ninja.SmokeBomb.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Ninja.ThrowingStars.AdditionalStars",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Ninja.ThrowingStars.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Outlander.GroundWave.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Outlander.PhaseShift.PassThrough",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityEffect.Outlander.TEDDY.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Commando.FragGrenade",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Commando.GoinCommando",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Commando.LeftyAndRighty",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Commando.Shockwave",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Commando.WarCry",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.BASE",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.BullRush",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.Decoy",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.KineticOverload",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.MountedTurret",DevComment="Mounted Turret Asset Group") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Constructor.PlasmaPulse",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Ninja.CrescentKick",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Ninja.DragonSlash",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Ninja.KunaiStorm",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Ninja.SmokeBomb",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Ninja.ThrowingStars",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Outlander.AntiMaterialCharge",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Outlander.Bear",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Outlander.GroundWave",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Outlander.PhaseShift",DevComment="") ++GameplayTagList=(Tag="Asset.AbilityGroup.Hero.Outlander.ShockTower",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Achievements.HappyGhost",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Achievements.Lifeguard",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Achievements.RocketRiding",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.EnvItem.Lock.Scanner.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.EnvItem.PinkOatmeal",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.EnvItem.Sentry.Turret.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.ExplosiveBow.AltFire.Charge",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.ForagedItem.GoldenTruffle",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.HappyGhost.Pull",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.DogSweater.Cook",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.GravyBoat.AntiGrav",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.JollyRascal.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.JollyRascal.Damage.Anch",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.JollyRascal.Damage.Buoy",DevComment="Gameplay tag referring to the buoy junk from Junk Rift.") ++GameplayTagList=(Tag="Asset.Athena.Item.JollyRascal.Damage.Car",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.JollyRascal.Damage.Dino",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.JollyRascal.Damage.Porta",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Item.Snowball",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard001",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard002",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard003",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard004",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard005",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard006",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard007",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard008",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard009",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard010",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard011",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard012",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard013",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.BountyBoard014",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone001",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone002",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone003",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone004",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone005",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone006",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone007",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone008",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone009",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone010",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone011",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone012",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone013",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone014",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.PayPhone015",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench001",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench002",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench003",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench004",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench005",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench006",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench008",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench009",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench010",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench011",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench012",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench013",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench014",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench015",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench016",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench017",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench018",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.UpgradeBench019",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine001",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine002",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine003",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine004",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine005",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine006",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine007",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine008",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine009",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine010",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine011",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine012",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine013",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine014",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine015",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine016",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine017",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine018",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine019",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine020",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine021",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine022",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine023",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine024",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine025",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine026",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine027",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine028",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine029",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine030",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine031",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine032",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine033",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine034",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine035",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine036",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine037",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine038",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine039",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine040",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Map.VendingMachine041",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Quest.SkydivingFromBus",DevComment="") ++GameplayTagList=(Tag="Asset.Athena.Quest.TagCarrier",DevComment="") ++GameplayTagList=(Tag="Asset.Coin.Cluster.Fragment.Combo",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Ashton.Indigo.BoostJump",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.LeapGlider.General",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.LeapGlider.Generic",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.LeapGlider.Generic.Cancel",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.LeapGlider.InfCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.Melee.AirAttack.Generic",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.Melee.LeapAttack.Generic",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.WitchBroom.Generic",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Athena.WitchBroom.InfCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Commando.FragGrenade",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Commando.GoinCommando",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Commando.LeftyAndRighty",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Commando.Shockwave",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Commando.WarCry",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Constructor.BullRush",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Constructor.Decoy",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Constructor.MountedTurret",DevComment="Cooldown for Mounted Turret Ability") ++GameplayTagList=(Tag="Asset.Cooldown.Constructor.PlasmaPulse",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.AdrenalineRush",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.AirStrike",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.Banner",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.BotShield",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.BotTurret",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.SlowField",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Gadget.Teleporter",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Ninja.CrescentKick",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Ninja.DragonSlash",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Ninja.KunaiStorm",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Ninja.SmokeBomb",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Ninja.ThrowingStars",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Outlander.Bear",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Outlander.Groundwave",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Outlander.PhaseShift",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Outlander.ShockTower",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.ActionCharge",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.Assassination",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.DragonDaze",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.EmergencyOverride",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.FeelTheBASE",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.GnarlyPop.Item",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.GoinBananas.TropicalFruit",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.HeartyStikes",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.KineticOverload",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.Ninja.MantisLeap",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.SaurianInstinct",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.SaurianMight",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.ShortFuse",DevComment="") ++GameplayTagList=(Tag="Asset.Cooldown.Perk.SlowYourRoll",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility",DevComment="Is a Creative Ability") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Active",DevComment="Is an Active Creative Ability") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.BuildingActor",DevComment="Is intended to be run on a building actor") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Consumeable",DevComment="Is a Consumeable Item that puts a status effect on the player") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Invisibility.InvisibilityStart",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Invisibility.InvisibilityWarning",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Invisibility.RemoveInvisibility",DevComment="GE that removes invisibility") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Invisibility.SpeedModifier",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.InvisibilityStart",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Mutator.SpeedModifier",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.OnKillSiphon",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Passive",DevComment="Passive Creative Ability") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Pawn",DevComment="Is a Pawn-Generated Ability") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.RemainInvisible",DevComment="This Ability Asset will not break Invisibility when activated.") ++GameplayTagList=(Tag="Asset.Creative.CreativeAbility.Triggered",DevComment="Is a Creative Triggered Ability") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Cooldown",DevComment="Is a creative Cooldown") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Cooldown.EatFood",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.CustomPawn",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Damage",DevComment="A Damaging Effect") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Duration",DevComment="A Creative GE with a Duration") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.HealPowerup.HasDuration",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.HealPowerup.Instant",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Instant",DevComment="An Instantanious Creative Effect") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Negative",DevComment="A Negative Effect") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Neutral",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Periodic",DevComment="Has a Periodic Tick or Pulse") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Positive",DevComment="A Positive Effect") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.ShowCue",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.TagContainer",DevComment="This GE Carries a Tag, and nothing more") ++GameplayTagList=(Tag="Asset.Creative.CreativeEffect.Volume.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.NoSpray",DevComment="for preventing spray decals from being applied to this object") ++GameplayTagList=(Tag="Asset.Creative.Weapon.Melee.Flashlight",DevComment="The Flashlight") ++GameplayTagList=(Tag="Asset.Creative.Weapon.Ranged.Flashlight",DevComment="Creative Mode Flashlight") ++GameplayTagList=(Tag="Asset.Creative.WeaponAbility",DevComment="Is a Creative Weapon Ability") ++GameplayTagList=(Tag="Asset.Creative.WeaponAbility.Melee",DevComment="") ++GameplayTagList=(Tag="Asset.Creative.WeaponAbility.Ranged",DevComment="") ++GameplayTagList=(Tag="Asset.Deimos.ShieldOnFiendKill",DevComment="") ++GameplayTagList=(Tag="Asset.Dudebro.WaterDamage",DevComment="Water Damage Asset") ++GameplayTagList=(Tag="Asset.Gameplay.Defaults.DefaultResourceCap",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Effect.RemoveInVehicle",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ActiveCooldownReduction",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ActiveDurationModification",DevComment="Modifies effects which are currently in progress") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.AddedBaseDamage",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.AddedBaseImpact",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.AddedStunTime",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.AmmoClipSize",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ChanceToNotConsumeAmmo",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CooldownReduction",DevComment="Asset tag for the Cooldown Reduction Effect Parent.") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritMultiplier",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritMultiplier.Ability",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritMultiplier.Weapon",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritMultiplier.Weapon.Melee",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritMultiplier.Weapon.Ranged",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritRating",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritRating.Ability",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritRating.Weapon",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritRating.Weapon.Melee",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.CritRating.Weapon.Ranged",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DamageMultiplier.Ability",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DamageMultiplier.AbilityWeapon",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DamageMultiplier.Trap",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DamageMultiplier.Weapon",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DamageMultiplier.Weapon.Melee",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DamageMultiplier.Weapon.Ranged",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.DoubleLootChance",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.FallDamageImmunity",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.HeadshotMult",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.HealingBonus",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.HeavyAttackEfficiency",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ImpactMultiplier",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ImpactMultiplier.Ability",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ImpactMultiplier.Trap",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ImpactMultiplier.Weapon.Melee",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ImpactMultiplier.Weapon.Ranged",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.IncomingDurationModifier",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ItemRegenCooldownReduction",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ItemRegenMaxStack",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.MaxStamina",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.OutgoingDurationModifier",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.PeriodicShieldRegen",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ResourceCap",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.ShieldHeal",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.Stability",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.StaminaCostReduction",DevComment="Asset tag for the Stamina Cost Reduction Mod Parent") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.StaminaGrant",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.StaminaRegen",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.TrapDurability",DevComment="") ++GameplayTagList=(Tag="Asset.Gameplay.Mod.WeaponStability",DevComment="") ++GameplayTagList=(Tag="Asset.IgnoreDoubleLoot",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AbsolutelyStunning.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AbsolutelyStunning.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ActionCharge.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ActionCharge.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ActuatedAttacks.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ActuatedAttacks.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AintDoneYet.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AintDoneYet.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AllTogetherNow.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AnatomyLessons.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AnatomyLessons.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Assassination.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Assassination.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AssaultCritDamage.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.AssaultCritDamage.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BassSolo.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BassSolo.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Bearricade.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Bearricade.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Bearserker.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Bearserker.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BearStare.Damage",DevComment="Asset tag for the Bear Stare Damage GE") ++GameplayTagList=(Tag="Asset.Perk.BearTogether.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BiggerIsBetter.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BiggerIsBetter.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BioEnergySource.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BlakebeardsStash.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BlastFromThePast.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BlastKick.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BlastKick.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Boomstick.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Boomstick.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BouquetOfROSIES.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BrainFreeze.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BringinDownTheHouse.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BringinDownTheHouse.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BurgerBuff.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.BurgerBuff.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CantStop.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CantStop.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Cascade.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Cascade.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ColdToTheTouch.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ColdToTheTouch.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CombatantsMight.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CombatantsMight.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Commando.StayFrosty.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Commando.SuppressiveFire.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CommandPresence.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CommandPresence.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Constructor.BASE.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Constructor.KineticOverload.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CoolCustomer.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CorrosiveStars.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CorrosiveStars.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CorrosiveStrikes.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CorrosiveStrikes.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CriticalBlast.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CriticalBlast.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.CrystalDaggers.T01",DevComment="Asset T01 Crystal Daggers") ++GameplayTagList=(Tag="Asset.Perk.CrystalDaggers.T02",DevComment="T02 Crystal Daggers Perk") ++GameplayTagList=(Tag="Asset.Perk.DangerDanger.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DeadDrop.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DeadDrop.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DeepPockets.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DeepPockets.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DemoDerby.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DemoDerby.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DevastatingDuet.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DimMak.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DirectThreat.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DirectThreat.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DonatingPlasma.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DopplerEffect.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DopplerEffect.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DragonDaze.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.DragonDaze.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EasyOperation.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EasyOperation.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EasySword.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EasySword.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EggsplosiveEntrace.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EggsplosiveEntrace.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ElectricAttraction.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Electroshock.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Electroshock.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EmergencyOverride.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EmergencyOverride.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EndlessShadow.T01",DevComment="Asset Tag for Endless Shadow Leader Perk") ++GameplayTagList=(Tag="Asset.Perk.EndlessSmoke.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EndlessSmoke.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EnduranceTraining.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EnduranceTraining.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EnduringMachine.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EnduringMachine.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EnergySiphon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EnergySiphon.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EscapeArtist.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.EscapeArtist.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ExplosiveOptimization.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ExplosiveOptimization.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FanOfStars.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FanOfStars.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FastBuild.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FastBuild.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FasterExplosions.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FasterExplosions.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FaultLine.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FaultLine.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FeelTheBASE.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FeelTheBASE.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FightOrFlight.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FightOrFlight.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FirstCupOfTheDay.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FirstCupOfTheDay.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FishInABarrel.T01",DevComment="Fish in a Barrel Tier 1") ++GameplayTagList=(Tag="Asset.Perk.FishInABarrel.T02",DevComment="Asset Fish in a Barrel Tier 2") ++GameplayTagList=(Tag="Asset.Perk.FiveWindsCut.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FiveWindsCut.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Fleet.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Fleet.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FollowTheLeader.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Footloose.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Footloose.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FourOnTheFloor.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FourOnTheFloor.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FromTheDepths.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.FromTheDepths.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Fumblerooski.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Fumblerooski.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Gadgeteer.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Gadgeteer.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GnarlyElimination.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GnarlyElimination.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GnarlyPop.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GnarlyPop.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GoinAgain.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GoinAgain.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GoinBananas.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GoinBananas.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GoingandGoing.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GoingandGoing.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GreaseTheWheel.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GreaseTheWheel.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GrenadeDamage.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GrenadeDamage.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GrenadeGeneration.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.GrenadeGeneration.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HandCannons.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HandCannons.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HardwareCritChance.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HardwareCritChance.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HeartOfStone.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HeartOfStone.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HeartyStrikes.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HeartyStrikes.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HighTide.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HighTide.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Hipshot.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Hipshot.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HolidayCheer.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HolidaySurprise.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HolidaySurprise.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Hotfix.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Hotfix.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HotFoot.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HotFoot.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HotLead.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.HotLead.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.IcyShot.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.IcyShot.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ImJustNotUserFriendly.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ImJustNotUserFriendly.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.InAndOutlander.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.InAndOutlander.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.InAPinch.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.InAPinch.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KeepOut.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Kickback.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Kickback.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KickEmWhenTheyreDown.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KineticOverdrive.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KnightsImpact.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KnightsImpact.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KunaiCollection.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.KunaiCollection.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Kunaihilation.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Kunaihilation.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Leadership.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Leadership.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LegendaryBlade.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LegendaryBlade.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LoftyArchitecture.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LoftyArchitecture.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LoneDragon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LonelyBear.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LongArmOfTheLaw.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LongRush.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LongRush.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.LowOrdinance.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MadTidings.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MadTidings.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MakeItCount.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MakeItCount.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MakeItRain.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MakeItRain.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Malfunction.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Malfunction.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MaximumOverload.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MaximumOverload.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MedicinalFumes.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MedicinalFumes.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MightyRoar.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MightyRoar.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MonkeyingAround.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MonkeyingAround.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MonsterSmash.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MonsterSmash.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MoralBoost.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MUSTPROTECT.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.MUSTPROTECT.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.NeverTellMeTheOdds.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.NightStalker.T01",DevComment="NightStalker T01 Asset") ++GameplayTagList=(Tag="Asset.Perk.NightStalker.T02",DevComment="NightStalker Perk T02 Asset") ++GameplayTagList=(Tag="Asset.Perk.Ninja.MantisLeap.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Ninja.ShadowStance.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.OneHotMinute.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.OneHotMinute.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.OneTwoPunch.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Outlander.AntiMaterialCharge.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Outlander.InTheZone.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Overbearing.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Overbearing.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PartingGift.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PartingGift.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PhaseCannon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PhaseCannon.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PhasedOut.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PhasedOut.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PhaseSiphon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PhaseSiphon.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PlasmaArc.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PlasmaArc.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PlasmaOverdrive.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PlasmaOverdrive.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PlasmaPulseBlast.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PlasmaPulseBlast.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PointyFury.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PointyFury.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PokingHoles.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PokingHoles.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PowerImpact.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PowerImpact.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PowerModulation.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PowerModulation.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PowerPulse.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PowerPulse.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PracticedInCombat.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PracticedInCombat.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PresentArms.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PresentArms.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PressingCharges.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PressingCharges.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PullThePin.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PullThePin.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PumpMetal.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.PumpMetal.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.QuickFingers.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.QuickFingers.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.QuickKick.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.QuickKick.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.QuickScope.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.QuickScope.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RapidFire.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RapidFire.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Rebound.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Rebound.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ReturnOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ReturnOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ReturnToSender.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ReturnToSender.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RogueWave.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RunAndStun.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RunAndStun.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RunningWithTheBulls.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RushedRush.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.RushedRush.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianClaws.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianClaws.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianFight.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianFight.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianFocus.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianFocus.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianGrit.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianGrit.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianHide.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianHide.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianInstinct.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianInstinct.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianMight.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianMight.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianVigor.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SaurianVigor.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ScytheToMeetYou.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ScytheToMeetYou.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SeaSickness.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SeaSickness.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SecondWind.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SeismicStanza.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SeismicStanza.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SetPhasersToKill.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SetPhasersToKill.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShadowAssassin.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShadowAssassin.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShakeItOff.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Shatter.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShellShock.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShellShock.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShiftingGears.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShockinglyAlone.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShootyMcShootsAlot.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShortFuse.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShortFuse.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShredTheGnar.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ShredTheGnar.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SlowReleaseBombs.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SlowYourRoll.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SnowinCommando.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SnowinCommando.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SoaringMantis.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Software.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Software.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SoloStarPower.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SpaceTechnology.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SpaceTechnology.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SpoilsOfWar.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SpoilsOfWar.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Stormborne.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Stormborne.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.StormyWeather.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.StrikeCost.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.StrikeCost.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SuperchargedTrap.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SureShot.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SureShot.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SustainedImpact.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.SustainedImpact.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.TailOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.TailOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.TotallyGnarly.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ToughTraps.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ToughTraps.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.TrainedThrow.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.TrainedThrow.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Twinblast.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.Twinblast.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UneggspectedDeparture.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UneggspectedDeparture.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UpInSmoke.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UpInSmoke.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UpTheVoltage.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UpTheVoltage.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UtilityBelt.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.UtilityBelt.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ViolentInspiration.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WarOfTheRosie",DevComment="War of the Rosie Asset Tag") ++GameplayTagList=(Tag="Asset.Perk.WarOfTheRosie.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WarOfTheRosie.T02",DevComment="Tier 2 War of the Rosie") ++GameplayTagList=(Tag="Asset.Perk.WasteNotWantNot.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WasteNotWantNot.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WheresLefty.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WheresLefty.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WingsOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WingsOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WorkWork.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.WorkWork.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.YoureComingWithMe.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.YourMoveCreep.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.YourMoveCreep.T02",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ZipAndZap.T01",DevComment="") ++GameplayTagList=(Tag="Asset.Perk.ZipAndZap.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.AbsolutelyStunning.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ActuatedAttacks.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.AllTogetherNow.DurationPerStack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.AnatomyLessons.CritRating",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Assassination.Active.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Assassination.Active.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.AssaultAmmoRecovery.RecoverChance",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.AssaultCritDamage.CritMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BassSolo.PlayingSolo",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Bearricade.Slow",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Bearserker.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BearTogether.DurationPerStack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BioEnergySource.ShieldHeal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BlastFromThePast.MaxHealth",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BombSuit.Armor",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BombSuit.ArmorRating",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BOOMBase",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Boomstick.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.BouquetOfROSIES.Stack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Buckle.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ClusterBomb.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ColdToTheTouch.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ColdToTheTouch.Freeze",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.CombatantsMight.CostReduction",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Commando.StayFrosty.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Commando.SuppressiveFire.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Constructor.BASE.FeelTheBASE.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Constructor.KineticOverload.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Constructor.KineticOverload.ImpactMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.CoolCustomer.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.CorrosiveStars.Affliction",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.CorrosiveStrikes.Snare",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.CorruptedAura.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.CriticalBlast.CritMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DangerDanger.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DeepPockets.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DemoDerby.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DevastatingDuet.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DimMak.ShieldRegen",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DonatingPlasma.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.DopplerEffect.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EasyOperation.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EasySword.HAE",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ElectricAttraction.DurationPerStack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Electroshock.ImpactMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EmergencyOverride.ActiveCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EmergencyOverride.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EmergencyOverride.SelfCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EnduranceTraining.MaxStamina",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EnduringMachine.DurabilityReduction",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EnergySiphon.ShieldHeal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EscapeArtist.Movespeed.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.EscapeArtist.Movespeed.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ExplosiveOptimization.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FastBuild.BuildSpeed",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FasterExplosions.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FaultLine.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FeelTheBASE.Applied",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FeelTheBase.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FeelTheBASE.Overflow",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FeelTheBASE.StackCount",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FeelTheBASE.StackCount.Overflow",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FightOrFlight.Buff.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FightOrFlight.Buff.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FireOfTheDragon.Affliction",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FireOfTheDragon.AfflictionDurationModifier",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FirstCupOfTheDay.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FiveWindsCut.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Fleet.Movespeed",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FollowTheLeader.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FootLoose.AddedStunTime",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FourOnTheFloor.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FromTheDepths.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FrozenCastle.Freeze",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FrozenCastle.InternalCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.FrozenCastle.Snare",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.GetReadyForASurprise.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.GoinAgain.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.GoinBananas.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.GoinBananas.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.GreaseTheWheels.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.GrenadeDamage.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HandCannons.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Hangtime.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Hangtime.Slowfall",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HardwareCritChance.CritRating",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HeartOfStone.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HeartyStikes.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Hipshot.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HipshotAssaultDamage.DamageMultSMG",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HolidayCheer.EnergyIncrease",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Hotfix.RepairSpeed",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HotFoot.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HuntersInstinct",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HuntersInstinct.DamageStack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.HuntersInstinct.Stack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.IcyShot.Affliction",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.IcyShot.Freeze",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ImJustNotUserFriendly.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.InAndOutlander.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.InAndOutlander.Tracker",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.InAPinch.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.InAPinch.ReloadSpeed",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.InAPinch.ShieldHeal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.IronKnuckles.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.KeepOut.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Kickback.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.KickEmWhenTheyreDown.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.KineticOverload.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.KineticPunch.ImpactMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.KnightsImpact.ImpactMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.KunaiCollection.ActiveCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Kunaihilation.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Leadership.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LegendaryBlade.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LockedAndReloaded.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LoftyArchitecture.MaxHealth",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LoneDragon.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LonelyBear.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LonelyBear.EffectGrant",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.LowOrdinance.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MadTidings.Buff",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MakeItCount.CritMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MakeItCountAssaultCritDamage.CritMultSMG",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MakeItRain.FireRate",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MakeItRain.HeadshotMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Malfunction.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MaximumOverload.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MedicinalFumes.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MonsterSmash.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MoralBoost.DR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.MUSTPROTECT.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.NeverTellMeTheOdds.HealingMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.NightStalker.Applied",DevComment="Is the NightStalker Asset") ++GameplayTagList=(Tag="Asset.PerkEffect.Ninja.MantisLeap.AdditionalJumps",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Ninja.MantisLeap.FallImmune",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Ninja.ShadowStance.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OneHotMinute.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OneTwoPunch.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OnImpactBreakHuskDance.InternalCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OnKillHuskDance.InternalCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OnMeleeHitStreakHuskDance.InternalCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OnRangedHitStreakHuskDance.InternalCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.OnRangedHitStreakHuskDanceAfflict.InternalCooldown",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Outlander.AntiMaterialCharge.EnvDamage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Outlander.AntiMaterialCharge.PawnDamage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Outlander.InTheZone",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Outlander.InTheZone.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Overbearing.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PartingGift.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Perk.EndlessShadow.Active",DevComment="Asset of the Shadow Stance Extension Tool") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseBlaster.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseBlaster.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseCannon.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseCannon.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhasedOut.ItemRegenCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseFoward.Movespeed",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseSiphon.AddedBaseDamage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PhaseSiphon.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PlasmaOverdrive.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PlasmaPulseBlast.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PointyFury.HAE",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PokingHoles.Affliction",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PowerModulation.Applied",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PowerPulse.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PreemptiveStrike.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PreemptiveStrike.Stack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PresentArms.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PresentArms.Movespeed",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PresentArms.StaminaRegen",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.PullThePin.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.QuickKick.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.QuickScope.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.QuickScope.FireRate",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RackingUpTheCoal.Applied",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RackingUpTheCoal.Applied.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RackingUpTheCoal.Applied.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RackingUpTheCoal.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RapidCharge.StaminaGrant",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RapidFire.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Rebound.StaminaGrant",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ResonantFrequency.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ReturnOfTheDragon.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ReturnToSender.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RogueWave.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RuckSack.MaxCharges",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RunAndStun.ImpactStun",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RunningWithTheBulls.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.RushedRush.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SaurianFight.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SaurianFocus.StaminaRegen",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SaurianHide.Armor",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ScytheToMeetYou.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ScytheToMeetYou.Snare",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SecondWind.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SeismicStanza.Stun",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SetPhasersToKill.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShellShock.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShiftingGears.MaxCharges",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShockinglyAlone.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShockinglyAlone.EffectGrant",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShootyMcShootsAlot.DurationPerStack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShortFuse.ActiveCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShortFuse.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ShortFuse.SelfCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SlowReleaseBombs.DurationPerStack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SlowYourRoll.Freeze",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SlowYourRoll.Slow",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SnowinCommando.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SoaringMantis.AddJumps",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Software.HAE",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SoloStarPower.AddedStar",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SpaceTechnology.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SpaceTechnology.DamageMult.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SpaceTechnology.DamageMult.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SpoilsOfWar.AmmoRecovery",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SpoilsOfWar.AmmoRecovery.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SpoilsOfWar.AmmoRecovery.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.StartUp.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Stormborne.Movespeed.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Stormborne.Movespeed.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.StormyWeather.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.StrikeCost.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SuperChargedTraps.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SureShot.CritMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Survivalist.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SustainedImpact.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.SwabTheDeck.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Swash.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.TailOftheDragon.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.TailOftheDragon.Slow",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.TotallyGnarly.Active",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.TotallyGnarly.CueContainer",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ToughTraps.TrapDurability",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.TrainedThrow.SCR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Twinblast.DamageAOE",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.Twinblast.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.UnderWarranty.FirstHitCrit",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.UpInSmoke.Movespeed.T01",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.UpInSmoke.Movespeed.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.UpTheVoltage.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.UtilityBelt.CDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ViolentInspiration.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.WarOfTheRosie.DamageMult.T01",DevComment="War of the Rosie Damage Mult") ++GameplayTagList=(Tag="Asset.PerkEffect.WarOfTheRosie.DamageMult.T02",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.WasteNotWantNot.AmmoClipSize",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.WheresLefty.ActiveCDR",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.WingsOfTheDragon.DamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.WorkWork.PickaxeDamageMult",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.YoureComingWithMe.Stack",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.YourMoveCreep.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ZipAndZap.Damage",DevComment="") ++GameplayTagList=(Tag="Asset.PerkEffect.ZipAndZap.DamageDOT",DevComment="") ++GameplayTagList=(Tag="Asset.Projectile.BPrjArrow",DevComment="") ++GameplayTagList=(Tag="Asset.Projectile.BPrjMBall",DevComment="") ++GameplayTagList=(Tag="Asset.Projectile.Petrol",DevComment="") ++GameplayTagList=(Tag="Asset.Property.Cannonball",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.Confront",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.Mindwipe",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q10.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q10.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q10.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q11.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q11.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q11.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q11.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q2.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q2.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q2.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q2.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q3.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q3.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q3.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q4.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q4.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q4.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q4.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q5.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q5.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q5.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q5.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q6.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q6.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q6.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q6.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q7.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q7.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q7.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q8.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q8.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q8.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q8.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q9.Obj0",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q9.Obj1",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q9.Obj2",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q9.Obj3",DevComment="") ++GameplayTagList=(Tag="Asset.Quests.Ability.PlayVO.Dice.Q9.Obj4",DevComment="") ++GameplayTagList=(Tag="Asset.Silky",DevComment="") ++GameplayTagList=(Tag="Asset.Silky.SilkyDino.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.Silky.SilkyVeteran.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.Silky.SuperSilkyWolf.Heal",DevComment="") ++GameplayTagList=(Tag="Asset.Status.AbilityBlock",DevComment="") ++GameplayTagList=(Tag="Asset.Status.Elemental",DevComment="") ++GameplayTagList=(Tag="Asset.Uranium.PushCart.Heal",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.AssaultLauncher",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.CageSaddle.RanOutOfAir",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.CageSaddle.StillRecharging",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.CageSaddle.StillRepairing",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.CannotReload",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.Crafting",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.DishFries",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.Failed.GenericFail",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.FullAmmo",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GasFlopper.Bloated",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GasPumpRefill",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GravyGoblin.TargetIsAlreadyGrabbed",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GravyGoblin.TargetIsIncompatible",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GravyGoblin.TargetIsOwnVehicle",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GravyGoblin.TargetIsPlayer",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.GravyGoblin.TargetIsTooBig",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.NoMessage",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.NoPlayerEffect",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.NotEnoughEnergy",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.NotEnoughResources",DevComment="Tag for triggering the failed action given a lack of resources.") ++GameplayTagList=(Tag="Athena.Ability.Failed.NoValidTarget",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.OutOfAmmo",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.RealitySeed.BadGround",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.RealitySeed.NeedSpace",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.RealitySeed.TooManySaplings",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.Recharging",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.ReplenishAmmo",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.SnowmanEquipped",DevComment="Tag for failing to commit Sneaky Snowman application") ++GameplayTagList=(Tag="Athena.Ability.Failed.SpawnBot",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.Swimming",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.TentDeploy",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.TentDeploy.InvalidSurface",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.TentDeploy.TooManyTents",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.TreasureLooted",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.Turbo.SlamOnCooldown",DevComment="Tag to display fail message when player is trying to attack when slam is on cooldown") ++GameplayTagList=(Tag="Athena.Ability.Failed.WestSausage.DidntHold",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.WestSausage.NotHoldingForward",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.WestSausage.ObstacleAvoidanceTipOne",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.WestSausage.ObstacleAvoidanceTipTwo",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.WestSausage.PlayerGotShot",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.Failed.WestSausage.WebGotShot",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.GasFlopper",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.GasPump.Explode",DevComment="") ++GameplayTagList=(Tag="Athena.Ability.RiotShield",DevComment="") ++GameplayTagList=(Tag="Athena.Accolade.Exclude.FirstLanding",DevComment="Use this tag to prevent the \"Splash_First_FirstLanding\" Accolade from triggering.") ++GameplayTagList=(Tag="Athena.Accolade.Exclude.ReachedNewCircle",DevComment="Use this tag to prevent the \"Popup_ReachedNewCircle\" Accolade from triggering.") ++GameplayTagList=(Tag="Athena.Accolade.Exclude.Top10",DevComment="Use this tag to prevent the \"Acknowledgement_Survivor_Top10\" Accolade from triggering.") ++GameplayTagList=(Tag="Athena.Accolade.Exclude.Top25",DevComment="Use this tag to prevent the \"Acknowledgement_Survivor_Top25\" Accolade from triggering.") ++GameplayTagList=(Tag="Athena.Accolade.Exclude.Top50",DevComment="Use this tag to prevent the \"Acknowledgement_Survivor_Top50\" Accolade from triggering.") ++GameplayTagList=(Tag="Athena.Achievements.DeployParachute",DevComment="") ++GameplayTagList=(Tag="Athena.Achievements.LobsterFight",DevComment="") ++GameplayTagList=(Tag="Athena.Achievements.StormDamage",DevComment="") ++GameplayTagList=(Tag="Athena.AI.BTInjection.Phoebe.Augments",DevComment="") ++GameplayTagList=(Tag="Athena.AI.BTInjection.Phoebe.Blob",DevComment="") ++GameplayTagList=(Tag="Athena.AI.BTInjection.Phoebe.Grind",DevComment="") ++GameplayTagList=(Tag="Athena.AI.BTInjection.Phoebe.Traversal",DevComment="") ++GameplayTagList=(Tag="Athena.AI.DoNotDropLoot",DevComment="") ++GameplayTagList=(Tag="Athena.AI.EnvironmentalDanger.Flee",DevComment="Tag applied to AI to cause them to flee environmental danger") ++GameplayTagList=(Tag="Athena.AI.EnvironmentalDanger.Inside",DevComment="Tag to mark that an AI is inside of a danger source") ++GameplayTagList=(Tag="Athena.AI.EnvironmentalDanger.Source",DevComment="Used with the actor registry subsystem to register danger sources") ++GameplayTagList=(Tag="Athena.AI.GameplayAbility.BT.ForceWait",DevComment="To use in abilities to disable AI behavior in the BT") ++GameplayTagList=(Tag="Athena.AI.Gibson.Cozy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.HBSolo",DevComment="") ++GameplayTagList=(Tag="Athena.AI.HBVehicle",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Hidden",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Lager.Exclusion.Smackie",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Lager.SpawnerData.SingleUse",DevComment="Use this tag to prevent the LWM to spawn you spawner data more then once in a match.") ++GameplayTagList=(Tag="Athena.AI.Lager.TandemTest",DevComment="") ++GameplayTagList=(Tag="Athena.AI.NugS.SFE",DevComment="") ++GameplayTagList=(Tag="Athena.AI.ObjectTracking.HighPriority",DevComment="") ++GameplayTagList=(Tag="Athena.AI.PassivePhase.Phasing",DevComment="") ++GameplayTagList=(Tag="Athena.AI.ShowAlertState",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SmartObject.AwardThanks.PatrolStops",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SmartObject.Enabled",DevComment="Used when a smartobject is in its \"Enabled\" state (ie. someone has used it and the SO has this concept)") ++GameplayTagList=(Tag="Athena.AI.SmartObject.Events.GenericCondition",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SmartObject.Events.GenericCondition2",DevComment="If you already using GenericCondition, you can use this one as an alternative") ++GameplayTagList=(Tag="Athena.AI.SmartObject.RiftGate",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.AwardThanks.Empty",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.AwardThanks.NonPOI",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.AwardThanks.POI",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Cosmos",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.CosmosCantina",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Emblem",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Freaky",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.FreakyPresummon",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Habitat",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.HabitatCreature",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Inner",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround.05",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.MotherGround.06",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Nightmare",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Outter",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.05",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.06",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.07",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.08",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.09",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.Slone.10",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.TameFoot",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.00",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.05",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Boss.ThunderMeow.06",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Buddy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.BurtMandatory",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.ButterCakeMandatory",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.ButterCakeSneeze",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Encounter.Scripted",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Encounter.Sideways",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Events.VB.Boss01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Events.VB.Boss02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Events.VB.Boss03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Events.VB.Faction",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.FiringRange",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.HB",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.HB01.G",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.HB01.S",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.HB01.T",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.HBDriver",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.HBI",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Airfield",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Airfield.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Airfield.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Airfield.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Airfield.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Airfield.05",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.B",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.DrillF",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Entourage.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Entourage.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Entourage.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.05",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.06",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeam.07",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.05",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.06",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.FieldTeamBase.07",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Grunt",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Grunt.00",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Grunt.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Grunt.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Grunt.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Grunt.04",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.Guard",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.LG",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.LS",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.IO.M",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Irwin.Avian.AroundPlayers",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Irwin.Avian.TestVolume",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Lonely.G.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Lonely.G.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Lonely.G.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Lonely.S.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Lonely.S.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Lonely.S.03",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.LoneWolf",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.LW",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Mash.Wildlife",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.MonkeyGrave",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.MotherWall",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.MotherWall.Grunt",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.MotherWall.Lonely",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Realm",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.RiftGate",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Seven.Guard",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Seven.GuardMS",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Seven.GuardV",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Seven.GuardW",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.SilkWalker",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.SilkWalkerAcolyteLonely",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.SipTea",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Abstrakt",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.AgentJones",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.AlienSummer",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.AlienTrooper",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Antique",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Aura",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BabaYaga",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Bandolette",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Bandolier",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BaoBros",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BeachBomber",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BeefBoss",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Believer",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BigChuggus",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BigFoot",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BigMouth",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Binary",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BistroAstronaut",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Blackheart",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Blaze",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BoardwalkRuby",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Brainiac",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Brat",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Brutus",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BuffLlama",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Bullseye",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Bulma",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.BunkerJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Burnout",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Bushranger",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Cabbie",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CastawayJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CerealBox",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ChickenWarrior",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ChillCat",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ChromePunk",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Cobb",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Cole",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Columbus",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CritterManiac",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CritterRaven",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Crustina",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Cryptic",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CubeThief",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Cuddlepool",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CuddleTeamLeader",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.CurdleScreamLeader",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.DarkJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Deadfire",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.DerbyDominator",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.DiamondDiva",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.DinoHunter",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Dire",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Division",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Doggo",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Dreamflower",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Dummy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Dusk",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.EightBall",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Ember",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Emperor",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Evie",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ExoSuit",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FarmerSteel",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Faux",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Fishstick",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FNM.HayMan",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FNM.Mincemeat",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FNM.Nitehare",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FNM.Willow",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FrozenFishstick",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FrozenRedKnight",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.FutureSamurai",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Galactico",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Generic",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.GhostHunter",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Giggle",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Gladiator",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.GoldenSkeleton",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Goldie",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.GrillSeargent",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Grimbles",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.GrimFable",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Guaco",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Guide",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Guild",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.GutBomb",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Hayseed",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.HeadbandK",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.HenchmanSummer",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Hush",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Inferno",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Insight",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.IslandNomad",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Jekyll",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.JonesUnchained",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.JonesyTheFirst",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Jules",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Kit",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Kitbash",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Kyle",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.LilWhip",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.LoneWolf",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Longshot",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Ludwig",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Madcap",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Malcore",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Mancake",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Mandalorian",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Marigold",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.MaryJane",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Maven",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Maximillian",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Meowscles",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.MetalMouth",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.MetalTeamLeader",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Mitosis",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.MoistyMerman",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.MoonHawk",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.MulletMarauder",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Mystic",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Mystify",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Nitehare",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.NoSweat",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Obsidian",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.OmegaKnight",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Outcast",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Outlaw",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Panther",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Peely",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Penny",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Pitstop",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.PowerChord",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.PrincessFelicityFish",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Prisoner",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.PumpkinPunkGlitch",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.PunkKoi",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Quackling",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Ragnarok",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Ragsy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Rapscallion",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Raptor",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.RaptorianTheBrave",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Raven",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Rebel",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.RedOasisGooseberry",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.RelaxedFitJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Remedy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.RenegadeShadow",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Revolt",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Rex",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Rickus",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Riot",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Rook",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Ruckus",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Rustler",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.RustLord",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sabina",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SashSeargent",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ScrapknightJules",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ScubaJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SevenSentryNPC.Female",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SevenSentryNPC.Male",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Shade",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ShadowOps",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Shapeshifter",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SharpFang",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.ShotStopper",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Silencer",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sledgehammer",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sleuth",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SlurpJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Snomando",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Snowheart",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SnowSniper",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sorana",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SpaceChimp",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sparkplug",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SpecialForces",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Splode",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.StageSlayer",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Stashd",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sterling",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SummerDrift",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sunbird",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Sunflower",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SunrisePalace",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SuntanJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SurrrBurger",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.SwampStalker",DevComment="S17") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TandemTemplate",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TandemTestA",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TandemTestB",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TandemTestServices",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Temple",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TeriyakiFishToon",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Tess",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheFoundation",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheOrder",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheOrigin",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheParadigm",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheReaper",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheScientist",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheUnderwriter",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TheVisitor",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.TomatoHead",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Triggerfish",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Troops",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Tsuki",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Turk",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Unpeely",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.WeaponsExpert",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.WickedDuck",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.WildCard",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Willow",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Wrath",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.WreckRaider",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.York",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Zadie",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Tandem.Zenith",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.TestLocation.01",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.TestLocation.02",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Umami",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.Weather",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Bamboo",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Cabin",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.ChickenCoop",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Class",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Desert",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Haunted",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.House",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Lake",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.MagicForest",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Med",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Ocean",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Plain",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpawnLocation.World.Snow",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Specialist.Medic.Hiree",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Specialist.Medic.RecentlyHealed",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Specialist.Supply.Hiree",DevComment="") ++GameplayTagList=(Tag="Athena.AI.SpeechBubbles.RiftGate.Approach",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Tempest.Exclusion.All",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Tempest.Exclusion.LightningStorm",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Tempest.Exclusion.Tornado",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Umami.IgnoreAndDetachForKidnap",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Vehicle.AnySeat",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Vehicle.Driver",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Vehicle.Passenger1",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Vehicle.Passenger2",DevComment="") ++GameplayTagList=(Tag="Athena.AI.Vehicle.Passenger3",DevComment="") ++GameplayTagList=(Tag="Athena.BattleLab",DevComment="") ++GameplayTagList=(Tag="Athena.BattleMap",DevComment="") ++GameplayTagList=(Tag="Athena.BattleMap.NoSquadMember",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.Damaged",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.FirstWielder",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.Leap",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.PullCooldown",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.Swing",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.SwingTimeGE",DevComment="") ++GameplayTagList=(Tag="Athena.Blade.Wielder",DevComment="") ++GameplayTagList=(Tag="Athena.BlingPack.CantActivate",DevComment="") ++GameplayTagList=(Tag="Athena.Bounty.Exclude",DevComment="") ++GameplayTagList=(Tag="Athena.Chrome.CanBePetrified",DevComment="Apply this tag to assets that should turn petrified when Chromed.") ++GameplayTagList=(Tag="Athena.Chrome.DoNotChrome",DevComment="Apply tag to objects that should not be chromed ever") ++GameplayTagList=(Tag="Athena.Chrome.NoPropComponent",DevComment="") ++GameplayTagList=(Tag="Athena.Chrome.NotPhaseable",DevComment="") ++GameplayTagList=(Tag="Athena.Chrome.Valet",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.BattleBus",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.BattleBusMobile",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.BiplaneRoll",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.CampfireFail",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.CampfireSuccess",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ChugSplash",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ChugSplashInformative",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.CrouchSlide",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.EnterSlide",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.EnterSlide.Complete",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.EquipWeapon",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.EquipWeaponMobile",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.FishingCan",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.FishingFishFlopper",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.FishingFishSlurpfish",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.FishingFishSmallFry",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.FishingTrophy",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.Fly",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.Harvest",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.HealBazooka",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.Landing",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.LandNearPoi",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.MarkEnemy",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.MarkLocation",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearbyChest",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearbyChestMobile",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearbyLootMobile",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearbyLootPC",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearFactionChange",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearHideableObject",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearObjectFactionScanner",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearUpgradeBench",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.NearUpgradeBenchWithMaterial",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.PreferredItemSlots",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.PrepareToJump",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.Recycler",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ResolveCannon",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ResolveCannon.Complete",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ShieldBlazer",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.Skydiving",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.SquadComs",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.SquadComs.HiredNPC",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.SquadComs.OpenedPicker",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.SquadComs.ShowedMultipleCategories",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.StormForming",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.StormFormingMobile",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ThrownConsumable",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UpgradeBenchSidegrade",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseBandage",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseBandageInformative",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseChugJug",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseMedKit",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseMedKitInformative",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseShieldPotion",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseShieldPotionInformative",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseSlurpJuice",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseSlurpJuiceInformative",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseSmallShieldPotion",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.UseSmallShieldPotionInformative",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.VehicleValetCar",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.VehicleValetGasCan",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.VehicleValetGasPump",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.VehicleValetLowFuel",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.WeakSpot",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.Welcome",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.WhirlpoolGeyser",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ZeroPointCrystal",DevComment="") ++GameplayTagList=(Tag="Athena.ContextualTutorial.ZeroPointFish",DevComment="") ++GameplayTagList=(Tag="Athena.Crafting.ForceCraftableIcon",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.Competitive",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HideForLoading",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideBuildMenu",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideCraftingResources",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideEliminationCounter",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideEquippedItem",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideExperienceBar",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideExperienceLevel",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideExperienceSupercharged",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideExperienceUI",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideHealth",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideHealthNumbers",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideHUDInfo",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideInteractionPrompts",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideMapPrompt",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideMiniMap",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HidePickupStream",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HidePlayerCount",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HidePlayerInventory",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideRoundInfo",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideRoundTimer",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideShieldNumbers",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideShields",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideStormNotifications",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideStormTimer",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.HUDController.HideTeamInfo",DevComment="") ++GameplayTagList=(Tag="Athena.Creative.MM",DevComment="Used to identify Matchmaking playlist") ++GameplayTagList=(Tag="Athena.Damage.IgnoreTrapDamage",DevComment="Actors with this tag will ignore damage from traps. New damage traps will need to add this tag to the exclude-tags section in their GA\'s target selection settings.") ++GameplayTagList=(Tag="Athena.Disco.Rutabagasandwich.active",DevComment="") ++GameplayTagList=(Tag="Athena.Disco.Rutabagasandwich.Key",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.BuriedChest",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.Agency",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.BoxFactory",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.Date",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.Flotilla",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.HollowBase",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.OilRig",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.SharkIsland",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.Tomato",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.UndergroundBase",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Keycard.Yacht",DevComment="") ++GameplayTagList=(Tag="Athena.EnvItem.Lock.Scanner",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.CanTeleport",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.CanTriggerKnock",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.EmptyHands",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.Flopper.Eat",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.Instorm",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.SupressSuperJump",DevComment="") ++GameplayTagList=(Tag="Athena.Event.Gameplay.Swim",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Alter",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.ArsenicCore.Ghost",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Columbus",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Default.Donut",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Default.POI.Box",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Default.POI.MountainBase",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Default.POI.OilRig",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Default.POI.SJI",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Deimos",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Dev.Dev1",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Dev.Dev2",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Dev.Dev3",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Dev.Dev4",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Dev.Dev5",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Ego",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.GameParticipant",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Gasket.Date",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Gasket.Lonely",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Gasket.Mang",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Gasket.Shield",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Gasket.Tomato",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Gasket.Wasabi",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Generic.Friendly",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Generic.Hostile",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Generic.Neutral",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.IO",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.IO.LS",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Lonelies",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.LonelyIO",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.MeleeEnemy",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Mother",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.MotherBirdParticipant",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.MotherGround",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PassivePhase.Burt",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PassivePhase.Crow",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PassivePhase.Grandma",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PassivePhase.Nug",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PassivePhase.Smackie",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PassivePhase.SpicySake",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.PhysicsObjects",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Seven",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Seven.Encounter",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Spire.Boss",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.STW.Defender",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.STW.Held",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.STW.Husk",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.STW.Tamed",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Amphibious.Smackie",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Aquatic.Shark",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Generic.Passive",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Generic.PreditorAggressive",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Generic.Prey",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Prey.Avian.Crow",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Special.Held",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Wildlife.Special.Whiff",DevComment="") ++GameplayTagList=(Tag="Athena.Faction.Zombie",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Prop.FrozenLoot",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Prop.Runestone.Held",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.BeingMoved",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.CatchFirefly",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.Cloaked",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.Converted",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.EnteringPropFromInventory",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.FuelEquipped",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.Hitchhiker",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.LeavingpropFromInventory",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.RefillGas",DevComment="Tag to track if player is refilling gas tank from gas pump.") ++GameplayTagList=(Tag="Athena.Gameplay.Status.RefillVehicle",DevComment="Tag to change animation if player is refueling vehicle") ++GameplayTagList=(Tag="Athena.Gameplay.Status.TeamupBehaviorDisabled",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.Under",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.Unlocked",DevComment="") ++GameplayTagList=(Tag="Athena.Gameplay.Status.Wind.InWindSpline",DevComment="") ++GameplayTagList=(Tag="Athena.GuidedTutorial",DevComment="") ++GameplayTagList=(Tag="Athena.GuidedTutorial.StartPoint",DevComment="") ++GameplayTagList=(Tag="Athena.HideJustPack",DevComment="") ++GameplayTagList=(Tag="Athena.HUD.Disable",DevComment="") ++GameplayTagList=(Tag="Athena.HUD.PostpartyMask",DevComment="") ++GameplayTagList=(Tag="Athena.IsAPhoebe",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Blade",DevComment="") ++GameplayTagList=(Tag="Athena.Item.DangerGrape.UseSmoke",DevComment="") ++GameplayTagList=(Tag="Athena.Item.EnvCampfire",DevComment="") ++GameplayTagList=(Tag="Athena.Item.NPC.Assignment.Loot.Safe",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Passives.DoubleJump",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Passives.HealthRegen",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Passives.MovementSpeed",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Passives.ShieldBoost",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Passives.ShieldRegen",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Passives.SwimSpeed",DevComment="") ++GameplayTagList=(Tag="Athena.Item.ShieldBubble.Core",DevComment="") ++GameplayTagList=(Tag="Athena.Item.ShieldBubbleSafeZone",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Snowman",DevComment="Item Tag for Sneaky Snowman") ++GameplayTagList=(Tag="Athena.Item.Utility.Flaregun.Firework",DevComment="") ++GameplayTagList=(Tag="Athena.Item.Wumba.CannotUpgrade",DevComment="") ++GameplayTagList=(Tag="Athena.ItemAction.Secondary.Throw",DevComment="") ++GameplayTagList=(Tag="Athena.ItemAction.Secondary.Use",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.Coastal",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.Cold",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.Cold.WholeMap",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.HotSprings",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.Lava",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.Reality",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.StarterIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Biome.Swamp",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Blank",DevComment="") ++GameplayTagList=(Tag="Athena.Location.FloatingGarbage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.JunkFishArea",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.AnimalBuilding.Pig",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.AnimalBuilding.Rabbit",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.BirthdayCake.CakeZoneGE",DevComment="This GE represents being inside the Cake Zone") ++GameplayTagList=(Tag="Athena.Location.Meta.BirthdayCake.NearACake",DevComment="This is applied when a player is near one of the birthday cakes.") ++GameplayTagList=(Tag="Athena.Location.Meta.Corner.NE",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.Corner.NW",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.Corner.SE",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.Corner.SW",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.GasStation.11",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.GasStation.12",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.GasStation.14",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Meta.Season7.C",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.AirborneButterBarn",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.BeachyBluffs",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CampCod",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CampCuddles",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Carl",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CattyCorner",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Cauliflower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.ChonkersSpeedway",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.ClassyCondos",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.ColesMining",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Coliseum",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CondoCanyon",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CovertCavern",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CrazyCave",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CreamyCrossroads",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.CreativeIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.DailyBugle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.DirtyDocks",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.DustyDepot",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Farm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Festivus",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.FortaniumQuarry",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Fortilla",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.FrenzyFarm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.GovernmentComplex",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HauntedHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HeraldFortress",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HerosHarvest",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HeyBoo",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HollowHangout",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HollyHatchery",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HollyHedges",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HunterHaven",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.HuntersHaven",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.IcebergStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.IceCavern",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.IndustrialTown",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.karl",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.KitsCantina",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.LazyLagoon",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.LazyLake",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.LightCastle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.LogJam",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.MainTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.MedievalTown",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.MoistyPalms",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.MountainFishing",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.MountainMeadow",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.NightmareJungle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.NorthHarbor",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.OilrigIslands",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.BoatRace.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.BoatRace.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.GliderDrop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.MainStage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.ObstacleCourse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.PirateCove",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.RaceCenter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.Theater",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Papaya.TheHub",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.PowerPlant",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.PressurePlant",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.PretzelCrater",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.PretzelRestaurant_B",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.PretzelRestaurant_P",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.PretzelWarehouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.RealityFalls",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.RealityFortress",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Richville",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.RockyReels",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SaltyTowers",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Sanctuary",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.ScientistLab",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SentinelGraveyard",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.ShadowAgency",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.ShuffledShrines",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Sideways",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Skyfire",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SleepySound",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SlurpySwamp",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpicySteps",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SplitTown",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpyBase.HQ",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpyBase.MountainBase",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpyBase.OilRig",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpyBase.Shark",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpyBase.Test",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SpyBase.Yacht",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SunnyShores",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SunnySteps",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.SynapseStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.Carl",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.MountainMeadow",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.PleasantPark",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.PowerPlant",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.RetailRow",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.SlurpySwamp",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.SunnyShores",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.Tandem.WeepingWoods",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.TheBlock",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.TheFortress",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.TheJoneses",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.TheTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.TomatoLab",DevComment="") ++GameplayTagList=(Tag="Athena.Location.POI.WeepingWoods",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.BiFrostMark",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.FriendMonument",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.LawOffice",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.MountainRuin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.Statue",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.StormCenter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.Throne",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.WeatherStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Awake.Workshop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.BeachRentals.Sandcastles",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Blockbuster.VillainLair",DevComment="S10 Blockbuster Mission") ++GameplayTagList=(Tag="Athena.Location.Quest.ButterflyShard.Air",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.ButterflyShard.Buried",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.ButterflyShard.JewelPuzzle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.ButterflyShard.Statues",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.ButterflyShard.Vault",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Campfire.Night",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.DriveInCornField",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.EmpBox",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FlushHouse.Sandcastles",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.08",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.09",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.FNM.Gravestone.10",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.GiantSeat",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.MT4.CP.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.MT4.CP.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.MT4.CP.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.RedOasis.Pitch.01",DevComment="Locaotion tag for football pitches") ++GameplayTagList=(Tag="Athena.Location.Quest.RedOasis.Pitch.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.RedOasis.Pitch.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.RedOasis.Pitch.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.SandRocks.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.SandRocks.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.SandRocks.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.SkyPlatform",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Tower.CultArtifact",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Tower.LastLog",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Turbine",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.08",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.09",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.10",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.11",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.12",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.13",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.14",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.15",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.16",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.17",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.18",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.19",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.20",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.21",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.22",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.23",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.24",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Generic.25",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Kevin.EntryMemorial",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.Kevin.ExitMemorial",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Visit.PinkOatmeal",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.08",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.09",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quest.Wasabi.Marking.10",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.RecCenter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.Skeleton",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SpookyCornfield",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.08",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.09",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Quests.SwampHouse.10",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack01",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack02",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack03",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack04",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack05",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack06",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedlpoi.witchshack07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Adrift",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.AmberForest",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.AngryApples",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedpoi.AngryApples.FarmersMarket",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ArenaFloor01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ArenaFloor02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ArenaFloor03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ArenaFloor04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ArenaFloor05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ArkBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Awake.FriendMonument",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Awake.LawOffice",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Awake.Ruins",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Awake.Statue",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Awake.Throne",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Awake.Workshop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BallerCoaster.TopDeck",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BaseCampFoxtrot",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BaseCampGolf",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BaseCampHotel",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Battlements",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BeachBus",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BeachCarPark",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BeachParty",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BeachShacks",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BeachsideMansion",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BedlamButte",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BigBridge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BigBridge.Blue",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BigBridge.Green",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BigBridge.Purple",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BigBridge.Red",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BigBridge.Yellow",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BigDogHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BigGasStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BigIceLake",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BirdingDunes",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BlacksmithSpring",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Blimp.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BlockHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BoatClub",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BoatLaunch",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BoatRace",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BoatRentals",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BoatSales",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BobsBluff",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BobsHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Bobtimus",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BonfireCampsite",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BoT",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BoxFactory",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Bshop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BungalowBlooms",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BuoyBoat",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BuriedDesertTown",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.BuriedVillage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.BushFace",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ButterBarn",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CampCod",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CanoeRentals",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CaptainCarpsTruck",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CarMan",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CastleTower.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CastleTower.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Cattus",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CattyCornerGarage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Cauliflower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Chair",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ChopShop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Clams",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CliffHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CliffsideRuinedHouses",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CoastalContainers",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CoastalGas",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CoastalTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CoralCove",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CorruptedIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CosmosCrashSite",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrackshotCabin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CrackshotsCabin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CrashedAirplane",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.CrashedCargo",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrashedHelicopter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrashSite.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrashSite.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrashSite.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrashSite.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrashSite.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Crater.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Crater.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Crater.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CrazyCactus",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.CuddleTrack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DateHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DateRoom",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DBlimp.Bugle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DDMachine",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DeadCabin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DeadTreePond",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DenseForest",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DenseMushroomGrove",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DesertArch",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DesertMansion",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DesertOasis",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.DigSite",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DirtTrack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DockingBay.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DockingBay.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DockingBay.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DockingBay.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DockingBay.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DownedBlimp.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DownedBlimp.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DownedBlimp.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DownedBlimp.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DownedBlimp.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.DrillHill",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.EyeLake",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FancyMansion",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FernIsle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishFarm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishingShack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishingShacks",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishRestaurantBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishRestaurantBarge.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishRestaurantBarge.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishyIceberg",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FishyIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FloatingShaft",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FloodedCraggyCliffs",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FloodedDirtyDocks",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FloodedWeepingWoods",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.FlopperPond",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FlushBuilding",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.ForkKnife",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.FortRuin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Hermit",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault.4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault.5",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FP.Vault.6",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FriendlyCube",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FrostyFields",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FrozenFrog",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FrozenWaterfall",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.FuelDocks",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Galileo.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Galileo.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Galileo.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Galileo.4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Galileo.5",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.GeyserShortcut",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.GlassCases",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.GoldenIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.GreenGas",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.GrottoRuins",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.GuardedVillage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Hatch.A",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Hatch.B",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Hatch.C",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.HayHillbilly",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HenchHut",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HeroesPark",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenRuin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenUFOVehicle.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenUFOVehicle.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenUFOVehicle.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenUFOVehicle.4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenUFOVehicle.5",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HiddenVillage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HighHoop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HillHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.HilltopHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HolidayStore",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.HomelyHills",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.HorseShoeBend",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IceBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IcebergStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IceBlockFactory",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IceCaverns",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IceChair",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IceHotel",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ImpossibleRock",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.08",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.09",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Convoy.10",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.DS.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.DS.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.DS.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.DS.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.DS.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Outpost",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Outpost.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Outpost.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Outpost.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Outpost.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IO.Outpost.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IODrill",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IslandHomes",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.IslandLodge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IslandTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.IsolatedKnightTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.08",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.09",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.10",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.11",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.12",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.13",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.14",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.15",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.16",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Jetlanding.17",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Joels",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.KayakCampsite",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.KitsKantina",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.KnightFuel",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.LawnmowerRaces",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.LazyLakeIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Lighthouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LlamaFarm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LoggingDock",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LoggingShack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LoopersLanding",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LootLake",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LootLakeHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LootLakeIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.LoversLookout",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.LushLogs",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Mansion",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.MeowsculesCabin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MilitaryCamp.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MilitaryCamp.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MilitaryCamp.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MilitaryCamp.4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MilitaryCamp.5",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MountainDanceClub",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.MountainSummit",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MountainVault",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MountF8",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.MountH7",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.MushroomFarm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.MushroomVillage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.NoahHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.NorthHarbor",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.NoSweatHQ",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.OnyxSphere",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Outpost.Alpha",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Outpost.Beta",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Outpost.Charlie",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Outpost.Delta",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Outpost.Echo",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.OvergrownHeads",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.BoatRace1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.BoatRace2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.BoatRamps",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.FishingPond",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.GiantSkeleton",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.MountainPeak",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.ObstacleCourse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.PirateCove",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.RaceCenter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.SecretBeach",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Papaya.SoccerField",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ParaConduit",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ParaConduit.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ParaConduit.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ParaConduit.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ParaConduit.4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ParaConduit.5",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PartyBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PartyBlimp",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PawnBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Pawntoon",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PinnaclePeak",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.PipePerson",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.PipePlayground",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PirateBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PirateRadioBoat",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PirateRowBoat",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PizzaPitBarge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PlantNursery",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.PowerDam",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PowerTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PretzelRestaurant_B",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PretzelRestaurant_P",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PretzelWarehouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PrezelCrater",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PrimalPond",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.PuddlePond",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadarDishBase.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RadioStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RadioTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RangerTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RapidsRest",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RaptorFarmHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RealityFalls",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RealityRoots",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RealityRoots.ParaConduit",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ReasearchLab.A",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RecruitmentOfficeAlter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RecruitmentOfficeEgo",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RentalLodge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ResearchLab.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ResearchLab.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ResearchLab.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RestaurantGas",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RiftGateTower",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.RiskyReels",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RockFace",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RockMounds",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RuinedTempleJungle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RuinedTempleTropical",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Runway",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RuralGasStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.RustyCarBeach",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SawMill",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.ScrapYard",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SentinelGraveyard",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.FB.07",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.RocketBase",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Seven.RockRock",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SharkRemains",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SheriffOffice",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.ShipwreckCove",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ShroomStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SinkHole",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SirenIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SkyPirateship",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SlapStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SleepyShrubs",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SmallFarm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SmallFarm2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SmallMine",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SmallQuarryDock",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SmallTemple",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SnowConeTruck",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SnowShelter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SouthernIsle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SpyBase.MountainBase",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SpyBase.OilRig",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SpyBase.SharkIsland",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SpyBase.Yacht",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SpyObstacleCourse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.StackShack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SteelFarm",DevComment="") ++GameplayTagList=(Tag="athena.location.unnamedpoi.SteelFarm.CornFields",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.StumpyRidge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SunnyStepRuin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SurfaceHubIO-D1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SurfaceHubIO-E6",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SurfaceHubIO-F4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SurfaceHubIO.D1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SurfaceHubIO.E6",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.SurfaceHubIO.F4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.SwampVille",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.TallestMountain",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Abstrakt",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.AlienSummer",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.AlienTrooper",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Antique",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.BabaYaga",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Believer",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.BigBridge.Green",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Bigfoot",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.BigMouth",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.BistroAstronaut",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Brat",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Broccoli",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.BunkerJonesy",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.CatBurglar",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.CavernArmored",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.CerealBox",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.CosmosCrashSite",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.CritterManiac",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.CritterRaven",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Crush",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.DarkJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Dire",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Division",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Dreamflower",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Dusk",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.EightBall",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Ember",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Emperor",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Faux",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.FlushBuilding",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.GhostHunter",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Giggle",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.GrimFable",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Hayseed",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.HeadbandK",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.HenchmanSummer",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Hush",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Invader",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Kitbash",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Madcap",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Malcore",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Marigold",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Maven",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.MoistyMerman",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Nitehare",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.Tandem.NotYetPlaced",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Penny",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Pitstop",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Polygon",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.PowerDam",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.PretzelRestaurant_P",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.PrimalPond",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Prisoner",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.PunkKoi",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.RadioStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Ragsy",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Raven",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.RetailRow",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Rickus",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Riot",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.RiskyReels",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Rook",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Ruckus",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.RustLord",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.ScrapYard",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.ScubaJonesy",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.ShadowOps",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Sledgehammer",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Snomando",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Sorana",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.SpaceChimp",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.SpecialForces",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.StoneViper",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.StreetCuddles",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.SunrisePalace",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.SwampStalker",DevComment="S17") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.TeriyakiFishToon",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Tsuki",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.VikingShip",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.WeepingWoods",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.Wrath",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tandem.York",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TantorSphere",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.TeddyBearSpies",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TempleOutpost.1",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TempleOutpost.2",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TempleOutpost.3",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TempleOutpost.4",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TempleStairs",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TheYacht",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TinyTimberTent",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TinyTown",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.ToiletThrone",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.ToiletTitan",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TomatoHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TomatoSphere",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.tomatowater",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Inside",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Outside.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Outside.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Outside.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Outside.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Outside.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Tower.Outside.06",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TowerAftermath",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TowerCrater",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TowerPoint",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TowerRuins",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.ToyFactory",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TrailerPark",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.TreeFragment",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Trophy",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Turbo",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.UndergroundRadioStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.UnderGroundSoccer",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.UnremarkableShack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.VanLife",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.VikingShip",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.Village",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.VillageShack",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.VolcanoMonitoringStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.WagonTrail",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.WaterfallGorge",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnnamedPOI.WeatherStation",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.WindmillFarm",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.WindPowerBuilding",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.WindyCanyon",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.WoodsmanCabin",DevComment="") ++GameplayTagList=(Tag="Athena.Location.UnNamedPOI.WreckRavine",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Grove.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Grove.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Grove.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Grove.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Grove.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Map",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Scope.01",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Scope.02",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Scope.03",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Scope.04",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.BVG.Scope.05",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.Festivus.Stage",DevComment="") ++GameplayTagList=(Tag="Athena.Location.Visit.Quest.NOMS",DevComment="Quest Tag for NOMS quest") ++GameplayTagList=(Tag="Athena.Locations.POI.MountainMeadows",DevComment="") ++GameplayTagList=(Tag="Athena.Locations.POI.PretzelCrater",DevComment="") ++GameplayTagList=(Tag="Athena.Locations.POI.PretzelWarehouse",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.Placement",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.01",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.02",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.03",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.04",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.05",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.06",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.07",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.08",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.09",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.10",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.11",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.12",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Argon.TP.13",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.Chicago.Equipped",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.HasPurple",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.Hero",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.Map",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.MiloWeap",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.MiloWeap.Launcher",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.MiloWeap.Sniper",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Ashton.Villain",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Bagel.ObjectiveArea",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Bismuth.Death",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.CobaltRespawn.FirstPoint",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.CobaltRespawn.POI.Lab",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.CobaltRespawn.RoundComplete",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.CobaltRespawn.RoundWin",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.CobaltRespawn.Win",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Fill.TouchingBigMesh",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Hydrogen.MatchComplete",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Hydrogen.PointCaptured",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Attackers",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.CapturePoint",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Defenders",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.DanceClub",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.FrenzyFarm",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.GhostSafehouse",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.Hydro16",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.LazyLake",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.Lighthouse",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.Lumberyard",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.MistyMeadows",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.PleasantPark",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.RadioTower",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.SandyShores",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.ShantyTown",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Location.WeepingWoods",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Played",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.RoundComplete",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.ShowTechSelectScreen",DevComment="triggers showing the Perk Select Screen UI") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.SupplyDropCollected",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Infiltration.Win",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.AI.Boss.Brute",DevComment="Marks an AI as a Mash LTM Brute boss.") ++GameplayTagList=(Tag="Athena.LTM.Mash.AI.Died",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.AI.DiedFromChainExplosion",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.EndingPOI",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.FinalObjective",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.JasonPOI01",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.JasonPOI02",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.JasonPOI03",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.NormalObjective",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.ObjectiveArea",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.Player.RessourceTicker",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.POI",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.ScoreMultiplier.Opened",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.ScoreMultiplierObjective",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.StartingPOI",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Mash.Title",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Poutine.FinalTest",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Poutine.StartTest",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.RespawnOp.TechAlert",DevComment="Alert when the player has an available tech selection") ++GameplayTagList=(Tag="Athena.LTM.RespawnPB.ShowMarkerUI",DevComment="Used to tell killer players to show the marker UI above their character") ++GameplayTagList=(Tag="Athena.LTM.Rumble",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Tag.ChangeTeam",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.POICamera",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.PushCart",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.RoundComplete",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.Spline",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.Spline.Checkpoint01",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.Spline.Checkpoint02",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.Spline.End",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.Uranium.Spline.Start",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.UraniumP.FinalRoundWin",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.UraniumP.MatchWin",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.UraniumP.RoundComplete",DevComment="") ++GameplayTagList=(Tag="Athena.LTM.UraniumP.RoundWin",DevComment="") ++GameplayTagList=(Tag="Athena.MiniMap.FullScreen.GameInfo",DevComment="") ++GameplayTagList=(Tag="Athena.MiniMap.FullScreen.PlayersLeft",DevComment="") ++GameplayTagList=(Tag="Athena.MiniMap.FullScreen.ResourceCount.Gold",DevComment="") ++GameplayTagList=(Tag="Athena.MiniMap.FullScreen.SquadEliminations",DevComment="") ++GameplayTagList=(Tag="Athena.MiniMap.FullScreen.StormInfo",DevComment="") ++GameplayTagList=(Tag="Athena.NoQuests",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.DrunkMissile",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.Jump",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.KickStomp",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.LoadSingleRocket",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.Missiles",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.ReloadShotgun",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.SelfDestruct",DevComment="") ++GameplayTagList=(Tag="Athena.Ostrich.Shotgun",DevComment="") ++GameplayTagList=(Tag="Athena.Papaya.HideForPostGame",DevComment="") ++GameplayTagList=(Tag="Athena.Phoebe.Escape",DevComment="") ++GameplayTagList=(Tag="Athena.PinkOatmeal.GreenGlop.Bucket.Inside",DevComment="") ++GameplayTagList=(Tag="Athena.PinkOatmeal.TriggerInsideObelisk",DevComment="") ++GameplayTagList=(Tag="Athena.Player.ClamberingBlocked",DevComment="") ++GameplayTagList=(Tag="Athena.Player.InVent",DevComment="") ++GameplayTagList=(Tag="Athena.Player.InVortex",DevComment="Added to the player pawn when skydiving in a vortex") ++GameplayTagList=(Tag="Athena.Player.SkytubeBlocked",DevComment="") ++GameplayTagList=(Tag="Athena.Player.UseOverrideSearchMontage",DevComment="") ++GameplayTagList=(Tag="Athena.Player.VentBlocked",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.33",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50CE",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50Cube",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50HE",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50SAU",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50Soaring",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.50v50.Showdown",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.5x20",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Antares",DevComment="LTM Gameplay Tag for Knockout Air Raid (Cobalt Antares)") ++GameplayTagList=(Tag="Athena.Playlist.Arena.Duo",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Arena.Solo",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Arena.Squads",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Arena.Trios",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Argon",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.ArgonHT",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.ArsenicCore",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Ashton",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Auto",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Bagel",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Barrier",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Barrier2",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Beagles",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Behind",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Bison",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Blades",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Bling",DevComment="Tag for Bling-specific challenges") ++GameplayTagList=(Tag="Athena.Playlist.Bling.Duos",DevComment="Tag for Bling-specific challenges") ++GameplayTagList=(Tag="Athena.Playlist.Bling.Solo",DevComment="Tag for Bling-specific challenges") ++GameplayTagList=(Tag="Athena.Playlist.Bling.Squads",DevComment="Tag for Bling-specific challenges") ++GameplayTagList=(Tag="Athena.Playlist.Blitz",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.BlockQuestProgressReport",DevComment="Block Quest/XP Reporting") ++GameplayTagList=(Tag="Athena.Playlist.BLPlugin",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Blue",DevComment="") ++GameplayTagList=(Tag="athena.playlist.bluecheese",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Bodyguard",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.BountiesOff",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Builder",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Care",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Carmine",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.ChaCha",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Classic",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Close",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Cobalt",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.CobaltRespawn",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Comp",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.CompPlugin",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Concert",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Creative.TDM",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Creative.Test",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.CreativeRoyale.Edit",DevComment="CreativeRoyale Edit Playlist") ++GameplayTagList=(Tag="Athena.Playlist.CreativeRoyale.NoLoot",DevComment="Tag added to playlist when no loot is set.") ++GameplayTagList=(Tag="Athena.Playlist.CreativeRoyale.Play",DevComment="CreativeRoyale Play Playlist") ++GameplayTagList=(Tag="Athena.Playlist.Crucible",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.DADBRO",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Daybreak.PE",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Daybreak.PP",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Default",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.DefaultXP",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.DisableAthenaHUD",DevComment="Disables DynamicUI AthenaHUD for a given playlist") ++GameplayTagList=(Tag="Athena.Playlist.DisableTacticalShield",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Disco",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Dropzone",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Explosives",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Fill",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Final",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Fluoride",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.FortniteB",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Galileo",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.GG",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Goose",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Green",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Ground",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Hard",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.HasTopUI",DevComment="Playlist contains elements in the top center of the HUD") ++GameplayTagList=(Tag="Athena.Playlist.Haven",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.HeadsetCup",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Heavy",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.HighExplosive",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Hightower",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Hover.LargeTeam",DevComment="Tag for the large team version of the hoverboard LTM") ++GameplayTagList=(Tag="Athena.Playlist.Hydrogen",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.HydrogenHightower",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Impact",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Infiltration",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Infiltration.2",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Infiltration.3",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Infiltration.4",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Infiltration.5",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.IsALargeTeamMode",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.ItemsTest",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Junior",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Keep",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.LagerTandemTest",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.LagerTest",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.LeadAlloy",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Lite",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Lotus",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Low",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.LTM.AllAI",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Madman",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Mash",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Melt",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Mira",DevComment="LTM Gameplay Tag for Knockout Shuffle (Cobalt Mira)") ++GameplayTagList=(Tag="Athena.Playlist.Nickel",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Nitrogen",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildBR.Comp",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildBR.Duo",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildBR.PluginLoot",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildBR.Solo",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildBR.Squad",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildBR.Trios",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.NoBuildingMaterials",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.O2",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Omaha",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Paradise",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Payback",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Phoebe",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.PhoebeGym.Solo",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Phosphorus",DevComment="Tag for Phosphorus LTM Playlists") ++GameplayTagList=(Tag="Athena.Playlist.Poutine",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Pow",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Purple",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Race",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Rebirth",DevComment="LTM Rebirth Playlist Gameplay Tag") ++GameplayTagList=(Tag="Athena.Playlist.RestrictedLoot",DevComment="For specific playlists with highly-specific loot.") ++GameplayTagList=(Tag="Athena.Playlist.Score",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Seize",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Showdown.Crafting",DevComment="Controls crafting plugin exclusions for competitive.") ++GameplayTagList=(Tag="Athena.Playlist.Showdown.NPCServices",DevComment="Controls what is sold by NPC\'s in Arena & Tournaments") ++GameplayTagList=(Tag="Athena.Playlist.SkySupply",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Slide",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Sneaky",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Snipers",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Soaring",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.SolidGold",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.SpecialEvent",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.SpecialEvent.CycloneJerky",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.SpecialEvent.FritterAdd",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.SpecialEvent.Kiwi",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.SpecialEvent.Yogurt",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Spygames",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Stage",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Steady",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Strategic",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Swap",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Sword",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Tag",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Tank",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Taxes",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.TeamRumble",DevComment="Tag for team rumble playlists.") ++GameplayTagList=(Tag="Athena.Playlist.TeamRumble.Low",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.TeamRumble.SolidGold",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.TeamRumble.Unvaulted",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.TeamRumble.Vamp",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Test",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Titanium",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Toss",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Tournament",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Trailers",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Traverse",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Unvaulted",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.UraniumP",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Vamp",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Varg",DevComment="Test varg features on user map") ++GameplayTagList=(Tag="Athena.Playlist.Velocity",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Vendetta",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.Wax",DevComment="") ++GameplayTagList=(Tag="Athena.Playlist.WW",DevComment="") ++GameplayTagList=(Tag="Athena.Plugin.GameMod.Gasket",DevComment="") ++GameplayTagList=(Tag="Athena.Plugin.GameMod.Gibson",DevComment="") ++GameplayTagList=(Tag="Athena.Plugin.GameMod.GibsonPerfTest",DevComment="") ++GameplayTagList=(Tag="Athena.PluginSpawner.Curacao.Pawn",DevComment="") ++GameplayTagList=(Tag="Athena.POIText.ResolveConflict",DevComment="") ++GameplayTagList=(Tag="Athena.POIText.Sideways",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.AugCategory.Changer",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.AugCategory.Combat",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.AugCategory.Looting",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.AugCategory.Mobility",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Augment.GrantedWeapon",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Extinguish",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Fortanium.Destroy.Object",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Fortanium.Hit",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Fortanium.Launch",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.KnightShield.BlockedHits.Deployed",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.KnightShield.BlockedHits.Held",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.KnightShield.DeployedShield",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.Aerialist",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.Bloodhound",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.BowSpecialist",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.BushRange",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.ChugGunner",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.DangerHero",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.DeftHands",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.DemoMunitions",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.FirstAssault",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.Forecast",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.Harvester",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.IcySlide",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.JellyAngler",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.LightAmmo",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.LightFingers",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.MechanicalArcher",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.MidasTouch",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.Parkour",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.PartyTime",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.PeelyPlunder",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.PistolAmp",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.RarityCheck",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.RifleRecycle",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.RiftJectorSeat",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.RushingReload",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.ShadowStriker",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.ShotgunSiphon",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.SoaringSprint",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.SplashMedic",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.StormMark",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.Supercharged",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.TacticalArmory",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.TrickedOut",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Muster.ZeroChance",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.POICalimed",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.POIClaimed",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.RecentlyMantledWaiting",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.ShockwaveMace.Launch.Enemy",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Shotgun.DoubleTap",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.SlapsDrink.Consumed",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.SlapsDrink.Sprint.Completed",DevComment="") ++GameplayTagList=(Tag="Athena.Quest.Visit.Kevin.EntryMemorial",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Chicago.Shield",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Hippo.HasGrappled",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Indigo.Hovering",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Milo.Jetpack",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Milo.Primary",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Milo.Secondary",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Ashton.Turbo.Secondary",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Balloons.Active",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.DishFries",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Generic",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Goose.Weapon.C",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Goose.Weapon.R",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Goose.Weapon.SR",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Goose.Weapon.UC",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Goose.Weapon.VR",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Grapple.Grape",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Grapple.Grape.Timer",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Heal.BirthdayCake",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.AppleSauce",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.BoomBox",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.BottleRocket",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.C4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.ChillBronco",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.ChillerGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.DanceGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.DogSweater",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.Dynamite",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.ExplosiveGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.GasGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.ImpulseGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.IncendiaryGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.JollyRascal",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.Launchpad",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.Molotov",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.PortaFort",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.PortaShack",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.Present",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.SmokeBomb",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.SneakySnowman",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Thrown.StickyGrenade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Use.Balloon",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Use.Bush",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Use.RiftToGo",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ability.Use.SuperMedkit",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Butterfly.Air",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Butterfly.Buried",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.ButterflyShard.Air",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.ButterflyShard.Buried",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.ButterflyShard.JewelPuzzle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.ButterflyShard.Statue",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.ButterflyShard.Vault",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.JewelPuzzle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Q5.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Statue.Crouch",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Statue.Emote",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Statue.Jump",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.AgentJones.Vault",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ashton.RockPickup",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Awake.BabyGroot",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Awake.Hammer",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Awake.Sapling",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Balloons",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BaneAxe",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BanePack",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BigChuggus.LovePotion.Place",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BigOneTap",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.BeachyBluffs",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.D4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.DirtyDocks",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.G7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.MountainMeadows",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.PleasantPark",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.RetailRow",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BillboardMissionKit.SunnyShores",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BiomeScanner",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BiplaneTimeTrial.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BiplaneTimeTrial.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BiplaneTimeTrial.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BiplaneTimeTrial.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BiplaneTimeTrial.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Jam",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BlackMonday.Strawberry.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BoatTimeTrial.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BoatTimeTrial.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BoatTimeTrial.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BoatTimeTrial.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BodyScanner.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Book.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BotMultiKill",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BottleFlipTarget",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BouncyBallBounces",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BouncySlurpshroomUsed",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Bounty.Hunter",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Bounty.Hunter.Headshot",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Bounty.Hunter.Poached",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Bounty.Poacher",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Bounty.Protector",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Bounty.Target",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.BreakbarrelTwoTap",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Buffcat.ALTER",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Buffcat.EGO",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Build.NearAllyBuilding",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Build.NearQuestActor",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Building.Cactus",DevComment="For destroy cacti in the desert biome quest") ++GameplayTagList=(Tag="Athena.Quests.Building.Chair",DevComment="For destroy chair quest") ++GameplayTagList=(Tag="Athena.Quests.Building.Furniture",DevComment="Tag for destroy furniture quest") ++GameplayTagList=(Tag="Athena.Quests.Building.Prop.IOFiles",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Building.Prop.Monitor",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Building.Prop.SandCastle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Building.Prop.Scarecrow",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Building.TelephonePoles",DevComment="For destroy telephone quest, will tag both telegraph and telephone poles") ++GameplayTagList=(Tag="Athena.Quests.Building.Vault.Door",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Building.Wood.Fences",DevComment="Tag for destroying wood fences quest") ++GameplayTagList=(Tag="Athena.Quests.Building.Wood.Palettes",DevComment="Tag for destroying wood palettes quest") ++GameplayTagList=(Tag="Athena.Quests.Burrowing.Resurfaced",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Cashier.Emote",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.MapS10.Theodolite.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Charm.Virus",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ChilledDistanceTraveled",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.CFood.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.CFood.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.CFood.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.CFood.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.CFood.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Clue.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Clue.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Clue.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Clue.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Clue.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Guide.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Guide.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Guide.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Guide.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.ParentingBook.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Collect.Records.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CollectSidewaysPillar",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ConspiracyBoard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CookBook.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CorruptionCluster",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CorruptionCluster.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CorruptionCluster.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CorruptionCluster.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CorruptionPuzzle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CowDecoy.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CrouchNearEnemy",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.ChocolateBox.0",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.ChocolateBox.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.ChocolateBox.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.ChocolateBox.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.ChocolateBox.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.ChocolateBox.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Crush.GiveValentine",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CycloneJerkyBillboard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.CycloneJerkyBillboard.Progress",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DamageToBoogieBombed",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceDistanceTraveled",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceGardener",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.Festivus.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.Festivus.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.Festivus.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.Generic.31",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.Generic.32",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.Generic.33",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.HolidayTree.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.HolidayTree.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.HolidayTree.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceLocation.HolidayTree.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DanceNearGuard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DeadDrop",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Destroy.EnemyBuilding.Biplane",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Destroy.Nutcracker",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DestroyedHauntedItem",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Box",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Box.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Box.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Camera.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Camera.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Camera.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Camera.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Camera.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Container.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.DataCard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Electronic.Parts",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Electronic.Props",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Explosives",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Explosives.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Explosives.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Explosives.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Explosives.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Explosives.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Jammer.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Jammer.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Jammer.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Jammer.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Jammer.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Rock.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Seismometer.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Stump",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Supplies.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Supplies.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Supplies.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Supplies.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Supplies.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Dice.Telescope.Part",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DisasterBook.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DisasterBook.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DisasterBook.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DisasterBook.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DisasterBook.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DisasterBook.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.Biplane",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.Gliding",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.InSkyTube",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.InSpookyMist",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.OffroadIO",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.OnFoot.MistyMeadows",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.SandTunneling",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Distance.Sliding.Slide25",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Doorbell",DevComment="Tag for doorbell interactables quest") ++GameplayTagList=(Tag="Athena.Quests.Doorbell.Broken",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Blue.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.21",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.22",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.23",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.24",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DragonEgg.Red.25",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DrivePassengers",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DriveToPOI.Roadtrip",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DriveToPOI.SweatyToMisty",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DropOffatCCorLL",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Duck.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Duck.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Duck.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.DuelDamage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Elevation.Highest",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Elim.DBNO.Harvester",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.EmoteNearAlliedStructure",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.EmoteNearWildlife",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.EmoteSmokingVehicle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.EquipDisguise",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Festivus.Poster",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.FindBodysnatcher",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.FindPirateBooty",DevComment="Quest hook for treasure map") ++GameplayTagList=(Tag="Athena.Quests.Fireworks",DevComment="Tag for interactable fireworks quest") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fireworks.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Firststrike",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fishstick.Dinner",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fishstick.Rose",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.FlyingDisc.SelfCatch",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.cb.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.DownedHelicopter",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Sign",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Sign.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Sign.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Sign.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Sign.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Sign.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.Telescope.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Foreshadowing.TV.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Fortbyte.001",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.FoundCoolestPlayer",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Galileo.Lobster.BlockedDamage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Galileo.Lobster.BlockedDamage.Lobster",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Galileo.Lobster.BlockedLobster",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Galileo.RaiseBanner",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Galileo.SupplyCrate",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GasStation.CattyCorner",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GasStation.LazyLake",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GasStation.Winterfest",DevComment="POI volume tag for winterfest gas station") ++GameplayTagList=(Tag="Athena.Quests.Generic.HiddenThing.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Glyph.00",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.01",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.02",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.03",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.04",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.05",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.06",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.07",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.08",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.Glyph.09",DevComment="Tags for Fortbyte interactable actors") ++GameplayTagList=(Tag="Athena.Quests.GoalScorer.Soccer",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.01",DevComment="Tag for the destroy check with quests") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.21",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.22",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.23",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.24",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GoldBalloon.25",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Golden.Alter",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Golden.Alter.PaidTribute",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grapple.Used.Successful",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.FindGnome",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.FindGnome.0",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.FindGnome.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.FindGnome.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.FindGnome.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.FindGnome.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.LovePotion.Take",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.UnburyGnome",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.UnburyGnome.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.UnburyGnome.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.UnburyGnome.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Grimbles.UnburyGnome.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Guyser",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.GuyserMultiple",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HackGasketTomato",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HarpoonPull",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HasDueled",DevComment="Player has been in a duel (bounty system variant)") ++GameplayTagList=(Tag="Athena.Quests.Hawaii.MassDestruction",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Heal.Squadmate",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Heal.Teammate",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HealFromCampfire",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HiddenXP.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HideInProp",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HideInProp.Hide5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HighTower.DateChain",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HighTower.Power.Date",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HighTower.TomatoAssault",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HighTower.WasabiPickaxe",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HightowerGrapeAbsorb",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HightowerSoyDistance",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HighTowerSquashHit",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Hiker",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HitHeadshots",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HitWeakPoints",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HitWeakPointsConsecutive",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HomingRocketLock",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HotSaucers.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Hurdle.HurdleFinished",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.HVAC",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.IDScan.Start",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Ignite",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.InDuel",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.InsideSideways",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Inspire.Token.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ActivateTinfoil",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.BuriedTreasure",DevComment="Target tag for buried treasure chest for athena quest") ++GameplayTagList=(Tag="Athena.Quests.Interact.MissionKit.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.MissionKit.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.MissionKit.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.MissionKit.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.MissionKit.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.MissionKit.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.ReplayLog.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Interact.Transformer.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.InVault",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Irwin.Nug.HeldDistanceTraveled",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Irwin.Nug.HeldFallDistance",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.AlienArtifact",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.Fireflies.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w01.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w01.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w01.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w01.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w02.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w02.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w02.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w02.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w03.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w03.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w03.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w03.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w03.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w04.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w04.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w04.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w04.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w04.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w05.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w05.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w05.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w05.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w05.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w06.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w06.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w06.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w06.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w06.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w07.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w07.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w07.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w07.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w07.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w08.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w08.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w08.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w08.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w08.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w09.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w09.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w09.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w09.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w10.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w10.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w10.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w10.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w11.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w11.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w11.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w11.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w12.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w12.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w12.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w12.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w13.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w13.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w13.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w13.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w14.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w14.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w14.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w14.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w15.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w15.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w15.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w15.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w16.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w16.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w16.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Blue.w16.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w01.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w01.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w02.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w02.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w03.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w03.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w04.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w04.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w05.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w05.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w06.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w06.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w06.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w07.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w07.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w07.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w08.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w09.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w09.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w10.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w10.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w11.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w11.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w12.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w12.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w13.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w13.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w14.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w14.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w15.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w15.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w16.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Gold.w16.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w01.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w02.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w03.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w04.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w05.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w06.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w07.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w08.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w09.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w10.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w11.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w12.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w13.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w14.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w15.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Green.w16.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Mini",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.MiniCombo",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w01.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w01.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w01.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w01.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w02.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w02.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w02.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w02.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w03.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w03.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w03.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w03.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w04.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w04.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w04.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w04.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w05.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w05.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w05.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w05.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w06.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w06.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w06.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w06.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w07.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w07.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w07.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w07.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w08.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w08.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w08.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w08.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w09.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w09.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w09.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w09.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w10.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w10.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w10.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w10.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w11.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w11.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w11.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w11.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w12.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w12.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w12.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w12.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w13.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w13.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w13.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w13.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w14.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w14.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w14.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w14.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w15.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w15.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w15.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w15.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w16.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w16.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w16.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.Purple.w16.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.21",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.22",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.23",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.24",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.25",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.26",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.27",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.28",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.29",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.30",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.31",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.32",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.33",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.34",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.35",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.36",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.37",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.38",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.39",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.40",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.41",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.42",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.43",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.44",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.45",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.46",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.47",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.48",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.49",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.50",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.51",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.52",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.53",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.54",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.55",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.56",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.57",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.58",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.59",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.60",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.61",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.62",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.63",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ItemCollect.XPCoin.SM.64",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Jenkins.Default.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.JerkyBounce",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.JerkyDance",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.LandFourHits",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.AbductedSlurpFactory",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.AlienCommunication.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.AlienCommunication.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.AlienCommunication.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Avacado.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Avacado.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Avacado.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BetweenDinosaurs",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BetweenHotsprings",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BetweenIceStatues",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.BusStop.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Clue.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Clue.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Clue.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Clue.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Crater.01a",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.DJBooth",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.21",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Donation.22",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutpost.01",DevComment="Location volume tags for s7 quests") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.A",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.B",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.C",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.D",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.E",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.F",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ExplorerOutposts.G",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Farthest.East",DevComment="FortPOIVolume Tag") ++GameplayTagList=(Tag="Athena.Quests.Location.Farthest.North",DevComment="FortPOIVolume Tag") ++GameplayTagList=(Tag="Athena.Quests.Location.Farthest.South",DevComment="FortPOIVolume Tag") ++GameplayTagList=(Tag="Athena.Quests.Location.Farthest.West",DevComment="FortPOIVolume Tag") ++GameplayTagList=(Tag="Athena.Quests.Location.FloatingPool",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ForkKnife",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Galileo.Ferry.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Galileo.Ferry.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Galileo.Ferry.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Galileo.Ferry.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Galileo.Ferry.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Galileo.Ferry.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.GiantFace.Desert",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.GiantFace.Jungle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.GiantFace.Snow",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Hatchery.Mountain",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Hatchery.Sea",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Hatchery.Swamp",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HeroMansion",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HighestElevation.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HighestElevation.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HighestElevation.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HighestElevation.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HighestElevation.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Camp",DevComment="Quest tag for Artemis Winterfest Tree at Camp") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Condo",DevComment="Quest tag for Artemis Winterfest Tree at Condo") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Daily",DevComment="Quest tag for Artemis Tree at Daily B") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.GreasyGrove",DevComment="Quest Tag for Artemis Winterfest tree at Greasy Grove") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.LogJam",DevComment="Quest tag for Artemis Winterfest Tree at Log Jam") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Reels",DevComment="Quest tag for Artemis Winterfest Tree at Reels") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Seven",DevComment="Quest tag for Artemis Winterfest Tree at Seven") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Shifty",DevComment="Quest tag for Artemis Winterfest Tree at Shifty") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Sound",DevComment="Quest tag for Artemis Winterfest Tree at Sleep Sound") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Speedway",DevComment="Quest tag for Artemis Winterfest Tree at Speedway") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.Suburban",DevComment="Quest Tag for Artemis Winterfest Tree at Suburban") ++GameplayTagList=(Tag="Athena.Quests.Location.HolidayTree.SummerCamp",DevComment="Quest tag for Artemis Winterfest Tree at Summer Camp") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Bounce.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Bounce.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Bounce.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Bounce.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Bounce.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Dance.Ski",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Dance.Yacht",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Jerky.Main",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.MotelOrRVPark",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.OnTop.ImpossibleRock",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.OnTop.Windbreakers",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateCamp.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.PirateShip",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.RaceTrackOrDanceClub",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.SharkNose",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.ShootTarget.ParadisePalms",DevComment="Location tag for quest") ++GameplayTagList=(Tag="Athena.Quests.Location.ShootTarget.RetailRow",DevComment="Location tag for quest") ++GameplayTagList=(Tag="Athena.Quests.Location.ShootTarget.WailingWoods",DevComment="tag for quest location") ++GameplayTagList=(Tag="Athena.Quests.Location.SlurpVat",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.StaminaHouse",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Sticks",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.TomatoShrine",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Umbrella",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.VikingShip",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.VillainLair",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.01",DevComment="Location volume tags for s7 overtime waterfall challenge") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.Waterfall.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.WebBouncers.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Location.YachtBow",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.LockScanner.Carried",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.LockScanner.Self",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.LTM.Close.2in30",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.LTM.Close.AirElim",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.LTM.Smash.50Combo",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mancake.SyrupBottle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mancake.SyrupBucket.0",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mancake.SyrupBucket.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mancake.SyrupBucket.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mancake.SyrupBucket.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mancake.SyrupBucket.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MantleXTimes",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MantleXTimes.Passive",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MarigoldDrop",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MarkedEnemy",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MaxHealthMaxShield",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Midas.ALTER",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Midas.EGO",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mindcontrol.Billboard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Mindcontrol.Disguise",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MirrorTagAdd",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.MirrorTagRemove",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Final",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Metal",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Metal.Collect",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Metal.Deposit",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Metal.Install",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Pyramid",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Sphinx",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Sphinx.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Sphinx.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Sphinx.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.CrudeBarrel.Wood1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Billboard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Escape",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Graffiti",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Map",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Paperboy",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Stage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Statue",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Tank.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Tank.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Trap",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Trap.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Trap.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Trap.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Trap.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Downfall.Trap.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.Attack",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.CollectP1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.CollectP2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.CollectP3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.Defend",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.InitialLocate",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.PowerUp",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.Returned",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.ReturnP1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.ReturnP2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Narrative.Traveler.ReturnP3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.NoBuild.Exclude",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.NOMS.01",DevComment="Tag for NOMS interactables quest") ++GameplayTagList=(Tag="Athena.Quests.NOMS.02",DevComment="Tag for NOMS interactables quest") ++GameplayTagList=(Tag="Athena.Quests.NOMS.03",DevComment="Tag for NOMS interactables quest") ++GameplayTagList=(Tag="Athena.Quests.NOMS.04",DevComment="Tag for NOMS interactables quest") ++GameplayTagList=(Tag="Athena.Quests.NOMS.05",DevComment="Tag for NOMS interactables quest") ++GameplayTagList=(Tag="Athena.Quests.NugChase.Active",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Oracle.Scroll.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Oracle.Scroll.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Oracle.Scroll.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Oracle.Scroll.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Oracle.Scroll.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Parasites.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Parasites.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Parasites.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Parasites.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Parasites.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Peppermint",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PhaseAfterSplash",DevComment="Player phased quickly after using chug splash") ++GameplayTagList=(Tag="Athena.Quests.Photographer.ALTER",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Photographer.EGO",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Boombox.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Boombox.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Boombox.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Boombox.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Briefcase.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Briefcase.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Briefcase.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Briefcase.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Briefcase.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Coin.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Hatchery.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Hatchery.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Hatchery.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Jammer.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Jammer.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Jammer.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Jammer.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Jammer.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Jammer.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Light.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Light.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Light.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Light.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Light.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Pallet.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Pallet.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Pallet.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Pallet.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Pallet.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Missing.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Warning.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.00",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Place.Sign.Welcome.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PlayerDamaged",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PlayerRevived",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.16",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.17",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.18",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.19",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.20",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Poster.Propaganda.21",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PostGeyserLanding",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PostShadowBomb",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PrepperSupplies",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.PropActorParent",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Propifier",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Props.IO.Barricade",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Props.IO.Dish",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Purple.Overheat",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.QueuedForDuel",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RaceCourse.Finished",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RealitySapling.Planted",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RealitySeedThrownCheck",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RebootRally.Frontend",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RecentlyBlob",DevComment="Player was recently a chrome blob") ++GameplayTagList=(Tag="Athena.Quests.RecentlyHonked",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RecentlyScored",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RecentlyStoppedHiding",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RedOasis.Quest",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RedOasis.Token",DevComment="Token for RedOasis quests") ++GameplayTagList=(Tag="Athena.Quests.RerolledAugments",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RestaurantKitchen.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Rideshare",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RodeWitchBroom",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RollDistance.Boulder",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RollDistance.Tree",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RoofRing.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RoofRing.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RoofRing.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RoofRing.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.RoofRing.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.DeviceUplink.RealityTree.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.DeviceUplink.RealityTree.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.DeviceUplink.RealityTree.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.DumpsterNotes",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.Bunker.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.Bunker.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.Bunker.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.Bunker.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.Bunker.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.Habitat",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.RealityRoot.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.RealityRoot.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.RealityRoot.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.RealityRoot.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Location.RealityRoot.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Terminals.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Terminals.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Terminals.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Terminals.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Terminals.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S22.Terminals.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.S23.SlideIce",DevComment="") ++GameplayTagList=(Tag="athena.quests.Sandcastle.01",DevComment="") ++GameplayTagList=(Tag="athena.quests.Sandcastle.02",DevComment="") ++GameplayTagList=(Tag="athena.quests.Sandcastle.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SandTunnelNearEnemy",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SawbladeDestroyX",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Season7.A",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Season7.B",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SecondChanceMachineRevive",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ShieldBubble",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ShieldBubbleBlockProj",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ShiftyShaftsPeak",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ShockwaveGrenadeSelf",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SidewaysEncounter.EncounterCompleted",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SidewaysEncounter.WaveCompleted",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Skulldude.ALTERDeposit",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Skulldude.EGODeposit",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Skulldude.Looted",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Skulldude.OnQuest",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Evidence.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scanners.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scannners.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scannners.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Scannners.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Video.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Sleuth.Wiretap.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Soccer.DanceAfterSlide",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Soccer.SlideAfterScored",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Speakers",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpiritVessel",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpiritVessel.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpiritVessel.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpringActivation.Consume.Egg",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpringActivation.Jumped",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SprintDistance",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SpyProbe.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.ColdWater",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stamina.VersusBoard.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stonehenge.Rock.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stonehenge.Rock.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stonehenge.Rock.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stonehenge.Rock.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stonehenge.Rock.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Stonehenge.Rock.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SuperDuper.A",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SuperDuper.B",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SuperDuper.C",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SuperDuper.D",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SuperDuper.E",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SupplyDropDrone",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.SupplyDropIsFalling",DevComment="Tag for GE to know when the supply drop is falling and apply a tag for the quest manager") ++GameplayTagList=(Tag="Athena.Quests.SupplyDropRecentlyLanded",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Swim.DistanceTimed",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Swing.Seven",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TandemService.BuyRandomRarity",DevComment="Stat tracking for the random rarity vending machine.") ++GameplayTagList=(Tag="Athena.Quests.TandemService.OutfitDisguise",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TandemService.PatchUp",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TandemService.RollTheDice",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TandemService.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TandemService.TipBusDriverLarge",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TandemService.TipBusDriverSmall",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TankAirTime",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TargetDummy.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Threshold.Shields",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Throw.Enemy.FallDamage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ThrowCabbage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tinfoil.Device",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tinfoil.Parts",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tinfoil.Parts.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tinfoil.Parts.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tinfoil.Parts.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TNTina.ALTER",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TNTina.EGO",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.BPHiddenCharacter.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.ClearRadius",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.Close",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.DistantEcho",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.EGS.Volcanic",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.Herald",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.NPCQuestline.Complete",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.S22Fortnitemares",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.Smash",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.Teriyaki",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.Winterfest",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Token.YorkYork",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TomatoShrine.FirePot",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TomatoShrine.HasBasket",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TomatoShrine.TomatoBasket",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TomatoShrine.TomatoBasket.Place",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TomatoShrine.TomatoBasket.Take",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TouchCube",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tournament",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.BonePunkArtifact.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.BonePunkArtifact.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.BonePunkArtifact.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.BonePunkArtifact.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.BonePunkArtifact.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Crystal.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Crystal.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Crystal.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Crystal.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Crystal.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.CrystalMessage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.CultArtifact",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.DictationDevice",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Disguise",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Glyph",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Tower.Launched",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Transient.Completed",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TravelDistance.Frozen",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TreasureMap.Signpost.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TreasureMap.Signpost.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TreasureMap.Signpost.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TreasureMap.Signpost.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.TreasureMap.Signpost.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.UnhideWithEnemyClose",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Use.CageSaddle",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Use.Consumable.Medkit",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Use.JumpVent",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.UseCreepinCardboard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.UseDriftBoard",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.UseQuadCrasher",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.UseSneakySnowman",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Vehicle.Damage",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Vehicle.Horn",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Vehicle.Horn.HonkAtPOI",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Vehicle.Horn.HonkNearOpponent",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Vehicle.Submerged",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Vehicle.Submerged.Car",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VehicleDelivery.AlienArea",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VehicleDelivery.Truck",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VehicleDelivery.UFO",DevComment="") ++GameplayTagList=(Tag="athena.quests.vehicledistance.Hoagie",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VehicleDistanceAquatic",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VehicleDistanceWaterOnLand",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VendingMachine.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.VideoCamera.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Visit.Anchor01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Visit.Anchor02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Visit.Anchor03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarmWest.Launch",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WarningScanner.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Wasabi.HeadActivate",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Wasabi.LS",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Wax.CollectCoins",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Wax.CollectCoinsSingleElimination",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WelcomeGift.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WildlifePet",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.WindTunnel",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.BushesDestroyed",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.CollectedPizza",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.ConsumeItems",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.CrackshotQuadcrasherDistance",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Decoration",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Decoration.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.EmoteTimed",DevComment="requires sustained emote at location from GA") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Frontend.CozyFireplace",DevComment="quest tag for visiting the Frontend cozy lodge fireplace") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.IcyFeetOpponent",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.NotFood",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Pizza8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Snowman",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.SnowmanNearEnemy",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane0",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.Toy.Biplane12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.Winterfest.ToyBiplanes",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Man.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Parts",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Seismograph.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.13",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.14",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Sign.15",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.1",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.2",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.3",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.4",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.5",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.6",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.7",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.8",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.9",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.11",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.York.Trap.12",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.01",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.02",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.03",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.04",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.05",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.06",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.07",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.08",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.09",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.10",DevComment="") ++GameplayTagList=(Tag="Athena.Quests.ZeroWeek.Token.11",DevComment="") ++GameplayTagList=(Tag="Athena.Replay.CameraMode.BattleMap",DevComment="") ++GameplayTagList=(Tag="Athena.Replay.Client",DevComment="") ++GameplayTagList=(Tag="Athena.Replay.HUD.FollowedPlayerInfo",DevComment="") ++GameplayTagList=(Tag="Athena.Replay.HUD.MiniMapStormWatch",DevComment="") ++GameplayTagList=(Tag="Athena.ShieldGenerator.Throw",DevComment="") ++GameplayTagList=(Tag="Athena.SkipWeaponsPrototyping",DevComment="") ++GameplayTagList=(Tag="Athena.Status.AimAssistIgnored",DevComment="") ++GameplayTagList=(Tag="Athena.TacOvershield.Disable",DevComment="") ++GameplayTagList=(Tag="Athena.Tether.Begin",DevComment="") ++GameplayTagList=(Tag="Athena.Tether.Boost",DevComment="") ++GameplayTagList=(Tag="Athena.Tether.Boost.Begin",DevComment="") ++GameplayTagList=(Tag="Athena.Tether.Boost.End",DevComment="") ++GameplayTagList=(Tag="Athena.Tether.Jump.Player",DevComment="") ++GameplayTagList=(Tag="Athena.Valet.ExplodeDamage",DevComment="Tag for tracking explosive damage when vehicles explode") ++GameplayTagList=(Tag="Athena.Vehicle.AI.AwardThanks",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.AI.MonkeyGrave",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.AI.ShowAlertState",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.AI.Silkwalker",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.AI.SilkwalkerAcolyte",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Antelope",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.ArmoredBattleBus",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.ArmoredBattleBus.Party",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Biplane",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Biplane.OnHoldExitStarted",DevComment="Event tag to start the GC which displays the timer") ++GameplayTagList=(Tag="Athena.Vehicle.Biplane.OnHoldExitStopped",DevComment="Event tag for when a player stops holding exit button to leave Biplane") ++GameplayTagList=(Tag="Athena.Vehicle.Cannon",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ferret.FX.Boost",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ferret.FX.HitDust",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ferret.FX.HitDustPlayer",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ferret.FX.LargeImpact",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ferret.FX.PropBlur",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ferret.FX.PropGrind",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Foray",DevComment="Gameplay Vehicle Tag for Foray.") ++GameplayTagList=(Tag="Athena.Vehicle.GolfCart",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Jackal",DevComment="Gameplay Tag for Jackal vehicle.") ++GameplayTagList=(Tag="Athena.Vehicle.Meatball",DevComment="Meatball Vehicle Gameplay tag") ++GameplayTagList=(Tag="Athena.Vehicle.Meatball.FX.Boost",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Meatball.FX.GroundInteraction",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Meatball.FX.LargeImpact",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.MistyBop",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.MountedTurret",DevComment="Gameplay tag for Mounted Turret.") ++GameplayTagList=(Tag="Athena.Vehicle.Octopus",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.OnEnterDriverSeat.ReleaseBalloons",DevComment="If this tag is present on a vehicle then the players balloons will be released when they enter the driver seat") ++GameplayTagList=(Tag="Athena.Vehicle.OnEntry.ReleaseBalloons",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Ostrich",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.RoundSmall",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.ShoppingCart",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.AlwaysSpawn",DevComment="Tag for identifying Vehicles that should always spawn via LWM") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BasicCar.Base",DevComment="Spawn Location tag for Valet Basic Car") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BasicCar.IO",DevComment="LWM Spawn Location tag for IO Sedan") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BasicCar.Taxi",DevComment="Spawn Location for Valet Taxi vehicle") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BasicCar.Upgraded",DevComment="Spawn Location tag for Upgraded Valet Basic Car") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BasicTruck.Base",DevComment="Spawn Location for Valet Pickup Truck vehicle") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BasicTruck.Upgraded",DevComment="Spawn Location tag for Upgraded Valet Pickup Truck") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BigRig",DevComment="Spawn location Tag for Valet Big Rig Semi vehicle.") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BigRig.Base",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BigRig.IO",DevComment="Tag for LWM spawn location for IO Big Rig") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.BigRig.Upgraded",DevComment="Vehicle Spawn Location tag for Upgraded Big Rig Vehicle") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.SportsCar.Base",DevComment="Spawn Location tag for Valet Sports Car") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.SportsCar.Upgraded",DevComment="Spawn Location Tag for Upgraded Sports Car") ++GameplayTagList=(Tag="Athena.Vehicle.SpawnLocation.Valet.Taxi",DevComment="Spawn Location tag for Valet Taxi vehicle") ++GameplayTagList=(Tag="Athena.Vehicle.SpecialActor.Ostrich",DevComment="") ++GameplayTagList=(Tag="Athena.Vehicle.Tank",DevComment="Gameplay Tag for Tank Vehicle") ++GameplayTagList=(Tag="Athena.Vehicle.Valet.BasicPickup",DevComment="Vehicle tag for basic pickup truck") ++GameplayTagList=(Tag="Athena.Vehicle.Valet.BasicSedan",DevComment="Valet Vehicle tag for Basic Sedan") ++GameplayTagList=(Tag="Athena.Vehicle.Valet.Foray",DevComment="Valet Vehicle Tag for Foray vehicle.") ++GameplayTagList=(Tag="Athena.Vehicle.Valet.Semi",DevComment="Vehicle tag for Valet Semi truck") ++GameplayTagList=(Tag="Athena.Vehicle.Valet.SportsCar",DevComment="Vehicle tag for Valet Sports Car.") ++GameplayTagList=(Tag="Athena.Vehicle.Valet.Taxi",DevComment="Vehicle tag for Valet Taxi Cab") ++GameplayTagList=(Tag="Athena.Volume.AngryApple",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.Crucible",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.GreenGlop",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.MeatPizza",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.Moss",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.Oak",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.Sepia",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.SleepyTime",DevComment="") ++GameplayTagList=(Tag="Athena.Volume.TheBlock",DevComment="") ++GameplayTagList=(Tag="Athena.WeaponStatBounds.Bow.Bone",DevComment="") ++GameplayTagList=(Tag="Athena.WeaponStatBounds.Bow.Flame",DevComment="") ++GameplayTagList=(Tag="Athena.WeaponStatBounds.Bow.Stink",DevComment="") ++GameplayTagList=(Tag="Athena.WeaponStatBounds.Pistol.Bone",DevComment="") ++GameplayTagList=(Tag="Augment.Category.Combat",DevComment="") ++GameplayTagList=(Tag="Augment.Category.GameChange",DevComment="") ++GameplayTagList=(Tag="Augment.Category.Loot",DevComment="") ++GameplayTagList=(Tag="Augment.Category.Mobility",DevComment="") ++GameplayTagList=(Tag="Augment.Category.Scouting",DevComment="") ++GameplayTagList=(Tag="Bacchus.Use.Wear.Snowman",DevComment="Gameplay Bacchus tag for Sneaky Snowman") ++GameplayTagList=(Tag="basiccar",DevComment="") ++GameplayTagList=(Tag="BattleLabDevice.AutoLauncher.Explosive.Projectile",DevComment="") ++GameplayTagList=(Tag="BGA.ExternalShield",DevComment="") ++GameplayTagList=(Tag="BGA.Propane",DevComment="") ++GameplayTagList=(Tag="BGA.SuperShield",DevComment="") ++GameplayTagList=(Tag="Bling.Backpack.Glow",DevComment="") ++GameplayTagList=(Tag="BP12_Main_Wide",DevComment="") ++GameplayTagList=(Tag="Buff_Llama",DevComment="") ++GameplayTagList=(Tag="Building.AI.Prohibits.Landing",DevComment="Building that AIs are not allowed to land on (crows for example)") ++GameplayTagList=(Tag="Building.ForcePermissionsTrue",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Banana",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.BirthdayCake",DevComment="Birthday Cake Gameplay BGA Tag") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Cabbage",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.CandyCorn",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Carrot",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Coconuts",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Glitch",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.HopDrop",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.JellyBean",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Pepper",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Peppermint",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Rift",DevComment="BGA tag for Rift foragable item") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.Rift.Ignore",DevComment="Ignore Item Rift Portal") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.ShieldHops",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.SilkyMoose",DevComment="") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.SpookyMist",DevComment="Spooky Mist Gameplay BGA Tag") ++GameplayTagList=(Tag="Building.GameplayActor.Consumable.ThermalTaffy",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Barrel.Wooden.Fishing",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.CashRegister",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Cooler",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.FoodBox.Produce",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Furniture.Bathtub",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Furniture.Fireplace",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Furniture.Sink",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.GasPump",DevComment="Identifier tag for the Gas Pump container") ++GameplayTagList=(Tag="Building.Type.Container.MedicalBox",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Organic.Mushroom.Daisy",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.RealitySapling",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Slaps.Barrel",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Slurp.Barrel",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Slurp.Silo",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.Slurp.Tanker",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.SneakySnowman",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.StarlightUrn",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Alter",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Booty",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Ego",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.FactionChest",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Galileo",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Hightower",DevComment="") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Locked",DevComment="Chest that requires a key to open") ++GameplayTagList=(Tag="Building.Type.Container.TreasureChest.Prepper",DevComment="S16 Prepper / \"Bunker\" (player facing name) chest") ++GameplayTagList=(Tag="Building.Type.Container.Umbrella",DevComment="") ++GameplayTagList=(Tag="Building.Type.Curved",DevComment="Generic tag for curved builds such as curved walls or curved prop walls.") ++GameplayTagList=(Tag="Building.Type.Gameplay.HeldObject",DevComment="") ++GameplayTagList=(Tag="Building.Type.Gameplay.Pylon",DevComment="") ++GameplayTagList=(Tag="Building.Type.IgnorePhysics",DevComment="") ++GameplayTagList=(Tag="Building.Type.ItemContainer.RepairBench",DevComment="") ++GameplayTagList=(Tag="Building.Type.ItemContainer.Wumba",DevComment="") ++GameplayTagList=(Tag="Building.Type.Launcher",DevComment="") ++GameplayTagList=(Tag="Building.Type.Physics",DevComment="") ++GameplayTagList=(Tag="Building.Type.Player.Floor",DevComment="") ++GameplayTagList=(Tag="Building.Type.Player.Roof",DevComment="") ++GameplayTagList=(Tag="Building.Type.Player.Roof.Pyramid",DevComment="") ++GameplayTagList=(Tag="Building.Type.Player.Stair",DevComment="") ++GameplayTagList=(Tag="Building.Type.Player.Wall",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Athena.NoDancingSign",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Athena.Quest.Buoy",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Athena.Quest.Celebration",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Athena.Quest.Inflatable",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Athena.Quest.Pumpkin",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Athena.ShootingTarget",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.BeaconDrill",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Boat",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.CreativeMatchmakingPortal",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Deco.AddOn",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.FellablePhysicsProp",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.FruitPlant",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.GarageDoor",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.Barrel",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.Dumpster",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.FreshGrave",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.HayStack",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.OilDrum",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.PhantomBooth",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.Portapotty",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.Teleporter",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.TrashCan",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.Whirlpool",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Hiding.WilliePete",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Organic.PhysicsBoulder",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Organic.PhysicsTree",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Organic.Thrown",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Organic.TreeStump",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.PhysicsBeachBall",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.ProduceBox.Apple",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.ProduceBox.Corn",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.ProduceBox.Tomato",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Shelf.Apples",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Shelf.Corn",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Shelf.Tomatos",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Tires",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Vehicle.FoodTruck.Burger",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Vehicle.FoodTruck.Pizza",DevComment="") ++GameplayTagList=(Tag="Building.Type.Prop.Wall",DevComment="") ++GameplayTagList=(Tag="BuildingActor.HealthbarAsNonPlayerBuilt",DevComment="Used for player built buildings whose healthbars should behave like non player built buildings.") ++GameplayTagList=(Tag="BuildingActor.NonDestructable",DevComment="Used for building actors that are NonDestructable and should be treated as such for targeting in specific situations. See interaction with the JollyRascal and SilverBlazer.") ++GameplayTagList=(Tag="ButterBarn",DevComment="") ++GameplayTagList=(Tag="Camera.DonutRoom.Letter",DevComment="") ++GameplayTagList=(Tag="ChaCha.RecentlyFired",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Accolades",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Coins",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Combat",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Combat.Gear",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Combat.Opponents",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Fishing",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Harvesting",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Looting",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Meta",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Misc",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Mystery",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Placement",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.MetaData.PunchCard.Powers",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Survival",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.Teamwork",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Metadata.PunchCard.UpgradeMachine",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.MetaData.PunchCard.Valet",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season11",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season12",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season13",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season14",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season15",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season16",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season17",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season18",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season19",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season20",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season21",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season22",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season23",DevComment="") ++GameplayTagList=(Tag="ChallengeBundle.Source.Season24",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Bars",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.BoosterPack",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ClearRadius",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Clutch",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily.BargainBinWeekend",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily.Battle",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily.Discovery",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily.Quests",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily.Supercharged",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Daily.WildWeekend",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Discovery",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.DistantEcho",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.EGS.Volcanic",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.BFF",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Bicycle",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Birthday",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.BPHiddenCharacter",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Buffet",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.EGS",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Emperor",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Foreshadowing",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.GoldenSkeleton",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Hardwood",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Hiddenrole",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Inspire",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Inspire.Quest",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.IslandGames",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Nightmare",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.O2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.OperationSnowdown",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Plumretro",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.RebootRally",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.RebootRally.Bonus",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.RebootRally.Quest",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Rustybolt",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.S22Birthday",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.S22ChromePunk",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.S22Fortnitemares",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.S22Fortnitemares.LooseQuests",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.SpringActivation",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.1",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.3",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.4",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.5",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.6",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.7",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Stamina.8",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Summer",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Tower",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Unvaulted",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Vaultbreakers",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Event.Winterfest",DevComment="") ++GameplayTagList=(Tag="Challengecategory.Event.ZeroWeek",DevComment="") ++GameplayTagList=(Tag="Challengecategory.Event.ZeroWeek.Quest",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Feats",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Grind",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Herald",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.IceQueen",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.LTM.Close",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.LTM.Smash",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Majesty",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Milestone",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Milestone.Quests",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Miscellaneous",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.001",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.002",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.003",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.004",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.005",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.006",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.007",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.008",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.009",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.010",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Narrative.011",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.RebootRally",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.RedOasis",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.RedOasis.Quests",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Sienna",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Stamina",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Style",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Style.Exosuit",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Style.HavenMask",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Style.Phantom",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Style.PickaxeCollectible",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek3",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek5",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek6",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek9",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek10",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek11",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek12",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek.02",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek.03",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek.05",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek.06",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek10.Creative",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek10.Quest",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek11.Creative",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek11.Quests",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek12.Creative",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek12.Quests",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek2.1",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek2.2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek3.1",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek3.2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek5.1",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek5.2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Thisweek6.1",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek6.2",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek9.Creative",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.ThisWeek9.Quest",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Tutorial",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.000",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.001",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.002",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.003",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.004",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.005",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.006",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.007",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.008",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.009",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.010",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.11",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.Weekly.12",DevComment="") ++GameplayTagList=(Tag="ChallengeCategory.XP",DevComment="") ++GameplayTagList=(Tag="ChallengeDetails.Default",DevComment="") ++GameplayTagList=(Tag="ChallengeDetails.Display.HideChaseItems",DevComment="") ++GameplayTagList=(Tag="ChallengeDetails.Display.HideEndTimer",DevComment="") ++GameplayTagList=(Tag="ChallengeDetails.Display.HideQuestRewards",DevComment="") ++GameplayTagList=(Tag="ChallengeDetails.Fortbytes",DevComment="") ++GameplayTagList=(Tag="ChallengeDetails.Missions",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.ArmLeft",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.ArmRight",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.FootLeft",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.FootRight",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.ForearmLeft",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.ForearmRight",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.HandLeft",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.HandRight",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.Head",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.LowerlegLeft",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.LowerlegRight",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.VehicleClip.Ostrich.Driver",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.WeaponLeft",DevComment="") ++GameplayTagList=(Tag="CharacterPart.BoneSet.WeaponRight",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Character",DevComment="Character part belonging to a specific character") ++GameplayTagList=(Tag="CharacterPart.Character.Barium",DevComment="Character part belonging to Barium") ++GameplayTagList=(Tag="CharacterPart.Character.Barium.Reactive",DevComment="Character part belonging to Barium that should be affected by reactivity") ++GameplayTagList=(Tag="CharacterPart.Character.Calavera.Reactive",DevComment="Character part belonging to Calavera that should be affected by reactivity") ++GameplayTagList=(Tag="CharacterPart.Type.Backpack",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.Body",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.Face",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.Gameplay",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.Hat",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.Head",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.MiscOrTail",DevComment="") ++GameplayTagList=(Tag="CharacterPart.Type.MiscOrTail.Tail",DevComment="") ++GameplayTagList=(Tag="ClaimPOI.Ammo",DevComment="") ++GameplayTagList=(Tag="Clambering.State.Disabled",DevComment="") ++GameplayTagList=(Tag="Clambering.State.InActivationRange",DevComment="Applied to the player while the clambering target location is within activation range") ++GameplayTagList=(Tag="Class.BodySize.Medium",DevComment="Tag to see if a hero is on a medium body.") ++GameplayTagList=(Tag="cobalt.noAmmo",DevComment="") ++GameplayTagList=(Tag="coldbreath",DevComment="") ++GameplayTagList=(Tag="Conversation.Choice.SetFocusUsingCurrentIndex",DevComment="Uses the current index to determine which node to focus on instead of forcing things to use center") ++GameplayTagList=(Tag="Conversation.Context.ServiceJustPurchased",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.AgentJones",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Aura",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.BaoBros",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.BeachBomber",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Binary",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.BoardwalkRuby",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.BountyBoardStation",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Brainiac",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.BuffLlama",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Bulma",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Cashier",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Cashier.Quest",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.ChillCat",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.ChromePunk",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Columbus",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Cryptic",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Cuddlepool",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.CuddleTeamLeader",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.CurdleScreamLeader",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.DiamondDiva",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Evie",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.ExoSuit",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FirstAid",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.01",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.02",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.03",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.04",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.05",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.06",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.07",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.08",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.09",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.GS.10",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FNM.Willow",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Follower.H",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FrozenFishstick",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.FrozenRedKnight",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Goldie",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Guaco",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Guild",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Inferno",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Insight",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.IslandNomad",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.JonesUnchained",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.LilWhip",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.LoneWolf",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Longshot",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Ludwig",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Mancake",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.MaryJane",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Maximillian",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Meowscles",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.MetalMouth",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.MetalTeamLeader",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Mitosis",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.MoonHawk",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.MulletMarauder",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Mystic",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Mystify",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.NoSweat",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.OmegaKnight",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Panther",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Payphone",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Payphone.Quests",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Peely",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Peely.LTDstory",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.PrincessFelicityFish",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Printer",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.PumpkinPunkGlitch",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Quackling",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.RaptorianTheBrave",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Rebel",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.RedOasisGooseberry",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.RelaxedFitJonesy",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.RenegadeShadow",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Revolt",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.RiftGate",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Rustler",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Sabina",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.ScrapknightJules",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Brawny",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Narrative.Mindwipe",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Narrative.Payphones",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Narrative.Week03",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Narrative.Week05",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Narrative.Week06",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week1",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week2",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week3",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week5",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week8",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week13",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week14",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season17.Week8.Impostor",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season18.Fortnitemares",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season20.Narrative.ArchiveServer",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season22.Narrative.Code.07231",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season22.Narrative.Code.53864",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season22.Narrative.Code.82133",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season22.Narrative.Q05",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season22.Narrative.Q08",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Season22.Narrative.Q12",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.SevenSentryNPC.Female",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.SevenSentryNPC.Male",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.SharpFang",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Silencer",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.SmallBag",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Snowheart",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Specialist.Heavy",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Specialist.Medic",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Specialist.Melee",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Specialist.Scout",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Specialist.Supply",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Stashd",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Sterling",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.SummerDrift",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Sunbird",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.SurrrBurger",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TameFoot",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TandemTemplate",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TandemTest",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TheOrder",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TheOrigin",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TheParadigm",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TheScientist",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TheUnderwriter",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TheVisitor",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.TournamentBoard",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Troops",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Unpeely",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.UpgradeBench",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Exotic",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Malfunctioning",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Malfunctioning.Stamina",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Medical",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.RandomRarity",DevComment="Conversation Entry point used when a player interacts with a random rarity vending machine.") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Special",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Stamina",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Universal",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Vaultbreakers",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons.Assault",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons.DMR",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons.Pistol",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons.Shotgun",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons.SMG",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.VendingMachine.Weapons.Sniper",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.WildCard",DevComment="") ++GameplayTagList=(Tag="Conversation.Entry.Zadie",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerDate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerGrape",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerHoneydew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerMango",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerRadish",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerSquash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerTapas",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerTomato",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Awakenings.HightowerWasabi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Category.Character",DevComment="Key for cosmetics flows that cover the entire character (Outfit (i.e. CID) + Backbling (BID))") ++GameplayTagList=(Tag="Cosmetics.CategoryFlow.Primary",DevComment="Key for the primary flow of any particular cosmetics category.") ++GameplayTagList=(Tag="Cosmetics.Character.BunkerMan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Character.comet",DevComment="") ++GameplayTagList=(Tag="Cosmetics.CharmSlots.2",DevComment="Should be used with backbling that have 2 charm slots") ++GameplayTagList=(Tag="Cosmetics.DBNO.SupressDBNORevivalDrone",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Emote.Canary",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Emote.CharacterGroup.Banana",DevComment="Peely and friends") ++GameplayTagList=(Tag="Cosmetics.Emote.CharacterGroup.BigHead",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Emote.CharacterGroup.Skirts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Emote.CharacterGroup.Toon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Emote.Turtleneck",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusion.SubGame.Athena",DevComment="Cosmetics with this tag will be excluded from Athena subgame.") ++GameplayTagList=(Tag="Cosmetics.Exclusion.SubGame.Campaign",DevComment="Cosmetics with this tag will be excluded from Campaign subgame.") ++GameplayTagList=(Tag="Cosmetics.Exclusives.ArcticIceBlue",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.ArcticIceTalus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Bites",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Cherry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Clash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.ClashV",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.CubeQueen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Emperor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.EnsembleMaroon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.HightowerDate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.HightowerSquash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.HightowerTapas",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.HightowerTapasPickaxe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.HightowerTomato",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.MercurialStorm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Nightmare",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Parfait",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.ParfaitGlider",DevComment="Characters that can use the Parfait glider") ++GameplayTagList=(Tag="Cosmetics.Exclusives.ParfaitPickaxe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.PlumRetro",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Shapeshifter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Trifle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.TrifleGlider",DevComment="Character can use the Trifle glider") ++GameplayTagList=(Tag="Cosmetics.Exclusives.TriflePickaxe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Troops",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Turtleneck",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Exclusives.Vertigo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.14DaysofFortnite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.14DaysofSummer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Birthday2018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Birthday2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Butterfly",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Fortbytes",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Fortnitemares2018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Fortnitemares2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.IceStorm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.SaveTheWorld.Dungeons.2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.SaveTheWorld.Frostnite.2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.SaveTheWorld.HitTheRoad.2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.SaveTheWorld.Holdfast.2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.SquishyConcert",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.Unvaulting",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.WinterOlympics2018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Event.WorldCup2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Granted.Founders",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.9",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.10",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.12",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.13",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.14",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.15",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.16",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.17",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.18",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.19",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.20",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.21",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.22",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.23",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Filter.Season.24",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Glider.BackpackGlider",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Glider.Bistro",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Glider.Cherry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.AlienAgent",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.AlienFlora",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ApexWild",DevComment="Pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ArcticIce.Cape",DevComment="Tag used for asset detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ArmadilloGreen",DevComment="Emote detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ArmadilloOrange",DevComment="Emote detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ArmadilloPink",DevComment="Emote detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ArmadilloRobot",DevComment="Pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.BackSpin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.BannerRedFemale",DevComment="Character detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.BariumDemon",DevComment="for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Bites",DevComment="Pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.BlueJet",DevComment="Tag for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.BuffCatFan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.BuffLlama",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CapeOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CapeOn",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CarabusFull",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CarabusFullFive",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CarabusFullFour",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CarabusFullSix",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CarbideKnightMale",DevComment="Vert hiding detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Chainmail",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Citadel.Armor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Citadel.Armor.Cape",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Citadel.Default",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Citadel.Default.Cape",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Citadel.Helm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Citadel.NoHelm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Clash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm1",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm2",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm3",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm4",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm5",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm6",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Collectable.Arm7",DevComment="Emote variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ConvoyTarantulaMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CoyoteTrail.Activated",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CoyoteTrail.BlackAlt",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CoyoteTrail.Default",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CoyoteTrail.Helm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CoyoteTrail.NonReactive",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CoyoteTrail.PinkAlt",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CrazyEightTech",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.CycloneSkin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.DarkAzalea",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.DarkNinjaPurple",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.DeliSandwich",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.DesertShadowBlade",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Despair",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.DinoCollector",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Downpour",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.DragonfruitMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Elevate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.EmeraldGlass.Default",DevComment="Tag for variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.EmeraldGlass.Style",DevComment="Tag for variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.EnsembleMaroon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.EnsembleMaroon.Glider",DevComment="Check for Ensemble Maroon Male Glider") ++GameplayTagList=(Tag="Cosmetics.MetaTags.EnsembleSnake.Fangs",DevComment="Meta tag that is required for EnsembleSnake\'s built-in emote") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ExoSuitCape",DevComment="Meta tag to check if cape version of her backpack is equipped") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Forsake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ForsakeBuiltin",DevComment="Built-in pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.FutureSamurai",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.GelatinGummi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gender.Female",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gender.Male",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.GimmickMale",DevComment="Character detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gladiator.Heavy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gladiator.HeavyFlame",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gladiator.HeavyHelm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gladiator.HeavyHelmFlame",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gladiator.LightHelm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Gladiator.LightHelmFlame",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Grandeur",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Grim",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.GrimPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hair",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hair.00",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hair.01",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hair.02",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hair.03",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hat.00",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hat.01",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hat.02",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hat.03",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Hat.04",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.HeadbandS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.HeartBreakerFemale",DevComment="For hiding sword when pickaxe is equipped") ++GameplayTagList=(Tag="Cosmetics.MetaTags.HenchmanMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.HenchmanSummer",DevComment="Hide oar on backpack when pickaxes are equipped") ++GameplayTagList=(Tag="Cosmetics.MetaTags.HightowerTapas",DevComment="Cosmetic in the HightowerTapas set") ++GameplayTagList=(Tag="Cosmetics.MetaTags.HoustonSkin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Imitator",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Impulse.ScarfOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Impulse.ScarfOn",DevComment="For detecting presense of Impulse scarf") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Indigo",DevComment="Character detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.IndigoMale",DevComment="Pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Invader",DevComment="M_LRG_Invader") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Invisible",DevComment="The specific variant of the cosmetic renders it invisible") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Jacket",DevComment="Meta tag for cosmetic variants with a jacket") ++GameplayTagList=(Tag="Cosmetics.MetaTags.KansasSkin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.KittyWarrior",DevComment="Pickaxe Hiding") ++GameplayTagList=(Tag="Cosmetics.MetaTags.KnightCat",DevComment="Pickaxe and Glider detection tag") ++GameplayTagList=(Tag="Cosmetics.MetaTags.LoneWolf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.LurkFemale",DevComment="Pickaxe/Backpack hiding support") ++GameplayTagList=(Tag="Cosmetics.MetaTags.MagicMeadow.Silver",DevComment="Tag for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.MagicMeadow.Steel",DevComment="tag used for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.MainframeMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.MaskedWarriorSpring",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.MetalScout",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ModNinjaMale",DevComment="Pickaxe Detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Monarch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.MythMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.NeonCatTech",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.NinjaWolf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.NobleMale",DevComment="Pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.NotStyle.OptionC",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Nox",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.OceanBreeze",DevComment="Tag for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Ohana",DevComment="Backpack detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.OrderGuardMale",DevComment="Unique tag for hand hiding") ++GameplayTagList=(Tag="Cosmetics.MetaTags.OverridePreviewActor.ItemOnPawnPreview",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Parfait",DevComment="Cosmetic in the Parfait set") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PeelyTech",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PhotographerFemale",DevComment="Used for anim blueprint logic in backpacks when using PhotographerFemale") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PhotographerHoliday",DevComment="head detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PinkTrooperDark",DevComment="Pickaxe Vert Hiding") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PinkWidowFemale",DevComment="Pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Polygon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PumpkinPunk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.PunkKoi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.RacerZero",DevComment="Character detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ReactiveOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ReactiveOn",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.RedSleeves",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.RestrictsBackpack",DevComment="Character with this meta tag cannot use backpacks") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Reverence",DevComment="For emote detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ReverenceAlt",DevComment="For emote detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SaharaCloak",DevComment="tag for character cloak detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SailorSquadRebel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SAM",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SAM.Dark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Seesaw_Slipper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ShapeShifter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ShapeShifter.HeavyArmor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ShapeShifter.LightArmor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ShapeShifter.SnakesOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ShapeShifter.TailOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ShinyStar",DevComment="Tag used for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SirWolf.HideFace",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Special.FaceIsHead",DevComment="Special Tags for Gameplay Assets") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Special.Large",DevComment="Boneset identifying") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Special.XLarge",DevComment="Boneset Identifying") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Stamina",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.StreetFashionHoliday",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Style.OptionA",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Style.OptionB",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Style.OptionC",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Style.OptionD",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Style.OptionE",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SunBurstAlt",DevComment="Vert hiding") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SunTide.Shirt",DevComment="Variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.SunTide.Upgrade",DevComment="Variant detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.TacticalRedPunkFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.TeriyakiAtlantis",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Theme.LTM",DevComment="For specific LTMs where the cosmetic theme may not adhere to a generic label") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Theme.LTM.Bounty",DevComment="Generic version of Wick") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ThriveFemale",DevComment="Vert hide pickaxe") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Tiramisu",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Titanium",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Trait.HeadCovered",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Trait.NeckOnBody",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Trait.SkipSuitSwap",DevComment="Skip Suit Swap for Bodyguard LTM") ++GameplayTagList=(Tag="Cosmetics.MetaTags.TreasureHunterFashionMint",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Trifle",DevComment="Cosmetic in the Trifle set") ++GameplayTagList=(Tag="Cosmetics.MetaTags.TroubleMaker",DevComment="Tag for Glider detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Turtleneck",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.TurtleneckDualWield",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.TyphoonFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.VampireHunter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Venice",DevComment="Tag for pickaxe detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Vest.00",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Vest.01",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Vest.02",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Vest.03",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Vest.04",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Vivid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.WayfareFemale",DevComment="backpack detection") ++GameplayTagList=(Tag="Cosmetics.MetaTags.Werewolf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.WombatFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.WombatMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.WoodlandTacticalBlue",DevComment="") ++GameplayTagList=(Tag="Cosmetics.MetaTags.ZestMale",DevComment="Vert hide pickaxe") ++GameplayTagList=(Tag="Cosmetics.NoSweat.SignDance",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Random.CrouchIdle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Random.Idle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Random.Sprint",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Collect.EpicItem",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Collect.LegendaryItem",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Collect.LegendaryWeapon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Collect.RareItem",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.GotElimination",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Happy.RevivedOtherPlayer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Happy.SuppyDrop",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Happy.SuppyLlama",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Happy.TreasureChest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Response.Happy.VictoryRoyale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Cower.End",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Cower.Start",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Cower.StartLong",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.DBNO.End",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.DBNO.Start",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Emote.Dance",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Happy.Clap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Happy.Howl",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Happy.Start",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Idle.Pacify",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Idle.Sip",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Jump.Fall",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Jump.Land",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pet.Sound.Skydiving.Start",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Backspin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.BaseballBat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.CandyAppleSour",DevComment="CandyAppleSour Pickaxe") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.CyberArmorDualWield",DevComment="Cyber Armor Dual Wield anim") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.CyberArmorSingleWield",DevComment="Cyber Armor one handed anim") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.DonutPlate",DevComment="Tag to associate with correct animation for pickaxes similar to the DonutPlate pickaxe; correct animation is specified in DefaultGameDataCosmetics.") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Embers",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Flail",DevComment="Flail/Fruitcake anim") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.GalileoFerry",DevComment="Galileo Pickaxe") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Gauntlet",DevComment="Pickaxe tag for Gauntlet style pickaxes") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Gladiator",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.HightowerTapas",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.HightowerWasabi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.KnightCat",DevComment="KnightCat tag for STW anim") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Lance",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Nightmare",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.OrderGuard",DevComment="OrderGuard Anim tag for StW") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.SeaSalt",DevComment="SeaSalt Pickaxe") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Staff",DevComment="Staff Pickaxe Harvesting Combo") ++GameplayTagList=(Tag="Cosmetics.Pickaxe.Vertigo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Prohibit.ShadowStanceMaterials",DevComment="Tag that should be put on custom character part data assets to make them NOT attempt to apply shadow stance materials. Needed for some cosmetics that just vanish when shadow is applied, like Neon Wings.") ++GameplayTagList=(Tag="Cosmetics.Prohibit.SprayVariantUse",DevComment="Prohibits this spray from being used in any spray variants") ++GameplayTagList=(Tag="Cosmetics.QuestMetaData.Festivus.MellowMedly",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.BackBling.Galileo.Ferry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Charm.Virus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Emote.GroundPound",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Emote.HappySkipping",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Emote.TPose",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Festivus.MellowMedly",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Glider.Galileo.Ferry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Glider.Galileo.Kayak",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Glider.Galileo.Rocket",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Glider.Galileo.ZeppelinFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Glyph.RoosterCID",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Glyph.StrawberryPilotCID",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Bane",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.BuffCat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Canary",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Catalyst",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Columbus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Donut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Emperor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Galileo.Ferry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Galileo.Kayak",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Galileo.Rocket",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Galileo.Sled",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Galileo.Zeppelin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Golden",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.MountainClimber",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.ProfessorPup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.SlurpMonster",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.StreetRacerDriftRemix_B",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.TacticalFisherman.B",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Outfit.Turtleneck",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Pickaxe.Grillin1H",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Pickaxe.HightowerWasabi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Season7.ABC",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.SquadTeamChat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.SuperDuper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Wrap.CandyCane.Vehicle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Wrap.CandyCane.Weapon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Wrap.HoverSurfboard.Vehicle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.QuestsMetaData.Wrap.HoverSurfboard.Weapon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Sepia.Exclude",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AbandonedCrowd",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AbsorbDrain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AcidVain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ActBorder",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AcutePanic",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AdvancedForces",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AerosolAssassins",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AirforceLinked",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AlwaysFrozen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AmbushOfficer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AmpleChaos",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Anarchy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AngelSchool",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AngleHeard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AngryGiant",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AnimalClock",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AnimalJackets",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AppleChance",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AppleThief",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ArcticCommand",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ArcticSniper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AreaFlame",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ArmedRobotSquad",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ArtistPhysics",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AshtonBoardwalk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AshtonSaltLake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AsideTraveler",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AssassinSuit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Asteroid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AutomaticDuke",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AverageDisk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AvidSpeed",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AwakeChrome",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.AwfulAspect",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Aztec",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BadGoose",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Balloon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Banana",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BandageNinja",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Bandolier",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Banger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Bani",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Banner",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Barbarian",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BarbequeLarry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Barelander",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BarrelGrudge",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BaseballKitbash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Basketball",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BattleSuit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BdayFive",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BDayJam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BeamPepper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Beginnings",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BelowCenter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BendAct",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BeYours",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BigBoom",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BikeCatch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Biker",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Billiards",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Birthday2018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Birthday2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BiteScratch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlackCatOps",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlackVector",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlackWidow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlandAbolish",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlastingSuns",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlazingBrain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Bling",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlotGroup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlueAnime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlueBadass",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlueSamurai",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BlushNail",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BoldGreen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BoneWasp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BonkTeam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BotTaught",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BountyHunter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BoxRuler",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BraveryTime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BreachDog",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BreezeTune",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BrightShot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BrokenLight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Bubblegum",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Buccaneer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BuddingMorale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Bullseye",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BunkerMan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BureauOutlaw",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BuriedFlow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BurnFinal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BurritoDay",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.BurstAccident",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Butterfly",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Capoeira2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Carbide",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CarbideBlackFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CarbideOrangeFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CarbideWhite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CartelHero",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CarvedBane",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CasualSize",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CautiousLoaf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Celestial",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CenterSoldier",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CentralCut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ChapterProfile",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CharmingFeather",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ChaseMonster",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ChickenAce",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Chillin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ChocoSoldier",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ChompSelf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ChorusReference",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CircleLeader",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CircleMachine",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ClanNew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ClearDefend",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ClearSight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Cliffhanger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CoachBucket",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CoalMonarch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Cocoa",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CodeGalaxy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CoffinDriver",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColdCompete",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColdDestroy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColderCompete",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColdScares",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColdWoods",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Collective",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColoredForest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColorfulSlot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ColorRiot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Comeback",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ComedyElbow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ComfyDirection",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Conqueror",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ContactSweet",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ContraryPlace",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CookieArmy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CooksDream",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CoolBreak",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CoolImpact",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CosmicTeam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CosmoBunch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CosmoDrifter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Cosmonaut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CounterSpy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CoverGift",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Cowgirl",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CowStrong",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrashContact",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrazyCowboy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrazyLung",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CreditBent",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CreepStep",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrepeWeek",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrescentCycles",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrewTradition",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrispDrink",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrocCommand",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CrossedForce",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CruelEvil",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CruelSafe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CuttingCrew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CyberFu",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CyberKarate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CyberRunner",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CyberScavenger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.CyberSyndicate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DangerArmy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DapperCandy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DarkBomber",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DarkChaser",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DarkEagle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DarkPunch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DarkShaman",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DarkViking",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Deceit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DecliningImp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Deco",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DelicateWatch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DemonHunter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DevilIsland",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DevilRock",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DevoteCatch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DieselPunk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DifferentShipment",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DimDated",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DineGrudge",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DinnerThief",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DinoGuard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DivineAspect",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DoubleReverb",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DouseIt",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DrabGoop",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DragonNinja",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Drift",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DrinkChamps",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DroppedModel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DroppedSun",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DroppedWater",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DuckBrigade",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DudeGadgets",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DueEgo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DumplingMan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.DurrrBurger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EarthSprint",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EasyBreeze",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EatingCraze",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EdConstant",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EdgyDefunct",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EerieBoats",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ElectodeDefect",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ElectrodeDefect",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Emperor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EndAspect",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Endless",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EntryCurtain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EternalStruggle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EvilBunny",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.EvilSuit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Exercise",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Exiled",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ExtraTool",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ExtraTools",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FabledStrength",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FadAhead",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FadeGuard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FailPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FairGlory",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FairyDance",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FairyTrawl",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FakeCharge",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FallStrike",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FalseEvolution",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FancyHare",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FareDebut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Farmer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FashionSimple",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FearCircle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FearlessMotor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Feathers",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FelineRat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FelineSoldier",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FieldBooth",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FieldPals",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FiestaDesign",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FiestaWatch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FightingKing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FightPardon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FinalChuckle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FinalTract",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FireElf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FireFortress",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FireMarrow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FireRoad",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FishCastle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FishClipper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Fisherman",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FishyBeef",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FishyParadise",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Flamingo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FlatPiece",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FlockCherry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FlockInsure",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FlowerStepper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FNCS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FNM2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FNOriginals",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FollowSeek",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Football",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ForemanSquad",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Forest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ForestTracker",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ForeverCommit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ForthYear",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FortKnights",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FortniteDJ",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FortniteFever",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FortuneClaw",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FosterRush",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FourthYear",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Freedom",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FreeSnake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FreeSpirit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FreshFable",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FrighteningOddity",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FrostedOats",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FrozenForward",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FrozenLegends",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FrozenTraitor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FugitiveBandit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FunnySofa",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FurnaceFace",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FurthestEnd",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.FussJam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GadgetLater",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GalaxyCreatureCatcher",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GalaxyQueen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GarageBand",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Garbo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GardenBones",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GateUncle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Geisha",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GekkoFight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GetSetGo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GhostReprisal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GildedCoast",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GildedFame",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Gingerbread",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GlareAssault",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GlowBro",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GlowCrew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GlowingGlory",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GnarlBear",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GoingNowhere",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GoingWayOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GoldHand",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GoldScore",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GoldSentinel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Golf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Goo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GoodParachute",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GooeySavior",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GradualVertical",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GraveDestroy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GravelAloof",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GreekGroup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GreenClover",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GreenPlastic",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GripHate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GroupAgony",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GroupEnergy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GroupStart",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GuanYu",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GuestFiction",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GuideRobot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Gumshoe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.GustGuide",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Hacivat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Hairy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HairyFreeze",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HalfGuide",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HarborBuffet",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HardEntry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HarshInvaders",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HarshNews",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HayMenu",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Hazmat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HeartLaw",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HeatHitter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HeftyUnit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HerdSnow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HeroAct",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HeroicMotors",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HiddenCollection",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HighSerpent",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Hippie",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HiredGun",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HitStart",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HoloCars",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HopeBetray",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HopeWatcher",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HoppityHeist",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HornedMask",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HorsingAround",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HoundGroup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HueCrash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HueDrive",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.HypothesisEpisode",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IceCream",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IceFeline",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IceKing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IceMaiden",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IceStories",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ImpendingSpiral",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ImperialRemedy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IncognitoAssassin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IndigoBird",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.InitialBandits",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.InnerGlow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.InsideEyes",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.InspirationExceed",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Interstellar",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.IntoDye",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JackedKitty",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Jailbird",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JellyBond",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JestTap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JobAdjust",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JobHeat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JokeAngel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JudgePace",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Jumpstart",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JusticeSin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.JustSoft",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KeyBot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KindaAhead",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KingClubs",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KinGhost",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KittyKungFu",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KneePorter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KnockoutBoss",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.KpopFashion",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Krampus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LabChaser",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LabSave",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LadyTreat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LandForm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Laoch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Lasagna",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LateGroove",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LateralDestiny",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LateShot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LateShuttle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LatestTerminal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LavaLeaders",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LaxBlast",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LeanScale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LeapForward",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LeapSquad",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LeaveExpert",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LeaveFast",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LemonLime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LethalBite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Lifeguard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LightHistory",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LightRain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LionArmy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LiquidHole",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LizardDriver",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LlamaWarrior",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LongDepth",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LooseBeam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Lopex",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LostConnection",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.LoudStop",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Luchador",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MadCommander",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MagicInstitute",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MagmaSlayer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MainStake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MajorDog",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MajorStaircase",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MangaCrew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MannerVisual",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MarbleShot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Markers",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MarrowArmy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MarrowBully",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MarrowCheck",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MarrowThug",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Masako",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MaskedWarrior",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MasterKey",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Math",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Mechanimal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MechDoggo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MechPilotShark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Medic",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Medusa",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MentionPremium",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MetalSheep",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MetroHub",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MiddlePotassium",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MileWild",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MilitaryFashion",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MissStem",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MoarPower",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MobPatrol",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ModernMilitary",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ModernMilitaryRed",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Molten",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MoonBark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MorningSalon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MoshingMenace",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Moth",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Mountaineer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MouseFight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Muertos",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.MushroomQueen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NativeMultiply",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Nautilus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NeonCat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NeonGlow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NeverAlways",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Nevermore",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NewHotness",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NewSnow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NewStealth",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NightThoughts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NineRing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NoneBattle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NotedDude",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Nothingness",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NothingRoyalty",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NotionRemark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NumberSeventeen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Nutcracker",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.NYE2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OceanAttack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OceanLight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OceanSee",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Octoberfest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OctoQuest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OldExplorer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OldForever",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OldRestart",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OldThreat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Omega",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OpenSkim",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OperationDiva",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OperativeBlonde",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OrnateChaser",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OutlawFire",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OverallStake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Overclocked",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.OvernightMoon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PaddedArmor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PartyLot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PastelPatrol",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PeakBet",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PenRatio",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Perimeter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PersonalMade",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PieFight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Pilots",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PioneerBoom",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Pirate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PizzaPit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Plague",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PlotStatement",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PloyCandy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PolitePlatform",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PortionCut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PortraitFolk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PotassiumWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PowerLight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PraiseKudos",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PresentForm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PriceDish",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PrimitiveSpirit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ProcessNotebook",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ProperPastel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PropertySector",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PublicRefund",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Pug",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Pumpkin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PumpkinGang",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PunchOut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Punchy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PunkDemon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.PuzzleCrux",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.QualifiedBow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.QuietWatcher",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RabbitStrength",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RainyDays",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RanchSpite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RaptorArcticCamo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RaptorBlackOps",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RareEvoke",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RebelFish",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RedNight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RedRiding",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ReefModel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ReformBike",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RelativeCall",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RemarkGlimpse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RememberDeath",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RepaymentUpdate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RevealMug",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Revival",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RhythmElbow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RichFlood",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoadGhosts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoadMouse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoadShade",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoadSnake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RobotRed",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RobotTrouble",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RockZombie",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RookieRemake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Rooster",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoseLeader",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RotateRatio",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoughStreet",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoutineWest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RoyaleHearts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RubbishMouse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RuneMood",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.RuneParallel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.S4Cumulative",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.S5Cumulative",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.S6Cumulative",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.S7Cumulative",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.S8Cumulative",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.S9Cumulative",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SaladInsure",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SamuraiUltra",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SantaPirate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Sarong",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SaveTheWorld",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ScalpExtreme",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Scarecrow",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ScareReact",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ScaryBall",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ScaryTime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ScatterBan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SciOps",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ScoreCard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Scrapyard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Scuba",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SeaIncreasing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SearchPals",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SeatRelax",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SecretHeat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ShadeChaser",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ShadowChoice",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Shark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SharpDresser",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SharpPunk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ShellState",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Shiny",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ShoalKing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ShortDefend",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ShredKing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SiegeStrike",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SillyTeam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SilverSpirit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SkaterBeast",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SkeletonHunter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Sketchy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SkullBrite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SkullTrooper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Slam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SleepComfort",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SleepyArmy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SleepyTime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SliverOperator",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SloganStar",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SloppyMortar",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SmallCrimson",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SmallFrights",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SmallLight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SnakeSmack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SnekRock",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SniperHood",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Snowboard",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SnowDudes",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SnowNinja",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SoapPure",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SoarClock",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Soccer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SolidSteel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SoloShade",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SourChum",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpaceBunny",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpaceExplorers",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpacePolice",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SparkleCrew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Speedcraft",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Speedy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpeedyMidnight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpeedyRed",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpellMovie",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SphereKickGroup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpiderCrime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpillSell",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpinHouse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpiralsCry",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpiritDarkness",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpookyOath",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SpottedTime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SquadImpact",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Squared",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SquareHurl",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SquireMeal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Squishy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StableMove",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StampArtist",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StarPumpkin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StarsAndStripes",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StartingMove",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StarWalker",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StealthSyndicate",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Stealthy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Steel",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SteepOrbit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StickPolish",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StoneDude",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StoreSlam",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StormFusion",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StormScavenger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StormSoldier",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StormTracker",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StrangeReady",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StrawberryPilot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreamMachine",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetAssassin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetDemon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetGoth",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetOps",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetPosse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetRacer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetRacerCobra",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetRacerWhite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StreetStripes",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StrengthWound",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.StrollPeanut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Stuffed",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SuddenBoundary",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SunCord",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SunFight",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SunMerc",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SunshineAndRainbows",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Sup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Sushi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SuspectSpectrum",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SweetMeaning",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.SweetTooth",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Syko",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Synergy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TacticalSanta",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TailoredPrecision",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Taxi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TealGroup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TeamShowdown",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TechMage",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TechOps",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TellEm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TenderMonarch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TentPetty",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TeriyakiFish",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TestingZylox",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TheBomb",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ThemeAgile",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ThesisCinema",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ThinCraft",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ThinGroup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TimeRuler",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TimeSheriff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TinLake",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TolerantFortune",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TollTribe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ToneTrip",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TopPole",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TotallyKnew",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ToxicKitty",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ToyedUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TrackSpeeder",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TreeLion",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TreeSmell",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TriMech",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TripCarrier",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Tropical",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TruthTenant",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TuneBeast",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TurningSolstice",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TwinReady",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.TwoActor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.UnitPoison",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.UrbanScavenger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VacationFable",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Valiant",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Vampire",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VeganVigilance",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Venus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VeryAdvanced",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VeryDangerous",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VeryEarthy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VeryFossil",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Vigilante",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Viking",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.VividEgo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Volume11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WaffleWarrior",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WakeUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Wanted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WarDefeat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WarmDealings",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WarmExoticHeat",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Warpaint",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WaterBike",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WavyMan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WeirdMessages",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WeirdObjects",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Werewolf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WheatCrisis",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WhiteTiger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WildChair",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WildHearts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WildLinen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WildName",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WinnerChain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WinterSki",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WiseMaster",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Witch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WordRemark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WorldCup2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WorldExplorers",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Wukong",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.WWIIPilot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Yeti",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ZeroPoint",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.Zodiac",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Set.ZoneWars",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Sienna.Spray",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.AnnualPass.2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.AnySeason.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CampaignHero",DevComment="Tag for Character Item Definitions that are paired with heroes and usable only when that hero is the commander in Save the World.") ++GameplayTagList=(Tag="Cosmetics.Source.Challenge",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CompReward",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Contextual.InGame.Vehicle.Ostrich",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.April2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.April2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.August2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.August2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.December2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.December2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.Feb2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.February2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.February2023",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.Jan2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.January2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.January2023",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.July2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.July2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.June2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.June2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.March2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.March2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.May2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.May2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.November2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.November2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.October2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.October2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.September2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.CrewPack.September2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.DefaultItem",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Donut",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Donut.Challenges",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfFortnite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Backpack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Banner",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Contrail",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Emoji",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.LoadingScreen",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Music",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Pickaxe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Spray",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Toy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.14DaysOfSummer.Wrap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Ashton.Emoji.Chicago",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Ashton.Emoji.Turbo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Ashton.Glider.Boardwalk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Ashton.LS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Ashton.Spray.Boardwalk",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Ashton.Spray.Jim",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.AssassinSuit.Backpack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.AssassinSuit.Emote",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.AssassinSuit.Umbrella",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.AssassinSuit.Wrap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.BackToSchool2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Birthday.2018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Birthday.2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Birthday.2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Birthday.2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Birthday.2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Competitive.ShareTheLove",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.CosmicSummer",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.CreativePromo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.CreativePromo.Backpack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.CreativePromo.Character",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.CreativePromo.Spray",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.CreatorBattle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Cyclone",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Cyclone.Challenges",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Cyclone.Concert",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.E3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Festivus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Fortbytes",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Fortnitemares.2018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Fortnitemares.2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Fortnitemares.2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Fortnitemares.2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Fortnitemares.2022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Goose",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Heist",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.IceStorm",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.MarvelKnockoutSS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Mash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Mash.LS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Mash.Wrap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Oak.Spray",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Oak.Wrap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.PirateParty",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.RebootRally",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S10OutOfTime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S7Overtime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S8ExtraCredit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S8ExtraCredit.LS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S8ExtraCredit.VTID.FireElf",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S8ExtraCredit.VTID.MasterKey",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S8ExtraCredit.VTID.Medusa",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.S9Overtime",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.SoloShowdown",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.SpringBreakout",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.SpyWithin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.StormTheAgency",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Valentines.2020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.Valentines.2021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.WorldCup2019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Event.WorldCup2019.Spray",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.FNCS",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Galileo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Galileo.Challenges",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Granted.AllAccounts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Granted.Founders",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Granted.MakeGood",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Granted.SaveTheWorld",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Granted.Twitch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.ItemShop",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.LevelUpQuest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.LiveReward",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.LoveAndWarLTM",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.MandosBountyLTM",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.Huawei",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.Nintendo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.PS4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.Samsung",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.SonyPlusPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.Switch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Platform.XboxHW",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.PlaystationActivation",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Promo",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.QuestPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.QuestReward",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.BlackIce",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.CobaltStarterPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.Eclipse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.FrozenLegends",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.Inferno",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.LagunaStarterPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.LavaLegends",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.LoveRanger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.Mash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.MintyLegends",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.MossLegends",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.NextGenStarterPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.PolarLegends",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.RoyaltyWarriors",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.STWStarterPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.SummerSplash",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.RMT.WhiteTiger",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season1.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season1.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season1.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season1.Progression",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season1.SeasonShop",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season10.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season10.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season10.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season10.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season10.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season11.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season11.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season11.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season11.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season11.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season12.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season12.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season12.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season12.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season12.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season13.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season13.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season13.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season13.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season13.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season14.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season14.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season14.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season14.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season14.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season14.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season15.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season15.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season15.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season15.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season15.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season15.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season16.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season16.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season16.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season16.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season16.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season16.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season17.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season17.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season17.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season17.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season17.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season17.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season18.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season18.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season18.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season18.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season18.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season18.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season19.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season19.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season19.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season19.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season19.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season19.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season2.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season2.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season2.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season2.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season2.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season20.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season20.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season20.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season20.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season20.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season20.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season21.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season21.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season21.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season21.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season21.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season21.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season22.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season22.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season22.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season22.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season22.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season22.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season23.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season23.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season23.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season23.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season23.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season23.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season24.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season24.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season24.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season24.BattlePass.Paid.Additional",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season24.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season24.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season3.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season3.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season3.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season3.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season3.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season4.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season4.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season4.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season4.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season4.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season5.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season5.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season5.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season5.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season5.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season6.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season6.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season6.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season6.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season6.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season7.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season7.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season7.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season7.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season7.LevelUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season8.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season8.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season8.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season8.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season8.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season9.AllGranted",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season9.BattlePass.Free",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season9.BattlePass.Paid",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season9.FirstWin",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Season9.Levelup",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.SocialActivation",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.StarterPack",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.testing",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.VMT",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Winterfest",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Winterfest.Challenges",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Source.Winterfest.Presents",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Spray.FaceMask",DevComment="Identify that this spray is a \"face mask\" spray that needs to use override settings for projectors when it\'s a spray variant on the face slot.") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Emote.Traversal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Emoticon.Animated",DevComment="") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Enlightened",DevComment="") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.GearUp",DevComment="") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.HasUpgradeQuests",DevComment="Used to avertise that this item unlocks additional variants via quests") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.HasVariants",DevComment="Used to advertise that this item has variants in the shop, etc... (not required on all cosmetics with variants)") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.LoadingScreen.Animated",DevComment="") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Reactive",DevComment="This cosmetic changes in response to your actions during a match") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Synced",DevComment="Syncd emotes tag") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Transform",DevComment="") ++GameplayTagList=(Tag="Cosmetics.UserFacingFlags.Wrap.Animated",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Actions.Reconfig.BecomeSingleWield.DualWield1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Actions.Reconfig.BecomeSingleWield.DualWield2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Actions.Reconfig.BecomeSingleWield.DualWield3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Actions.Reconfig.BecomeSingleWield.OneHanded1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Actions.Reconfig.BecomeSingleWield.SingleWield1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Actions.Reconfig.BecomeSingleWield.SingleWield2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.ClothingColor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Corruption",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Emissive",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Hair",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.JerseyColor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Material",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Numeric",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Particle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Parts",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Pattern",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.PetTemperament",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.ProfileLoadout2",DevComment="Exactly the same as ProfileLoadout, just another one") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.ProfileLoadout3",DevComment="Exactly the same as ProfileLoadout, just another one") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Progressive",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.RichColor2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.RichColor3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.RichColor4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.RichColor5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.RichColor6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.0",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.9",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.10",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.12",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.13",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.14",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.15",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.16",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.17",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.18",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.19",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Slot.20",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.TagDriven.Secondary",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.NameTag",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.RibbonColor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.RibbonType",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.ShakeSound",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.ShakeSound.Defined",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.ShakeSound.Random",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.WrappingColor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Channel.Winterfest.WrappingStyle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.AlienTrooper",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Carabus",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Collectable",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.CyberArmor",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Elastic",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Elastic.Backs",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Elastic.Hologram",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Elastic.Male",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Impulse",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.ImpulseSpring",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.JumpsuitFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.JumpsuitMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.Motorcyclist",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.TeriyakiFishToon",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.TieDyeOCVA",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.ZombieElasticFemale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.FilterSet.ZombieElasticMale",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.AlienTrooper.Cyclops",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.AlienTrooper.Horns",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.AlienTrooper.Owl",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.AlienTrooper.Tentacle",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.BadWitch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Bicycle.Jersey",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Bicycle.Suit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Broccoli.Casual",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Broccoli.Human",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Broccoli.Monkey",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Broccoli.Monkey.Casual",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.ClashV.Alien",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.ClashV.Human",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.CyberArmor.DualWield",DevComment="Emote detection tag") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.CyberArmor.SingleWield",DevComment="Emote detection tag") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Emperor.Cape",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Emperor.Dark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Emperor.Light",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Emperor.Suit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Emperor.Super",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Faux.Female",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Faux.Male",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.GhostHunter.Human",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.GhostHunter.Hunter",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.GoodWitch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.HighTowerHoneydew.Normal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.HighTowerHoneydew.Swole",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.HighTowerTomato.Casual",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.HighTowerTomato.Suit",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.HighTowerWasabi.Pickaxe",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Jekyll.Man",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Jekyll.Monster",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Lexa.Girl",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Lexa.Robot",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.NeonCatSpy.Black",DevComment="Neon Cat Spy Black Variant") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.NeonCatSpy.Blue",DevComment="Neon Cat Spy Blue Variant") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.NeonCatSpy.Grey",DevComment="Neon Cat Spy Grey Variant") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.NeonCatSpy.Red",DevComment="Neon Cat Spy Red Variant") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Nightmare.MaskOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Nightmare.MaskOn",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Scholar.Student",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.Scholar.Witch",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.TowerSentinel.HelmOff",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Meta.TowerSentinel.HelmOn",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.003",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.AppliedWrap",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier1.Epic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier1.Legendary",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier1.Mythic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier1.Rare",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier1.Uncommon",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier2.Epic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier2.Legendary",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier2.Mythic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier2.Rare",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier2.Uncommon",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier3.Epic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier3.Legendary",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier3.Mythic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier3.Rare",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier3.Uncommon",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier4.Epic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier4.Legendary",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier4.Mythic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier4.Rare",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier5.Epic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier5.Legendary",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignHero.Tier5.Mythic",DevComment="Tag used to label the minimum rarity and tier required from a hero to unlock a cosmetic variant on Campaign Hero Outfits and Backbling.") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.00",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.01",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.02",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.03",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.04",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.05",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.06",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.07",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.08",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.09",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.CampaignOptions.10",DevComment="Used for StW Heroes for options unlocked specially for StW (via Rarity and Tier of the Hero).") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.000",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.001",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.002",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.003",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.004",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.005",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.006",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.007",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.008",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.009",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.010",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.011",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.012",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.013",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.014",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.015",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.016",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.017",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.018",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.019",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.020",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.021",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.022",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.023",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.024",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.025",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.026",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.027",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.028",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.029",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.030",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.031",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.032",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.033",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.034",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.035",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.036",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.037",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.038",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.039",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.040",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.041",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.042",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.043",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.044",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.045",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.046",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.047",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.048",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.049",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.050",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.051",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.052",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.053",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.054",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.055",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.056",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.057",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.058",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.059",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.060",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.061",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.062",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.063",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.064",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.065",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Argentina",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Australia",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Belgium",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Brazil",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Canada",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Colombia",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Denmark",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Egypt",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.England",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Fortnite",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.France",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Germany",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Iceland",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Ireland",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Italy",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Japan",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Korea",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Mexico",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Netherlands",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.NewZealand",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Nigeria",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Norway",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Peru",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Poland",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Portugal",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Russia",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Saudi",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Spain",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Sweden",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Switzerland",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Terkey",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.Uruguay",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Color.USA",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive0",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emissive8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emote.CharacterGroup1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Emote.CharacterGroup2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat0",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat9",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat10",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat12",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat13",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat14",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat15",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat16",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat17",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat18",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat19",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat20",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat21",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat22",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat23",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat24",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat25",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat26",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat27",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat28",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Mat29",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.0",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.9",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.10",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.12",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.13",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.14",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.15",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.16",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.17",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.18",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.19",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.20",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.21",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.22",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.23",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.24",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.25",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.26",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.27",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.28",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.29",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.30",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.31",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.32",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.33",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.34",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.35",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.36",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.37",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.38",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.39",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.40",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.41",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.42",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.43",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.44",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.45",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.46",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.47",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.48",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.49",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.50",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.51",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.52",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.53",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.54",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.55",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.56",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.57",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.58",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.59",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.60",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.61",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.62",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.63",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.64",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.65",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.66",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.67",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.68",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.69",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.70",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.71",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.72",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.73",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.74",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.75",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.76",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.77",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.78",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.79",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.80",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.81",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.82",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.83",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.84",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.85",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.86",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.87",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.88",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.89",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.90",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.91",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.92",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.93",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.94",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.95",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.96",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.97",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.98",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Numeric.99",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle0",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle9",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle10",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle12",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle13",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle14",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle15",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle16",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle17",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle18",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle19",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle20",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Particle21",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage0",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage1",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage2",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage3",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage4",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage5",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage6",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage7",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage8",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage9",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage10",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage11",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage12",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage13",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage14",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage15",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage16",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage17",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage18",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage19",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage20",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage21",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage22",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage23",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage24",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage25",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage26",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage27",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage28",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage29",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage30",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage31",DevComment="") ++GameplayTagList=(Tag="Cosmetics.Variant.Property.Stage32",DevComment="") ++GameplayTagList=(Tag="CowerStart",DevComment="") ++GameplayTagList=(Tag="Crafting.Object.Player",DevComment="Indicates the object is crafting formulas defined for a player to self craft.") ++GameplayTagList=(Tag="Creative.Beta.DeviceSAC",DevComment="") ++GameplayTagList=(Tag="Creative.CaptureArea.Item",DevComment="Actors with this tag can be accepted by the capture area. For points to be awarded to a player, the actors instigator must be set.") ++GameplayTagList=(Tag="Creative.Device.AlwaysAllowPlacementInsideVolume",DevComment="Allow other device to be placed inside the device\'s volume") ++GameplayTagList=(Tag="Creative.Device.Boss.RecentlyTargeted",DevComment="") ++GameplayTagList=(Tag="Creative.Device.Boss.Wolf.LeapStart",DevComment="") ++GameplayTagList=(Tag="Creative.Device.CanTrigger",DevComment="Actors with this tag can explicitly activate device trigger plates.") ++GameplayTagList=(Tag="Creative.Device.DontTrigger",DevComment="") ++GameplayTagList=(Tag="Creative.Device.ItemSpawner.PickupOnTouch",DevComment="") ++GameplayTagList=(Tag="Creative.Device.Volume.DoT",DevComment="") ++GameplayTagList=(Tag="Creative.DynamicXp.CreatorKnobs.Level.10",DevComment="") ++GameplayTagList=(Tag="Creative.DynamicXp.CreatorKnobs.Level.20",DevComment="") ++GameplayTagList=(Tag="Creative.DynamicXp.CreatorKnobs.Level.30",DevComment="") ++GameplayTagList=(Tag="Creative.DynamicXp.CreatorKnobs.Level.40",DevComment="") ++GameplayTagList=(Tag="Creative.DynamicXp.CreatorKnobs.Level.50",DevComment="") ++GameplayTagList=(Tag="Creative.DynamicXp.Status.InActiveMap",DevComment="Player is in a map that is granting dynmic XP") ++GameplayTagList=(Tag="Creative.Event.TeleportPlayerToActor",DevComment="") ++GameplayTagList=(Tag="Creative.Event.TeleportToActor",DevComment="") ++GameplayTagList=(Tag="Creative.FeaturedIsland.Stamina",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Coin.Bowl",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Coin.Musical",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Coin.Note",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Coin.Paper",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Coin.StaminaBall",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Coin.StaminaCapsule",DevComment="") ++GameplayTagList=(Tag="Creative.Item.ColorPanel",DevComment="") ++GameplayTagList=(Tag="Creative.Item.Ingredient.DisplayedAsResource",DevComment="This item has the Ingredient resource type, but we don\\\'t want to display \\\"Crafting Ingredient\\\" messages to the player (e.g. in the AthenaItemDetailsHeader).") ++GameplayTagList=(Tag="Creative.Melee.Designer.HasItem",DevComment="Used when the Melee Designer has an item set.") ++GameplayTagList=(Tag="Creative.Options.CostPreviewer",DevComment="") ++GameplayTagList=(Tag="Creative.Options.PhoneToolBinding",DevComment="") ++GameplayTagList=(Tag="Creative.Options.PullToPlayer",DevComment="") ++GameplayTagList=(Tag="Creative.Options.UIMetricsDisplay",DevComment="") ++GameplayTagList=(Tag="Creative.PowerBar.Show",DevComment="Can be added to the local player to show the power bar UI in creative") ++GameplayTagList=(Tag="Creative.Property.OneTimeChange",DevComment="") ++GameplayTagList=(Tag="Creative.PropPossessor.Active.Cooldown",DevComment="") ++GameplayTagList=(Tag="Creative.PropPossessor.MakeNoise.Cooldown",DevComment="") ++GameplayTagList=(Tag="Creative.PropPossessor.PropPlaced",DevComment="") ++GameplayTagList=(Tag="Creative.Register.HidingProp.TeleportGroup",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.Heal",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageHealthDamage",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageHealthDecrease",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageHealthIncrease",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageHealthShieldDecrease",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageHealthShieldIncrease",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageShieldDecrease",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.PercentageShieldIncrease",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.SetStat",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.Abilities.UTDamage",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.ExplosiveBarrelChainReaction",DevComment="Tag for ability that allows chain reactions of barrels") ++GameplayTagList=(Tag="Creative.Toys.ExplosiveBarrelDetonate",DevComment="Tag for when a barrel explodes") ++GameplayTagList=(Tag="Creative.Toys.SpeedModifier",DevComment="") ++GameplayTagList=(Tag="Creative.Toys.SpeedModifierVehicle",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Accolades",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Accolades.LimitAwardCount",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.BarrierDevice.UseDevOptions",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.CreativeCampfire.DamageAI",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.IsInvulnerable",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.LevelInstanceDevice.Editable",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.NamePlate.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.NamePlate.FocusMode.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.NamePlate.LineOfSight.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PlayerTracker.AlwaysTrackPosition",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PlayerTracker.NoneFirstItemTracked",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PlayerTracker.NoneSecondItemTracked",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PlayerTracker.ShowMarker",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PopUpDialog.UseDialogTimeout",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PopUpDialog.UseTemplateWidgets",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PowerupDevice.EnableDevEffects",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PowerupDevice.ShowFXOnPlayer",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.PropOMaticManager.PingHiddenProps",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Quest.OverrideScoreboard",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.ScoreManager.HUD",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Sentry.CanRespawn",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Sentry.Invulnerable",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Skydome.UseMatchingPosition",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.StormController.StormSickness",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.SupplyDrop.ShowFX",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.Teleporter.ChangeTargetOnTimer",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.TimerDevice.UrgencyModeEnabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptions.VehicleSpawner.Baller.GlassBreak",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.AllowInteract",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DamageVolume.DamageOverTime",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DBNO.AllowsRevive.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DBNO.AllowsShakedowns.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DBNO.UseCustomHealthAfterRevive.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DBNO.UseCustomTenacityDepletionRate",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DBNO.UseCustomTenacityDepletionRate.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.DBNO.UseDefaultTenacityDepletionRate",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.ItemGranter.EquipGrantedItem",DevComment="Used to show what item number to equip after granting.") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.ItemGranter.GrantOnTimer",DevComment="Grant items on a timer.") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.PowerUp.UseCustomEffectDuration.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.PowerUp.UseCustomRespawnTime.Enabled",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.SBVideoScreenProps.HasCollision.Suppress",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.SwitchDevice.AllowInteraction",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.SwitchDevice.InfiniteCooldown",DevComment="") ++GameplayTagList=(Tag="Creative.UserOptionsFilter.SwitchDevice.LimitedToggles",DevComment="") ++GameplayTagList=(Tag="Cube.Movement.Crushed",DevComment="") ++GameplayTagList=(Tag="Curie.Asset.State.ElementalStatus.Burning",DevComment="") ++GameplayTagList=(Tag="Curie.Element.Fire.Extinguished",DevComment="") ++GameplayTagList=(Tag="Curie.Manager.FirePropagation",DevComment="") ++GameplayTagList=(Tag="Curie.Manager.IcePropagation",DevComment="") ++GameplayTagList=(Tag="Curie.Manager.Spatial",DevComment="") ++GameplayTagList=(Tag="Curie.Manager.VoxelFirePropagation",DevComment="") ++GameplayTagList=(Tag="Curie.Material.EnvCampFire",DevComment="") ++GameplayTagList=(Tag="Curie.Material.FleshNoFire",DevComment="") ++GameplayTagList=(Tag="Curie.Material.FueledObject",DevComment="") ++GameplayTagList=(Tag="Curie.Material.MetalProp",DevComment="") ++GameplayTagList=(Tag="Curie.Material.Tree",DevComment="") ++GameplayTagList=(Tag="Curie.NativeFX.Charred",DevComment="") ++GameplayTagList=(Tag="Curie.NativeFX.Electricity",DevComment="") ++GameplayTagList=(Tag="Curie.NativeFX.Ice",DevComment="") ++GameplayTagList=(Tag="Curie.Property.Material.InstantCombustion",DevComment="") ++GameplayTagList=(Tag="Curie.Property.Material.Magnetic",DevComment="") ++GameplayTagList=(Tag="Curie.Property.Material.MovableInteractsOnly",DevComment="") ++GameplayTagList=(Tag="Curie.Property.Material.SingleVoxel",DevComment="") ++GameplayTagList=(Tag="Curie.Property.Material.VoxelPropagation.Fast",DevComment="") ++GameplayTagList=(Tag="Curie.Property.Material.VoxelPropagation.Slow",DevComment="") ++GameplayTagList=(Tag="Curie.State.FullyIgnited",DevComment="") ++GameplayTagList=(Tag="DamageNumbers.AtHitLocation",DevComment="") ++GameplayTagList=(Tag="DamageNumbers.NotACreature",DevComment="") ++GameplayTagList=(Tag="DelMar.Map.DeathRace.Wedge",DevComment="") ++GameplayTagList=(Tag="DelMar.Map.Racing.WhiplashCircuit",DevComment="Del Mar Map Racing Whiplash Circuit") ++GameplayTagList=(Tag="Deprecated.R.HMI",DevComment="") ++GameplayTagList=(Tag="Deprecated.R.IBR",DevComment="") ++GameplayTagList=(Tag="DisableVending",DevComment="Root tag for mesh network command to disable all papaya vending machines") ++GameplayTagList=(Tag="DisableVending.Items.BoogieBomb",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.Burger",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.CrashPad",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.Fishingpole",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.Grappler",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.Jetpack",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.PaintGrenade",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.PaintLauncher",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.PlungerBow",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.Tomato",DevComment="") ++GameplayTagList=(Tag="DisableVending.Items.WaterBalloon",DevComment="") ++GameplayTagList=(Tag="DisableVending.Vehicles.Boat",DevComment="") ++GameplayTagList=(Tag="DisableVending.Vehicles.Choppa",DevComment="") ++GameplayTagList=(Tag="DisableVending.Vehicles.Quad",DevComment="") ++GameplayTagList=(Tag="DisplayCase.Ammo",DevComment="") ++GameplayTagList=(Tag="Edit.Lobby.Playerstart",DevComment="") ++GameplayTagList=(Tag="Emote.ActivationTrigger.RemoveDisguise",DevComment="") ++GameplayTagList=(Tag="Emote.BuiltIn.Transform",DevComment="Emotes that change the appearance of the character") ++GameplayTagList=(Tag="Emote.Mech.Disabled.Art",DevComment="Emote disabled while driving a mech due to art concerns") ++GameplayTagList=(Tag="Emote.Mech.Disabled.Design",DevComment="Emote disabled while driving a mech due to gameplay design concerns.") ++GameplayTagList=(Tag="Encounter.Enable.Endless.MeleeFriendlyMinibosses1",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.Endless.MeleeFriendlyMinibosses2",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.Endless.MeleeFriendlyMinibosses3",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.Endless.MiniBosses1",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.Endless.Minibosses2",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.Endless.Minibosses3",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.MiniBoss.MeleeFriendly",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.Miniboss.Modifiers.Easy",DevComment="") ++GameplayTagList=(Tag="Encounter.Enable.RiotCity.MiniBosses2",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless.T01",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless.T02",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless.T03",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless.T04",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless.T05",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Endless.T06",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.IronCity",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Mayday.Defense.Casual",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Mayday.Defense.Final",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.Mayday.Defense.Generic",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.OutpostT01.Endless",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.OutpostT02.Endless",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.OutpostT03.Endless",DevComment="") ++GameplayTagList=(Tag="Encounter.Type.PvE.Primary.OutpostT04.Endless",DevComment="") ++GameplayTagList=(Tag="Endless.Challenge.NewWaveHoliday.Enemy.Husk",DevComment="") ++GameplayTagList=(Tag="Endless.HeaterGenerator",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave1",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave2",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave3",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave4",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave5",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave6",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave7",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave8",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave9",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave10",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave11",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave12",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave13",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave14",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave15",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave16",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave17",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave18",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave19",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave20",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave21",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave22",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave23",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave24",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave25",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave26",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave27",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave28",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave29",DevComment="") ++GameplayTagList=(Tag="Endless.Reward.Badge.Wave30",DevComment="") ++GameplayTagList=(Tag="EnergyRegenOverride.Athena.Default",DevComment="") ++GameplayTagList=(Tag="EnvItem.Daisy.Mushroom.Heal",DevComment="") ++GameplayTagList=(Tag="EnvItem.ReactiveProp.GasPump",DevComment="") ++GameplayTagList=(Tag="EnvItem.ReactiveProp.PropaneTank",DevComment="") ++GameplayTagList=(Tag="EnvItem.SuperDingo",DevComment="") ++GameplayTagList=(Tag="EnvItem.SuperDingo.HighTower",DevComment="") ++GameplayTagList=(Tag="ERG.Bang.CustomRoom",DevComment="") ++GameplayTagList=(Tag="ERG.Bang.Donut",DevComment="") ++GameplayTagList=(Tag="ERG.Bang.Elevator",DevComment="") ++GameplayTagList=(Tag="ERG.Bang.Faction",DevComment="") ++GameplayTagList=(Tag="ERG.Bang.Map",DevComment="") ++GameplayTagList=(Tag="ERG.Bang.PunchCard",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.ChangingRoom.Character",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.ChangingRoom.Choice",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.ChangingRoom.ChoiceLow",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.ChangingRoom.Main",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.DonutRoom",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.DonutRoom.Calendar",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.DonutRoom.Computer",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.DonutRoom.Letter",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.DonutRoom.MilkCarton",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.FlyBy",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.1",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.2",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.3",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.4",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.5",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.6",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.7",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.8",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.9",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Focus.10",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.BuffCat",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.BuffCat.FactionInspect",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.BuffCat.FactionInspect.01",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.BuffCat.FactionInspect.02",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.BuffCat.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.BuffCat.Intro",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.CatBurglar",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.CatBurglar.FactionInspect.01",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.CatBurglar.FactionInspect.02",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.CatBurglar.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.CatBurglar.Intro",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.OceanRider",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.Photographer",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.Photographer.FactionInspect.01",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.Photographer.FactionInspect.02",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.Photographer.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.Photographer.Intro",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.SkullDude",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.SkullDude.FactionInspect.01",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.SkullDude.FactionInspect.02",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.SkullDude.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.SkullDude.Intro",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.TnTina",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.TnTina.FactionInspection",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.TnTina.FactionInspection.01",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.TnTina.FactionInspection.02",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.TnTina.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Henchmen.TnTina.Intro",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Interaction",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Lodge.Close",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Lodge.Far",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.BattlePass",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.BattlePassExplore",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Challenges.DailyBounty",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Challenges.Event",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Challenges.Hardcore",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Challenges.Styles",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.ChangeRoomDoor",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Closeup",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.ComicStand",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Elevator",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Main",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Outside",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Table",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.MainRoom.Umbrella",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Movement.Exterior",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Movement.Interior",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Source",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.SourceFar",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Staging",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.StyleChallenges",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.Transition",DevComment="") ++GameplayTagList=(Tag="ERG.Camera.UsePreviouseSelectedCamera",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Banana",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Dino",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Duck",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.EasterHead",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Fireworks",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Football",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.GrantQuests",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Llama",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.NutcrackerBird",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.SantaHat",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Saxaphone",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Shield",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Tomato",DevComment="") ++GameplayTagList=(Tag="ERG.Interaction.Tree",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.7",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.8",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.9",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.10",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.11",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.12",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.A.13",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.AdventureGirl",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.Boardgame",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.PoolFloaty",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.Stars.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.Stars.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.Stars.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.AdventureGirlRoom.Stars.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.B.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.B.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.B.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.B.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.B.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.B.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.BuffCatRoom.BuffCat",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.BuffCatRoom.CatToy",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.BuffCatRoom.Dartboard",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.BuffCatRoom.Pistol",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.BuffCatRoom.Speedbag",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.BuffCatRoom.Unicorn",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.7",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.8",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.9",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.10",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.11",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.12",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.C.13",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.CatBurglarRoom.CatBurglar",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.CatBurglarRoom.GoldBar.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.CatBurglarRoom.GoldBar.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.CatBurglarRoom.GoldBar.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.CatBurglarRoom.Phonograph",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.CatBurglarRoom.Shorts",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.7",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.D.8",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.DonutRoom.Calendar",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.DonutRoom.Computer",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.DonutRoom.Katana",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.DonutRoom.Letter",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.DonutRoom.MilkCarton",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.E.7",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Exterior.CloseView",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Exterior.FarView",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.F.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.G.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.G.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.G.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Interior.FlyBy",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Interior.Inside",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Interior.OutsideWindow",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.BattlePass",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.BattlePass.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.BattlePass.InfoPanels",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.ChangingRoom",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.BlackKnight",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.Cyclone",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.MechanicalEngineer",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.OceanRider",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.ProfessorPup",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.Sandcastle",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.SpaceWandered",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Character.TacticalScuba",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.CharacterInspection.OceanRider",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Chimi1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Chimi2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Chimi3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.ComicBookStand",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.DailyScreen",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.DailyScreen.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.DonutPistol",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.DonutVent",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.DonutVent.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.DonutVent.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Elevator",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.EventScreen",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.EventScreen.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.HardCoreScreen",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.StyleScreen",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Table",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.MainRoom.Umbrella",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.7",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.P.8",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.PaperDollRoom.Katana",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.PaperDollRoom.PaperDoll",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.HeavyBag",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.Marker",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.PaperTarget",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.SkullDude",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.Valve.1",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.Valve.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.Valve.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.Valve.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.SkullDudeRoom.WobblyPistol",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.TnTinaRoom.DonutPistol",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.TnTinaRoom.Machine",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.TnTinaRoom.Plunger",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.TnTinaRoom.TnTina",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.TnTinaRoom.Wobbly",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.2",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.3",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.4",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.5",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.6",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.7",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.8",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.9",DevComment="") ++GameplayTagList=(Tag="ERG.NavObject.Z.10",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.8",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.9",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.10",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.11",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.12",DevComment="") ++GameplayTagList=(Tag="ERG.Node.A.13",DevComment="") ++GameplayTagList=(Tag="ERG.Node.B.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.C.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.D.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.8",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier01.9",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier02.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.8",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.9",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier03.10",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier04.8",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.8",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.9",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.10",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.11",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.12",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.13",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.14",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier05.15",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier06.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier06.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier06.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier06.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier06.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier07.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier07.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier07.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier07.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier07.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier07.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.5",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.6",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier08.7",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier09.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier09.1",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier09.2",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier09.3",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier09.4",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier10.0",DevComment="") ++GameplayTagList=(Tag="ERG.Node.Tier10.1",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.BuffCatRoom.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.CatBurglar.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.ChangeRoom.Categories",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.ChangeRoom.Main",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.HenchmanSkullRoom",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.HenchmanSkullRoom.FactionInspection",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.HenchmanSkullRoom.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.BattlePass",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.BattlePassExplore",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.ChallengePunchCard",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Customization",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Daily",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Donut",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Donut.Computer",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Elevator",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Event",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Main",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.MainRoom.Table",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.OceanRider",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.OceanRider.FactionSelect",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.Photographer.FactionSelection",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.StyleChallenges",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.TnTinaRoom",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.TnTinaRoom.FactionInspect",DevComment="") ++GameplayTagList=(Tag="ERG.Screen.TnTinaRoom.FactionSelect",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Ninja.Perks.Eggtermination.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Ninja.Perks.HangTime.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Ninja.Perks.HangTime.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Ninja.Perks.NightStalker.Activate",DevComment="Enables NightStalker") ++GameplayTagList=(Tag="Event.Abilities.Ninja.Perks.NightStalker.Deactivate",DevComment="Removes Night Stalker") ++GameplayTagList=(Tag="Event.Abilities.Outlander.AntiMaterialCharge.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Outlander.Perks.PhaseCannon.Activate",DevComment="Activate Event For Phase Cannon") ++GameplayTagList=(Tag="Event.Abilities.Outlander.Perks.PhaseCannon.Deactivate",DevComment="Event Tag for Phase Cannon Deactivate") ++GameplayTagList=(Tag="Event.Abilities.Weapon.Melee.Axe.LeapAttack.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Weapon.Melee.Sword.BlinkStrike.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Weapon.Melee.Sword.BlinkStrikeStormking.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Weapon.Melee.Sword.CrescentStrike.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Weapon.Melee.Tool.LeapAttack.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Abilities.Weapon.Melee.Tool.MeteorSlam.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.FragGrenade.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.FragGrenade.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.FragGrenade.GrenadeExploded",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.GoinCommando.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.GoinCommando.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.LeftyAndRighty.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.LeftyAndRighty.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.QuickFingers",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.QuickFingers.Effects",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.QuickFingers.T02",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.Shockwave.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.Shockwave.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.Shockwave.Explode",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.Warcry.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Commando.Warcry.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.BullRush.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.BullRush.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.Decoy.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.Decoy.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.GoinConstructor.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.GoinConstructor.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.GoinConstructor.ShieldBlock.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.GoinConstructor.ShieldBlock.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.PlasmaPulse.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Constructor.PlasmaPulse.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Gadget.BotShield.AOEBeginOverlap",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Gadget.BotShield.AOEEndOverlap",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Gadget.BotShield.BotDeath",DevComment="") ++GameplayTagList=(Tag="Event.Ability.HeavyAttack.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.HeavyAttack.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.HeavyAttack.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Hero.Activate",DevComment="Event used to tell the player that they activated and ability.") ++GameplayTagList=(Tag="Event.Ability.Ninja.CrescentKick.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.CrescentKick.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.CrescentKick.TriggeredDamageEvent",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.DragonSlash.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.DragonSlash.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.Kunaihilation.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.KunaiStorm.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.KunaiStorm.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.KunaiStorm.KunaiThrown",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.SmokeBomb.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.SmokeBomb.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.ThrowingStars.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.ThrowingStars.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Ninja.ThrowingStars.Destroyed",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Outlander.GroundWave.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Outlander.GroundWave.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Outlander.GroundWave.Explosion",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Outlander.GroundWave.MaxRangeReached",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Outlander.PhaseShift.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Outlander.PhaseShift.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.BassSolo.RockOut",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Constructor.BASE.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Constructor.KineticOverload.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Constructor.KineticOverload.Deactivation",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.DangerDanger.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Gnarly.Active",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.HuntersInstinct.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.HuntersInstinct.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Kunaihilation.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Ninja.MantisLeap.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Ninja.MantisLeap.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Ninja.ShadowStance.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Ninja.ShadowStance.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Outlander.InTheZone.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Outlander.InTheZone.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.Phantasm.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.PlasmaPulseBlast.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.SpaceTechnology.Explosion",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.TailOfTheDragon.Trigger",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.YoureComingWithMe",DevComment="") ++GameplayTagList=(Tag="Event.Ability.Perk.YourMoveCreep.Triggered",DevComment="") ++GameplayTagList=(Tag="Event.Animation.Alerted",DevComment="") ++GameplayTagList=(Tag="Event.Animation.Combat",DevComment="") ++GameplayTagList=(Tag="Event.AppleSunUsed",DevComment="") ++GameplayTagList=(Tag="Event.Ashton.Indigo.Boosted",DevComment="") ++GameplayTagList=(Tag="Event.Athena.AppleSauce.SpawnRockets",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Ashton.Carmine.FinalGem",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Ashton.Carmine.PickedUpStone",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Balloons.Deflate",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Balloons.Inflate",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Balloons.RemoveAll",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Balloons.Shot",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.BeginOverlap",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.EndOverlap",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.Equipped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.ForceDrop",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.Input",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.Looted",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.PassRef",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Booty.Unequipped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.ChillBronco.Hit",DevComment="") ++GameplayTagList=(Tag="Event.Athena.DiscoItem.BrokenBranch.Equipped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.DiscoItem.BrokenBranch.SendRef",DevComment="") ++GameplayTagList=(Tag="Event.Athena.DiscoItem.BrokenBranch.UnEquipped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.DogSweater.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Athena.EnvCampFire.Douse",DevComment="") ++GameplayTagList=(Tag="Event.Athena.EnvCampFire.Expired",DevComment="Event used when the time limit of the camp fire expires") ++GameplayTagList=(Tag="Event.Athena.EnvCampFire.Light",DevComment="") ++GameplayTagList=(Tag="Event.Athena.EnvItem.Lock.Locked",DevComment="") ++GameplayTagList=(Tag="Event.Athena.EnvItem.Lock.Scanner.ScanStart",DevComment="") ++GameplayTagList=(Tag="Event.Athena.EnvItem.Lock.Unlocked",DevComment="") ++GameplayTagList=(Tag="Event.Athena.EnvItem.Sentry.Target",DevComment="") ++GameplayTagList=(Tag="Event.Athena.ExplosiveBow.AltFire",DevComment="") ++GameplayTagList=(Tag="Event.Athena.FireworksMortarHolder.Fire",DevComment="") ++GameplayTagList=(Tag="Event.Athena.FireworksMortarHolder.Spawned",DevComment="") ++GameplayTagList=(Tag="Event.Athena.FireworksMortarHolder.Trajectory.Activate",DevComment="") ++GameplayTagList=(Tag="Event.Athena.FireworksMortarHolder.Trajectory.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Athena.FitHopper.Fired",DevComment="") ++GameplayTagList=(Tag="Event.Athena.FreeTeleportingPawn",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Galileo.Lobster.BlockedDamage",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.AbortTeleporting",DevComment="A generic way to abort the teleport. Initially needed in Creative when the minigame stops.") ++GameplayTagList=(Tag="Event.Athena.HidingProp.AnimMontageEntry",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.Hide",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.Interact",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.LandedOn",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.PropExited",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.StopHiding",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.Teleport",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.UpdatePropReference",DevComment="") ++GameplayTagList=(Tag="Event.Athena.HidingProp.ZeroPointEngergy.HealthAndShields",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.AppleSauce.SpawnRockets",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Chicago.Catch",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Chicago.Defense",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Chicago.Defense.End",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Chicago.Throw",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Turbo.Catch",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Turbo.Recall",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Turbo.SpawnProj",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.Ashton.Turbo.Strike",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.BadgerBangs.Explode",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.BadgerGrape.Destroy",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.ChromeSplash.Explode",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.Fire",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.ForceEnd",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.Hooked",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.InWorld",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.ProjDestroyed",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.Snapped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.Stop",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FloppingRabbit.Unhooked",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank.AmmoChanged",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank.Application",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank.Equipped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank.ProjSpawned",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank.Toss",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.FringePlank.Unequipped",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.GliderGrappler.Destroy",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.HappyGhost.Destroy",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.HappyGhost.Fire",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.HappyGhost.Hit",DevComment="Event that gets sent to the instigating pawn when the projectile hits something") ++GameplayTagList=(Tag="Event.Athena.Item.HappyGhost.ProjSpawned",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.HappyGhost.Stop",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.ItemThrown",DevComment="To react to thrown projectiles such as grenades and consumeables.") ++GameplayTagList=(Tag="Event.Athena.Item.LumpyRug.OnPress",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.LumpyRug.OnRelease",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Item.ThrowingAxe.Fire",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Lotus.StartCoolDown",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Quest.TankDamageThreshold",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Sand.Unburrow",DevComment="") ++GameplayTagList=(Tag="Event.Athena.SneakySnowman.StopHiding",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Tether.ForceDisconnect",DevComment="") ++GameplayTagList=(Tag="Event.Athena.Tether.FullStop",DevComment="") ++GameplayTagList=(Tag="Event.Athena.TimeTrial.Checkpoint",DevComment="") ++GameplayTagList=(Tag="Event.Athena.TimeTrial.Fail",DevComment="") ++GameplayTagList=(Tag="Event.Athena.TimeTrial.Start",DevComment="") ++GameplayTagList=(Tag="Event.Athena.TimeTrial.Success",DevComment="") ++GameplayTagList=(Tag="Event.Athena.TimeTrialWait",DevComment="") ++GameplayTagList=(Tag="Event.Athena.WilliePete.Enter",DevComment="") ++GameplayTagList=(Tag="Event.BattleLab.AutoLauncher.Fire",DevComment="") ++GameplayTagList=(Tag="Event.BattleLab.Barrel.Explode",DevComment="") ++GameplayTagList=(Tag="Event.BrawnySmasher.Reveal",DevComment="") ++GameplayTagList=(Tag="Event.Canary.Run",DevComment="") ++GameplayTagList=(Tag="Event.Checkpoint.Add",DevComment="To mark that the Gameplay Ability is added when a player passes a checkpoint for the first time.") ++GameplayTagList=(Tag="Event.Checkpoint.End",DevComment="To pass an event to the Gameplay Ability to remove itself") ++GameplayTagList=(Tag="Event.Checkpoint.Pass",DevComment="To pass an event to the Gameplay Ability that a player has passed a checkpoint.") ++GameplayTagList=(Tag="Event.Checkpoint.Update",DevComment="") ++GameplayTagList=(Tag="Event.ControlEnded.FullBodyHit",DevComment="") ++GameplayTagList=(Tag="Event.ControlEnded.Knockback",DevComment="") ++GameplayTagList=(Tag="Event.ControlEnded.Pushback",DevComment="") ++GameplayTagList=(Tag="Event.ControlEnded.Stun",DevComment="") ++GameplayTagList=(Tag="Event.Creative.Device.SBDanceVolume.Dance.Pause",DevComment="") ++GameplayTagList=(Tag="Event.Creative.Device.SBDanceVolume.Dance.Resume",DevComment="") ++GameplayTagList=(Tag="Event.Creative.Device.SBDanceVolume.Dance.Start",DevComment="") ++GameplayTagList=(Tag="Event.Creative.Device.SBDanceVolume.Dance.Stop",DevComment="") ++GameplayTagList=(Tag="Event.Explorers.FragmentUsed",DevComment="") ++GameplayTagList=(Tag="Event.HCChallenge.TacoTime.DanceCancelled",DevComment="") ++GameplayTagList=(Tag="Event.HeldObject.ForceDrop",DevComment="") ++GameplayTagList=(Tag="Event.HeldObject.Released",DevComment="") ++GameplayTagList=(Tag="Event.Hitchhiker.Lay",DevComment="") ++GameplayTagList=(Tag="Event.Hurdle.Started",DevComment="") ++GameplayTagList=(Tag="Event.Interact.Gather",DevComment="Fired when gathering a CBGA") ++GameplayTagList=(Tag="Event.IO.Elevator.KnockBack",DevComment="") ++GameplayTagList=(Tag="Event.Marked.EnemyTarget",DevComment="") ++GameplayTagList=(Tag="Event.MeatPizza.AlreadyCrouched",DevComment="") ++GameplayTagList=(Tag="Event.Melee.Block",DevComment="") ++GameplayTagList=(Tag="Event.Melee.BlockBreak",DevComment="") ++GameplayTagList=(Tag="Event.Melee.Dodge",DevComment="") ++GameplayTagList=(Tag="Event.Melee.HeavyAttack",DevComment="") ++GameplayTagList=(Tag="Event.Melee.InAirAttack",DevComment="") ++GameplayTagList=(Tag="Event.Melee.Parry",DevComment="") ++GameplayTagList=(Tag="Event.Melee.Stagger",DevComment="") ++GameplayTagList=(Tag="Event.PossessedProp.Placed.Activate",DevComment="") ++GameplayTagList=(Tag="Event.PossessedProp.Placed.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.PossessedProp.Preview.Activate",DevComment="") ++GameplayTagList=(Tag="Event.PossessedProp.Preview.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.PossessedProp.Remove",DevComment="") ++GameplayTagList=(Tag="Event.Projectile.SpawnedBGA",DevComment="") ++GameplayTagList=(Tag="Event.PropPlaced.Preview.Activate",DevComment="") ++GameplayTagList=(Tag="Event.PropPlaced.Preview.Deactivate",DevComment="") ++GameplayTagList=(Tag="Event.Quest.FishedUpWeapon",DevComment="") ++GameplayTagList=(Tag="Event.Quest.SignalBooster",DevComment="") ++GameplayTagList=(Tag="Event.Quest.SpawnQuestActor.Fishing",DevComment="") ++GameplayTagList=(Tag="Event.Quests.SwingingHit",DevComment="") ++GameplayTagList=(Tag="Event.SeedInteracted",DevComment="") ++GameplayTagList=(Tag="Event.Shield.Hit",DevComment="") ++GameplayTagList=(Tag="Event.Starlight.D5Science.Boss.FightStart",DevComment="") ++GameplayTagList=(Tag="Event.Starlight.D5Science.Boss.IntoIdleCancel",DevComment="") ++GameplayTagList=(Tag="Event.Starlight.D5Science.Boss.IntroIdle",DevComment="") ++GameplayTagList=(Tag="Event.Starlight.D5Science.Boss.StunEnded",DevComment="") ++GameplayTagList=(Tag="Event.Starlight.D5Science.Boss.TransformToHyde",DevComment="") ++GameplayTagList=(Tag="Event.Starlight.D5Science.Boss.TriggerStun",DevComment="") ++GameplayTagList=(Tag="Event.Tandem.Duel.BounceBack",DevComment="") ++GameplayTagList=(Tag="Event.TinStack.Action.Crouched",DevComment="") ++GameplayTagList=(Tag="Event.TinStack.Trigger.HighFlyerExplode",DevComment="") ++GameplayTagList=(Tag="Event.TouchConsumableThrown",DevComment="") ++GameplayTagList=(Tag="Event.TriggeredControl.Dance",DevComment="") ++GameplayTagList=(Tag="Event.TriggeredControl.FullBodyHit",DevComment="Event to send to the actor that triggered the control effect") ++GameplayTagList=(Tag="Event.TriggeredControl.KnockBack",DevComment="") ++GameplayTagList=(Tag="Event.TriggeredControl.Stun",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Charge.End",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Charge.Start",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Dash",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Dash.End",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Float.End",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Float.Falling",DevComment="") ++GameplayTagList=(Tag="Event.Uncle.Float.Start",DevComment="") ++GameplayTagList=(Tag="Event.Vehicle.Entered",DevComment="") ++GameplayTagList=(Tag="Event.Vehicle.Exit",DevComment="") ++GameplayTagList=(Tag="Event.Volume.Damage",DevComment="") ++GameplayTagList=(Tag="Event.Volume.Damage.Add",DevComment="") ++GameplayTagList=(Tag="Event.Volume.Damage.Remove",DevComment="") ++GameplayTagList=(Tag="Event.Weapon.Reload.Camera.End",DevComment="") ++GameplayTagList=(Tag="Event.Weapon.Thrown",DevComment="") ++GameplayTagList=(Tag="Event.Weapons.Flashlight.Toggle.Off",DevComment="") ++GameplayTagList=(Tag="Event.Weapons.Flashlight.Toggle.On",DevComment="") ++GameplayTagList=(Tag="Event.Weapons.Overheat",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Banana",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Birdshot",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Crackshot",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.CrackShot.Unapproved",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Dino",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Fire",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.GalileoRocket",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.LeftPile",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.RightPile",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Saxaphone",DevComment="") ++GameplayTagList=(Tag="EventLevel.UI.IndicatorTextList.Tree",DevComment="") ++GameplayTagList=(Tag="EventMessage.DebugCommand",DevComment="") ++GameplayTagList=(Tag="EventMessage.GameFlow",DevComment="") ++GameplayTagList=(Tag="Evolution.Schematic.Weapons.Ranged.SMG.Tier2a.3a.4a.5a.6a",DevComment="") ++GameplayTagList=(Tag="Faction.npc.neutral",DevComment="") ++GameplayTagList=(Tag="Fallbacks.NPC.Slone",DevComment="") ++GameplayTagList=(Tag="Fallbacks.NPC.Tandem",DevComment="") ++GameplayTagList=(Tag="FancyFlyDisc_Score",DevComment="") ++GameplayTagList=(Tag="Feat.Hidden",DevComment="") ++GameplayTagList=(Tag="Feat.HiddenObjectives",DevComment="") ++GameplayTagList=(Tag="Fish.Anger.Silver",DevComment="") ++GameplayTagList=(Tag="Fish.Angler.Green",DevComment="") ++GameplayTagList=(Tag="Fish.Angler.Purple",DevComment="") ++GameplayTagList=(Tag="Fish.Angler.PurpleOrange",DevComment="") ++GameplayTagList=(Tag="Fish.Angler.RedGreen",DevComment="") ++GameplayTagList=(Tag="Fish.Angler.Silver",DevComment="") ++GameplayTagList=(Tag="Fish.BattleFish.Default",DevComment="") ++GameplayTagList=(Tag="Fish.CuddleFish.Blue",DevComment="") ++GameplayTagList=(Tag="Fish.CuddleFish.Default",DevComment="") ++GameplayTagList=(Tag="Fish.CuddleFish.Green",DevComment="") ++GameplayTagList=(Tag="Fish.CuddleFish.Orange",DevComment="") ++GameplayTagList=(Tag="Fish.CuddleFish.Red",DevComment="") ++GameplayTagList=(Tag="Fish.EffectiveFlopper.BlackBlue",DevComment="") ++GameplayTagList=(Tag="Fish.EffectiveFlopper.Blue",DevComment="") ++GameplayTagList=(Tag="Fish.EffectiveFlopper.Purple",DevComment="") ++GameplayTagList=(Tag="Fish.EffectiveFlopper.White",DevComment="") ++GameplayTagList=(Tag="Fish.EffectiveFlopper.Yellow",DevComment="") ++GameplayTagList=(Tag="Fish.FlameyFish.Black",DevComment="") ++GameplayTagList=(Tag="Fish.FlameyFish.Drift",DevComment="") ++GameplayTagList=(Tag="Fish.FlameyFish.LightBlue",DevComment="") ++GameplayTagList=(Tag="Fish.FlameyFish.Orange",DevComment="") ++GameplayTagList=(Tag="Fish.FlameyFish.Tan",DevComment="") ++GameplayTagList=(Tag="Fish.Flopper.Blue",DevComment="") ++GameplayTagList=(Tag="Fish.Flopper.Green",DevComment="") ++GameplayTagList=(Tag="Fish.Flopper.Orange",DevComment="") ++GameplayTagList=(Tag="Fish.FlopperShadow.Blue",DevComment="") ++GameplayTagList=(Tag="Fish.FlopperShadow.Default",DevComment="") ++GameplayTagList=(Tag="Fish.FlopperShadow.Green",DevComment="") ++GameplayTagList=(Tag="Fish.FlopperShadow.Pink",DevComment="") ++GameplayTagList=(Tag="Fish.FlopperShadow.Teal",DevComment="") ++GameplayTagList=(Tag="Fish.Gas.Blue",DevComment="") ++GameplayTagList=(Tag="Fish.Gas.Clown",DevComment="") ++GameplayTagList=(Tag="Fish.Gas.Default",DevComment="") ++GameplayTagList=(Tag="Fish.Gas.Orange",DevComment="") ++GameplayTagList=(Tag="Fish.Gas.Purple",DevComment="") ++GameplayTagList=(Tag="Fish.JellyFish.Black",DevComment="") ++GameplayTagList=(Tag="Fish.JellyFish.LightBlue",DevComment="") ++GameplayTagList=(Tag="Fish.JellyFish.Pink",DevComment="") ++GameplayTagList=(Tag="Fish.JellyFish.Purple",DevComment="") ++GameplayTagList=(Tag="Fish.JellyFish.Yellow",DevComment="") ++GameplayTagList=(Tag="Fish.KevFish.Brown",DevComment="") ++GameplayTagList=(Tag="Fish.KevFish.Drift",DevComment="") ++GameplayTagList=(Tag="Fish.KevFish.Olive",DevComment="") ++GameplayTagList=(Tag="Fish.KevFish.Pink",DevComment="") ++GameplayTagList=(Tag="Fish.KevFish.White",DevComment="") ++GameplayTagList=(Tag="Fish.LessEffectiveFlopper.Black",DevComment="") ++GameplayTagList=(Tag="Fish.LessEffectiveFlopper.Blue",DevComment="") ++GameplayTagList=(Tag="Fish.LessEffectiveFlopper.LightBlue",DevComment="") ++GameplayTagList=(Tag="Fish.LessEffectiveFlopper.Purple",DevComment="") ++GameplayTagList=(Tag="Fish.LessEffectiveFlopper.Tan",DevComment="") ++GameplayTagList=(Tag="Fish.MechanicFish.Default",DevComment="") ++GameplayTagList=(Tag="Fish.Rift.BlackSpotted",DevComment="") ++GameplayTagList=(Tag="Fish.Rift.Butterfly",DevComment="") ++GameplayTagList=(Tag="Fish.Rift.Galactic",DevComment="") ++GameplayTagList=(Tag="Fish.Rift.Sandstorm",DevComment="") ++GameplayTagList=(Tag="Fish.Rift.Volcanic",DevComment="") ++GameplayTagList=(Tag="Fish.ShieldFish.BlackBlue",DevComment="") ++GameplayTagList=(Tag="Fish.ShieldFish.BlackStripes",DevComment="") ++GameplayTagList=(Tag="Fish.ShieldFish.Green",DevComment="") ++GameplayTagList=(Tag="Fish.ShieldFish.LightBlue",DevComment="") ++GameplayTagList=(Tag="Fish.ShieldFish.Pink",DevComment="") ++GameplayTagList=(Tag="Fish.Snowman.Default",DevComment="") ++GameplayTagList=(Tag="Fish.ZeroPoint.Crimson",DevComment="") ++GameplayTagList=(Tag="Fish.ZeroPoint.Leafy",DevComment="") ++GameplayTagList=(Tag="Fish.ZeroPoint.Skull",DevComment="") ++GameplayTagList=(Tag="Fish.ZeroPoint.Tidal",DevComment="") ++GameplayTagList=(Tag="Fish.ZeroPoint.Tiger",DevComment="") ++GameplayTagList=(Tag="FlopperSpawn",DevComment="") ++GameplayTagList=(Tag="FlopperSpawn.DefaultLootTag",DevComment="") ++GameplayTagList=(Tag="FlopperSpawn.Type.Default",DevComment="") ++GameplayTagList=(Tag="FlopperSpawn.Type.World",DevComment="") ++GameplayTagList=(Tag="FoleySound.Fauna.VO.Idle.Break",DevComment="") ++GameplayTagList=(Tag="FoleySound.Fauna.VO.Idle.Flying",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Banana",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Cabbage",DevComment="") ++GameplayTagList=(Tag="ForagedItem.CandyCorn",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Carrot",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Coconuts",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Corn",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Fireflies",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Glitch",DevComment="") ++GameplayTagList=(Tag="ForagedItem.GreenGlop",DevComment="") ++GameplayTagList=(Tag="ForagedItem.HopDrop",DevComment="") ++GameplayTagList=(Tag="ForagedItem.JellyBean",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Pepper",DevComment="") ++GameplayTagList=(Tag="ForagedItem.PepperMint",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Rift",DevComment="Tag for Rift foraged Item") ++GameplayTagList=(Tag="ForagedItem.ShieldHops",DevComment="") ++GameplayTagList=(Tag="ForagedItem.SpookyMist",DevComment="Spooky Mist Foraged Item Tag") ++GameplayTagList=(Tag="ForagedItem.ThermalTaffy",DevComment="") ++GameplayTagList=(Tag="ForagedItem.Tomato",DevComment="") ++GameplayTagList=(Tag="ForagedItem.ZeroPointCrystal",DevComment="") ++GameplayTagList=(Tag="Fort.Athena.CollisionReactionComp.BlacklistBuildingActor",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Chrome",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Cube",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Default",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Dirt",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Explosive",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Glass",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Grass",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HumanFlesh",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HumanOppositionFlesh",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HuskFlesh",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HuskFleshElectric",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HuskFleshFire",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HuskFleshIce",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.HuskFleshMetal",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Ice",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Lava",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Metal",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Objective",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.PurpleFlesh",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Sand",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Snow",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Stone",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.TakerFlesh",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Water",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.WeakSpot",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.WeakSpot_Metal",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.WeakSpot_Stone",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.WeakSpot_Wood",DevComment="") ++GameplayTagList=(Tag="Fort.SurfaceType.Wood",DevComment="") ++GameplayTagList=(Tag="Freaky",DevComment="") ++GameplayTagList=(Tag="FreshCheese.XPAccolade.Common",DevComment="") ++GameplayTagList=(Tag="FreshCheese.XPAccolade.Epic",DevComment="") ++GameplayTagList=(Tag="FreshCheese.XPAccolade.Legendary",DevComment="") ++GameplayTagList=(Tag="FreshCheese.XPAccolade.Rare",DevComment="") ++GameplayTagList=(Tag="FreshCheese.XPAccolade.Uncommon",DevComment="") ++GameplayTagList=(Tag="Gadget.Perk.Constructor.BASE",DevComment="") ++GameplayTagList=(Tag="Gameplay.Ability.PickaxeBooty",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Attacking.Melee",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Attacking.Ranged",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Drown",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.NPC.ManualControl",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.AthenaInTurret",DevComment="Gameplay Tag Action when player enters Mounted Turret.") ++GameplayTagList=(Tag="Gameplay.Action.Player.AthenaInValet",DevComment="Tag granted to players when they are in a valet vehicle.") ++GameplayTagList=(Tag="Gameplay.Action.Player.AthenaSmokeBomb",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.BlockEditMode",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.BlockExplosionDamage",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.BlockRespawnChip",DevComment="Tag granted to players to prevent a respawn chip from being spawned after they die.") ++GameplayTagList=(Tag="Gameplay.Action.Player.BlockVehicleEjectOnDBNO",DevComment="This prevents the ejection of dbno players from this vehicle") ++GameplayTagList=(Tag="Gameplay.Action.Player.BlockWhileFalling",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Cannonball",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.ConvertPawn",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.CornFieldMovement",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Creative.Flying",DevComment="When in Creative and flying.") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceFestivus",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceGalileo.A",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceGalileo.B",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceGalileo.C",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceGalileo.D",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceStunned.GreenGlop",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceStunned.NYE",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceStunned.SpyTechVictoryDance",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.DanceStunnedNYE",DevComment="New tag for NYE Dance") ++GameplayTagList=(Tag="Gameplay.Action.Player.Dive",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.ForceAutoRun",DevComment="Gameplay tag that forces the afflicted player to auto-run forward regardless of player input.") ++GameplayTagList=(Tag="Gameplay.Action.Player.HeldObject.Drop",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.HeldObject.Hold",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.HeldObject.Throw",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.HidingProp.Teleport",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Hurdle.Hurdling",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.InBushProp",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.InGrass",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.InterruptAttachment",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.InVehicle.Motorcycle",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.InVehicle.Tank",DevComment="Gameplay Tag for Players inside the Tank Vehicle") ++GameplayTagList=(Tag="Gameplay.Action.Player.InWindup",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.JetpackAlt",DevComment="Child versions of jetpacks like cosmos that we need to branch GE set on") ++GameplayTagList=(Tag="Gameplay.Action.Player.Land",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Melee.Basic",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Melee.Block",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.MobileConsumablePrimary",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.MobileConsumableSecondary",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.OperatingTurret",DevComment="Tag applied to a player currently operating a Mounted Turret.") ++GameplayTagList=(Tag="Gameplay.Action.Player.PossessedProp.Previewing",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.PreventADS",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.PreventLaunch",DevComment="Blocks the player from being launched using LaunchCharacter.") ++GameplayTagList=(Tag="Gameplay.Action.Player.PreventPersonalVehicle",DevComment="Blocks and cancels Player.PersonalVehicle abilities") ++GameplayTagList=(Tag="Gameplay.Action.Player.PreventReload",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.PreviewingProp",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.PropFromInventory",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.PropFromInventory.PropPlaced",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Ragdoll",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Riding",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Slide",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Slide.Jump",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.SpecialAbility",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.SprintJumping",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.StoppedRiding",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.TacticalSprint",DevComment="Tag for triggering Tactical Sprint ability") ++GameplayTagList=(Tag="Gameplay.Action.Player.Uncle.Block",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.Uncle.Float",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.VehicleSeatCanNotPlaceDBNO",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.Player.ZiplineBlocked",DevComment="") ++GameplayTagList=(Tag="Gameplay.Action.StormDeath",DevComment="") ++GameplayTagList=(Tag="Gameplay.Alterations.OnFire.DamageBuffBurst",DevComment="") ++GameplayTagList=(Tag="Gameplay.Application.Explode.Additonal2",DevComment="Additional Explosion Targeting Filter tag") ++GameplayTagList=(Tag="Gameplay.Athena.Elastic.Emote",DevComment="Adding GC for Elastic Emote FXs") ++GameplayTagList=(Tag="Gameplay.Athena.Item.BadgerGrape.Active",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.DadBro.WP",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.DadBro.WP.Crystal",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.DadBro.WP.Horn",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.DadBro.WP.RightHorn",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.GoalScorer.Soccer",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.ScoreBalloonBoard",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.ShootTarget",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.UseBiplane",DevComment="") ++GameplayTagList=(Tag="Gameplay.Athena.Quests.UseSneakySnowman",DevComment="") ++GameplayTagList=(Tag="Gameplay.Ballercoaster.Ejecting",DevComment="") ++GameplayTagList=(Tag="Gameplay.Ballercoaster.FlameHoop",DevComment="") ++GameplayTagList=(Tag="Gameplay.Ballercoaster.Riding",DevComment="") ++GameplayTagList=(Tag="Gameplay.Creative.OOCHealth",DevComment="") ++GameplayTagList=(Tag="Gameplay.Creative.OOCShield",DevComment="") ++GameplayTagList=(Tag="Gameplay.Creative.UTDoubleDamage",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Boombox",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.EliminationZone",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Environment.Cube.Lightning",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Environment.Falling.Balloons",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Environment.Falling.SneakySnowmando",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Environment.IgnoreKillVolumes",DevComment="Apply this tag to actors that should not be destroyed when overlapping a kill volume.") ++GameplayTagList=(Tag="Gameplay.Damage.Environment.IgnoreStormDamage",DevComment="Apply this tag to actors that should not be damaged by the storm.") ++GameplayTagList=(Tag="Gameplay.Damage.Environment.Tornado",DevComment="Tag for the Damage Received by Midland Tornado") ++GameplayTagList=(Tag="Gameplay.Damage.Gas",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.IgnorePawnDamageFX",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Override.StealthGrass",DevComment="allows damage exception for stealth grass") ++GameplayTagList=(Tag="Gameplay.Damage.Physical.Beam",DevComment="Damage comes from a persistent beam") ++GameplayTagList=(Tag="Gameplay.Damage.Physical.Creature.Wildlife.Nug",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Physical.Creature.Wildlife.NugZ",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Physical.Explosive.Gas",DevComment="") ++GameplayTagList=(Tag="Gameplay.Damage.Underwater",DevComment="") ++GameplayTagList=(Tag="Gameplay.DamageSource.Alteration.AOE",DevComment="") ++GameplayTagList=(Tag="Gameplay.DamageSource.EliminationZone",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.BouncePadCount",DevComment="Tag to track number of times player hits a Bounce Pad within a certain time.") ++GameplayTagList=(Tag="Gameplay.Effect.BouncePadCount.Max",DevComment="Bounce Pad Tracker count has reached max value") ++GameplayTagList=(Tag="Gameplay.Effect.Dancing",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Fear",DevComment="Tag to apply fear to player") ++GameplayTagList=(Tag="Gameplay.Effect.FullBerserkGauge",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Immobilize",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.InstantDeath.Detonation",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Toy.Activate.ResetScore",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Toy.Activate.ScoreTeamA",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Toy.Activate.ScoreTeamB",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Toy.Activate.ToggleTimer",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Trap.Snare.Activate",DevComment="") ++GameplayTagList=(Tag="Gameplay.Effect.Trap.Snare.Deactivate",DevComment="") ++GameplayTagList=(Tag="Gameplay.Flopper.FreeFishing",DevComment="") ++GameplayTagList=(Tag="Gameplay.Interact.Multiplayer",DevComment="") ++GameplayTagList=(Tag="Gameplay.Item.Stamina.Hawaii",DevComment="") ++GameplayTagList=(Tag="Gameplay.KnockbackType.Suction",DevComment="") ++GameplayTagList=(Tag="Gameplay.Melee.Block",DevComment="") ++GameplayTagList=(Tag="Gameplay.Melee.Buff",DevComment="") ++GameplayTagList=(Tag="Gameplay.Melee.EndHeavyAttack",DevComment="") ++GameplayTagList=(Tag="Gameplay.Melee.FindTargetToMoveTo",DevComment="Used to find targets to move to with gap closing abilities (e.g. Leap Attack).") ++GameplayTagList=(Tag="Gameplay.Melee.SpawnProjectile",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.CoolingRate",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.Damage.Ranged",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.HeatCapacity",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.MovementAbility.Active",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.MovementAbility.Inactive",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.Shield",DevComment="") ++GameplayTagList=(Tag="Gameplay.Mod.SwimSpeed",DevComment="") ++GameplayTagList=(Tag="Gameplay.Octopus.Boosting",DevComment="") ++GameplayTagList=(Tag="Gameplay.SpeedIncrease.Rush",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.AFK.IsConfirmed",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Chrome",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Chrome.Splashed",DevComment="Applied to targets hit by Chrome Splash") ++GameplayTagList=(Tag="Gameplay.Status.Chrome.Surface",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Chugging",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Dancing",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.DBNOBlocked",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.GearEquipped",DevComment="Set when gear is equipped") ++GameplayTagList=(Tag="Gameplay.Status.Ghost",DevComment="Used by STW Ghost Mode") ++GameplayTagList=(Tag="Gameplay.Status.HidingInProp",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HidingProp.BlockEnter",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HidingProp.BlockExit",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HidingProp.BlockInteract",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HidingProp.GettingInProp",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HidingProp.HidingInProp",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HidingProp.Teleporting",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.HotFeet.Surface",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.IcyFeet.Grenade",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.IcyFeet.LTM",DevComment="Icy feet tag for Slide LTM") ++GameplayTagList=(Tag="Gameplay.Status.IcyFeet.Surface",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.IcyFeet.Trap",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Immune.LockOn",DevComment="Actors with this tag are immune to lock on abilities") ++GameplayTagList=(Tag="Gameplay.Status.ImmuneFallingDamage.TillLand",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.ImmuneToAntiAirTrap",DevComment="Actors with this tag can\'t be targeted by AntiAir Trap.") ++GameplayTagList=(Tag="Gameplay.Status.InCombat",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.InsideTornado",DevComment="If an actor has this tag it is currently under the influence of tornado forces.") ++GameplayTagList=(Tag="Gameplay.Status.InteractingWithNPC",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.InteractingWithPlayer",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.InVehicle.Seat.Open",DevComment="Tag applied to players in a Vehicle seat without a roof.") ++GameplayTagList=(Tag="Gameplay.Status.InVehicle.Seat.Roofed",DevComment="Tag applied to players in a vehicle seat with a roof.") ++GameplayTagList=(Tag="Gameplay.Status.InVehicle.ShoppingCart.Driver",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.InVehicle.ShoppingCart.Passenger",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.JumpBoosted",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.KnockedDown",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.NoCoinDrop",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.OnSnow",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Phoenix.AcidRain.Cover",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Phoenix.AcidRain.DOT",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Phoenix.CloseQuarters.Vulnerable",DevComment="Adds a tag to negate enemy damage resist") ++GameplayTagList=(Tag="Gameplay.Status.PinkOatmeal",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.PinkOatmeal.Moss",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.PinkOatmeal.Sepia",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.PinkOatmealPreview",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Ragdoll",DevComment="Top-level for Ragdoll tags") ++GameplayTagList=(Tag="Gameplay.Status.Rift.Skydiving",DevComment="Tag to indicate player is in skydive mode after teleporting through Rift.") ++GameplayTagList=(Tag="Gameplay.Status.Rift.Teleported",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Sand.BurrowedAbilityActive",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Sand.EnableApplication",DevComment="When hotfix value Default.Sand.RequiresSandEnabledTag = 1 in AthenaGameData, players are required to have this tag for sand to apply snare/sinking/burrowed effects. Used to limit sand effects to gameplay effect volumes that apply GE_SurfaceChange_Sand_EnableApplication") ++GameplayTagList=(Tag="Gameplay.Status.Sand.PreventApplication",DevComment="Prevents Sand effects (snare/sinking/burrow) from being applied to the target when granted") ++GameplayTagList=(Tag="Gameplay.Status.Sand.RecentlyHitBuilding",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Sand.RemoveAllEffects",DevComment="When granted to the targets, unburrows them if applicable and removes all snare/sinking/burrowed effects. Does not prevent re-application") ++GameplayTagList=(Tag="Gameplay.Status.Sand.Sinking",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Sand.Snared",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Sand.Surface",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.ShieldBlock",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Skytubing",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.SlowFall",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.Stun.Lovestruck",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.SuppressEffect.IcyFeet",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.SuppressEffect.MoveSpeed",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.ThrownPhysObject.DamageOverride.GravyGoblin",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.ThrownPhysObject.DamageOverride.TractorBeam",DevComment="Thrown physics object damage override for TractorBeam") ++GameplayTagList=(Tag="Gameplay.Status.TikiCurse",DevComment="") ++GameplayTagList=(Tag="Gameplay.Status.TractorBeamAlwaysAllowed",DevComment="If an actor has this tag it can be picked up by the nevada tractor beam even if it would normally be restricted from being beamed.") ++GameplayTagList=(Tag="Gameplay.TriggerProxGrenade",DevComment="") ++GameplayTagList=(Tag="Gameplay.Vehicle.NoRefuel",DevComment="") ++GameplayTagList=(Tag="Gameplay.Weapon.AllowFireInterruptEquip",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.BBPromoPlay",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.DBNOResurrect.Athena",DevComment="Adding a Specific Athena only DBNO GC to allow removal of hard references in Player Pawn") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.DBNOResurrect.Athena.NoSound",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.Barrier.Destroy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.Boardgame.OtherPlayer.Outline",DevComment="Tag for a GameplayCue that applies an outline to the player when it isn\'t their turn in the boardgame.") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.Boardgame.PlayerTurn.Outline",DevComment="Tag for a GameplayCue that applies an outline to the player when it is their turn in the boardgame.") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballBumper.KnockSide",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballBumper.KnockUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.ClockwiseTrigger",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.ClockwiseTrigger.Shot",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.ClockwiseTriggerLarge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.CounterClockwiseTrigger",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.CounterClockwiseTrigger.Shot",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.CounterClockwiseTriggerLarge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Activation.Devices.PinballFlipper.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Behind.Invuln",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Boost.End",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Boost.End.OutOfFuel",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Boost.Infinite",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Hovering.Infinite",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Hovering.Lateral",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Hovering.Papaya",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Hovering.Up",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Infinite",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.InitialBoost",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Regen.Infinite",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.BoostJumpPack.Regen.Papaya",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Commando.QuickFingers",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.Aura",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.ConstructorArmFullyOpen",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.ConstructorArmPartiallyOpen",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.ShieldBash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.ShieldUnfold",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Constructor.GoinConstructor.Taunt",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Generic.BlueAllyRedEnemy",DevComment="Used to trigger a Gameplay cuewhere friendly players are highlighted in blue and enemy players are highlighted in red.") ++GameplayTagList=(Tag="GameplayCue.Abilities.Generic.BotTurret.Reload",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Generic.PlaysetTurret",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Abilities.GhostLoop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Ninja.KunaiStorm.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Outlander.GroundWave.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Outlander.GroundWave.ReturnToSender",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Outlander.PhaseCannon.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Outlander.PhaseCannon.Damage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Outlander.PhaseCannon.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Pow.SetVisibilityEffect",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Smokebomb.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.SpookyMist.Loop",DevComment="GC for looping fx and Audio on player") ++GameplayTagList=(Tag="GameplayCue.Abilities.SpookyMist.SoundIndicator",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.SpookyMist.Trail",DevComment="Spooky Mist fx trail") ++GameplayTagList=(Tag="GameplayCue.Abilities.SpookyMist.Wobble",DevComment="GC for Spooky Mist to make objects wobble.") ++GameplayTagList=(Tag="GameplayCue.Abilities.Tag.TeamBlue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Tag.TeamChanged",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Tag.TeamRed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.Score",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.Score.Elite",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.Score.Elite.ImprovNet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.Score.ImprovNet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.Score.ThreePointer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.Score.TwoPointer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Basketball.TeleportAway",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.BeachBall.TeleportAway",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Bottle.FancySuccess",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Bottle.LandedOnCap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Bottle.Score",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Bottle.Score.LandOnCap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Burger.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Burger.Splat",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.FlyDisc._Score",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.FlyDisc.TeleportAway",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Football.Score",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Football.TeleportAway",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.Score.Elite.HoleInOne",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.Score.Elite.MultipleStrokes",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.Score.Elite.WrongHole",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.Score.HoleInOne",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.Score.MultipleStrokes",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.Score.WrongHole",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.ShowDistanceMarker",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.GolfBall.TeleportAway",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.IcePuck.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Snowball.Impact",DevComment="Snowball impact with players") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Snowball.Splat",DevComment="Snowball hit world") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.SoccerBall.HitTarget",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.SoccerBall.Score",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.SoccerBall.Teleport",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Tomato.FancyImpact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Tomato.FancySplat",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Tomato.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Tomato.Splat",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Toys.Tomato.TeleportAway",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Vendetta.SetVisibilityEffect",DevComment="Tag used for Vendetta\'s SetVisibilityEffect gameplay effect cue") ++GameplayTagList=(Tag="GameplayCue.Abilities.Wax.SetVisibilityEffect",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.ApplyBrakes",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.AttachVFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.Downhill",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.LowGrav.Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.Smash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.SpeedLines",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Abilities.Zipline.Travel",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.Commando.FragGrenade.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.Commando.Shockwave.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.Commando.Shockwave.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.Commando.WarCry.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.Commando.WarCry.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.DragonSlash.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ability.Ninja.ThrowingStars.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Activities.StWAntiShield.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.ColdSnare.AoEBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.Freeze.AoEBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnFire.SpawnSparkBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnKill.SpawnSparkBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnKill.SpawnSparkBurst.Husks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnKill.SpawnSparkBurst.Player",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnMeleeCrit.Explode.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.DamageStack",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.DamageStack.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.DamageStack.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.Explode.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.Explode.MarkTarget",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Assault",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Default",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Husks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Launcher",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Launcher.Husks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Shotgun",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.SMG",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alteration.OnReload.SpawnSparkBurst.Sniper",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.Dance",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.DanceAoE",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.HestiaDemo.ExplodeOnCrit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.HestiaDemo.IceBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnFire.DamageBuff",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnFire.DamageBuffBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnFire.DamageBuffLoop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnHit.ExplosionStormkingAssault",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnHit.StormkingAssaultImpact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnKill.ChargeStormKingHammer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnReload.DamageBuff",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.OnReload.DamageBuffBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.StandStill.Dmg",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.StandStill.Dmg.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.StandStill.DmgVsMist",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.StandStill.DmgVsMist.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.StandStill.GiveShields",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Alterations.Stun.AOE",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ascender.Destroyed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.AppleSauce",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.AppleSauce.Warning",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.AppleSun.PlayerBounced",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Applied.Item.Backpack.BlingGlow",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Applied.Item.Backpack.BlingSparkle",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Athena.Applied.Item.PoolFloaty",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Applied.Item.Snowman",DevComment="GC for Sneaky Snowman") ++GameplayTagList=(Tag="GameplayCue.Athena.Applied.Item.SnowmanV2.Equipped",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ArmadilloRobot.Emote",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ashton.Carmine.ApplyStone",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ashton.Carmine.GemFlare",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ashton.Carmine.UnhideStone",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ashton.Indigo.BoostJump",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ashton.Indigo.LowGrav",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.BaconSnack.Consume",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.BaconSnack.HeldObjectThrow",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Balloons.Deflate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Balloons.DeflateMulti",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Balloons.Inflate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Bismuth.Feedback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Bismuth.Initiate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Bismuth.Reflect",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Bistro.Emote.Helmet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Bistro.Emote.Switchback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.BlackMonday.Deploy",DevComment="Adding GameplayCue to allow Deploy VFX to not use Local Rotation") ++GameplayTagList=(Tag="GameplayCue.Athena.BlackMonday.Emote",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Blade.Aura",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Blade.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Blade.Jumping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Blade.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Blade.Pull",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Boombox.Boom",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Boombox.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Boost",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Booty.DestroyItem",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ButterCake.SneezeSpecial.Nug",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ChillBronco.Explode",DevComment="Explosion GC for Chill Bronco") ++GameplayTagList=(Tag="GameplayCue.Athena.ChillBronco.Heal",DevComment="Chill Bronco tag to use when a player is healed") ++GameplayTagList=(Tag="GameplayCue.Athena.ChillBronco.Throw",DevComment="GC burst when thrown for Chill Bronco item") ++GameplayTagList=(Tag="GameplayCue.Athena.ChillBronco.Throw.Cook",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Conduit.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Cook.Candy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Cook.Generic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Cook.Shields",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Cook.Shields.Small",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Generic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Generic.Candy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Shields",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Pass.Shields.Small",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Throw.Cook.Generic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Throw.FragGrenade",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Consumable.Throw.Generic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Converted.TeleportEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Converted.TeleportStart",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Coronet.Emote",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Crafting.SoundIndicator",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.CuddleFish.Ink",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.daisy.portal.close",DevComment="gameplay cue tag for portal closing in whack-a-mole game") ++GameplayTagList=(Tag="GameplayCue.Athena.DDMachine.PlayerLaunched.Lightning",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DDMachine.PlayerLaunched.Thermal.PostProcess",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Death",DevComment="Base Player Res Out Sequence") ++GameplayTagList=(Tag="GameplayCue.Athena.Destruction.Large",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DirectionalLaunch.Generic.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DogSweater.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DogSweater.Cook",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DogSweater.Dissolve",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DogSweater.NearingEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.DogSweater.ZoneActivate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Elastic.Emote",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Bistro.Helmet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Bistro.Switchback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Headband.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Headband.Speedlines",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Headband.Trails",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Phonograph",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.Reverence.PickaxeCheck",DevComment="Added to allow way to turn off persistent effects on Lancelot Pickaxe") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.ScrubADub",DevComment="Scrub A Dub Decal") ++GameplayTagList=(Tag="GameplayCue.Athena.Emote.SharpFang",DevComment="Sharp Fang Emote Decal") ++GameplayTagList=(Tag="GameplayCue.Athena.Energy.OnGranted",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Energy.WildlifeMountConstantRegen",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvCampfire.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvCampfire.Doused",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvCampfire.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvCampfire.Fire.Janus",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvCampfire.Stoke",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvCampfire.StokeBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Lock.Keycard.Fail",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Lock.Keycard.Pass",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Lock.Scanner.Fail",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Lock.Scanner.Pass",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Lock.Scanner.Scan",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Lock.Scanner.Zap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.PortAShack.Tires.Bounce",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.Aggroed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.Alert",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.Damaged",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.Deactivated",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.LostTarget",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.ReturnedToIdle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Alarm.Targeted",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.Aggroed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.Damaged",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.Deactivated",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.LostTarget",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.ReturnToIdle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.Targeted",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Sentry.Turret.WindUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.SilkyVeteran",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.SilkyWolfEffect",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.EnvItem.Slurp",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Athena.EnvItem.Vault.Open",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Athena.EnvItem.Vault.Open.Special",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.butterflysoul",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.event.EarthquakeTremors",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.event.festivus.fireworks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.event.festivus.startevent",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.FestivusFlying",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.EnergyImpactLocalized",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.GravRocks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.GroundSmokeLocalized",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.Fritter.InStorm",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.Fritter.LightningStrike",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.RainBookend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.RainStarts",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.StormOut",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.WaterImpact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.athena.event.Fritter.WindPush",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.HitRipple",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Coaster",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Flames",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Geometry",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Goosebumps",DevComment="Adding Light Element to bottom of Player during sequence") ++GameplayTagList=(Tag="Gameplaycue.athena.event.Jerky.Image",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.LocalComet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.event.Jerky.LocalLightStreaks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.LocalStars",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Shockwave",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Sliding",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Sliding.StreakSprites",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Teleport",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Underwater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.Jerky.Water",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.LightningImpactCL",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.LightningImpactL",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.LightningImpactM",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.LightningImpactS",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.LowGrav",DevComment="GameplayCue tag for the Cube Low Gravity effect") ++GameplayTagList=(Tag="GameplayCue.Athena.Event.LowGrav.Zero",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.shake.burst",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.sleepy.Impact",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.special.waterexit",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.special.waterimpact",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.event.spooky.enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ExplosiveBow.AltFire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FallDamageImmunity",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.ExplosionLarge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.ExplosionMed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.ExplosionSmall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.ExplosionSmall.SmallRadius",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.ExplosionTiny",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.GroundImpactHuge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.GroundImpactLarge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.GroundImpactLarge1K",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Feedback.GroundImpactSmall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FireworksMortar.HolderDeath",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FireworksMortar.HolderSpawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FireworksMortar.MuzzleFlash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FireworksMortar.RocketExplode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FireworksMortar.RocketSpawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FlintlockPistol.KnockbackSelf",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FlintlockPistol.KnockbackTarget",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Athena.Flopper.Mechanic.Consume",DevComment="cue when consuming mechanic flopper") ++GameplayTagList=(Tag="Gameplaycue.Athena.Flopper.Snowman.Consume",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ForagedItem.Carrot.Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ForagedItem.Rift.Skydive",DevComment="Gameplay Cue for Rift while skydiving.") ++GameplayTagList=(Tag="GameplayCue.Athena.ForagedItem.Rift.Teleport",DevComment="Gameplay Cue for Rift on teleport.") ++GameplayTagList=(Tag="GameplayCue.Athena.FrozenLoot.Destroyed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.FX.LargeDestruction",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Galileo.Ferry.LaserImpact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Galileo.Lobster.Block",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Galileo.Lobster.Block.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Galileo.Lobster.Block.Reaction",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Galileo.Lobster.Dodge.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GasCan.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.EnterWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.EnterWater.TNTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.HitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.HitWorld",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.HitWorld.Small",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.HitWorld.TnTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.Pickup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.Pickup.TnTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GenericHeldObject.Throw.TnTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GeyserBurstUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GrenadeLauncher.Bounce",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GrenadeLauncher.EggBounce",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GrenadeLauncher.EggExplode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GrenadeLauncher.EggExplode_Water",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.GrenadeLauncher.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HCChallenge.Beacon",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HCChallenge.TacoTime.DanceEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HCChallenge.TacoTime.PreDance",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HealMinor",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Health.HealUsed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Health.ItemUsed",DevComment="Adding GCN to replace old Cascade Item Consumed Effect with new Niagara version of the same") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.AnimMontageEntry",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Collision",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Dumpster.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Dumpster.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Dumpster.ExitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Dumpster.LandedOn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Dumpster.Rustle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Dumpster.WhileEntering",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.HayStack.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.HayStack.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.HayStack.ExitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.HayStack.LandedOn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.HayStack.Rustle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.HayStack.WhileEntering",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.PhantomBooth.AlterLogo",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.PhantomBooth.EgoLogo",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.PhantomBooth.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.PhantomBooth.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.PhantomBooth.ExitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Portapotty.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Portapotty.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Portapotty.Exitplayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Portapotty.Rustle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.PropTeleporting",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Teleporter.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Teleporting",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.ExitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.LandedOn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.Rustle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.Teleporting",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.WilliePete.WhileEntering",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.Wobble",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.ZeroPointEnergy.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.ZeroPointEnergy.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.ZeroPointEnergy.ExitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.ZeroPointEnter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.ZeroPointPropTeleporting",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HidingProp.ZeroPointTeleporting",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HotFeet.Surface",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.HVAC.Wind",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceDeimos",DevComment="Adds Ice Material Change to Deimos AI") ++GameplayTagList=(Tag="GameplayCue.Athena.IceFeet.AoE",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceFeet.AoE.IceGrenade",DevComment="IceFeet AoE without the initial camera shake, since grenade already has a camera shake.") ++GameplayTagList=(Tag="GameplayCue.Athena.IceFeet.Surface",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceFeet.Trap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceFeetEnv",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceGrenade.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceWheels",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IceWheelsEnv",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.IncendiaryGrenade.FuelSplash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Infiltration.IntelCase.Reveal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Avian.Crow.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Death.Slurp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Death.SlurpPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Death.SlurpTarget",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Dev.Dev1",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Dev.Dev2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Dev.Dev3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Dev.Dev4",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Dev.Dev5",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Nug.Float",DevComment="Used to add the loop effect on the player while floating") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Nug.Land",DevComment="Plays when player lands from gliding") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.PlayerInteractions.State1",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.PlayerInteractions.State2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.PlayerInteractions.State1Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.PlayerInteractions.Untamed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Shared.PassivePhase.HealthRegenLoop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Shared.PassivePhase.NA",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Shared.PassivePhase.Phased",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Shared.PassivePhase.PhasedHit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Irwin.Shared.PassivePhase.Solid",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Athena.Irwin.Sliding",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Airborne.LiftBalloon.Bounce",DevComment="GCN tag for the Airborne life balloon bounce FX") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Chicago.Catch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Turbo.Catch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Turbo.LeapAttack.Axe",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Turbo.LeapAttack.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Turbo.LeapAttack.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Turbo.Strike",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Ashton.Turbo.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BadgerBangs.ChargeUpLight",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BigBush.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BigBush.Burst.Autumn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BigBush.Burst.Infested",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BigBush.Burst.Jungle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BucketNice.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BucketNice.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BucketOld.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.BucketOld.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Chicago.Block.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Coal.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Coal.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.DishFries.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.DishFries.Applying",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.DishFries.End",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.DishFries.Start",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Flopper.GasFlopper.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Flopper.GasFlopper.Trail",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Flopper.HopFlopper.LowGravity",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Flopper.JellyFish.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Flopper.Thermal.PostProcess",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.FloppingRabbit.Cast",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.FloppingRabbit.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.FloppingRabbit.LineSnapped",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.FloppingRabbit.MiniGame.Splash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.FloppingRabbit.PlayerHit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Main.Destroy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Main.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Proj.Destroy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Proj.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Secondary.Destroy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Secondary.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Spawner.End",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.JollyRascal.Spawner.Start",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.LumpyRug.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.LumpyRug.Explosion.HitPawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.MistyBop.LockOn.Warning",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Mustache.Heal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Mustache.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Mustache.Muzzle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Passive.HealthRegen",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Slurp.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SlurpBazooka.Muzzle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SlurpBazooka.PowerDown",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SmokeBomb.WallJump",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.StealthGrass.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SugarGrouse.LockedOn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.Damaged",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.Despawned",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.Destroyed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.DestroyedBottom",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.Spawned",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.Spawned.AmbientSound",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.SuperDingo.SplashBottom",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.Thermal.PostProcess",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.ThrowingAxe.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.ValetMod.OffRoadTire.BouncePlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.ValetMod.OffRoadTire.HitGround",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Item.WaterBuffalo.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.JetpackContainer.Destroyed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.JetpackContainer.Searched",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.KnockbackTrail",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LavaBounce",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LevelUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Lotus.Heart.Beam",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.ExplodingFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.IncreasedDamage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.IncreasedEverything",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.IncreasedHealth",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.IncreasedSpeed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.PoisonFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.LTM.Mash.ShieldingFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.MedConsumable.SoundIndicator",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Melee.Attack1",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Melee.Attack2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Melee.Attack3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Melee.Attack4",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Melee.Attack5",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Melee.Dodge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.NightNight.Knock",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.NightNight.Movement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.NightNight.SlowMovement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.NPC.Abilities.GrabPickup.Destroy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Octopus.Ballercoaster.ForceFeedback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Octopus.Ballercoaster.MagField",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Octopus.Ballercoaster.PartyMusic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Octopus.Ballercoaster.Sparks",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Octopus.Water.EnterWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Octopus.Water.ExitWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.OilSlickFeet",DevComment="GC on player for creative oil slick trap") ++GameplayTagList=(Tag="GameplayCue.Athena.Origin.Emote.Switchback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Origin.Emote.Transform",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Dash",DevComment="Dash GC for Ostrich") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Dash.Cooldown.End",DevComment="GC when Ostrich dash comes off cooldown") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Death",DevComment="GC when Ostrich is killed NOT USED FOR EXPLOSION") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.DeathExplode",DevComment="GC for explosion after Ostrich Dies") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Drop.Destroy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Drop.SecondStart",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Drop.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Enter.Driver",DevComment="GC for Ostrich when driver enters") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Enter.Gunner",DevComment="GC on Ostrich when Gunner Enters") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Fire.Single",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Fire.Start",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Fire.Stop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Jump.Charging",DevComment="Looping GC to play when charging Ostrich Jump") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Jump.InAir",DevComment="GC looping on Ostrich while in air.") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Jump.Landed",DevComment="GC for Ostrich after landing from a high Jump") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Jump.Landed.Small",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Jump.Release",DevComment="Burst GC when Ostrich jumps.") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.JumpSlam.Activate",DevComment="Ostrich GC when driver activates primary fire while in air.") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.JumpSlam.Landed",DevComment="Ostrich GC for when driver activates primary fire while in air then lands on ground") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.KnockPlayerTrail",DevComment="Trails applied to player when knocked back by Ostrich") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Load",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.LoadSingleRocket",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Missile.Explode",DevComment="GC for Ostrich Missile explosion") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.OnSpawn.SmokeIndicator",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Reload",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.SelfDestructPrime",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Shotgun",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Stomp",DevComment="GC burst for Ostrich Stomp attack") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Water.EnterWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Ostrich.Water.ExitWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.OutsideSafeZone2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.OutsideSafeZone3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.OutsideSafeZoneDamage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Passive.HealthRegen.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Ash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.AutumnBushMovement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.BigBushMovement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.ColdBreath",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.ColdBreath.Deimos",DevComment="Adding a unique GC for Snow in Deimos") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.ColdBreath.V2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.CornFieldMovement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Cucumber.Active",DevComment="") ++GameplayTagList=(Tag="gameplaycue.athena.player.FestivusAudio",DevComment="For setting up audio mix for festivus") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.JungleBushMovement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.ResIn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.ResOut",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Sliding.Camera",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Sliding.OneShot.Notify",DevComment="Use for anim notify") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Sliding.Player",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Sliding.Speedlines",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.SnowFlakes.CameraAttached",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.SnowFlakes.Niagara",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.StealthGrassMovement",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Surface.Chrome",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Surface.Sand.Burrowed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Surface.Sand.HitBuilding",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Surface.Sand.Sinking",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Surface.Sand.Snare",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.Surface.Snow",DevComment="GC for Changin VFX when on a Snow surface") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Acceleration",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.CameraShake",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.DoorBash.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.DoorBash.LaunchPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Ended",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Energy.Draining",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Energy.FullyDrained",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Jumped",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.RadialBlur",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Speedlines",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.TomatoSprinkle.Sprint.Started",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Player.WorldPFX.Persistent",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyBingo.Heal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyBingo.HealCue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyDino.Ambient.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyDino.Heal.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyTanker.Heal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyTrombone.Heal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyTrombone.Small.Heal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyUranium.Heal.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyVeteran.Ambient.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SilkyVeteran.Top.Destroyed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SuperSilkyWolf.Ambient.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Prop.SuperSilkyWolf.Heal.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ProxGrenadeLauncher.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.FireExtinguisher.Explode",DevComment="Gameplay") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.FireExtinguisher.Smoking",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.GasPump.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.GasPump.OnFire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.GasPump.RetractCable",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.EnterWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.HitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.HitWorld",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.OnFire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.Pickup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PetrolPickup.Refuel",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PropaneTank.Bounce.TNTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PropaneTank.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PropaneTank.Explode.TNTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PropaneTank.ExplodeWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PropaneTank.OnFire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ReactiveProp.PropaneTank.OnFire.TNTina",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.RefinedRift.Teleport",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.RefinedRift.Teleport.WeirdObjects",DevComment="New Portal GC for WeirdObjects Teleporter") ++GameplayTagList=(Tag="GameplayCue.Athena.Reloading",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.BattleLabBot",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.CreativeRespawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.FrenchYedoc",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.Janus",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.Respawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.SCMachineRespawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.In.SCMachineRespawn.Redux",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out",DevComment="GC for when a Player is eliminated") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.DangerGrape",DevComment="Adding Rez Out GC for Decoy Grenade") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.Janus",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.Mannequin",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.Mannequin.NoDrone",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.NPC",DevComment="Adding Rez Out GC for Generic NPCs") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.NPC.Glitch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.NPC.Irwin",DevComment="Irwin Teleport Out Sequence") ++GameplayTagList=(Tag="GameplayCue.Athena.Rez.Out.NPC.Mannequin",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.RuneVent",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.RuneVent.Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.RustyFish.AppliedFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.CDEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.HoloTransition",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.PowerDown",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Redux.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Redux.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Redux.CDEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Redux.HoloTransition",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SCMachine.Redux.Rift.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ShieldGenerator.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SidewaysEncounter.SpawnAI",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SilkyMoose.ShieldRestore",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SkyTube.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SkyTube.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SmallHealthSpeed.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Snow.BuildupQuake",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Snow.Invulnerable",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Snow.PinKerchunk",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Snow.Whiteout",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SpicySake.DamagePlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SpicySake.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SpicySoda.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SpicySoda.Heal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.SpicySoda.Throw.Cook",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.StarterIsland.BusThruster.Impulse",DevComment="Knocking a player back from the bus thruster") ++GameplayTagList=(Tag="GameplayCue.Athena.StarterIsland.BusWarp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Storm.Bus",DevComment="Loop Cue for Storm on Bus") ++GameplayTagList=(Tag="GameplayCue.Athena.Storm.MegaStorm.Damage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Tether.Jump.Player",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Tether.Jump.Player.Land",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Tether.Smash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Tinfoil.Beacon.Pulse",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Titanium.UpgradeCue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Trap.ZippyTrout.Player",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Trap.ZippyTrout.ProjectileHit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Attack1",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Attack2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Attack3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Attack4",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Attack5",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Attack5.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Block.Break",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Block.Break.DamageCauser",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Block.Deflect.DamageCauser",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Block.Deflect.Melee.Uncle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Charging",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Charging.Tier1",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Charging.Tier2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Charging.Tier3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Close",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Fail",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Impact.Player",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Short",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Tier1",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Tier2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dash.Tier3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dodge.Backward",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dodge.Forward",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dodge.Left",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Dodge.Right",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Float.Falling",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Float.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Uncle.Open",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.Valet.Boost.RadialBlur",DevComment="GC for Valet vehicles radial blur camera effect.") ++GameplayTagList=(Tag="GameplayCue.Athena.Valet.Boost.RadialBlur.NoSpeedline",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.WaffleTruck.HopRockDualies.Crush",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.WaffleTruck.SlurpBazooka.Explode",DevComment="Explosion GCN for Slurp Bazooka") ++GameplayTagList=(Tag="GameplayCue.Athena.WhiteOut",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.WitchBroom.TakeOff",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ZeroPoint.Dash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ZeroPoint.Dash.AboutToExpire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ZeroPoint.Dash.Ambient",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ZeroPointEnergy.LowGrav.Land",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ZeroPointEnergy.LowGrav.Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Athena.ZombieElastic.Emote",DevComment="") ++GameplayTagList=(Tag="GameplayCue.AthenaAI.Monster.Spawn.MiniBoss",DevComment="") ++GameplayTagList=(Tag="GameplayCue.AthenaAI.Monster.Spawn.Poison",DevComment="") ++GameplayTagList=(Tag="GameplayCue.AthenaAI.Monster.State.Flee",DevComment="") ++GameplayTagList=(Tag="GameplayCue.AthenaAI.NonParticipant.AlertState",DevComment="") ++GameplayTagList=(Tag="GameplayCue.AthenaAI.NonParticipant.Specter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.AutoLauncher.Electric.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.AutoLauncher.Explosive.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.AutoLauncher.Sniper.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.BaconSnack.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.BaconSnack.State",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.Barrel.Bomb.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.Barrel.Electric.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.Barrel.Ice.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.Barrel.Impulse.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.Barrel.Shockwave.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.Barrel.Stink.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.BotSpawner.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.DeviceSpawner.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.ElephantGarlic.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.FrenchYedoc.Hit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.FrenchYedoc.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.FrenchYedoc.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.GenericObject.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.GenericObject.EnterWater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.GenericObject.HitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.GenericObject.HitWorld",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.GenericObject.Pickup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.GenericObject.Throw",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.PlateTrigger.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.SpeedBoost.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BattleLab.SpeedBoost.Player.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Buffet.StormKing.ControllerVibration",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BuffetBubble.Bubble.BubbleFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BuffetPlaylist.Bubble.BubbleFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BuffetPlaylist.Escher.DoorContact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BuffetPlaylist.Escher.DoorExit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.BuffetPlaylist.Escher.DoorTeleport",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Chameleon.ClassAbilities.Whistle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Cosmos.Jetpack.Hovering",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Cosmos.Rifle.Melee.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.ClassAbilities.DoubleJump.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.ClassAbilities.HealthRegen.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.ClassAbilities.Rush.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.ClassAbilities.ShieldRegen.Burst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Flying",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.FX.ConcertRainGlobal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.FX.ConcertRainLocal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.FX.PlayerBubbles",DevComment="Tag for a Gameplay Cue that attaches a music reactive bubble particle effect to the player") ++GameplayTagList=(Tag="GameplayCue.Creative.FX.PlayerHearts",DevComment="Tag for a Gameplay Cue that attaches a music reactive heart particle effect to the player") ++GameplayTagList=(Tag="GameplayCue.Creative.FX.PlayerSparkles",DevComment="Tag for a Gameplay Cue that attaches a music reactive sparkle particle effect to the player") ++GameplayTagList=(Tag="GameplayCue.Creative.FX.SnowLocal",DevComment="Snow gcnl for the vfx spawner in creative") ++GameplayTagList=(Tag="GameplayCue.Creative.IslandPortal.PlayerContrails",DevComment="Tag for a Gameplay Cue that spawns and manages the player\'s contrail VFX when teleporting to a Creative island via the Creative portal.") ++GameplayTagList=(Tag="GameplayCue.Creative.ItemGranter.ItemGranted",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Phone",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Phone.Ghost",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Phone.Invulnerable",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Phone.ModeratorMode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Powerup.CharacterGlow.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Powerup.CharacterLevelup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Powerup.CharacterLevelup.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Powerup.CharacterOutline.PerfActivate",DevComment="Character outline for PC Performance mode") ++GameplayTagList=(Tag="GameplayCue.Creative.Powerup.CharacterSpark.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Ranged.Flashlight.Toggle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.TeamColor.Apply",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.ActionTrigger.PlayerTargeted",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.CaptureAreaCollected",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.ColorChangingCube.ColorChangedByPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.EliminationZone.DOT",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.ExplosiveBarrel.Player",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.Grinder.GrinderFeet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.NoWeaponZone.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.NoWeaponZone.Deactivate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.Powerup.InvisibleWeapon",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.Powerup.PlayerGlow",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.SpeedModifiedVehicle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.SpeedModifierPawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Creative.Toys.TeamSelector.TeamChanged",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Cube.Area",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.DecalPerfTest",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.Drying",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.ElementalStatus.Electrocution",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.ElementAttached.Electricity",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.ElementAttached.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.ElementAttached.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.ElementInteraction.Fire.Combined",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.State.ElementInteraction.Water",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Curie.VisualOnlyState.ElementInteraction.Fire.Combined",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Daisy.EnvItem.HealSFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Daisy.EnvItem.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Daisy.EnvItem.Impact.Large",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Daisy.EnvItem.Slurp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Daisy.Geyser.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Daisy.Geyser.Prime",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Damage.DogSweater",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Damage.LavaPool",DevComment="LavaPool cue for damage sounds") ++GameplayTagList=(Tag="GameplayCue.Damage.NonInterrupting",DevComment="This damage does not interrupt searching.") ++GameplayTagList=(Tag="GameplayCue.Damage.Physical.Beam",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Damage.Physical.Poison.Darts",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Monster.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Monster.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Monster.Spawn.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.ChargeUp.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Create",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Create.Creative",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Create.Dadbro",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Create.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Destroyed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Destroyed.Dadbro",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Destroyed.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Dmgd_00",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Dmgd_01",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Dmgd_02",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.Dying",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.LifeTimeLapsed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.PreCreate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.SpawnFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.RiftSpawner.SpawnFiend.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.ATV",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.ATV.HitFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.ATV.HitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.GolfCart.HitFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.GolfCart.HitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.ShoppingCart.HitFiend",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Deimos.Vehicle.ShoppingCart.HitPlayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.DogSweater.OnDamage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Endless.Superheated.InRadius",DevComment="") ++GameplayTagList=(Tag="GameplayCue.event.Fritter.FinalTeleport",DevComment="") ++GameplayTagList=(Tag="GameplayCue.event.Kiwi.ApplyGoopSplash",DevComment="Apply goop splash effects to the player") ++GameplayTagList=(Tag="GameplayCue.Event.Kiwi.Player.TakeDamage",DevComment="Player damage effect on screen") ++GameplayTagList=(Tag="GameplayCue.Event.SkinSwap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.FX.BurstLatent.RadialForce",DevComment="For spawning a Force Actor.") ++GameplayTagList=(Tag="GameplayCue.FX.MountedTurretSTW.Knockback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.Escalation.Threshold.Reached",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.Escalation.Threshold.Reached.Dance",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.Escalation.Threshold.Reached.Energy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.Escalation.Threshold.Reached.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.Escalation.Threshold.Reached.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.Escalation.Threshold.Reached.Nature",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.MiniBoss.Push",DevComment="Used to show the push modifier telgraphing and firing") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.MountedTurret.Disabled",DevComment="") ++GameplayTagList=(Tag="GameplayCue.GameplayModifiers.MountedTurret.KnockbackExplode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Generic.AdrenalineRush.DirectHeal.Athena",DevComment="Adding Athena specific version of Direct Heal VFX to allow StW Material changes to be stored outside of Player Pawn") ++GameplayTagList=(Tag="GameplayCue.Guava.TiltLandingFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Hightower.Grape.BrambleShield.Impact",DevComment="Burst GCN executed when bramble shield hits pawns, vehicles, building pieces or terrain, etc.") ++GameplayTagList=(Tag="GameplayCue.Hightower.Mango.Interrupted",DevComment="") ++GameplayTagList=(Tag="GameplayCue.HighTower.Squash.WhirlwindBlast.Deflect",DevComment="GCN for deflecting hits, projectiles, hitscans, etc.") ++GameplayTagList=(Tag="GameplayCue.HighTower.Vertigo.Punch.Impact.World",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Impact.Generic.Small",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Impact.Physical.Melee.Mantis",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Impact.Physical.Unbatched",DevComment="Will avoid automatically batching this impact cue. This cue may still be batched if it is sent with a batched impact cue.") ++GameplayTagList=(Tag="GameplayCue.Interact.Multiplayer",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Interact.Multiplayer.Add",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Interact.Multiplayer.Remove",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Item.SurpriseParty.TrapManager",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Item.TNT.Fuse.Cook",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Item.TNT.Fuse.Projectile",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.BirdDoor",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.MachineDoor",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.MachineTart",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.MachineTartBlue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.RedStick",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.RedTart",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Junior.SpikeTart",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Loop.DagwoodSpeedLines",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Loop.Shadow.Bomb",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Loop.Shadow.Bomb.Creative",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Loop.Shadow.Bomb.CreativeEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Loop.Shadow.Bomb.CreativeNearEnd",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.FlameOnFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.FullHealth.Audio",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.FullHealth.FX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.HighHealth.FX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.LowHealth.Audio",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.LowHealth.FX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.MidHealth.Audio",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.MidHealth.FX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.Overheated",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.Repairing",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.StoppedAudio",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Mayday.StoppedAudioDamaged",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Block.Ready",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Block.Unready",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.BlockBreak",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Knockback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.NotEnoughEnergy",DevComment="Unable to perform an action due to low energy") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.ComboGroundImpact",DevComment="Impact VFX for ground interaction during regular combo attacks") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.ComboGroundImpact.HammerStrikeB",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.ComboGroundImpact.HammerStrikeC",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.Damage.Enemy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.Damage.Environment",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.DragOnGround",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.JumpAttack.Fall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.JumpAttack.Land",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.JumpAttack.LoopingVFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.JumpAttack.Slam",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.SpearThrust",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Primary.SprintAttackFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.DashAttack",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.DashAttack.Damage.Enemy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Dodge.Backward",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Dodge.Forward",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Dodge.Left",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Dodge.Right",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.HeavyAttack.Damage.Enemy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.HeavyAttack.Flip",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.HeavyAttack.Jump",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.HeavyAttack.JumpVFX",DevComment="Jump VFX for the heavy attack") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.HeavyAttack.Land",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.SlamAttack",DevComment="Slam attack for mantis weapon") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.SlamAttack.Jump",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Whirlwind.End",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Whirlwind.LoopingFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Whirlwind.Repeating",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Secondary.Whirlwind.Start",DevComment="") ++GameplayTagList=(Tag="GameplayCue.MeleeMantis.Test",DevComment="TestCue") ++GameplayTagList=(Tag="GameplayCue.Ninja.KunaiStorm.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Ninja.KunaiStorm.Leap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Boss.FreakySpawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DADBRO.AOE.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DADBRO.LightningStrike",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DADBRO.Summon.GroundEffect",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DADBRO.Sweep.Beam",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DADBRO.Sweep.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DUDEBRO.DBNO.SpawnHuskBolt",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.DUDEBRO.Sweep.Beam",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Elemental.Fire.Conversion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.EvilSummon",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Husk.Bace.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Husk.HuskThrownByFlinger.Landing",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.HuskNurse.Heal",DevComment="Healing FX on target of Nurse heal ability.") ++GameplayTagList=(Tag="GameplayCue.NPC.Inner.Ambient",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Inner.Warped",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Interactive.Death",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Interactive.Glitch.Heavy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Interactive.Glitch.Light",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Starlight.S4Smasher",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Vlad.Berserker.Phase2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Vlad.Berserker.Phase3",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Vlad.SoulDrain",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPC.Warp.Feedback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.NPCServices.OutfitDisguise.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Outlander.Phaseshift.PhaseSiphon.Dash",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PaddleGinger.Prop.Activating",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Papaya.FX.ConcertRainGlobal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Papaya.FX.ConcertRainLocal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Parallel.WestSausage.JustDetached",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.ActionCharge.Shockwave",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Assassination.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.BassSolo.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.BlastKick.Prj",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.ColdToTheTouch.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.ColdToTheTouch.FrozenTarget",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.ColdToTheTouch.Ice",DevComment="") ++GameplayTagList=(Tag="Gameplaycue.Perk.Commando.StayFrosty.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.CorruptedAura.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.CorruptedAura.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.CorruptedAura.ImpactV2",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.CrystalDaggers.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.DangerDanger.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.DangerDanger.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.DangerDanger.Laser",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.ExplosiveRounds.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FireOfTheDragon.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FireOfTheDragon.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FireOfTheDragon.FireFX",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FirstCupOfTheDay.Aura",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FirstCupOfTheDay.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FirstCupOfTheDay.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FourOnTheFloor.ChargeUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FourOnTheFloor.MainDrums",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.FourOnTheFloor.Pulse",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Gnarly.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.GoinBananas.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.HandCannons.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.HeartOfStone.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.HolidaySurprise.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.HotLead.AoEBurst",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.NightStalker.Active",DevComment="Looping GC for NightStalker") ++GameplayTagList=(Tag="GameplayCue.Perk.Ninja.MantisLeap.Activate",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Ninja.ShadowStance.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Outlander.AntiMaterialCharge.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Outlander.AntiMaterialCharge.EnvDamage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Outlander.AntiMaterialCharge.PawnDamage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Outlander.InTheZone.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Phantasm.Explode",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Phantasm.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.PhaseCannon.Active",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.PhaseCannon.Damage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.PhaseCannon.Fire",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.PlasmaArc.Beam",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.PlasmaPulseBlast.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.SpaceTechnology.Charge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.SpaceTechnology.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.Twinblast.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.UnderWarranty.Malfunction",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.UnderWarranty.Zap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.UnderWarranty.ZapSecondary",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Perk.YourMoveCreep.Explosion",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Basketball",DevComment="Gameplay Cue tag for basketball impact") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.DogHouseTennisBall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Generic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.GiantBeachBall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.GiantBeachBall.Water",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Metal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.PhysicsBoulder",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.PhysicsTree",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.PropaneTank",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Safe",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Stone",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Water",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Impact.Wood",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Movement.BeachBall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.Basketball",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.Boulder",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.Generic",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.Metal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.PhysicsTree",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.PropaneTank",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.Stone",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Physics.Rolling.Wood",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PhysicsTree.Destruction",DevComment="When the Physics Tree is destroyed, play tis burst") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.AngryApple.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.AngryApple.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.GreenGlop.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.GreenGlop.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.GreenGlop.PreDance",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.MeatPizza.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.MeatPizza.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.MeatPizza.PossessProp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Moss.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Moss.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Oak.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Oak.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Oak.SGen",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Sepia.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Sepia.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Sepia.Swap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.SleepyTime.Drop.Despawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.SleepyTime.Drop.Land",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.SleepyTime.Drop.Open",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.SleepyTime.Drop.Spawn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.SleepyTime.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.SleepyTime.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Stark.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.Stark.Exit",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.TheBlock.Enter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.PinkOatmeal.TheBlock.Inside",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.ColoredOutline",DevComment="Tag for a GameplayCue that applies a colored outline to the player pawn.") ++GameplayTagList=(Tag="GameplayCue.Player.FlareGun.Reveal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Generic.Positive.Buff",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Damage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Minigame.Fail",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Minigame.Success",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Minigame.Success.Good",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Minigame.Success.Great",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Minigame.Success.Perfect",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Reveal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Reveal.Interrogator",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Reveal.Stencil",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Voice.PickedUp",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.Interrogation.Voice.PutDown",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.InWater.HandSplash.Sprint",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.InWater.HandSplash.Sprint.L",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.InWater.Under",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.PaintDecal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.PaintDecal.Blue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.PaintDecal.Red",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Player.ViewingCharacter",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Quests.CycloneJerkyBillboard.Complete",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Quests.DanceOff.Point",DevComment="Burst VFX for Point Awarded in Dance Off Quest") ++GameplayTagList=(Tag="GameplayCue.Quests.Default.Success",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Quests.DeviceUplink.Postinteract",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Quests.DeviceUplink.Preinteract",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Quests.FireworksFXGCN",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Shield.Reapplied.StW",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Shotgun.MaxCharge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Shotgun.StartCharge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.SpecialEvents.Sneezy",DevComment="") ++GameplayTagList=(Tag="GameplayCue.SpookyEvent.Dscowlf.Howl.SoundCue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.SpookyEvent.Dscowlf.TransformOnPickup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.DanceStunned.GreenGlop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.IceFeet",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.HuntersMarked",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.Lovestruck",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.Stun.Lovestruck",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Blaster",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Dwarf",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Flinger",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Husk",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Husky",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Lobber",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Zapper",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Negative.TrapKnockback.Zapper.RetroScifi",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Positive.HealthRegen",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.Positive.SlowFall",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.StarlightCurse",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Status.TikiCurse",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Sulfer.IncreaseMovementSpeed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Sulfer.Landed",DevComment="") ++GameplayTagList=(Tag="GameplayCue.SwimSprintBoost",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Tandem.BrawnySmasher",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Tandem.BrawnySmasher.BeginTransform",DevComment="") ++GameplayTagList=(Tag="GameplayCue.TastyStew.Explosion",DevComment="Explosion VFX for friendly or enemy") ++GameplayTagList=(Tag="GameplayCue.TastyStew.Trail",DevComment="Trail VFX and material applied to a grenade where the vfx/material will change color based on if the grenade is friendly or enemy") ++GameplayTagList=(Tag="GameplayCue.Test.BA.01",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Test.BA.02",DevComment="") ++GameplayTagList=(Tag="GameplayCue.TestCue",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Trap.CannonWall.Damage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Trap.Endless.FrostyTurf",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Trap.PoisonDarts.DoT",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Trap.Speaker.ActivateTrap",DevComment="") ++GameplayTagList=(Tag="GameplayCue.TwoTeam.AllyVsEnemyColor.Outline.Distance",DevComment="Generates a glowing outline around players, but with editable distance visibility.") ++GameplayTagList=(Tag="GameplayCue.UnderwaterDamage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vehicle.Baller.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vehicle.Boat.ScreenDrips",DevComment="Screen drips and drops from water splashes on the boat") ++GameplayTagList=(Tag="GameplayCue.Vehicle.Boat.Surface.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vehicle.ShipCannon.PlayerLaunched.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vehicle.ShipCannon.PlayerLaunched.Looping",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vendetta.Abilities.MarkTarget.Highlight",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vendetta.BountyPuckReveal",DevComment="Radar effect from interrogation system without the visual effect on the player\'s hand") ++GameplayTagList=(Tag="Gameplaycue.Vent.Area",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vent.White",DevComment="New GC for Snow White") ++GameplayTagList=(Tag="GameplayCue.Volume.DanceFloor",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Volume.Stage",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Volume.StageDistant",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vortex.Area",DevComment="When a player enters the island vortex") ++GameplayTagList=(Tag="GameplayCue.Vortex.Subtle",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vortex.Subtle.Yogurt",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vortex.Tree",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Vortex.Volcano",DevComment="") ++GameplayTagList=(Tag="GameplayCue.WaffleTruck.BigMoney.Kill",DevComment="") ++GameplayTagList=(Tag="GameplayCue.WaffleTruck.DragonBreath.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Waterfall.EnvItem.Through",DevComment="Execute when player through waterfall.") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Blunt.Light.GolfClubRtClickAttack1.Boombox.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Blunt.Light.GolfClubRtClickAttack1.Boombox.Windup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Blunt.Light.GolfClubRtClickAttack1.BoxingGlove.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Blunt.Light.GolfClubRtClickAttack1.BoxingGlove.Windup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Blunt.Light.GolfClubRtClickAttack1.Windup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Club.HomeRunWithProjectile.Charge",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Club.HomeRunWithProjectile.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.GunLoop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.GunLoopOff",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.GunLoopOn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.ThrustGunLoop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.ThrustGunLoopOff",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.JetFueledBlast.ThrustGunLoopOn",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.LeapAttack.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Axe.LeapAttack.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.BlinkStrike.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.BlinkStrike.Impact.Stormking",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.BlinkStrike.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.BlinkStrike.Launch.Stormking",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.Crescentstrike.Available",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.CrescentStrike.Windup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.LeapAttack.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.LeapAttack.Impact.BlackMetal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.LeapAttack.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Edged.Sword.LeapAttack.Launch.BlackMetal",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.Joust.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.Joust.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.SonicPulse.GroundStab",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.SonicPulse.GroundStabAudio",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.SonicPulse.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.SonicPulse.Loop",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.Steampunk.UpAndOver.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Piercing.Spear.UpAndOver.GroundImpact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Slashing.Spear.SlamShockwave.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Tool.ComboAttack3.Boombox.Pulse",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Tool.ComboAttack3.Boombox.Windup",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Tool.LeapAttack.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Tool.LeapAttack.Impact.Stormking",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Tool.LeapAttack.Launch",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Melee.Tool.LeapAttack.Launch.Stormking",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.NonQuartzActivation",DevComment="Used to handle non-persistent weapon FX for clients without Quartz enabled (and for weapons that don\'t use quartz)") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Bow.Flintlock.TriggerGC",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Bow.VacuumTube.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Bow.Vindertech.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Launcher.Grenade.Impact.Water",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Launcher.Stormking.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Pistol.RetroSciFi.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Pistol.Stormking.Impact",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Weapons.Ranged.Pistol.TrackingPistol",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Winterfest.FoundPizza",DevComment="") ++GameplayTagList=(Tag="GameplayCue.Wumba.Upgraded",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Accumulations",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Accumulations.AlertedLevel",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Accumulations.ThreatenedLevel",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Accumulations.Unaware.IOandSeven",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Accumulations.UnawareLevel",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBossesThreatened",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsAlerted",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsAlerted.Freaky",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsDBNO",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsThreatened",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsThreatened.Freaky",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsUnaware",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsUnaware.Freaky",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumBotsUnaware.SilkWalker",DevComment="Tag for SgtWinter vehicle") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumCamerasAlerted",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumCamerasThreatened",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumCamerasUnaware",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumTurretsAlerted",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumTurretsThreatened",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.NumTurretsUnaware",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Events.TargetedBotThreatened",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.AlertedLevel",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.AlertedLevel.Low",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.ThreatenedLevel",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.ThreatenedLevel.High",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.ThreatenedLevel.Low",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.ThreatenedLevel.Medium",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.UnawareLevel",DevComment="") ++GameplayTagList=(Tag="GameplayDataTracker.Ranges.UnawareLevel.Low",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Ashton.Indigo.LowGrav",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Ashton.Indigo.ShootLowGrav",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Ballercoaster.Ejection",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Ballercoaster.FlameHoop",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Ballercoaster.Riding",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Blade.JumpFX",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.ForagedItem.SpookyMist",DevComment="GameplayEffect tracking tag for Spooky Mist") ++GameplayTagList=(Tag="GameplayEffect.Galileo.Lobster.BlockBreak",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Galileo.Lobster.Deflect",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Item.IceGrenade.IcyFeet",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Item.SurpriseParty.DanceStunImmunity",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Item.TNT.Cook",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Melee.BlockBreak",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.PushCannon.Fired",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.PushCannon.Launched",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.PushCannon.LowGravity",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.RuneVent.Applied",DevComment="tag for Rune Vent so player\'s don\'t get stuck overlapping.") ++GameplayTagList=(Tag="GameplayEffect.Starlight.CoilDebuff",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Surface.Ice",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Surface.Lava",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Surface.Snow",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Trap.Ice.IcyFeet",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Trap.MutatorZone",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Trap.NoWeaponZone",DevComment="Identify the gameplay effect that disables player weapon firing.") ++GameplayTagList=(Tag="GameplayEffect.Trap.Slide.SlickFeet",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Trap.Targeted",DevComment="Tag granted to target by trap indicating it\'s been targeted.") ++GameplayTagList=(Tag="GameplayEffect.Vehicle.Boost",DevComment="") ++GameplayTagList=(Tag="GameplayEffect.Vehicle.Hoagie.IgnoreRotorCollision",DevComment="Actors with this tag will be ignored by the Hoagie\'s rotor collision.") ++GameplayTagList=(Tag="GameplayEffect.Vehicle.Hoagie.RecentExit",DevComment="Tag applied to players after exiting Hoagie.") ++GameplayTagList=(Tag="GameplayEffect.Vehicle.Valet.AllowOnboardFlipping",DevComment="Allow vehicle to be flipped by the driver.") ++GameplayTagList=(Tag="GameplayEffect.Vehicle.Valet.AllowSelfDestruct",DevComment="Allow driver to self-destruct the vehicle when flipped.") ++GameplayTagList=(Tag="GameplayEffect.Vehicle.Valet.BlockFiring",DevComment="Valet tag used to grant the ability which blocks players from firing weapons.") ++GameplayTagList=(Tag="GameplayEvent.LockOnConsumableInteractionComplete",DevComment="") ++GameplayTagList=(Tag="GameplayEvent.Riding.Player.EnergyDepleted",DevComment="") ++GameplayTagList=(Tag="GameplayEvent.SurfaceChange.Cube",DevComment="Event when surface under pawn changes to cube.") ++GameplayTagList=(Tag="GameplayModifier.Enemy.Vlad.SoulDrain",DevComment="") ++GameplayTagList=(Tag="GameplayModifier.Starlight.SciBoss.AOEFire",DevComment="") ++GameplayTagList=(Tag="GameplayModifier.Starlight.SciBoss.AOEIce",DevComment="") ++GameplayTagList=(Tag="GameplayModifier.Starlight.SciBossMultiThrow",DevComment="") ++GameplayTagList=(Tag="GameplayVFX.Body.Cyclone.Space",DevComment="") ++GameplayTagList=(Tag="GameplayVFX.CCPM.Body",DevComment="") ++GameplayTagList=(Tag="GameplayVFX.CCPM.Head",DevComment="") ++GameplayTagList=(Tag="GameplayVFX.Pickaxe.AnimTrail.MainHand",DevComment="") ++GameplayTagList=(Tag="GameplayVFX.Pickaxe.AnimTrail.OffHand",DevComment="") ++GameplayTagList=(Tag="GampleplayVFX.CCPM.Superlevel",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.DateAlpha.BallLightning",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.Tomato.StartLeash",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.TomatoAlpha.RepulsorCannon",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.Wasabi.Dash.Activate",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.Wasabi.Engage",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.Wasabi.EngageCD",DevComment="Engage Cooldown") ++GameplayTagList=(Tag="Gasket.Abilities.Wasabi.Hurt",DevComment="") ++GameplayTagList=(Tag="Gasket.Abilities.Wasabi.Spin.Activate",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.Date",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.DateAlpha",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.DateVG",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.Generic",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.LonelyGhost",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.LonelyShadow",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.Mang",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.MangAlpha",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.MangLauncher",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.Tomato",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoAlpha",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet",DevComment="Spawn Locations for AI at Jet Landing Sites") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet01",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet02",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet04",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet05",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet06",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet07",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet08",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet09",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet10",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet11",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet12",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet13",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet14",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet15",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet16",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.TomatoQuinnJet17",DevComment="") ++GameplayTagList=(Tag="Gasket.Location.Wasabi",DevComment="") ++GameplayTagList=(Tag="Goose.PlayerStormImmune",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Generic.Teleporter.ArmTimeUpgrade",DevComment="Reduces Arm Time of the Teleporter") ++GameplayTagList=(Tag="Granted.Abilities.Generic.Teleporter.EnemyProjectile",DevComment="Allows Enemy Projectiles through the teleporter") ++GameplayTagList=(Tag="Granted.Abilities.Generic.Teleporter.FriendlyProjectile",DevComment="Allows Friendly projectiles through the teleporter") ++GameplayTagList=(Tag="Granted.Abilities.Generic.Teleporter.InteractionUpgrade",DevComment="Reduces the interaction time of the teleporter") ++GameplayTagList=(Tag="Granted.Abilities.Ninja.Perks.KunaiStorm.KunaiBarrage",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Ninja.Perks.KunaiStorm.KunaiBomb",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Ninja.Perks.KunaiStorm.KunaiCollection",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Outlander.GroundWave.OnCooldown",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Outlander.Perks.GroundWave.FaultLine",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Outlander.Perks.GroundWave.SustainedImpact",DevComment="") ++GameplayTagList=(Tag="Granted.Abilities.Outlander.Perks.PhaseCannon",DevComment="Phase Cannon Applied Tag") ++GameplayTagList=(Tag="Granted.Abilities.VictoryCrown.CrownWearer",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Commando.FragGrenade",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Commando.GoinCommando",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Commando.LeftyAndRighty",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Commando.Shockwave",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Commando.WarCry",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Constructor.BullRush",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Constructor.Decoy",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Constructor.MountedTurret",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Constructor.MountedTurret.BlockActivation",DevComment="Prevent Mounted Turret Activation") ++GameplayTagList=(Tag="Granted.Ability.Constructor.PlasmaPulse",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Ninja.CrescentKick",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Ninja.DragonSlash",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Ninja.KunaiStorm",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Ninja.SmokeBomb",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Ninja.ThrowingStars",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Outlander.Bear",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Outlander.GroundWave",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Outlander.PhaseShift",DevComment="") ++GameplayTagList=(Tag="Granted.Ability.Outlander.ShockTower",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.BotShield.CastBlocker",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.BotShield.DamageResistanceBuff",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.GoinCommando.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.GoinCommando.SelfSnare",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.LeftyAndRighty.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.LeftyAndRighty.Duration",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.WarCry.Buff",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.WarCry.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Commando.WarCry.FireRate",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Constructor.GoinConstructor.Duration",DevComment="") ++GameplayTagList=(Tag="Granted.AbilityEffect.Demeter.StaminaOverride",DevComment="dev only") ++GameplayTagList=(Tag="Granted.AbilityEffect.Ninja.ThrowingStars.AdditionalStars",DevComment="GRanted Tag for Effect that Grants Additional ThrowingStars") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnCritShieldRegen.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnFireBuffDamage.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnFireDamageStack.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnKill.ChargeStormKingHammer.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeCritBuffPistolHeadshotDamage",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeCritBuffPistolHeadshotDamage.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackAoeStun.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackBuffArmor.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackBuffCritRate.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackBuffDamage",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackBuffDamage.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackBuffFireRate.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnMeleeHitStackBuffMoveSpeed.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnRangedFireStackBuffCritRate.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnRangedFireStackBuffDamage.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnRangedFireStackBuffFireRate.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnRangedFireStackBuffShield.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnReloadBuffDamage.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnReloadDamageStack.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.OnReloadDamageStack.Applied",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.SlowToFreeze.Freeze",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.SlowToFreeze.Slow",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.StandStillGrantShield.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.StandStillStackBuffDamageAll.Active",DevComment="") ++GameplayTagList=(Tag="Granted.AlterationEffect.StandStillStackBuffDamageMist.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Ability.Charge.ControlOverride",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Ability.TomatoSprinkle.Decelerating",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Ability.TomatoSprinkle.OOS",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Ability.TomatoSprinkle.RegenStam",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Ability.TomatoSprinkle.Sprinting",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Achievements.HappyGhost",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Achievements.Lifeguard",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Achievements.RocketRiding",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.BATest",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.BGA.HealthRegen",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.BGA.ShieldRegen",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.BuiltInEmote",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Cannon.PlayerLaunched",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Converted.HireNPC",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Converted.Irwin",DevComment="Applied when player converts an irwin pawn") ++GameplayTagList=(Tag="Granted.Athena.Crafting.PlayerCrafting",DevComment="Crafting tag for player actively player crafting. Needs to be in the main game so that everything has access to the tag.") ++GameplayTagList=(Tag="Granted.Athena.Crafting.PlayerCrafting.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Cube.Actor",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.DiscoItem.BrokenBranch",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.DiscoItem.BrokenBranch.A",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.DiscoItem.BrokenBranch.B",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.DishFries.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.DishFries.Remove",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EmeraldGlassPush.Equipped",DevComment="Tag for EmeraldGlassPush ability detection") ++GameplayTagList=(Tag="Granted.Athena.Energy.WildlifeMountConstantRegen",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.AvacadoEaterBird.ImmunityFromBirds",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.Bouncer.Launch",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.Daisy.Mushroom.Heal",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.BlockEntrance",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.BlockExit",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.BlockInteract",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.ContainsPlayer",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.ExitingProp",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.GettingInProp",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.HidingInProp",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.PhantomBooth.AlterLogo",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.PhantomBooth.EgoLogo",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.RecentlyExitedProp",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.Teleporter.Teleporting",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.HidingProp.WilliePete.PlayerLaunch",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.Launcher.Tire.Launch",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.SandMound.HealthRegen",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.SandMound.RecentlyDamaged",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.Sentry.Turret.Hidden",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.Slurp",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.SuperSilkyWolf",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.EnvItem.SuperSilkyWolf.Combined",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Flopper.GasFlopper.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Flopper.GasFlopper.Immunity",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Flopper.GasFlopper.MaxStacks",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.ForagedItem.Cooldown.ZeroPoint.Dash",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.GrappleParent.HandsAreReplaced",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.GravyGoblin.Blacklisted",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.HandsAreReplaced",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.HappyGhost.Pull",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.HidePlayerName",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.HidingProp.SmashImmune",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Holding.NugZ",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Irwin.Burt.Tamed.BoostAggressivity",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.IsAPropDisguise",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.AppleSun.BlockJumpToDeploy",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Ashton.Turbo.ThrowBlock",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Booty.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Booty.MapInInventory",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Coconut.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Decoy.NoStartingWeapons",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.DogSweater.Damage",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.DogSweater.DOT",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.DogSweater.InDamageZone",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.ExplosiveBow.AltFire",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.FloppingRabbit.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.FloppingRabbit.InWorld",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.FloppingRabbit.ReelIn",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.GliderGrappler.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.HappyGhost.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Lotus.Heart.Effect",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Lotus.Mustache.Block",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Lotus.Scooter",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Lotus.Scooter.Marked",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.LumpyRug.AllowFire",DevComment="") ++GameplayTagList=(Tag="granted.athena.item.passive.AppleSun",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.Passive.HealthRegen",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.ShakingTurtle.Apply",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.ShakingTurtle.Attached",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.ShockGrenade.Knocked",DevComment="Used for other effects to filter knocked players") ++GameplayTagList=(Tag="Granted.Athena.Item.SpicySpeed.Remove",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Item.WaffleTruck.SlurpBazooka",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Listener.BuiltInEmote",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Locker.Exit",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Locker.Inside",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.PhysicsObject.Blacklisted",DevComment="Use to disable conversion to a physics object.") ++GameplayTagList=(Tag="Granted.Athena.Power.ActivePower.IsActive",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Power.Cancel",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Power.LeapGlider.Generic.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterClimbing5sec",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterDashing10s",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterElim10s",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterGliding15",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterGliding30",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterHiding30",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.AfterHidingGrass",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.DamageAfterLanding",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.DamageAfterLaunchpad",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.SearchAfterLanding",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.SkydivingFromBus",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.SmashNGrab.AfterJumping",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.SmashNGrab.AfterLanding30",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Quest.TireBounce",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Rebooted",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.ShieldWeaponEquipped",DevComment="Equip tag for item detection") ++GameplayTagList=(Tag="Granted.Athena.Sideways.Encounter.Stage.Complete.1",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Sideways.Encounter.Stage.Complete.2",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Sideways.Encounter.Stage.Complete.3",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Surface.Lava.Damage",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.TimeTrial",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.Tutorial.Clamber",DevComment="Tag to indicate if clambering onboarding UI should display") ++GameplayTagList=(Tag="Granted.Athena.Tutorial.Sprint",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.VendingMachine.InteractCooldown",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.WestSausage.Equipped",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.WestSausage.GoingFast",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.WestSausage.HoldingFireButton",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.WestSausage.NotHoldingFireButton",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.WestSausage.ReelingIn",DevComment="") ++GameplayTagList=(Tag="Granted.Athena.WestSausage.Skidding",DevComment="") ++GameplayTagList=(Tag="Granted.Coin.Cluster.Fragment.Combo",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnCrit.ColdSnare.AoE",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnCrit.SpawnScatterPrj",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnHit.ApplyHuntersMark",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnHit.Freeze.SingleTarget",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnImpact.Freeze.AoE",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnImpactBreak.HuskDance",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnKill.ChainLightning",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnKill.FirePool",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnKill.HuskDance",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnKill.RetroSciFiRing",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnKill.SpawnSparkBurst",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnKill.Steamcloud",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnMeleeHitStreak.HuskDance",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnRangedHitStreak.HuskDance",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Alteration.OnRangedHitStreak.HuskDanceAfflict",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ashton.Indigo.BoostJump",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ashton.Turbo.GroundSlam",DevComment="Cooldown for Turbo\'s Ground Slam ability") ++GameplayTagList=(Tag="Granted.Cooldown.Athena.LeapGlider.Generic",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Athena.LeapGlider.Generic.Cancel",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Athena.Melee.AirAttack.Generic",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Athena.Melee.LeapAttack.Generic",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Athena.WitchBroom.Generic",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Commando.FragGrenade",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Commando.GoinCommando",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Commando.LeftyAndRighty",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Commando.Shockwave",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Commando.WarCry",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Constructor.BullRush",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Constructor.Decoy",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Constructor.GoinConstructor",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Constructor.GoinConstructor.Refunded",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Constructor.MountedTurret",DevComment="Granted Cooldown for Mounted Turret") ++GameplayTagList=(Tag="Granted.Cooldown.Constructor.PlasmaPulse",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Converted.Teleport",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.AdrenalineRush",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.AirStrike",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.Banner",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.BotShield",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.BotTurret",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.SlowField",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Gadget.Teleporter",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Galileo.Lobster.Attack",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Galileo.Lobster.Block",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Galileo.Lobster.Dodge",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Melee.Parry",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ninja.CrescentKick",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ninja.DragonSlash",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ninja.KunaiStorm",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ninja.SmokeBomb",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Ninja.ThrowingStars",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Outlander.Groundwave",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Outlander.PhaseShift",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.ActionCharge",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.Assassination",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.DragonDaze",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.EmergencyOverride",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.EnduringMachine",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.FeelTheBASE",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.GnarlyPop.Item",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.GoinBananas.TropicalFruit",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.HeartyStikes",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.KineticOverload",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.Ninja.MantisLeap",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.Phantasm",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.SaurianInstinct",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.SaurianMight",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.ShortFuse",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Perk.SlowYourRoll",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.ThrowingAxe.Melee",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Uncle.Block",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Uncle.Dash",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Uncle.Dodge",DevComment="") ++GameplayTagList=(Tag="Granted.Cooldown.Uncle.Float",DevComment="") ++GameplayTagList=(Tag="Granted.Creative.CreativeAbility.EliminationZone.AppliedDOT",DevComment="The DOT Gameplay Portion of the Elimination Zone") ++GameplayTagList=(Tag="Granted.Creative.CreativeAbility.Flashlight.IsLit",DevComment="Target has been lit up by the flashlight.") ++GameplayTagList=(Tag="Granted.Creative.CreativeAbility.Invisibility.Active",DevComment="Creative Invisibility is active ") ++GameplayTagList=(Tag="Granted.Creative.CreativeEffect.Movement.Adjustment",DevComment="Used to track non-gameplay interacting movement speed adjustments") ++GameplayTagList=(Tag="Granted.Creative.CreativeEffect.Movement.StopMovement",DevComment="") ++GameplayTagList=(Tag="Granted.Creative.CreativeEffect.OnCooldown",DevComment="Player Is On Cooldown") ++GameplayTagList=(Tag="Granted.Creative.CreativeEffect.OnCooldown.EatFood",DevComment="") ++GameplayTagList=(Tag="Granted.Deimos.ShieldOnFiendKill",DevComment="") ++GameplayTagList=(Tag="Granted.Dudebro.Skydive.AllowPickup",DevComment="") ++GameplayTagList=(Tag="Granted.Dudebro.Skydive.OnlyDamagePlayerBuilds",DevComment="") ++GameplayTagList=(Tag="Granted.Dudebro.WaterDamage",DevComment="Water Damage from Dudebro") ++GameplayTagList=(Tag="Granted.FlopperSpawn.OverrideLoot",DevComment="") ++GameplayTagList=(Tag="Granted.FlopperSpawn.OverrideLoot.BlueFish",DevComment="") ++GameplayTagList=(Tag="Granted.Galileo.Lobster.Dodging",DevComment="") ++GameplayTagList=(Tag="Granted.Galileo.Lobster.Dodging.East",DevComment="") ++GameplayTagList=(Tag="Granted.Galileo.Lobster.Dodging.North",DevComment="") ++GameplayTagList=(Tag="Granted.Galileo.Lobster.Dodging.South",DevComment="") ++GameplayTagList=(Tag="Granted.Galileo.Lobster.Dodging.West",DevComment="") ++GameplayTagList=(Tag="Granted.Galileo.Lobster.InWater",DevComment="") ++GameplayTagList=(Tag="Granted.Gameplay.Mod.ResourceCap",DevComment="") ++GameplayTagList=(Tag="Granted.HasExternalShield",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.Beacon",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.HeadshotsOnly",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.NoBuilding",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.NoHealing",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.NoReload",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.ReducedDamage",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.ReducedHealth",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.TacoTime",DevComment="") ++GameplayTagList=(Tag="Granted.HCChallenge.TacoTime.Dancing",DevComment="") ++GameplayTagList=(Tag="Granted.HeldObject.Held",DevComment="") ++GameplayTagList=(Tag="Granted.HeldObject.PreventPickup",DevComment="") ++GameplayTagList=(Tag="Granted.HeldObject.Quest.Test",DevComment="") ++GameplayTagList=(Tag="Granted.HighTower.Power.Squash.WhirlwindBlast.LowGrav",DevComment="") ++GameplayTagList=(Tag="Granted.ItemEquipped.Consumable.Crashpad.VR",DevComment="") ++GameplayTagList=(Tag="Granted.ItemEquipped.Consumable.ShieldBubble.VR",DevComment="Tag assigned to the player when they have the very rare version of the Shield Bubble item equipped.") ++GameplayTagList=(Tag="Granted.Melee.BlockBreak",DevComment="") ++GameplayTagList=(Tag="Granted.Melee.Dodging.East",DevComment="") ++GameplayTagList=(Tag="Granted.Melee.Dodging.North",DevComment="") ++GameplayTagList=(Tag="Granted.Melee.Dodging.South",DevComment="") ++GameplayTagList=(Tag="Granted.Melee.Dodging.West",DevComment="") ++GameplayTagList=(Tag="Granted.PA.Muster",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AbsolutelyStunning.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AbsolutelyStunning.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ActionCharge.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ActionCharge.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ActuatedAttacks.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ActuatedAttacks.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AintDoneYet.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AintDoneYet.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AllTogetherNow.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AnatomyLessons.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AnatomyLessons.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Assassination.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Assassination.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AssaultAmmoRecovery.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AssaultAmmoRecovery.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AssaultCritDamage.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AssaultCritDamage.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AssaultDamage.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AssaultDamage.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AvastMaties.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.AvastMaties.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BASEMD.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BASEMD.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BassSolo.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BassSolo.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Bearricade.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Bearricade.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Bearserker.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Bearserker.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BearStare.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BearStare.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BearTogether.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BearWithMe.Reskin",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BearWithMe.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BearWithMe.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BiggerIsBetter.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BiggerIsBetter.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BioEnergySource.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BlakebeardsStash.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BlastFromThePast.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BlastKick.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BlastKick.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BombSuit.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BombSuit.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BOOMBASE.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Boomstick.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Boomstick.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BouquetOfROSIES.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BrainFreeze.T01",DevComment="Tier 1 (support) variant of Brainstorm\'s Brain Freeze perk") ++GameplayTagList=(Tag="Granted.Perk.BrainFreeze.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BringinDownTheHouse.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BringinDownTheHouse.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Buckle.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Buckle.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BullCrush.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BullCrush.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BurgerBuff.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.BurgerBuff.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CantStop.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CantStop.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Capacitor.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Capacitor.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Cascade.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Cascade.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ClusterBomb.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ClusterBomb.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ColdToTheTouch.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ColdToTheTouch.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CombatantsMight.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CombatantsMight.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Commando.StayFrosty.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Commando.SuppressiveFire.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CommandPresence.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CommandPresence.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Constructor.BASE.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Constructor.KineticOverload.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CoolCustomer.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CorrosiveStars.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CorrosiveStars.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CorrosiveStrikes.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CorrosiveStrikes.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CorruptedAura.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CorruptedAura.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CriticalBlast.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CriticalBlast.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.CrystalDaggers.T01",DevComment="T01 Crystal Daggers Perk") ++GameplayTagList=(Tag="Granted.Perk.CrystalDaggers.T02",DevComment="Granted Crystal Daggers Perk T02") ++GameplayTagList=(Tag="Granted.Perk.DangerDanger.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DangerDanger.T01.Activate",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DeadDrop.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DeadDrop.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DecoyStun.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DecoyStun.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DeepPockets.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DeepPockets.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DemoDerby.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DemoDerby.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DevastatingDuet.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DimMak.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DirectThreat.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DirectThreat.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DonatingPlasma.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DopplerEffect.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DopplerEffect.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DoubleDown.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DoubleDown.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DragonDaze.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.DragonDaze.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EasyOperation.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EasyOperation.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EasySword.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EasySword.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EggsplosiveEntrace.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EggsplosiveEntrace.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Eggtermination.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Eggtermination.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ElectricAttraction.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ElectrifiedFloors.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ElectrifiedFloors.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Electroshock.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Electroshock.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EmergencyOverride.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EmergencyOverride.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EndlessShadow.T01",DevComment="Granted Tag for Endless Shadow Leader Perk") ++GameplayTagList=(Tag="Granted.Perk.EndlessSmoke.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EndlessSmoke.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EnduranceTraining.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EnduranceTraining.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EnduringMachine.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EnduringMachine.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EnergySiphon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EnergySiphon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EscapeArtist.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EscapeArtist.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ExplosiveOptimization.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ExplosiveOptimization.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ExplosiveRounds.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ExplosiveRounds.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EyeOnThePrize.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.EyeOnThePrize.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FanOfStars.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FanOfStars.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FastBuild.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FastBuild.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FasterExplosions.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FasterExplosions.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FaultLine.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FaultLine.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FeelTheBASE.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FeelTheBASE.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FightOrFlight.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FightOrFlight.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FireOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FireOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FireWall.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FireWall.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FirstAssault.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FirstAssault.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FirstCupOfTheDay.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FirstCupOfTheDay.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FishInABarrel.T01",DevComment="Granted Fish in a Barrel Tier 1") ++GameplayTagList=(Tag="Granted.Perk.FishInABarrel.T02",DevComment="Granted Fish in a Barrel Tier 2") ++GameplayTagList=(Tag="Granted.Perk.FiveWindsCut.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FiveWindsCut.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Fleet.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Fleet.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FollowTheLeader.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Footloose.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Footloose.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ForceOfNature.T01",DevComment="Support perk for Autumn Queen hero") ++GameplayTagList=(Tag="Granted.Perk.ForceOfNature.T02",DevComment="Commander perk for Autumn Queen hero") ++GameplayTagList=(Tag="Granted.Perk.FourOnTheFloor.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FourOnTheFloor.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FragmentGeneration.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FragmentGeneration.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FromTheDepths.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FromTheDepths.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FrozenCastle.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FrozenCastle.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FueledByHatred.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FueledByHatred.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FullyContained.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.FullyContained.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Fumblerooski.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Fumblerooski.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Gadgeteer.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Gadgeteer.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GetReadyForASurprise.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GetReadyForASurprise.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GhoulishCackle.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GhoulishCackle.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GnarlyElimination.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GnarlyElimination.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GnarlyPop.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GnarlyPop.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GoinAgain.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GoinAgain.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GoinBananas.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GoinBananas.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GoingandGoing.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GoingandGoing.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GreaseTheWheels.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GreaseTheWheels.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GrenadeDamage.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GrenadeDamage.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GrenadeGeneration.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.GrenadeGeneration.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HandCannons.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HandCannons.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HangTime.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HangTime.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HardwareCritChance.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HardwareCritChance.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HeartOfStone.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HeartOfStone.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HeartyStrikes.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HeartyStrikes.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HighTide.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HighTide.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Hipshot.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Hipshot.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HolidayCheer.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HolidaySurprise.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HolidaySurprise.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Hotfix.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Hotfix.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HotFoot.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HotFoot.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HotLead.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HotLead.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HuntersInstinct.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.HuntersInstinct.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.IcyShot.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.IcyShot.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ILoveToReload.Active",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ILoveToReload.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ILoveToReload.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ImJustNotUserFriendly.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ImJustNotUserFriendly.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ImpossibilityMatrix.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ImpossibilityMatrix.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.InAndOutlander.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.InAndOutlander.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.InAPinch.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.InAPinch.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.IronKnuckles.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.IronKnuckles.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KeepOut.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Kickback.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Kickback.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KickEmWhenTheyreDown.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KineticOverdrive.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KineticPunch.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KineticPunch.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KnightsImpact.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KnightsImpact.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KunaiCollection.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.KunaiCollection.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Kunaihilation.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Kunaihilation.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Leadership.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Leadership.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LegendaryBlade.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LegendaryBlade.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LockedAndReloaded.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LockedAndReloaded.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LoftyArchitecture.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LoftyArchitecture.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LoneDragon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LonelyBear.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LongArmOfTheLaw.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LongRush.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LongRush.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.LowOrdinance.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MadTidings.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MadTidings.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MakeItCount.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MakeItCount.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MakeItRain.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MakeItRain.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Malfunction.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Malfunction.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MaximumOverload.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MaximumOverload.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MedicinalFumes.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MedicinalFumes.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MegaBASE.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MegaBASE.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MightyRoar.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MightyRoar.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MonkeyingAround.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MonkeyingAround.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MonsterSmash.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MonsterSmash.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MoralBoost.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MUSTPROTECT.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.MUSTPROTECT.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.NeverTellMeTheOdds.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.NightStalker.T01",DevComment="Level 1 NightStalker Perk") ++GameplayTagList=(Tag="Granted.Perk.NightStalker.T02",DevComment="NightStalker Perk T02") ++GameplayTagList=(Tag="Granted.Perk.Ninja.MantisLeap.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Ninja.ShadowStance.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.OneHotMinute.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.OneHotMinute.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.OneTwoPunch.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Outlander.AntiMaterialCharge.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Outlander.InTheZone.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Overbearing.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Overbearing.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.OwMyEye.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.OwMyEye.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PartingGift.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PartingGift.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseBlaster.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseCannon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseCannon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhasedAndConfused.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhasedAndConfused.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhasedOut.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhasedOut.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseForward.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseForward.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseSiphon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PhaseSiphon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PlasmaArc.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PlasmaArc.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PlasmaOverdrive.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PlasmaOverdrive.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PlasmaPulseBlast.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PlasmaPulseBlast.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PointyFury.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PointyFury.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PokingHoles.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PokingHoles.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PowerImpact.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PowerImpact.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PowerModulation.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PowerModulation.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PowerPulse.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PowerPulse.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PracticedInCombat.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PracticedInCombat.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PreemptiveStrike.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PresentArms.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PresentArms.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PressingCharges.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PressingCharges.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PullThePin.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PullThePin.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PumpMetal.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.PumpMetal.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.QuickFingers.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.QuickFingers.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.QuickKick.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.QuickKick.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.QuickScope.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.QuickScope.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RackingUpTheCoal.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RackingUpTheCoal.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RapidCharge.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RapidCharge.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RapidFire.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RapidFire.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Rebound.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Rebound.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Recycling.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Recycling.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ResonantFrequency",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ResonantFrequency.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ResonantFrequency.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ReturnOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ReturnOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ReturnToSender.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ReturnToSender.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RogueWave.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RoundTrip.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Rucksack.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Rucksack.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RunAndStun.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RunAndStun.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RunningWithTheBulls.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RushedRush.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.RushedRush.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianClaws.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianClaws.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianFight.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianFight.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianFocus.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianFocus.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianGrit.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianGrit.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianHide.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianHide.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianInstinct.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianInstinct.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianMight.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianMight.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianVigor.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SaurianVigor.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ScytheToMeetYou.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ScytheToMeetYou.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SeaSickness.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SeaSickness.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SecondWind.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SeismicStanza.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SeismicStanza.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SetPhasersToKill.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SetPhasersToKill.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShadowAssassin.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShadowAssassin.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShakeItOff.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Shatter.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShellShock.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShellShock.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShiftingGears.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShockinglyAlone.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShootyMcShootsAlot.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShortFuse.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShortFuse.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShredTheGnar.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ShredTheGnar.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SixShooter.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SixShooter.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SlowReleaseBombs.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SlowYourRoll.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SnowinCommando.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SnowinCommando.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SoaringMantis.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Software.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Software.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SoloImpact.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SoloImpact.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SoloStarPower.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SpaceTechnology.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SpaceTechnology.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SpoilsOfWar.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SpoilsOfWar.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.StartUp.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Startup.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.StaticCling.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.StaticCling.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Stormborne.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Stormborne.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.StormyWeather.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.StrikeCost.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.StrikeCost.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SuperchargedTraps.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SureShot.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SureShot.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Survivalist.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Survivalist.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SustainedImpact.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SustainedImpact.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SwabTheDeck.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.SwabTheDeck.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Swash.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Swash.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.TailOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.TailOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ThrowingStars.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ThrowingStars.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.TotallyGnarly.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ToughTraps.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ToughTraps.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.TrickAndTreat.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Twinblast.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.Twinblast.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UnderWarranty.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UnderWarranty.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UnearthlyTreat.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UnearthlyTreat.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UneggspectedDeparture.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UneggspectedDeparture.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UpInSmoke.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UpInSmoke.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UpTheVoltage.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UpTheVoltage.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UtilityBelt.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.UtilityBelt.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ViolentInspiration.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WarOfTheRosie.T01",DevComment="Granted War of the Rosie Perk") ++GameplayTagList=(Tag="Granted.Perk.WarOfTheRosie.T02",DevComment="War of the Rosie Tier 2 Granted") ++GameplayTagList=(Tag="Granted.Perk.WasteNotWantNot.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WasteNotWantNot.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WheresLefty.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WheresLefty.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WingsOfTheDragon.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WingsOfTheDragon.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WorkWork.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.WorkWork.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.YoureComingWithMe.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.YourMoveCreep.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.YourMoveCreep.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ZipAndZap.T01",DevComment="") ++GameplayTagList=(Tag="Granted.Perk.ZipAndZap.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.AbsolutelyStunning.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ActionCharge.Stack",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Assassination.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.BassSolo.PlayingSolo",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Bearricade.Slow",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.BlastFromThePast.MaxHealth",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ColdToTheTouch.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ColdToTheTouch.Freeze",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Commando.StayFrosty.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.CommandPresence.DamageVulnerability",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.CommandPresence.Snare",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Constructor.KineticOverload.ImpactMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.CorrosiveStars.Affliction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.CorrosiveStrikes.Affliction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.CorrosiveStrikes.Snare",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.DimMak.ShieldRegen",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.EmergencyOverride.SCR",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.EnduringMachine.DurabilityReduction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.EscapeArtist.Movespeed.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.EscapeArtist.Movespeed.T02",DevComment="") ++GameplayTagList=(Tag="Granted.Perkeffect.FeelTheBASE.Applied",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FeelTheBASE.Overflow",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FeelTheBASE.StackCount",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FeelTheBASE.StackCount.Overflow",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FightOrFlight.Buff.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FightOrFlight.Buff.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FirstCupOfTheDay.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FollowTheLeader.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FrozenCastle.Cooldown",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.FullyContained",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Gadgeteer.CooldownReduction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.GhoulishCackle.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.GoinBananas.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.GoinBananas.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Hangtime.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.HighTide.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.HotLead.Container",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.HotLead.OverheatMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Huntersinstinct.Applied",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Huntersinstinct.DamageStack",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.HuskDance.Cooldown",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.HuskFreeze.Cooldown",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.HuskLovestruck.Cooldown",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.IcyShot.Affliction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.IcyShot.Freeze",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.InAndOutlander.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.InAPinch.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.LegendaryBlade.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.LockedAndReloaded.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.LoftyArchitecture.MaxHealth",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Lovestruck",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.MadTidings.Buff",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.MoralBoost.DR",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.NeverTellMeTheOdds.HealingMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.NightStalker.Applied",DevComment="The Granted Applied Nightstalker Buff") ++GameplayTagList=(Tag="Granted.Perkeffect.Ninja.MantisLeap.AdditionalJumps",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Ninja.MantisLeap.FallImmune",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Ninja.ShadowStance.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.OneTwoPunch.SCR",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.OnHitStreakHuskFreeze.Stacks",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.OnMeleeHitStreakHuskDance.Stacks",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.OnRangedHitStreakHuskDance.Stacks",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.OnRangedHitStreakHuskDanceAfflict.Stacks",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Outlander.InTheZone",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Outlander.InTheZone.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Overbearing.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.OwMyEye.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Perk.EndlessShadow.Active",DevComment="Granted Effect Tag for Endless Shadow") ++GameplayTagList=(Tag="Granted.PerkEffect.PhaseBlaster.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PhaseCannon.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PokingHoles.Affliction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PowerModulation.Applied",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PresentArms.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PresentArms.Movespeed",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PresentArms.Movespeed.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PresentArms.Movespeed.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PresentArms.StaminaRegen",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PressingCharges.DamageMult.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.PressingCharges.DamageMult.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.RackingUpTheCoal.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.RackingUpTheCoal.DamageMult.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.RackingUpTheCoal.DamageMult.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ResonantFrequency.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SaurianFight.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SaurianFocus.StaminaRegen",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SaurianHide.Armor",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SaurianVigor.Heal",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ScytheToMeetYou.Snare",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SeaSickness.Affliction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SeaSickness.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SeismicStanza.Affliction",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SeismicStanza.Stun",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SetPhasersToKill.DamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ShadowAssassin.CritMods",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ShortFuse.SCR",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SlowYourRoll.Freeze",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SlowYourRoll.Slow",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SpoilsOfWar.AmmoRecovery",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SpoilsOfWar.AmmoRecovery.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.SpoilsOfWar.AmmoRecovery.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.StartUp.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.Survivalist.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.TotallyGnarly.Active",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.TotallyGnarly.CueContainer",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.UnderWarranty.FirstHitCrit",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.UpInSmoke.Movespeed.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.UpInSmoke.Movespeed.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.WarOftheRosie.DamageMult.T01",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.WarOftheRosie.DamageMult.T02",DevComment="") ++GameplayTagList=(Tag="Granted.PerkEffect.ZipAndZap.DamageDOT",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.Air",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.Buried",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.JewelPuzzle",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.Statue.Crouch",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.Statue.Emote",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.Statue.Jump",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.Discovery.Vault",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.AgentJones.JewelPuzzle.JewelCounter",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.Billboards.Disguised",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.Oracle.CorruptionPuzzle.Discovery",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.S20.W03.Q03.DataTransponderBlocked",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.S20.W03.Q04.Disguise",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.S20.W03.Q04.DisguiseGranter",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.Tower.Disguised",DevComment="") ++GameplayTagList=(Tag="Granted.Quest.Tower.Glyph.Discovery",DevComment="") ++GameplayTagList=(Tag="Granted.Quests.Ability.Confront",DevComment="") ++GameplayTagList=(Tag="Granted.Quests.Ability.Mindwipe",DevComment="") ++GameplayTagList=(Tag="Granted.Quests.Ability.VOPlaying",DevComment="") ++GameplayTagList=(Tag="Granted.Quests.TestMode",DevComment="") ++GameplayTagList=(Tag="Granted.Resolve.Item.Weapon.HomingRocket.MissileLocked",DevComment="") ++GameplayTagList=(Tag="Granted.Riding.Creature.Energy.Depleted",DevComment="") ++GameplayTagList=(Tag="Granted.Riding.Creature.IsBeingRidden",DevComment="") ++GameplayTagList=(Tag="Granted.Riding.Player.IsRiding",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Buff.SuppressedSMG+",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.GliderRedeploy",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.LowHealthDamageMult",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.PickaxeIncrease",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.PickaxeOverride",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.TrustyDualPistols",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.TrustyHeadhunter",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.TrustyMagazine",DevComment="") ++GameplayTagList=(Tag="Granted.Spy.Tech.Passive.TrustySuppressor",DevComment="") ++GameplayTagList=(Tag="Granted.Status.AbilityBlock",DevComment="") ++GameplayTagList=(Tag="Granted.Status.FullHealth",DevComment="Status when pawn is full health") ++GameplayTagList=(Tag="Granted.Status.Interrogated",DevComment="") ++GameplayTagList=(Tag="Granted.Status.Shielded",DevComment="") ++GameplayTagList=(Tag="Granted.Uncle.BlockBreak",DevComment="") ++GameplayTagList=(Tag="Granted.Uncle.Charge.Tier1",DevComment="") ++GameplayTagList=(Tag="Granted.Uncle.Charge.Tier2",DevComment="") ++GameplayTagList=(Tag="Granted.Uncle.Charge.Tier3",DevComment="") ++GameplayTagList=(Tag="Granted.Uncle.Damage",DevComment="") ++GameplayTagList=(Tag="Granted.Uncle.Passive",DevComment="") ++GameplayTagList=(Tag="Granted.WeaponEffect.SonicPulse.Buff.Damage.Active",DevComment="") ++GameplayTagList=(Tag="HeldObject.Athena.Generic.SnowBall",DevComment="") ++GameplayTagList=(Tag="HeldObject.Behavior.DropOnInteractPlace",DevComment="") ++GameplayTagList=(Tag="Henchmen.BlackKnight",DevComment="") ++GameplayTagList=(Tag="Henchmen.BuffCat",DevComment="") ++GameplayTagList=(Tag="Henchmen.CatBurglar",DevComment="") ++GameplayTagList=(Tag="Henchmen.MechanicalEngineer",DevComment="") ++GameplayTagList=(Tag="Henchmen.OceanRider",DevComment="") ++GameplayTagList=(Tag="Henchmen.Photographer",DevComment="") ++GameplayTagList=(Tag="Henchmen.ProfessorPup",DevComment="") ++GameplayTagList=(Tag="Henchmen.SandCastle",DevComment="") ++GameplayTagList=(Tag="Henchmen.SkullDude",DevComment="") ++GameplayTagList=(Tag="Henchmen.SpaceWanderer",DevComment="") ++GameplayTagList=(Tag="Henchmen.TacticalScuba",DevComment="") ++GameplayTagList=(Tag="Henchmen.TnTina",DevComment="") ++GameplayTagList=(Tag="Hero.StatLine.Ability",DevComment="") ++GameplayTagList=(Tag="Hero.Statline.Balanced",DevComment="") ++GameplayTagList=(Tag="Hero.StatLine.Health",DevComment="") ++GameplayTagList=(Tag="Hero.StatLine.Shields",DevComment="") ++GameplayTagList=(Tag="Homebase.Class.IsCommando.CapedValentine",DevComment="") ++GameplayTagList=(Tag="Homebase.Class.IsCommando.Drifter",DevComment="") ++GameplayTagList=(Tag="Homebase.Class.IsConstructor.BlackKnight",DevComment="") ++GameplayTagList=(Tag="Homebase.Class.IsConstructor.MountedTurret",DevComment="MountedTurretClass") ++GameplayTagList=(Tag="Homebase.Class.IsConstructor.SentryGun",DevComment="Base Class for new Mounted Sentry Turret ability") ++GameplayTagList=(Tag="Homebase.Class.IsNinja.Rogue",DevComment="") ++GameplayTagList=(Tag="Homebase.Class.IsNinja.ShadowKunai",DevComment="Subclass tag for Shadow Kunai Ninja") ++GameplayTagList=(Tag="Homebase.Class.IsNinja.Werewolf",DevComment="Werewolf hero ") ++GameplayTagList=(Tag="Homebase.Class.IsOutlander.DarkViking",DevComment="Subclass tag for Dark Viking Outlander") ++GameplayTagList=(Tag="Homebase.Class.IsOutlander.Uniform",DevComment="Subclass tag for Uniform Outlander") ++GameplayTagList=(Tag="Homebase.Class.IsOutlander.Vamp",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.AbilityCluster",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.AccoladeList",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.AmmoIndicator",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.ArsenicCore.GamePhase",DevComment="Widget component tag for the ArsenicCore version of the Game Phase widget") ++GameplayTagList=(Tag="HUD.Athena.ArsenicCore.Kills",DevComment="Widget component tag for the ArsenicCore version of the Kills display widget") ++GameplayTagList=(Tag="HUD.Athena.ArsenicCore.PlayersLeft",DevComment="Widget component tag for the ArsenicCore version of the PlayersLeft widget") ++GameplayTagList=(Tag="HUD.Athena.ArsenicCore.Teamkills",DevComment="Widget component tag for the ArsenicCore version of the Team Kills display widget") ++GameplayTagList=(Tag="HUD.Athena.Bounty.UI",DevComment="Hides Bounty UI") ++GameplayTagList=(Tag="HUD.Athena.BuildingPossession",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.CenterPopupMessage",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Collections.CollectedToast",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.ContentOnDemand",DevComment="Tag to hide the ContentOnDemand widget in game") ++GameplayTagList=(Tag="HUD.Athena.ContextualChallengePreview",DevComment="Shows a small number of relevant challenges (POI / inventory related)") ++GameplayTagList=(Tag="HUD.Athena.CurieStatus",DevComment="Shows when the player is on fire") ++GameplayTagList=(Tag="HUD.Athena.DuelOverlay",DevComment="Shows when the player starts a duel with a Tandem NPC and/or wins/loses the duel.") ++GameplayTagList=(Tag="HUD.Athena.ErrorIcon.Interaction.BackpackFull",DevComment="Icon used to mark that an item cannot be interacted with because the player\'s backpack is full.") ++GameplayTagList=(Tag="HUD.Athena.EventNotifications",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.FocalPoint",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.FPS",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.GamePhase.AlertMessage",DevComment="warmup countdown and phase alert message (can be disabled individually)") ++GameplayTagList=(Tag="Hud.Athena.Gamephase.Bounty",DevComment="Hides Bounty UI") ++GameplayTagList=(Tag="HUD.Athena.GamePhase.MinimapInfo",DevComment="timer under the minimap for storm phases (can be disabled individually)") ++GameplayTagList=(Tag="HUD.Athena.Hide.JustMapKeys",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Hide.VehicleFuelForNonDriverPassengers",DevComment="Hides the vehicle fuel widget for passenger players who are NOT the driver.") ++GameplayTagList=(Tag="HUD.Athena.Hide.WithAboveQuickbarOverride",DevComment="Used on widgets above the Primary quickbar") ++GameplayTagList=(Tag="HUD.Athena.HideEquipment",DevComment="Hides Equipment in the Inventory Panel") ++GameplayTagList=(Tag="HUD.Athena.HideForAbility",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideForBattleLab",DevComment="Tag to hide ui elements when the game is in Battle Lab.") ++GameplayTagList=(Tag="HUD.Athena.HideForBetweenRounds",DevComment="used to mark HUD elements that should be hidden between rounds in round based LTMs") ++GameplayTagList=(Tag="HUD.Athena.HideForConversation",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideForCreative",DevComment="Tag to hide ui elements when the game is in Creative.") ++GameplayTagList=(Tag="HUD.Athena.HideForCreativeLTM",DevComment="Tag to hide ui elements when the game is in Creative LTM.") ++GameplayTagList=(Tag="HUD.Athena.HideForEliminatedOverlay",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideforNoBuilding",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideForPlayground",DevComment="Tag to hide ui elements when the game is in Playground.") ++GameplayTagList=(Tag="HUD.Athena.HideForPostGame",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideForPostGameSpectate",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideForQuickTimeEvent",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideforRadial",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideforVehicle",DevComment="Hidden while in a vehicle") ++GameplayTagList=(Tag="Hud.Athena.HideOnCinematic",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HideOnMatchStats",DevComment="allows HUD elements to be hidden on postgame match stats screen") ++GameplayTagList=(Tag="HUD.Athena.HideOnMenu",DevComment="Tag to hide ui elements when the in game menu is opened.") ++GameplayTagList=(Tag="HUD.Athena.HidePlayerAlert",DevComment="Tag to hide player alerts.") ++GameplayTagList=(Tag="HUD.Athena.HidePrimaryForAbility",DevComment="") ++GameplayTagList=(Tag="Hud.Athena.HideSecondaryForAbility",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HitTicks",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.HumansAndBotsLeft",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.IndicatorLayer",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Inf.RoundWidget",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Inf.RoundWidget.ShowObjectiveScreen",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.IngredientsList",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.InputBlocked",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Interrogation",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Inventory.Resource.TopRow",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.LowFuelWarning",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.NetUI",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Pip.ActionPromptSpacer",DevComment="Used to adjust the position of action prompts") ++GameplayTagList=(Tag="HUD.Athena.PiP.HideForPip",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PiP.ShowForPip",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Health",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.HitPointBarText",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Overshield",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Overshield.LastKillerInfo",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Shield",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Timer.Icon",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Timer.Time",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.Vehicle",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.XP.Bar",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.XP.Level",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Player.XP.Supercharged",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PlayerVisited",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PostGameBottomBar",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PostGameOverlay",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PostGameSpectatedPlayerName",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PostpartyMask",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.PunchCard",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.QTE",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.RiderWidget",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.SkyDivingInfo",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.SpecialEventChallengePreview",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.SpectatorRebootWidget",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.TeamIndicators",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.TeamKills",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.Valet",DevComment="Valet contextual HUD") ++GameplayTagList=(Tag="HUD.Athena.VehicleMessage",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.VolumePrompt",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget.Accolades",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget.Bar",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget.Feed",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget.LevelUp",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget.Pop",DevComment="") ++GameplayTagList=(Tag="HUD.Athena.XPWidget.PostGameFeedEntry",DevComment="") ++GameplayTagList=(Tag="HUD.AudioVisualizerEffect.Hide",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.AvailablePickups",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeDeselect",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeGhostMode",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeGlobalOptions",DevComment="") ++GameplayTagList=(Tag="Hud.Bacchus.CreativeGrow",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeMultiselect",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativePhone",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeRotationReset",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeScaleAxisSelector",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeScaleReset",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeSelectedVolumes",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.CreativeShrink",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.EditButton",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.EditTargeted",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Hamburger.InGame",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Hamburger.Menu",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.HeldObjectDestroy",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.HelpButton",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.HUDPlaylistExtensionSlot",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Joystick.LookStick",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Joystick.MoveStick",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.LocalPlayerInfo",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Quickbar",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Quickbar.Bar",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Quickbar.ComboSlot",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Quickbar.InventoryButton",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Quickbar.ModeButton",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Quickbar.Resources",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.ThrowConsumable",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TopRightContainer",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.AlternateUse",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.BattleLabDeviceMenu",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.Crouch",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.EmoteMenu",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.Inventory",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.Jump",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.MapMarker",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.Mode",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.PushToTalk",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.QuickChat",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.QuickHeal",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.Shoot",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.ToggleCameraFocalPoint",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchButton.Zoom",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TouchControls",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TutorialHUDContainer",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.TutorialScreenContainer",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.Use",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.VehicleTertiary",DevComment="") ++GameplayTagList=(Tag="HUD.Bacchus.WumbaSidegrade",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.HamburgerButton",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.IslandDescription",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.IslandInfo",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.ObjectiveTracker",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.PropHunt.PropBeepTimer",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.PublishWatermark",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.HeatmapController",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.MultipleBar",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.PerformanceHUD",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.PerformanceHUD.Legacy",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.PerformanceHUD.SpatialDynamic",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.PerformanceHUD.SpatialStatic",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.UIMetrics.RuntimeStats",DevComment="") ++GameplayTagList=(Tag="HUD.Creative.WaitingForPlayers",DevComment="creative matchmaking countdown and waiting info") ++GameplayTagList=(Tag="HUD.Edit.Connection",DevComment="") ++GameplayTagList=(Tag="HUD.Hide.CreativeJoiningLobby",DevComment="") ++GameplayTagList=(Tag="HUD.Hide.DuringXpEverywhereUI",DevComment="") ++GameplayTagList=(Tag="HUD.Hide.WhileLoading",DevComment="") ++GameplayTagList=(Tag="HUD.LTM.Cobalt.Status",DevComment="") ++GameplayTagList=(Tag="HUD.LTM.HideForCrucible",DevComment="") ++GameplayTagList=(Tag="HUD.LTM.HideForGoose",DevComment="") ++GameplayTagList=(Tag="HUD.LTM.ObjectiveDisplay",DevComment="normally under the compass as the primary LTM objective messaging") ++GameplayTagList=(Tag="HUD.Player.VehicleFuel",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.AssaultRifle",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.Consumable",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.Launcher",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.Pistol",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.Shotgun",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.SMG",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.SniperBow",DevComment="") ++GameplayTagList=(Tag="HUD.PreferredSlotType.Utility",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Crypts",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.CryptsA2",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.CryptsA3",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Endless",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Endless.Channel.01",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Endless.Channel.02",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Gardens",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.GardensA2",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.GardensA3",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Inferno",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.InfernoA2",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.InfernoA3",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Science",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.ScienceA2",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.ScienceA3",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.ScienceBoss",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.StarlightBoss",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.Victorian",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.VictorianA2",DevComment="") ++GameplayTagList=(Tag="HUD.Prompt.VictorianA3",DevComment="") ++GameplayTagList=(Tag="HUD.Quickbar.Creative",DevComment="") ++GameplayTagList=(Tag="HUD.Quickbar.CreativeQBPrompt",DevComment="") ++GameplayTagList=(Tag="HUD.RoundCounter",DevComment="") ++GameplayTagList=(Tag="HUD.Special.SpecialEventStarted",DevComment="") ++GameplayTagList=(Tag="HUD.TopLeft.NetIssue",DevComment="hud element for network issue") ++GameplayTagList=(Tag="HUD.TopRight.FeatUpdatesLog",DevComment="") ++GameplayTagList=(Tag="HUD.TransientQuests.TransientQuestStarted",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.CanOpenChute",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.CanTarget",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.CanUseScopeTargeting",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.CanUseSecondaryAbility",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.ControllingRCPawn",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.Crouching",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.DBNO",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.FreeFalling",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.ImprovedDBNO",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.InEditMode",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.InLockedBus",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.InUnlockedBus",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.IsGliding",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.Targeting",DevComment="") ++GameplayTagList=(Tag="HUD.Visibility.UsingScopeTargeting",DevComment="") ++GameplayTagList=(Tag="HUD.VkEdit.ConnectionIndicator",DevComment="") ++GameplayTagList=(Tag="HUD.VoiceSpeakersWrapper",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Context.Building",DevComment="When the player is in the Building Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Combat",DevComment="When the player is in the Combat Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Creative",DevComment="When the player is in the Creative Context.") ++GameplayTagList=(Tag="HUDLayout.Context.KeyButtonGeneric",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Context.UniqueHUDLTM",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Antelope",DevComment="When the player is in the Antelope Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Biplane",DevComment="When the player is in the Biplane Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Dagwood",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Driver",DevComment="When the player is in the Driving Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.GolfCart",DevComment="When the player is in the GolfCart Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Hoagie",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Jackal",DevComment="When the player is in the Jackal Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Meatball",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.MountedTurret",DevComment="When the player is in the MountedTurret Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Octopus",DevComment="When the player is in the Octopus Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Ostrich",DevComment="When the player is in the Ostrich Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.Passenger",DevComment="When the player is in the Passenger Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.PushCannon",DevComment="When the player is in the PushCannon Context.") ++GameplayTagList=(Tag="HUDLayout.Context.Vehicle.ShoppingCart",DevComment="When the player is in the ShoppingCart Context.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Building",DevComment="Tag used to save the HUD Layout for Building.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Combat",DevComment="Tag used to save the HUD Layout for Combat.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Creative",DevComment="Tag used to save the HUD Layout for Creative.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.KeyButtonGeneric",DevComment="") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.AntelopeDriver",DevComment="Tag used to save the HUD Layout for Driving the Antelope.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.BiplaneDriver",DevComment="Tag used to save the HUD Layout for Driving the Biplane.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.Dagwood",DevComment="") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.GolfCartDriver",DevComment="Tag used to save the HUD Layout for Driving the GolfCart.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.HoagieDriver",DevComment="") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.JackalDriver",DevComment="Tag used to save the HUD Layout for Driving the Jackal.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.MeatballDriver",DevComment="") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.MountedTurretDriver",DevComment="Tag used to save the HUD Layout for Driving the MountedTurret.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.OctopusDriver",DevComment="Tag used to save the HUD Layout for Driving the Octopus.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.OstrichDriver",DevComment="Tag used to save the HUD Layout for Driving the Ostrich.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.OstrichPassenger",DevComment="Tag used to save the HUD Layout for Ostrich Passenger.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.PushCannonDriver",DevComment="Tag used to save the HUD Layout for Driving the PushCannon.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.PushCannonPassenger",DevComment="Tag used to save the HUD Layout for PushCannon Passenger.") ++GameplayTagList=(Tag="HUDLayout.ContextLayout.Vehicle.ShoppingCartDriver",DevComment="Tag used to save the HUD Layout for Driving the ShoppingCart.") ++GameplayTagList=(Tag="HUDLayout.Custom",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Preset.BuilderPro",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Preset.Claw",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Preset.CombatPro",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Preset.DevA",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Preset.DevB",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Preset.OldSchool",DevComment="") ++GameplayTagList=(Tag="HUDLayout.PresetContainer.Development",DevComment="") ++GameplayTagList=(Tag="HUDLayout.PresetContainer.Release",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.Anchors",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.ButtonIconSize",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.Clickthrough",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.Killfeed.NumberOfLines",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.LinkAcrossContexts",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.LockSlot",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.MoveStick.AlwaysShow",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.Quickbar.Reverse",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.Quickbar.SlotLocks",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.Size",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.SlotNumber",DevComment="") ++GameplayTagList=(Tag="HUDLayout.Property.SlotType",DevComment="") ++GameplayTagList=(Tag="In Combat",DevComment="") ++GameplayTagList=(Tag="Infiltration.OutOfCombatRegen.Ignore",DevComment="") ++GameplayTagList=(Tag="Islander.Location.BunkerJonesy",DevComment="") ++GameplayTagList=(Tag="Islander.Location.Generic",DevComment="") ++GameplayTagList=(Tag="Islander.Location.TestQuestSpot",DevComment="") ++GameplayTagList=(Tag="Item.AppleSun",DevComment="") ++GameplayTagList=(Tag="Item.AvacadoEater",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Apple",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.AppleSauce",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.AppleSun.CanJumpToDeploy",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.BadgerBangs",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Balloons",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Banana",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.BigFruit",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.BoomBox",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Booty",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Booty.Rover",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Booty.Rover.Cheat",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.BottomlessChugJug",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Bucket.Nice",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Bucket.Old",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Cabbage",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.CandyCorn",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ChillBronco",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ChromeSplash",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Coal",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Coconut",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.CoolCarpet",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Corn",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Decoy",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.DogSweater",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Drinkable",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Edible",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.FireworksMortar",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.CuddleFish",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.Flopper",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperBattle",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperEffective",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperFire",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperGas",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperGas.ActiveTrail",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperGift",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperHopRock",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperJellyFish",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperMechanic",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperRift",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperShadow",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperShield",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperSmall",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperSnowy",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperThermal",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Fish.FlopperZero",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.FlareGun",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.FloppingRabbit",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.FloppingRabbit.HighTier",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.GiftBox",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Glider",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.GoldenTruffle",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.GrapplingHoot",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.HappyGhost",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.HealSpray",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.HookGun",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.HookGun.GrapplingHoot",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.HookGun.Jas",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.HopDrop",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.IceCream.DeepFreeze",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.IceCream.Guzzle",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.IceCream.Normal",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.IceCream.Slurp",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.IceCream.Spicy",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.IceGrenade",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.JellyBean",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.JollyRascal",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.LaunchPad",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Meat",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Meat.Rare",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Molotov",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Pepper",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PepperMint",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PizzaParty",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PizzaSlice",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PoisonDartTrap.Ceiling",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PoisonDartTrap.Floor",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PoisonDartTrap.Wall",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.PurpleMouse.LOSChecker",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.RealitySeed",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.RejuvenationPotion",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Rift",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Saddler",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Sell4Wads.Large",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Sell4Wads.Medium",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Sell4Wads.Small",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ShieldBubble",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ShieldGenerator",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ShieldHops",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ShieldMushroom",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ShockGrenade",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.SlurpMushroom",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.SneakySnowman",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.SpikeTrap",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.SpikeTrap.Ceiling",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.SpikeTrap.Floor",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.SpikeTrap.Wall",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.TastyStew",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ThermalTaffy",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Tomato",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.Trait.Attachable",DevComment="This consumable can attach to players in some way.") ++GameplayTagList=(Tag="Item.Consumable.UnGiftBox",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.UpgradeBench",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ZapTrap.Ceiling",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ZapTrap.Floor",DevComment="") ++GameplayTagList=(Tag="Item.Consumable.ZapTrap.Wall",DevComment="") ++GameplayTagList=(Tag="Item.DisableAutoPickup.InputTag",DevComment="Disable Auto Pickup if we already have an item with the same activation input") ++GameplayTagList=(Tag="Item.Foraged.BigFruit",DevComment="") ++GameplayTagList=(Tag="Item.Foraged.Cabbage",DevComment="") ++GameplayTagList=(Tag="Item.Foraged.Corn",DevComment="") ++GameplayTagList=(Tag="Item.Foraged.Slurpshroom",DevComment="") ++GameplayTagList=(Tag="Item.Fuel.PetrolPickup",DevComment="") ++GameplayTagList=(Tag="Item.Fuel.PetrolPump",DevComment="") ++GameplayTagList=(Tag="Item.Gadget.DiscoWolf",DevComment="") ++GameplayTagList=(Tag="Item.Gadget.JumpBoots.AnimTracker",DevComment="JumpBoots tag in Main so it can be referenced outside of Item plugin. This tag is specifically so animation can swap meshes while JumpBoots are equipped.") ++GameplayTagList=(Tag="Item.Ingredient",DevComment="") ++GameplayTagList=(Tag="Item.Ingredient.AnimalBones",DevComment="") ++GameplayTagList=(Tag="Item.Ingredient.DoNotTriggerTutorial",DevComment="") ++GameplayTagList=(Tag="Item.Ingredient.ToxicSac",DevComment="") ++GameplayTagList=(Tag="Item.Pickup.StoredData.MultiItem.ItemXP",DevComment="The current amount of \"XP\" a multi-item has toward gaining its next power level.") ++GameplayTagList=(Tag="Item.Pickup.StoredData.MultiItem.SelectedItemIndex",DevComment="Key used to store what the current selected item index is for MultiItems so that they will remain what they were when dropped and picked back up.") ++GameplayTagList=(Tag="Item.Power.Witchbroom",DevComment="") ++GameplayTagList=(Tag="Item.Resource.StylePoint",DevComment="Generic tag to apply to style point currencies to get them to sort into the inventory\'s currency widget.") ++GameplayTagList=(Tag="Item.SkipSingletonCheck",DevComment="Allows the item to skip the singleton check that is done in AFortPlayerController::AddInventoryItem. Can be used for gadgets that should allow for multiple stacks in the inventory.") ++GameplayTagList=(Tag="Item.SortsRight",DevComment="") ++GameplayTagList=(Tag="Item.Trap.Electric",DevComment="") ++GameplayTagList=(Tag="Item.Trap.Fire",DevComment="") ++GameplayTagList=(Tag="Item.Trap.MountedTurret",DevComment="Item tag for Mounted Turret.") ++GameplayTagList=(Tag="Item.Trap.ZippyTrout",DevComment="") ++GameplayTagList=(Tag="Item.Utility",DevComment="") ++GameplayTagList=(Tag="Item.Variant.Shield",DevComment="Test tag") ++GameplayTagList=(Tag="Item.Water.FireExtinguisher",DevComment="") ++GameplayTagList=(Tag="Item.Weapon",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.CanBlock",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Galileo.Bun",DevComment="") ++GameplayTagList=(Tag="Item.weapon.Galileo.Bun.Cosmos",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Galileo.Lobster",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Galileo.Lobster.Realm",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.AmmoMult.Powerup.Minigun",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Burst.RedDot",DevComment="new weapon tag for Burst RedDot") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Burst.RedDot.Slone",DevComment="new tag for slone boss burst reddot") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Burst.Slone",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Chrome.Boss",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Paint",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Scoped.Tactical",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Assault.Thermal.HuntmasterSaber",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Bow.Flame.FullyChargedHit",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.GravyGoblin",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.ImpulsePhysicsObjects",DevComment="For ranged weapons that should still add impulse to physics objects.") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Launcher.HomingRocket",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Launcher.MistyBop",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Launcher.Paint",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Launcher.PetrolPump",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Launcher.ShockWave",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Launcher.Snowball",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Marksman",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Pistol.DualHandCannons",DevComment="new weapon tag for new dual hand cannons") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Pistol.DualPistols.HopRock",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Pistol.DualSuppressedPistols",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Pistol.Flintlock.YeetKnock",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Pistol.Thermal",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Pistol.Tracker",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.RepairTool",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.BreakAction",DevComment="new tag for break action shotgun") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.Charge.HighTier",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.Charge.LowTier",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.Charge.RoboMeow",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.DragonsBreath",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.Dub",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.SingleBreakAction",DevComment="new tag for new single barrel break action shotgun") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.Swing.HighTier",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Shotgun.Swing.LowTier",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.SMG.Charge",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Ranged.Thrown.ThrowingAxe",DevComment="") ++GameplayTagList=(Tag="item.weapon.ranged.yeetknock",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.UncleBrolly",DevComment="") ++GameplayTagList=(Tag="Item.Weapon.Vehicle.Meatball",DevComment="Weapon tag for boat missile launcher") ++GameplayTagList=(Tag="Item.Weapon.Vehicle.Tank.Cannon",DevComment="Weapon tag for the Tank\'s Cannon") ++GameplayTagList=(Tag="Item.Weapon.Vehicle.Tank.Turret",DevComment="Tank\'s Turret weapon tag") ++GameplayTagList=(Tag="item.ZeroPointEnergy2Point0",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.BattlePass",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.CommunityVoting",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.Daily",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.Featured",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.MegaBundle",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.RMTOffers",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.SpecialDaily",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.SpecialFeatured",DevComment="") ++GameplayTagList=(Tag="ItemShop.Section.StandAlone",DevComment="") ++GameplayTagList=(Tag="ItemShopCategory.RMTItemOffers",DevComment="") ++GameplayTagList=(Tag="Keyword.BandMember",DevComment="") ++GameplayTagList=(Tag="Keyword.Dino",DevComment="") ++GameplayTagList=(Tag="Keyword.Monster",DevComment="") ++GameplayTagList=(Tag="Keyword.Pirate",DevComment="") ++GameplayTagList=(Tag="Keyword.RetroSciFi",DevComment="") ++GameplayTagList=(Tag="Keyword.SteelWool",DevComment="") ++GameplayTagList=(Tag="Keyword.TotallyRad",DevComment="") ++GameplayTagList=(Tag="Keyword.WinterFest",DevComment="") ++GameplayTagList=(Tag="LeadAlloy.Weapon",DevComment="") ++GameplayTagList=(Tag="LoadoutSlot.Hero.Commander",DevComment="") ++GameplayTagList=(Tag="LoadoutSlot.Hero.SupportHero",DevComment="") ++GameplayTagList=(Tag="LoadoutSlot.Hero.TeamLead",DevComment="") ++GameplayTagList=(Tag="Loot.Type.Gadget.Keep",DevComment="") ++GameplayTagList=(Tag="Loot.Type.Gadget.Seize",DevComment="") ++GameplayTagList=(Tag="LowTower.Location.Hardy",DevComment="") ++GameplayTagList=(Tag="LTM.Behind.Damage",DevComment="") ++GameplayTagList=(Tag="LTM.Behind.Damage.Tag",DevComment="") ++GameplayTagList=(Tag="LTM.Generic.BlueAllyRedEnemy",DevComment="Used to trigger a GA effect where friendly players are highlighted in blue and enemy players are highlighted in red.") ++GameplayTagList=(Tag="LTM.HighTower.ShowAbilityIntro",DevComment="") ++GameplayTagList=(Tag="LTM.Tag.BlueTeam",DevComment="") ++GameplayTagList=(Tag="LTM.Tag.RedTeam",DevComment="") ++GameplayTagList=(Tag="LTM.Tin.Bandit",DevComment="Bandit") ++GameplayTagList=(Tag="LTM.Tin.Sheriff",DevComment="Sheriff") ++GameplayTagList=(Tag="Mayday.PlacementActor.PartyPlanner.BackupPlacement",DevComment="") ++GameplayTagList=(Tag="Mayday.PlacementActor.SignalBoost.SatelliteDish",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.BridgeOut",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.BugHunt",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.FeelinBlu",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.MinorDelay",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.Overheat",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.PartyPlanner",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.PitStop",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.RadioStation",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.SignalBoost",DevComment="") ++GameplayTagList=(Tag="Mayday.Reward.Badge.Strangelands",DevComment="") ++GameplayTagList=(Tag="Minigame.Objective.BuildRequirement",DevComment="") ++GameplayTagList=(Tag="Minigame.Objective.CapturePoint",DevComment="") ++GameplayTagList=(Tag="Minigame.RaceStart",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.LapTime",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.RaceTime",DevComment="Used as target tags query for MinigameStat_RaceTime") ++GameplayTagList=(Tag="Minigame.Stat.Score",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.Score.Coin",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.Score.Elimination",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.Score.Goal",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.Score.Pinball",DevComment="Score tag for Pinball devices (Bumpers/Flippers etc)") ++GameplayTagList=(Tag="Minigame.Stat.Score.ShootingTarget",DevComment="") ++GameplayTagList=(Tag="Minigame.Stat.TimeAlive",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.Alerts",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.BossInfo",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.FiendKills",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.LeavingTheArea",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.ModeratorMode",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.OverviewGameInfoTab",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.PreGame",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.QuestTracker",DevComment="Creative quest tracker") ++GameplayTagList=(Tag="Minigame.UIExtension.Rounds",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.RoundStatus",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.Score",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.TeamDisplay",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.Timer",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.TopCenterScoringHUD",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.Trackers",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.TrackersRanked",DevComment="") ++GameplayTagList=(Tag="Minigame.UIExtension.Warmup",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.Bonus6",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.Bonus7",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.DeliverTheBomb.ParticipationLauncherDestroyed",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Challenge",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T01",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T02",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T03",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T04",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T05",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T06",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T07",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T08",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T09",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T10",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T11",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T12",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T13",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T14",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T15",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T16",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T17",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T18",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T19",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T20",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T21",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T22",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T23",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T24",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.IronCity.Mission.T25",DevComment="") ++GameplayTagList=(Tag="MissionReward.Badge.OutpostEndless",DevComment="") ++GameplayTagList=(Tag="Missions.ExcludeSecondaryMission.Claypigeon",DevComment="") ++GameplayTagList=(Tag="Missions.SecondaryDisplay",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Actionbutton",DevComment="Mobile Action Button Widget Tag") ++GameplayTagList=(Tag="MobileHUD.ActiveSpeakers",DevComment="Mobile HUD tag for the active voice speaker widgets.") ++GameplayTagList=(Tag="MobileHUD.Button.AlternateUse",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.AntelopeBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ArsenicDash",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ArsenicScream",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.BattleLabDevice",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.BiplaneAirBrake",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.BiplaneBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.CannonCoast",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.AxisToggle",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Delete",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Deselect",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Exit",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.GhostMode",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.GlobalOptions",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Grab",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.GrabeOrPlace",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.GrabOrDuplicate",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.GravityToggle",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Grow",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Heatmap",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.LetGo",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.MirrorOnGrid",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Multiselect",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Phone",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Properties",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Pull",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Push",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.ResetRotation",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.ResetScale",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Rotate.Clockwise",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Rotate.CounterClockwise",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.ScaleAxisToggle",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.Shrink",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.SnapLocation",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.SnapRotation",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.ToggleCollision",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Creative.ToggleFlight",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.CycleWeaponDown",DevComment="Touch Control Button to Cycle Weapons down.") ++GameplayTagList=(Tag="MobileHUD.Button.DBNOCarryDrop",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.DBNOCarryHoist",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.DBNOCarryThrow",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.DPad.Down",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.DPad.Left",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.DPad.Right",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.DPad.Up",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.EditConfirm",DevComment="") ++GameplayTagList=(Tag="MobileHud.Button.EditPermanent",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.EditSelectReset",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.EditTargeted",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.EventFocus",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FaceButton.Bottom",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FaceButton.Left",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FaceButton.Right",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FaceButton.Top",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FaceButtonLeft",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FalconScoutDown",DevComment="") ++GameplayTagList=(Tag="MObileHUD.Button.FalconScoutUp",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.FerretFreeLook",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.GolfCartEBrake",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HamsterballBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HamsterballExit",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HamsterballExtend",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HamsterballGrapple",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HamsterballToggleExtend",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HamsterballToggleGrapple",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HeldObjectDestroy",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HeldObjectDrop",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HeldObjectPickupDrop",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HighTowerSecondary",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HighTowerUtility",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HoagieMoveDown",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.HoagieMoveUp",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Honk",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.JackalBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.JackalJump",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.LeftShoulder",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.LeftTrigger",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.LobsterBlock",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MapMarker",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MeatballBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MeatballZoomOut",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MechBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MechMissiles",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MechStomp",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.MeleeBlock",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.OstrichDestruct",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.OstrichJump",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.OstrichShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.PapayaMap",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.PropExit",DevComment="This is the exit button for the prop controller.") ++GameplayTagList=(Tag="MobileHUD.Button.PropLockInPlace",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.QuickHeal",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.RadioNext",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ScoutDroneMoveUp",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ShoppingCartCoast",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Shoulder.Left",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Shoulder.Right",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.SquadComs",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.TertiaryAbility",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.TetherBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.TetherExit",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ThrowConsumable",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ThumbStick.Left",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Thumbstick.Right",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ToggleRadio",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Trigger.Left",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Trigger.Right",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.Use",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ValetBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.ValetHandbrake",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.VehicleBoost",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.VehicleExit",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.VehicleSwitchSeat",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Button.WumbaSidegrade",DevComment="") ++GameplayTagList=(Tag="MobileHUD.CreativeQuickbar",DevComment="") ++GameplayTagList=(Tag="MobileHUD.DisplayADS",DevComment="") ++GameplayTagList=(Tag="MobileHUD.FPSCounter",DevComment="FPS Counter tag") ++GameplayTagList=(Tag="MobileHUD.HealthBarLocal",DevComment="Local player\'s healthbar") ++GameplayTagList=(Tag="MobileHUD.KeyButton",DevComment="") ++GameplayTagList=(Tag="MobileHUD.Killfeed",DevComment="") ++GameplayTagList=(Tag="MobileHUD.NetStats",DevComment="") ++GameplayTagList=(Tag="MobileHud.PhoneInfo",DevComment="") ++GameplayTagList=(Tag="MobileHud.QuickBar.1",DevComment="Quickbar placeable button 1.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.2",DevComment="Quickbar placeable button 2.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.3",DevComment="Quickbar placeable button 3.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.4",DevComment="Quickbar placeable button 4.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.5",DevComment="Quickbar placeable button 5.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.6",DevComment="Quickbar placeable button 6.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.7",DevComment="Quickbar placeable button 7.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.8",DevComment="Quickbar placeable button 8.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.9",DevComment="Quickbar placeable button 9.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.10",DevComment="Quickbar placeable button 10.") ++GameplayTagList=(Tag="MobileHUD.Quickbar.11",DevComment="Quickbar placeable button 11") ++GameplayTagList=(Tag="MobileHUD.Quickbar.12",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickbarComboslot",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHeal",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MajorHealth",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MajorHealthMajorShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MajorHealthMinorShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MajorShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MinorHealth",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MinorHealthMajorShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MinorHealthMinorShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.MinorShield",DevComment="") ++GameplayTagList=(Tag="MobileHUD.QuickHealPriority.NoNeed",DevComment="") ++GameplayTagList=(Tag="MobileHud.Resources",DevComment="") ++GameplayTagList=(Tag="MobileHUD.SquadList",DevComment="List of Squad Healthbars for the mobile hud.") ++GameplayTagList=(Tag="MobileHUD.Stats.GamePhase",DevComment="Displays the currect game phase. i.e : Storm Timer") ++GameplayTagList=(Tag="MobileHUD.Stats.Kills",DevComment="Display for amount of kills on mobile") ++GameplayTagList=(Tag="MobileHUD.Stats.PlayersLeft",DevComment="Display for amount of players left.") ++GameplayTagList=(Tag="MobileHUD.Stats.TeamKills",DevComment="Team Kill Amount Dispaly on Mobile") ++GameplayTagList=(Tag="MovementMode.Swinging",DevComment="") ++GameplayTagList=(Tag="Mutator.BlockEmotes",DevComment="") ++GameplayTagList=(Tag="Mutator.DADBRO",DevComment="") ++GameplayTagList=(Tag="Mutator.FortWeaponToAllowTargeting",DevComment="") ++GameplayTagList=(Tag="NavFilter",DevComment="") ++GameplayTagList=(Tag="NavFilter.NPC.AvoidDanger",DevComment="") ++GameplayTagList=(Tag="NavFilter.NPC.DefaultMove",DevComment="") ++GameplayTagList=(Tag="NavFilter.NPC.MoveToPickup",DevComment="") ++GameplayTagList=(Tag="NavFilter.NPC.Reposition",DevComment="") ++GameplayTagList=(Tag="NavFilter.NPC.Unstuck",DevComment="") ++GameplayTagList=(Tag="NavFilter.NPC.Wander",DevComment="") ++GameplayTagList=(Tag="NavObject.C.12",DevComment="") ++GameplayTagList=(Tag="NavObject.D.9",DevComment="") ++GameplayTagList=(Tag="NavObject.DonutRoom.MilkCarton",DevComment="") ++GameplayTagList=(Tag="Night1.PlacementActors.Attic",DevComment="") ++GameplayTagList=(Tag="Night1.PlacementActors.GymSensor",DevComment="") ++GameplayTagList=(Tag="NIght1.PlacementActors.Hotel",DevComment="") ++GameplayTagList=(Tag="Night1.PlacementActors.HotelBar",DevComment="") ++GameplayTagList=(Tag="Night1.PlacementActors.HotelBedroom",DevComment="") ++GameplayTagList=(Tag="Night1.PlacementActors.Labyrinth",DevComment="") ++GameplayTagList=(Tag="Night1.PlacementActors.Library",DevComment="") ++GameplayTagList=(Tag="NIght1.PlacementActors.LobbySensor",DevComment="") ++GameplayTagList=(Tag="Nitrogen.Location.Start.1",DevComment="") ++GameplayTagList=(Tag="Nitrogen.Location.Start.2",DevComment="") ++GameplayTagList=(Tag="Nitrogen.Location.Start.3",DevComment="") ++GameplayTagList=(Tag="Nitrogen.Location.Start.4",DevComment="") ++GameplayTagList=(Tag="Nitrogen.Location.Start.5",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.AoE.Warning",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Defense",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Footstep.Center",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Footstep.InPlace",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Footstep.Left",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Footstep.Right",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.Charge.ToLeap",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.ChargeControl.OverrideEnd",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.ChargeControl.OverrideStart",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.PawSwipe",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.RightPawSmash",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.Secondary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.Secondary.Target",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Melee.Warning",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Ranged.CheapFlyingPrimary",DevComment="Athena AI Controller uses this ability to smash obstacles while flying mode.") ++GameplayTagList=(Tag="NPC.Ability.Attack.Ranged.Flinger.ThrowAdditionalSkulls",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Attack.Ranged.Leap",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.ReduceCooldowns",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.Solo.Dev1",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.Solo.Dev2",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.Solo.Dev3",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.Solo.Dev4",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.Solo.Dev5",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Debug.Solo.RemoveAll",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.EQS.Generator.Circle.NumberOfPoints",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.EQS.Generator.Circle.Radius",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.MinStopRange",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Alert",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Avoid.Danger.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Avoid.Enemy.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Avoid.Enemy.Panic",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Avoid.Enemy.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Avoid.Enemy.WallAttack",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Avoid.Enemy.Wander",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Death.Drowned",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Defense",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.DespawnActors",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Disguise",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.DropLoot",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.DropPickup",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Exhausted.GetUp",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Exhausted.IsLayedDown",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Exhausted.LayDown",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Exhausted.LayDown.Cooldown",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Forage",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Frustration.Active",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Frustration.Build",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Frustration.Build.DueTo.Goal",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Frustration.Build.DueTo.Movement",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Frustration.DueTo.Goal",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Frustration.DueTo.Movement",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.GiveItems.StartingItems",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.GrabPickup.Mounted",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Heal.Area",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.HealAOE",DevComment="Nurse husks passive heal aoe") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Leap.AlongPath",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Leap.Lateral",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Leap.OverGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Leap.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Leap.StraightUp",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Move.ToLand",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Move.ToPatrol",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Move.ToPickup",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Move.ToWater",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.PlayMontage",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.PlayMontage.Intro",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.PlayMontage.Wander.Freely",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.PlayMontage.Wander.NearGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.AroundLeader",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.ForCooldown.Melee.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.ForCooldown.Melee.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.ForTargetSlots.Melee",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.ForTargetSlots.Ranged",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.Lateral",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MaxPreferredRange.Melee.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MaxPreferredRange.Melee.Charge.ToLeap",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MaxPreferredRange.Melee.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MaxPreferredRange.Ranged.Leap",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MaxPreferredRange.Ranged.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Melee.Backup",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Melee.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Melee.Charge.ToLeap",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Melee.MoveToTarget",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Melee.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Ranged.Backup",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Ranged.Leap",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Reposition.MinPreferredRange.Ranged.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Scanning",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.SpawnActors",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.SpawnIntro.Alternate",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.SpawnIntro.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.TargetSlots.Melee",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.TargetSlots.Ranged",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Wander.CircleGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Wander.Escape",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Wander.Flee",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Wander.Freely",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.NonAttack.Wander.NearGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.RangeToAutomaticallyAddEnemyPawnGoals",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.Riding.SpecialAbility",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.WallAttack.Melee.Ceiling",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.WallAttack.Melee.Floor",DevComment="") ++GameplayTagList=(Tag="NPC.Ability.WallAttack.Melee.Wall",DevComment="") ++GameplayTagList=(Tag="NPC.AlertLevel.Aggressive",DevComment="") ++GameplayTagList=(Tag="NPC.AlertLevel.Alerted",DevComment="") ++GameplayTagList=(Tag="NPC.AlertLevel.Unaware",DevComment="") ++GameplayTagList=(Tag="NPC.Assignment.HealHusks",DevComment="Assignment for nurse husk aoe heal") ++GameplayTagList=(Tag="NPC.Assignment.Taunt",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Avoid.Enemy",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Avoid.Enemy.Active",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Avoid.Enemy.Apply",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Idle",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Animation.Active",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Animation.Cooldown",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Animation.Pending",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Apply",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.BehaviorActive",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.BehaviorCooldown",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Remove",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Reset",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.Target",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Intro.UsesIntroBehavior",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Loot.Collector.Full",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Loot.Collector.RallyGatherers",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Outro.Despawn",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.Freely.Animation.Active",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.Freely.Animation.Cooldown",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.Freely.Animation.Pending",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.Freely.Animation.Pending.Initiator",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.Freely.Animation.Pending.Responder",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.NearGoal.Animation.Active",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.NearGoal.Animation.Cooldown",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Phase.Wander.NearGoal.Animation.Pending",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Reposition.ForCooldown.Melee.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.Reposition.ForCooldown.Melee.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.TargetSlots.Melee.Add",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.TargetSlots.Melee.Remove",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.TargetSlots.Ranged.Add",DevComment="") ++GameplayTagList=(Tag="NPC.Behavior.TargetSlots.Ranged.Remove",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Boss.Starlight",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Brute.Basic.Exploding",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Brute.Basic.Winter",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Brute.Major.Winter",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Fiend.Basic.Poison",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Fiend.Basic.Winter",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Fiend.Major.Winter",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Fiend.Ranged.Chill",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Fiend.Thrower",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Galileo",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Husk.Mimic",DevComment="") ++GameplayTagList=(Tag="NPC.CharacterType.Husk.Nurse",DevComment="Nurse Husk") ++GameplayTagList=(Tag="NPC.CharacterType.Husk.Pitcher.Villager",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.Attack.Melee.Cooldowns",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.Attack.Melee.PawSwipe",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.Attack.Melee.Secondary",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Alert",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Avoid.Danger.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Avoid.Enemy.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Avoid.Enemy.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Avoid.Enemy.Wander",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.ControlBreakCount",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Defense",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.DespawnActors",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.DropLoot",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Frustration.DueTo.Goal",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Frustration.DueTo.Movement",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.GrabPickup",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Leap.AlongPath",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Leap.Lateral",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Leap.OverGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Leap.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Leap.StraightUp",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Move.ToPickup",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.PlayMontage.Intro",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.PlayMontage.Wander.Freely",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.PlayMontage.Wander.NearGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.AroundLeader",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.ForCooldown.Melee.Charge",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.ForCooldown.Melee.Primary",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.ForTargetSlots.Melee",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.ForTargetSlots.Ranged",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.Lateral",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.MinPreferredRange.Melee.Backup",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.MinPreferredRange.Melee.MoveToTarget",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Reposition.MinPreferredRange.Ranged.Backup",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Wander.CircleGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Wander.Escape",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Wander.Flee",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Wander.Freely",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.Wander.NearGoal",DevComment="") ++GameplayTagList=(Tag="NPC.Cooldown.Ability.NonAttack.WanderFreely",DevComment="") ++GameplayTagList=(Tag="NPC.EnemyType.NestedSpawn",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.AssassinNoDecoy",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Dev.Dev1",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Dev.Dev2",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Dev.Dev3",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Dev.Dev4",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Dev.Dev5",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Labrador",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Wildlife.Aquatic",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Wildlife.Basic",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Wildlife.Monster.Caretaker",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Wildlife.Tamed",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Wildlife.Terrestrial",DevComment="") ++GameplayTagList=(Tag="NPC.GoalSelection.Wildlife.Terrestrial.Aggressive",DevComment="This goal selection category sets up the terrestrial AI to consider being aggressive to certain players.") ++GameplayTagList=(Tag="NPC.Immune.Buffs",DevComment="Not allowed to receive buffs") ++GameplayTagList=(Tag="NPC.Patrol.DynamicallyReserved",DevComment="") ++GameplayTagList=(Tag="NPC.Patrol.Type.Flying",DevComment="") ++GameplayTagList=(Tag="NPC.SpecialType.FakePawn",DevComment="") ++GameplayTagList=(Tag="NPC.Status.Airborne",DevComment="Indicates if pawn is thrown.") ++GameplayTagList=(Tag="NPC.Status.Failed.Move",DevComment="") ++GameplayTagList=(Tag="NPC.Status.Frustration.DueTo.Goal",DevComment="") ++GameplayTagList=(Tag="NPC.Status.Frustration.DueTo.Movement",DevComment="") ++GameplayTagList=(Tag="npc.status.noduel",DevComment="") ++GameplayTagList=(Tag="NPC.Status.NoHire",DevComment="") ++GameplayTagList=(Tag="NPC.Status.PreventStress",DevComment="") ++GameplayTagList=(Tag="NPC.Status.PreventWalkInPatrol",DevComment="") ++GameplayTagList=(Tag="NPC.Status.RecentlyAlerted",DevComment="NPC was recently in an alerted state and while no longer in the Alerted state anymore, the NPC may use this tag to do non-Alerted behaviors in a more \"suspicious\" manner.") ++GameplayTagList=(Tag="NPC.Status.Stressed",DevComment="") ++GameplayTagList=(Tag="NPC.Status.Teleport.Active",DevComment="") ++GameplayTagList=(Tag="NPC.Status.Tethered",DevComment="") ++GameplayTagList=(Tag="NPC.Tetherable",DevComment="Can be tethered to") ++GameplayTagList=(Tag="Object.Explosive",DevComment="") ++GameplayTagList=(Tag="Object.Num.01",DevComment="") ++GameplayTagList=(Tag="Object.Num.02",DevComment="") ++GameplayTagList=(Tag="Object.Num.03",DevComment="") ++GameplayTagList=(Tag="Object.Num.04",DevComment="") ++GameplayTagList=(Tag="object.num.05",DevComment="") ++GameplayTagList=(Tag="Out of Combat",DevComment="") ++GameplayTagList=(Tag="Papaya.ForceTOD",DevComment="") ++GameplayTagList=(Tag="PapayaBooth.Disable",DevComment="Used as a Mesh Network to dynamically disable/re-enable booths") ++GameplayTagList=(Tag="Pawn.Athena.CannotReviveTeammates",DevComment="If this Pawn is in a Team with other Players, it wont be allowed to revive it\'s teammates and the team can die immediatly if this Pawn is the last alive.") ++GameplayTagList=(Tag="Pawn.Athena.DoNotDisplayInKillFeed",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NonParticipant",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Bud",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Buttercake",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Creative.Guard",DevComment="Used for NPCs Spawned from Guard Spawners in Creative") ++GameplayTagList=(Tag="Pawn.Athena.NPC.CreativeSentry",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Decoy",DevComment="New Tag to specificy when a Pawn is a Decoy") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Deimos",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Dev.Dev1",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Dev.Dev2",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Dev.Dev3",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Dev.Dev4",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Dev.Dev5",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Duel.CanBeChallenged",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Duel.IsBeingChallenged",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Encounter.EngagedPlayers",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Date",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.DateAlpha",DevComment="Tag that Defines Pawn as Date Alpha") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Lonely",DevComment="Tag that Defines Pawn as Lonely AI") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.MangAlpha",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Tomato",DevComment="Tag that Defines Pawn as Tomato") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.TomatoAlpha",DevComment="Tag that Defines Pawn as Tomato Alpha") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Type01",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Type02",DevComment="Used for Loot Tables to Drop Medium Ammo") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Type03",DevComment="Used for Loot Tables to Drop Heavy Ammo") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Type04",DevComment="Used for Loot Tables to Drop Medium Ammo") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Type05",DevComment="Tag to tell AI to drop Rockets") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gasket.Wasabi",DevComment="Tag that Defines Pawn as Wasabi") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gibson",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gibson.Type01",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gibson.Type02",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gibson.Type03",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Gibson.Type04",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.IO",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.IO.LS",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Labrador",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Mang.Boss",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.MeleeEnemy",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Nightmare",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.SA",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Seven",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Attack",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Attack.Special",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.DBNO",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Death",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Death.Hacked",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Eject",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.FullAlert",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.FullAlertIdle",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Hacked",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Idle",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Interrogate",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.LostTarget",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.PreAttack",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Affirmative",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Amused",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Angry",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Annoyed",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Confused",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Congrats",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Despawn",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Disappointed",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Excited",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.FirstSpawn",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Greeting",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Happy",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Negative",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Respawn",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Quest.Sorry",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.ReturnToIdle",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Spawn",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Sound.Voice.Suspicious",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Tandem",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Umami",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Hardy",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Laurel",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Monster.Caretaker",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Predator",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Predator.Boss.GrandmaHabitat",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Predator.Grandma",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Predator.NugZ",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Predator.Robert",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Predator.SpicySake",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Prey",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Prey.Burt",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Prey.Nug",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Simple",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Simple.Avian.Crow",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Simple.Smackie",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.SpicySake",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.Wildlife.Variant.Loot",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.NPC.ZigZag.ScholarGhoul",DevComment="") ++GameplayTagList=(Tag="Pawn.Athena.Player",DevComment="If you were using Pawn.Player.Athena, you should be using this native tag.") ++GameplayTagList=(Tag="Pawn.HidePlayerPawn.Default",DevComment="The default gameplay tag used to hide the player pawn.") ++GameplayTagList=(Tag="Pawn.HidePlayerPawn.HidingProp",DevComment="HidePlayerPawn tag for Hiding Props") ++GameplayTagList=(Tag="Pawn.HidePlayerPawn.SneakySnowman",DevComment="") ++GameplayTagList=(Tag="Pawn.HidePlayerPawn.SynchronizedTeleport",DevComment="The gameplay tag used by the Synchronized Teleport Mutator to hide the player pawn.") ++GameplayTagList=(Tag="PCB.Challenges.Bundle1",DevComment="") ++GameplayTagList=(Tag="PCB.Partner.Neowiz",DevComment="") ++GameplayTagList=(Tag="PCB.Partner.Other",DevComment="") ++GameplayTagList=(Tag="Pegasus.DamageSource.Trap.FrostedTips",DevComment="Unique tag to identify DeathInfos caused by FrostedTips, for Pegasus cloud rendering") ++GameplayTagList=(Tag="Pegasus.NPC.WasabiAlpha",DevComment="Unique tag to identify eliminations by the Wasabi alpha NPC, for Pegasus cloud rendering") ++GameplayTagList=(Tag="Phoebe.AIState.Patrolling",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.BID_045_TacticalJungle",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.CID_005_Athena_Commando_M_Default",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.CID_117_Athena_Commando_M_TacticalJungle",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_BurgerFlipping",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_CowboyDance",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_GothDance",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_GuitarWalk",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_KungFuSalute",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_LlamaRider_Glitter",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_Mime",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_SneakingTraversal",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.EID_WindmillFloss",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.Emoji_RockOn",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.Glider_ID_007_TurtleShell",DevComment="") ++GameplayTagList=(Tag="phoebe.cosmetic.Trails_ID_004_BlueStreak",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ammo",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Arsenic.Claws",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Arsenic.Dash",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Arsenic.Jump",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Arsenic.Scream",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Arsenic.Slurper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.assault_rifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.AutoSniper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.AvacadoEaterHead",DevComment="") ++GameplayTagList=(Tag="phoebe.items.AwardThanks.Main",DevComment="") ++GameplayTagList=(Tag="phoebe.items.AwardThanks.Secondary",DevComment="") ++GameplayTagList=(Tag="phoebe.items.bolt_action_sniper_rifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.BoomBow",DevComment="") ++GameplayTagList=(Tag="phoebe.items.bow",DevComment="") ++GameplayTagList=(Tag="phoebe.items.bow.clusterbomb",DevComment="") ++GameplayTagList=(Tag="phoebe.items.bow.flame",DevComment="") ++GameplayTagList=(Tag="phoebe.items.bow.shockwave",DevComment="") ++GameplayTagList=(Tag="phoebe.items.bow.stinkbomb",DevComment="") ++GameplayTagList=(Tag="phoebe.items.BrawnySmasher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.BuildingTool",DevComment="") ++GameplayTagList=(Tag="phoebe.items.burst_assault_rifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.BurstSMG",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ChargeSMG",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ChillBroncoNPC",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ChromeShotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.CombatAssaultRifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.CombatPistol",DevComment="") ++GameplayTagList=(Tag="phoebe.items.CombatShotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.CombatSmg",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.Bandage",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.BattleFlopper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.BCfruit",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.EggplantDip",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.FlopperFish",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.FlopperShield",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.icecream",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.icecreamG",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.icecreamS",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.JellyFish",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.Meat",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.MechanicFlopper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.Medkit",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.PSlice",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.RPotion",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.Shield_Potion",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.SlurpFish",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.Small_Shield_Potion",DevComment="") ++GameplayTagList=(Tag="phoebe.items.consumable.SmallFryFish",DevComment="") ++GameplayTagList=(Tag="phoebe.items.crossbow",DevComment="") ++GameplayTagList=(Tag="phoebe.items.crossbowDualies",DevComment="") ++GameplayTagList=(Tag="phoebe.items.cshotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.DMR",DevComment="") ++GameplayTagList=(Tag="phoebe.items.DoubleBarrelShotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.DrumGun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.DrumShotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.DualPistols",DevComment="") ++GameplayTagList=(Tag="phoebe.items.FlintKnockPistol",DevComment="") ++GameplayTagList=(Tag="phoebe.items.gadget.key",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.GalileoBun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.generic.supported",DevComment="Generic tag to add to various items that are supported by bots") ++GameplayTagList=(Tag="phoebe.items.Grenade.BoogieBomb",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.C4",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.Dynamite",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.Grenade",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.GrenadeM",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.Ice",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.Impulse",DevComment="") ++GameplayTagList=(Tag="phoebe.items.grenade.kabob",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.Shockwave",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.SneakySnowman",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.Stink",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Grenade.StormFlip",DevComment="") ++GameplayTagList=(Tag="phoebe.items.grenade_launcher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.grenade_launcher.egg",DevComment="") ++GameplayTagList=(Tag="phoebe.items.grenade_launcher.junkgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.HandCannon",DevComment="") ++GameplayTagList=(Tag="phoebe.items.harpoon",DevComment="") ++GameplayTagList=(Tag="phoebe.items.heavy_assault_rifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.HeavyShotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.HeavySniper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.hightower.date",DevComment="") ++GameplayTagList=(Tag="phoebe.items.hightower.tomato",DevComment="") ++GameplayTagList=(Tag="phoebe.items.homing_launcher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.HuntingRifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.InfantryRifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ingredients",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ingredients.animalbones",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ingredients.mechanicalparts",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ingredients.toxicsac",DevComment="") ++GameplayTagList=(Tag="phoebe.items.key.vaultkeycard",DevComment="") ++GameplayTagList=(Tag="phoebe.items.LlamaRoaster",DevComment="") ++GameplayTagList=(Tag="phoebe.items.LMG",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Melee.ShockwaveMace",DevComment="") ++GameplayTagList=(Tag="phoebe.items.minigun",DevComment="") ++GameplayTagList=(Tag="Phoebe.items.Nevada.1",DevComment="") ++GameplayTagList=(Tag="phoebe.items.PassivePhase",DevComment="") ++GameplayTagList=(Tag="phoebe.items.PastaRipper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.pickaxe",DevComment="") ++GameplayTagList=(Tag="phoebe.items.pistol",DevComment="") ++GameplayTagList=(Tag="phoebe.items.pistol.marksman",DevComment="") ++GameplayTagList=(Tag="phoebe.items.PistolRapidFireSMG",DevComment="") ++GameplayTagList=(Tag="phoebe.items.PowerupMinigun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.PowerupRifle",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ProximityGrenadeLauncher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.pump_shotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.PumpkinLauncher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.QuadLauncher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ReactorGrade",DevComment="") ++GameplayTagList=(Tag="phoebe.items.repeater",DevComment="") ++GameplayTagList=(Tag="phoebe.items.resource",DevComment="") ++GameplayTagList=(Tag="phoebe.items.Revolver",DevComment="") ++GameplayTagList=(Tag="phoebe.items.rocket_launcher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.scopedAR",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ScopedPistol",DevComment="") ++GameplayTagList=(Tag="phoebe.items.SemiAutoSniper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.shockwave_launcher",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ShotgunBreach",DevComment="") ++GameplayTagList=(Tag="phoebe.items.SixShooterPistol",DevComment="") ++GameplayTagList=(Tag="phoebe.items.skyfire.alpaca",DevComment="") ++GameplayTagList=(Tag="phoebe.items.smg",DevComment="") ++GameplayTagList=(Tag="phoebe.items.StormScoutSniper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.suppressedAR",DevComment="") ++GameplayTagList=(Tag="phoebe.items.SuppressedPistol",DevComment="") ++GameplayTagList=(Tag="phoebe.items.SuppressedSMG",DevComment="") ++GameplayTagList=(Tag="phoebe.items.SuppressedSniper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.tactical_shotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.items.TacticalAR",DevComment="") ++GameplayTagList=(Tag="phoebe.items.TacticalSMG",DevComment="") ++GameplayTagList=(Tag="phoebe.items.ThermalSniper",DevComment="") ++GameplayTagList=(Tag="phoebe.items.waffletruck.01",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.02",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.03",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.04",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.05",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.06",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.07",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.08",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.09",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.10",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.11",DevComment="") ++GameplayTagList=(Tag="phoebe.items.WaffleTruck.12",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.melee",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.AwardThanks.Main",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.AwardThanks.Secondary",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.bow",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.crossbow",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.GalileoBun",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.grenade_launcher",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.grenade_launcher.junkgun",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.harpoon",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.hightower.date",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.hightower.tomato",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.LlamaRoaster",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.lmg",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.minigun",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.pistol",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.PowerupMinigun",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.ReactorGrade",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.rifle",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.rocket_launcher",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.shotgun",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.smg",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.ranged.sniper",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.throwable.grenade",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.throwable_nomiss",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.charge",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.chrome",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.directhit",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.explosive",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.fullauto",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.guided",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.homing",DevComment="") ++GameplayTagList=(Tag="Phoebe.weapon.trait.ignored",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.infiniteammo",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.scoped",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.semiauto",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.singleshot",DevComment="") ++GameplayTagList=(Tag="phoebe.weapon.trait.suppressed",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.AllowsImminentActivation",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Buoyancy.Sinker.Splasher",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Ball",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Ball.Small",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Barrel",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.BeachBall",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Boat",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Boat.Small",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Box",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Box.Chest",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Box.Small",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Box.Tall",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Car",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Log",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.PhysicsTreeLog",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.RebootCard",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Sinker",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.BuoyancyData.Sinker.Splasher",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.Average",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.Boulder",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.Large",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.Medium",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.None",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.Small",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.ImpactDamageData.Tree",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.BGA.HeldObject",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.BGA.HeldObject.PropaneTank",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.BGA.HeldObject.PropaneTank.NoFriction",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Cloth",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Generic",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.GraniteBoulder",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Metal",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Plastic",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Rubber",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Rubber.GenericBall",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Rubber.GiantBeachBall",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Stone",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Stone.Boulder",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Wood",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Wood.Dense",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Wood.Dense.PhysicsTree",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.PhysData.Wood.Foliage",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.BGA.HeldObject",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.BGA.HeldObject.PropaneTank",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Boulder.Desert",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Boulder.Granite",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Box",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Domino",DevComment="long, thin objects") ++GameplayTagList=(Tag="PhysicsObject.Preset.RebootCard",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller.Ball",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller.Ball.Boulder",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller.Ball.Generic",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller.Ball.GiantBeachBall",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller.Ball.GiantBeachBall.Creative",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Roller.Ball.Small",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Test",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Test.Wall",DevComment="") ++GameplayTagList=(Tag="PhysicsObject.Preset.Tree",DevComment="") ++GameplayTagList=(Tag="Pickup.Chest.Icebox",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.AmmoBox",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.BotElimination",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.Burt",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.Chest",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.Drone",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.FishingRodContainer",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.Grandma",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.ItemSpawner",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.LootDrop",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.NPCElimination",DevComment="") ++GameplayTagList=(Tag="pickup.source.nug",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.PlayerElimination",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.Safe",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="Pickup.source.TieredAmmo",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.TieredChest",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.TossedByPlayer",DevComment="") ++GameplayTagList=(Tag="Pickup.Source.Unset",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.AI",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.Container",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.Destruction",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.Fishing",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.FloorLoot",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.Other",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.Player",DevComment="") ++GameplayTagList=(Tag="Pickup.Type.Tossed",DevComment="") ++GameplayTagList=(Tag="PlacementActor.LoneWolf",DevComment="") ++GameplayTagList=(Tag="PlacementActor.PlayerSpawn.ForceHoldingArea",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Area.01",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Area.02",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Area.03",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Area.04",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Area.05",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Area.06",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.BossRoomEntered",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.InventorySpawn",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Key.Door01",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Key.Door02",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Key.Door03",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Key.Door04",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Key.Door05",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.Key.Door06",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.MinibossSpawn",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.MinionSpawn",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.PlayerSpawn",DevComment="") ++GameplayTagList=(Tag="PlacementActor.Starlight.TalismanSpawn",DevComment="Used to spawn talismans") ++GameplayTagList=(Tag="PlacementActor.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="PlayerVerb.HurdleFinished",DevComment="") ++GameplayTagList=(Tag="PlayerVerb.Test",DevComment="") ++GameplayTagList=(Tag="Playlist.GoofBall",DevComment="") ++GameplayTagList=(Tag="Playlist.Music.NPC.Disable",DevComment="") ++GameplayTagList=(Tag="Playlist.Music.NPC.Disable.Cosmos",DevComment="") ++GameplayTagList=(Tag="Playlist.Music.NPC.DisableAll",DevComment="") ++GameplayTagList=(Tag="Playlist.SpecialEventNoKilling",DevComment="") ++GameplayTagList=(Tag="Playspace.Papaya.Hub",DevComment="Identifying tag for the Playspace controlling the hub for Papaya") ++GameplayTagList=(Tag="Plugin.Playlist.Arena",DevComment="") ++GameplayTagList=(Tag="Plugin.Playlist.Astral",DevComment="") ++GameplayTagList=(Tag="Plugin.Playlist.BattleLab",DevComment="") ++GameplayTagList=(Tag="Plugin.Playlist.BigBattle",DevComment="tag to use for bigbattle") ++GameplayTagList=(Tag="Plugin.Playlist.LSCup",DevComment="") ++GameplayTagList=(Tag="Plugin.Playlist.TempFortUI",DevComment="A tag to help test Fortnite UI plugins for new game features") ++GameplayTagList=(Tag="Plugin.Playlist.Tournament",DevComment="") ++GameplayTagList=(Tag="Plugin.Playlist.ZFCup",DevComment="") ++GameplayTagList=(Tag="PostGameScreen.Type.Elimination",DevComment="") ++GameplayTagList=(Tag="PostGameScreen.Type.MatchXP",DevComment="") ++GameplayTagList=(Tag="PostGameScreen.Type.ReadyUp",DevComment="") ++GameplayTagList=(Tag="PostGameScreen.Type.Victory",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeEoSWeeklys.01",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeEoSWeeklys.02",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeEoSWeeklys.03",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeEoSWeeklys.04",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeEvent.Mash.StreakReached",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.Cavalry",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.CrimsonPeak",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.GelatinGummi",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.InfernoV2",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.PrimalFalcon",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.PunkKoi",DevComment="") ++GameplayTagList=(Tag="Quest.ChallengeStyle.ShinnyGoldCreature",DevComment="") ++GameplayTagList=(Tag="Quest.Daily",DevComment="Tag used in StW Starter Bundles") ++GameplayTagList=(Tag="Quest.HideWhenClaimed",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.0",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.1",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.2",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.3",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.4",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.5",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.6",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Distance.Grid.7",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.HideInAllChallengesMap",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.LTM.Wax",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.Mission.Primary.Outpost.Theater",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsFriendly.ChugSplash",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsFriendly.CozyCampfire",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsSquadmate",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsSquadmate.ChugSplash",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsSquadmate.CozyCampfire",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsTeammate.Chugsplash",DevComment="") ++GameplayTagList=(Tag="Quest.Metadata.TargetIsTeammate.CozyCampfire",DevComment="") ++GameplayTagList=(Tag="Quest.QuestChain",DevComment="") ++GameplayTagList=(Tag="Quest.QuestChain.NPC",DevComment="") ++GameplayTagList=(Tag="Quest.QuestChain.Special",DevComment="") ++GameplayTagList=(Tag="Quest.QuestChain.Weekly",DevComment="") ++GameplayTagList=(Tag="Quest.SpecialEventChallenge",DevComment="") ++GameplayTagList=(Tag="Quests.CreativeRoyale",DevComment="Tag used to enable quests in Creative Royale. Required on Playlist and quest asset.") ++GameplayTagList=(Tag="Radish.Gameplay.Intro.AllowEmotes",DevComment="") ++GameplayTagList=(Tag="Rarity.Mythic",DevComment="") ++GameplayTagList=(Tag="Rarity.Transcendent",DevComment="") ++GameplayTagList=(Tag="Resource.RebootPoints",DevComment="") ++GameplayTagList=(Tag="Resource.XP",DevComment="") ++GameplayTagList=(Tag="Riding.Creature.BlockRiding",DevComment="") ++GameplayTagList=(Tag="Riding.Player.BlockRiding",DevComment="") ++GameplayTagList=(Tag="SaltyTowers",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Mission.Special",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Mission.Weekly",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Season.5",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Season.6",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Season.7",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Season.8",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Season.9",DevComment="") ++GameplayTagList=(Tag="Schedule.Source.Season.10",DevComment="") ++GameplayTagList=(Tag="schedule.source.season.11",DevComment="") ++GameplayTagList=(Tag="SetByCaller.Damage",DevComment="") ++GameplayTagList=(Tag="SetByCaller.DamageVulnerability",DevComment="") ++GameplayTagList=(Tag="SetByCaller.DBNO.Bleed",DevComment="") ++GameplayTagList=(Tag="setbycaller.healing.keep",DevComment="") ++GameplayTagList=(Tag="SetByCaller.Magnitude",DevComment="") ++GameplayTagList=(Tag="SetByCaller.PhysicsObject.Impact.Damage",DevComment="The set by caller value used when a physics object impacts a damagable object. Combat DamageType") ++GameplayTagList=(Tag="SetByCaller.PhysicsObject.Impact.Damage.Environmental",DevComment="The set by caller value used when a physics object impacts a damagable object. Evnironment DamageType") ++GameplayTagList=(Tag="Setbycaller.shield.Keep",DevComment="") ++GameplayTagList=(Tag="SetByCaller.StaminaCost",DevComment="Stamina Cost Multiplier for Abilities") ++GameplayTagList=(Tag="Settings.Input.Category.Building",DevComment="Settings category for input keybindings related to building.") ++GameplayTagList=(Tag="Settings.Input.Category.Combat",DevComment="Settings category for input keybindings related to combat.") ++GameplayTagList=(Tag="Settings.Input.Category.Communication",DevComment="Settings category for input keybindings related to communication.") ++GameplayTagList=(Tag="Settings.Input.Category.Creative",DevComment="Settings category for input keybindings related to creative.") ++GameplayTagList=(Tag="Settings.Input.Category.Emote",DevComment="Settings category for input keybindings related to emotes.") ++GameplayTagList=(Tag="Settings.Input.Category.Misc",DevComment="Settings category for miscellaneous input keybindings.") ++GameplayTagList=(Tag="Settings.Input.Category.Movement",DevComment="Settings category for input keybindings related to movement.") ++GameplayTagList=(Tag="Settings.Input.Category.Other",DevComment="Settings category for input keybindings that come from a game feature plugin specifying the InputData Category Other.") ++GameplayTagList=(Tag="Settings.Input.Category.Replay",DevComment="Settings category for input keybindings related to replays.") ++GameplayTagList=(Tag="Settings.Input.Category.Vehicle",DevComment="Settings category for input keybindings related to vehicles.") ++GameplayTagList=(Tag="Silkwalker.AppliesBad",DevComment="") ++GameplayTagList=(Tag="Silkwalker.Bad",DevComment="") ++GameplayTagList=(Tag="SkydomeDevice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Ability.Leap.Start",DevComment="When leap ability starts. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Ability.Leap.Stop",DevComment="When leap ends as actor lands/hits on the terrain. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Ability.Leap.WhileInAir",DevComment="When leap ability happens. This is called while inair. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Ability.Object.Hit",DevComment="When object hits target whether it is pawn or terrain. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Ability.Object.Raise",DevComment="When Object is lift from the terrain. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Ability.Object.Throw",DevComment="When object is tossed after being lifted. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Ability.Object.WhileInAir",DevComment="While object is in the air after being tossed or lifted. Called from NPCLibrary Gameplaycue.") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Breath.Idle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Breath.Run",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Breath.Walk",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Death",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Dematerialize",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Melee",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Melee.PreTell",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Pain",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Spawn",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Spawn.Scare.Close",DevComment="For Prop spawner jump scares.") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Spawn.Scare.Distance",DevComment="For Prop spawner scare") ++GameplayTagList=(Tag="SoundLibrary.Deimos.Throw",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Jump.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Land.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.LandHard.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Asphalt",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Carpet",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Dirt",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.MetalSolid",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Sand",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.Snow",DevComment="") ++GameplayTagList=(Tag="Soundlibrary.Foley.Plant.Stone",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.WoodSolid",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Plant.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.ReloadMovement",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Scuff.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Scuff.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Scuff.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Scuff.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Scuff.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Scuff.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Carpet",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Dirt",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Metal",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Sand",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Snow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Stone",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.Wood",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideLong.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Carpet",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Dirt",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Metal",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Sand",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Snow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Stone",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.Wood",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SlideShort.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Carpet",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Dirt",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Ice",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Metal",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Sand",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Snow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Stone",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.Wood",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Stop.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.SwapWeapon",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn.Grass",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn.MetalAuto",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn.MetalHollow",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn.Puddle",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn.WoodFloor",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Foley.Turn.WoodThin",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Glider.Elevate.Wings",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.ID.ClothingType.Generic",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.ID.HandType.Bare",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.ID.StepType.BootsHeeled",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.Action.LayEgg",DevComment="Lay egg foley sound") ++GameplayTagList=(Tag="SoundLibrary.Irwin.Action.Morph_In",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.Action.Morph_Out",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.Action.Swim.Land",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.Eating",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.VO.Death",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.VO.Jump_Start",DevComment="") ++GameplayTagList=(Tag="SoundLibrary.Irwin.VO.Tamed",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.B",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.B.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.G",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.G.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.O",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.O.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.P",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.P.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.R",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.R.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.Y",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Ashton.Object.Y.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.DeimosSurvival.Objective",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Infiltration",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Infiltration.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Keep.Inventory",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Keep.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Sapling",DevComment="") ++GameplayTagList=(Tag="SpecialActor.SCMachine.Default",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Seize",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Seize.Inventory",DevComment="") ++GameplayTagList=(Tag="SpecialActor.Seize.Pickup",DevComment="") ++GameplayTagList=(Tag="SpecialActor.ShakingTurtle",DevComment="") ++GameplayTagList=(Tag="SpecialActor.SupplyDrop.Default",DevComment="Default supply drop spawned in most matches") ++GameplayTagList=(Tag="SpecialEvent.2018.Holiday.Krampus",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.2018.Holiday.LoneKrampus",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.2018.Roadtrip.Heroes.Ninja.Rogue",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.DriveToPOI",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.DriveToPOI.Noble",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HiredNPCEliminated",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HuntingPlayer.Failed",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.huntingplayer.isHunter",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HuntingPlayer.Poached",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HuntingPlayer.Protector.Failed",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HuntingPlayer.Protector.Success",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.huntingPlayer.Success",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HuntingPlayer.Target.Failed",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.HuntingPlayer.Target.Success",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.SearchChests",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.SearchChests.BeforeDamage",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.Tutorial.01",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.Tutorial.02",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.Tutorial.03",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.Tutorial.02.MoveForward",DevComment="") ++GameplayTagList=(Tag="SpecialEvent.Vendetta.ActiveBountyPuck",DevComment="Identifier for the Vendetta Special Event UI for the local player\'s active bounty puck.") ++GameplayTagList=(Tag="Spotlights.ForceOff",DevComment="Used as a Mesh Network Command to force spotlights off") ++GameplayTagList=(Tag="Spotlights.ForceOn",DevComment="Used as a Mesh Network command to force spotlights on") ++GameplayTagList=(Tag="SpyTech.Perk.Item.AR",DevComment="") ++GameplayTagList=(Tag="Stamina.Container",DevComment="") ++GameplayTagList=(Tag="Stamina.Hawaii.AbilityActive",DevComment="") ++GameplayTagList=(Tag="Stamina.Hawaii.BBActive",DevComment="") ++GameplayTagList=(Tag="Starlight",DevComment="") ++GameplayTagList=(Tag="Starlight.Actor.Important",DevComment="Important Non-destructible actors") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.01",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.02",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.03",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.04",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.05",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.06",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Floor.Final",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Gameplay",DevComment="") ++GameplayTagList=(Tag="Starlight.Foundation.Graveyard",DevComment="") ++GameplayTagList=(Tag="Starlight.Rewards.Badge.DungeonBoss",DevComment="Defeated the boss") ++GameplayTagList=(Tag="Starlight.Rewards.Badge.Keymaster",DevComment="") ++GameplayTagList=(Tag="Starlight.Rewards.Badge.LowerFloors",DevComment="Final Tier Floor Rewards") ++GameplayTagList=(Tag="Starlight.Rewards.Badge.MiddleFloors",DevComment="Tier 2 Floor Rewards") ++GameplayTagList=(Tag="Starlight.Rewards.Badge.Speedrun",DevComment="") ++GameplayTagList=(Tag="Starlight.Rewards.Badge.UpperFloors",DevComment="First Tier Floor Rewards") ++GameplayTagList=(Tag="Starlight.Trap.CeilingDrop.HasHit",DevComment="") ++GameplayTagList=(Tag="Starlight.Trap.Drain.HasHit",DevComment="") ++GameplayTagList=(Tag="Starlight.Trap.SecretDoor.HasHit",DevComment="") ++GameplayTagList=(Tag="Starlight.Trap.Snare.HasHit",DevComment="") ++GameplayTagList=(Tag="Starlight.Trap.Teleport.HasHit",DevComment="") ++GameplayTagList=(Tag="Starlight.Trap.WallArrows.HasHit",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Endless",DevComment="Endless Mode, developed for winter (2018) event") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Anomaly",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.AntiShield",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.Build",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.DangerClose",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.Explode",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.Foggy",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.HeadGames",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.Surprise",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.SwarmOfHusks",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.Time",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Challenge.Traps",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.EndlessFirstOpen",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Eruption",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.FirstOpen",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Generators",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Geyser",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.MidlandHusknado",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.MStorm",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Offspring",DevComment="") ++GameplayTagList=(Tag="stat.mission.ironcity.Pods",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.RiotCity",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Saucer",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.SSDFirstOpen",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.TeleBomb",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.Tiki",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.TpRifts",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.IronCity.WaitedForAssistance",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Landmark.Hexsylvania.Day1",DevComment="") ++GameplayTagList=(Tag="stat.mission.landmark.hexsylvania.day18257",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Landmark.Hexsylvania.HotelHuskEscape",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Landmark.MerryHellMaze",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Landmark.Meteor.S10",DevComment="Adding a tag to identify new S10 landmark") ++GameplayTagList=(Tag="Stat.Mission.Primary.Demeter",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.EliminateAndCollect",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Landmark.BuildasaurusRex",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Landmark.FinalFrontier",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Landmark.FinalRehearsal",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Landmark.PirateCove",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Landmark.ProtectThePresents",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Landmark.WalkThePlank",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.LostInTransit",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.BuildSpeed",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.GroupSpeed",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.HarvestUp",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.HealthRegenNearVehicle",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.MovementSpeed",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.RepairVehicle",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.StormShieldRadius",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Mayday.VotingReward.UpgradeVehicle",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Outpost.Endless",DevComment="Marks the final outpost mission which spawns endless attack waves.") ++GameplayTagList=(Tag="Stat.Mission.Primary.Resupply",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Starlight",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Starlight.D1",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.Starlight.D5Science",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.SurvivorExtraction",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Primary.TestTheLimits",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.IceBreaker",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.LapChallenge",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.BridgeOut",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.FeelinBlu",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.Infestation",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.MinorDelay",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.Overheat",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.PitStop",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.RecordDash",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.SignalBoost",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.Mayday.SubmissionEncounter",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.PirateBooty",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Secondary.PlayerHoldingArea",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Starlight",DevComment="") ++GameplayTagList=(Tag="Stat.Mission.Starlight.Encounter",DevComment="") ++GameplayTagList=(Tag="Stat.MissionAlert.BetaStorm",DevComment="") ++GameplayTagList=(Tag="Stat.MissionAlert.Dudebro",DevComment="Tag that mission alert can pass down to its hex.") ++GameplayTagList=(Tag="Stat.Theater.Endless",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Endless.NonChallenge",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Mayday",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Phoenix.Adventure",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Phoenix.Fortnitemares",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Phoenix.NewBeginnings",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Phoenix.RoadTrip",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Phoenix.Winterfest",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.PvE04.Difficulty06",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.PvE04.Difficulty07",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.PvE04.Difficulty08",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.PvE04.Difficulty09",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.PvE04.Difficulty10",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.PvE04.Difficulty11",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Starlight",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.Tropical",DevComment="") ++GameplayTagList=(Tag="Stat.Theater.WinterZone",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.AutumnFoothills",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.Desert",DevComment="Arid biome generic desert zone type") ++GameplayTagList=(Tag="Stat.ZoneTheme.Desert.Bunkers",DevComment="Arid biome generic desert zone with bunkers") ++GameplayTagList=(Tag="Stat.ZoneTheme.GhostTown",DevComment="Arid biome Ghost Town zone type") ++GameplayTagList=(Tag="Stat.ZoneTheme.HauntedHotel",DevComment="Used for S11") ++GameplayTagList=(Tag="Stat.ZoneTheme.PirateCove",DevComment="The Pirate Cove") ++GameplayTagList=(Tag="Stat.ZoneTheme.Route99",DevComment="Tag for the Arid zone biome, Route 99 setting") ++GameplayTagList=(Tag="Stat.ZoneTheme.Route99.Bunkers",DevComment="Arid sub-biome Route 99 with Bunkers") ++GameplayTagList=(Tag="Stat.ZoneTheme.SlasherCamp",DevComment="Used for Fortnitemares") ++GameplayTagList=(Tag="Stat.ZoneTheme.Swamp",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.TheAutumnCity",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.TheAutumnForest",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.TheAutumnIndustrialPark",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.TheAutumnSuburbs",DevComment="") ++GameplayTagList=(Tag="Stat.ZoneTheme.TheLakeside",DevComment="") ++GameplayTagList=(Tag="StatManager.AthenaKills",DevComment="") ++GameplayTagList=(Tag="StatManager.DamageDealt",DevComment="") ++GameplayTagList=(Tag="StatManager.DamageDealt_Players_BySquad",DevComment="") ++GameplayTagList=(Tag="StatManager.DamageReceived",DevComment="") ++GameplayTagList=(Tag="StatManager.LTM.Argon.RacePlacement",DevComment="") ++GameplayTagList=(Tag="StatManager.MetalAcquired",DevComment="") ++GameplayTagList=(Tag="StatManager.MetalExpended",DevComment="") ++GameplayTagList=(Tag="StatManager.OpenChests",DevComment="") ++GameplayTagList=(Tag="StatManager.StoneAcquired",DevComment="") ++GameplayTagList=(Tag="StatManager.StoneExpended",DevComment="") ++GameplayTagList=(Tag="StatManager.TotalRoyalRoyales",DevComment="") ++GameplayTagList=(Tag="StatManager.TotalVictoryCrowns",DevComment="") ++GameplayTagList=(Tag="StatManager.WeakPointHit",DevComment="") ++GameplayTagList=(Tag="StatManager.WeakPointSpawned",DevComment="") ++GameplayTagList=(Tag="StatManager.WoodAcquired",DevComment="") ++GameplayTagList=(Tag="StatManager.WoodExpended",DevComment="") ++GameplayTagList=(Tag="Status.DanceStunImmunity",DevComment="") ++GameplayTagList=(Tag="Status.LowGravity.LowGravZone",DevComment="") ++GameplayTagList=(Tag="Status.LowGravity.ShipCannon",DevComment="") ++GameplayTagList=(Tag="supplies.ammo.arrows",DevComment="") ++GameplayTagList=(Tag="supplies.ammo.fringeplank",DevComment="") ++GameplayTagList=(Tag="Supplies.ammo.hook",DevComment="") ++GameplayTagList=(Tag="SweatyToMisty",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.GiftItems",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.Gifts",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.GiftTypeDefinitions",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.VendingMachine.GiftItems",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.VendingMachine.Gifts",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.VendingMachine.GiftTypeDefinitions",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.VendingMachine.StationSales",DevComment="") ++GameplayTagList=(Tag="Tandem.Data.VendingMachine.StationServices",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Challenge",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Fund4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Fund5",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Fund6",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Hire",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Aircraft.Helicopter",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Automobile",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Campfire",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.NPC.Bigfoot",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.NPC.Shark",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.NPC.Tandem",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Safe",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.TreasureChest",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.TreasureChest.Rare",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Watercraft.Boat",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Wildlife.All",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Wildlife.Burt",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Wildlife.Grandma",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Wildlife.Nug",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Wildlife.Robert",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Intel.Wildlife.Smackie",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.OutfitDisguise",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.PatchUp",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.PlayerBounty",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Quest1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Quest2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Quest3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.RandomRarity",DevComment="Conversation service used by the random rarity vending machine.") ++GameplayTagList=(Tag="Tandem.Service.Repair",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.RollTheDice",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Sell",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Sell1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Sell2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Sell3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Sell4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Sell5",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory1.Sell1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory1.Sell2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory1.Sell3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory1.Sell4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory2.Sell1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory2.Sell2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory2.Sell3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory2.Sell4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory3.Sell1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory3.Sell2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory3.Sell3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory3.Sell4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory4.Sell1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory4.Sell2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory4.Sell3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory4.Sell4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory5.Sell1",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory5.Sell2",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory5.Sell3",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SellSubCategory5.Sell4",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Spawn.Vehicle",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.SupplyDrop",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.TipBusDriverLarge",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.TipBusDriverSmall",DevComment="") ++GameplayTagList=(Tag="Tandem.Service.Upgrade",DevComment="") ++GameplayTagList=(Tag="Targeting.ApplicationTag",DevComment="Tag for use as the Application Tag in Targeting of Gameplay Effect Containers.") ++GameplayTagList=(Tag="Targeting.ApplicationTag.0",DevComment="") ++GameplayTagList=(Tag="Targeting.ApplicationTag.1",DevComment="") ++GameplayTagList=(Tag="Targeting.ApplicationTag.2",DevComment="") ++GameplayTagList=(Tag="Targeting.ApplicationTag.3",DevComment="") ++GameplayTagList=(Tag="Targeting.ApplicationTag.4",DevComment="") ++GameplayTagList=(Tag="Targeting.Ignore.Melee",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.FirstRoll",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Airstrike",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.AR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.BandageBazooka",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.BoltSniper",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.BurstAR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.BurstSMG",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ChugSplash",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ChugSplash+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Clinger",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.CrashPad",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.CrashPad+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.CreepinCardboard",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.CreepinCardboard+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Decoy",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Decoy+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.DefaultPistol",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.DefaultPistol.Suppressed",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Disquise",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.DrumShotgun",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.DualPistols",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Dynamite",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Grappler",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HandCannon",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HandCannon+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HeavyAR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HeavyShotgun",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HeavyShotgun+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HeavySniper",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.HidableBarrel",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Impulse",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.JetPack",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.JetPack+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.JetpackFuelAdjust",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.MidasDrumGun",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Minigun",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.PistolHeadshot",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.PistolMagazine",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ProximityMine",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ProximityMine+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.PumpShotgun",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ScopedAR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ScopedRevolver",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.SemiSniper",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.ShieldBubble",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Shockwave+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.SmokeGrenade",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.StinkBomb",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.SuppressedAR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.SuppressedPistol",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.SuppressedSniper",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.SupressedSMG",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.TacSMG",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.TacticalAR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.TacticalShotgun",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.TimedExplosive",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.TNTinaBow",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Item.Umbrella",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.BurstAR+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.HeavyAR+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.HeavySniper+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.Minigun+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.ScopedAR+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.ScopedRevolver+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.SemiSniperVR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.SuppressedSMG+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.TacSMGVR",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.TacticalAR+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.TrustyDualPistols",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.TrustyHeadhunter",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.ItemUpgrade.TrustyMagazine",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.AdrenalineRush",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.AgileAim",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ComeAtMeBro",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.CopyCat",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.Crashpad+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.CrouchSpeed",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DecreasedRechargeRate",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DefaultPistol.Suppressed",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DmgWhileInAir",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DmgWhileInAir.GE",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DmgWhileInAir.GERemove",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DoubleDamageVehicles",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.DoubleJump",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.Dynamite",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.Dynamite+",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.EmoteHeal",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.FastCharge",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.FasterGlideSpeed",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.GlassCannon",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.GliderRedeploy",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.HastyHarvest",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.HealOnElimination",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.HealWhenDamaged",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.HipFireAccuracy",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.Informant",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.LowGrav",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.LowHealthDamageMult",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.MaterialsMaster",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.MetalForShields",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.MoveFasterWithIntel",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.MoveSpeedSmoke",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.OutOfCombatRegen",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.PartingGift",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.PickaxeRunFaster",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ReduceCameraShake",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ReduceDamageTaken",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.RegainHealthOnDmg",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ReloadSpeed",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.Resupply",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SecretPassage",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SharpenedStick",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SharpShooter",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ShieldsOverTime",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ShieldSteal",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.ShieldSynergy",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SpawnWithAmmo",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SpawnWithShields",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SwimFaster",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.SwimmingHealthRegen",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.TracerRounds",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.TrustyDualWield",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.TrustyHeadhunter",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.TrustyMagazine",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.UpgradeWeapon",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.VehicleIncreasedHealth",DevComment="") ++GameplayTagList=(Tag="Tech.Spy.Passive.VictoryDance",DevComment="") ++GameplayTagList=(Tag="Tempest.Tornado.IgnoreForTargeting",DevComment="A gameplay tag applied to actors that would be targeted by tornados, but should be explicitly ignored.") ++GameplayTagList=(Tag="TestCue",DevComment="") ++GameplayTagList=(Tag="tires",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage01",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage02",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage03",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage04",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage05",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage06",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage07",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage08",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage09",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage10",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage11",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage12",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage13",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage14",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage15",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage16",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage17",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage18",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage19",DevComment="") ++GameplayTagList=(Tag="Token.Milestone.Completed.Stage20",DevComment="") ++GameplayTagList=(Tag="Token.NPC.BabaYaga",DevComment="") ++GameplayTagList=(Tag="Token.NPC.BigMouth",DevComment="") ++GameplayTagList=(Tag="Token.NPC.BistroAstronaut",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Brat",DevComment="") ++GameplayTagList=(Tag="Token.NPC.CerealBox",DevComment="") ++GameplayTagList=(Tag="Token.NPC.DarkJonesy",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Dire",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Division",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Dusk",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Ember",DevComment="") ++GameplayTagList=(Tag="Token.NPC.GhostHunter",DevComment="") ++GameplayTagList=(Tag="Token.NPC.GrimFable",DevComment="") ++GameplayTagList=(Tag="Token.NPC.HeadbandK",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Kitbash",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Madcap",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Nitehare",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Penny",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Pitstop",DevComment="") ++GameplayTagList=(Tag="Token.NPC.PunkKoi",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Ragsy",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Raven",DevComment="") ++GameplayTagList=(Tag="Token.NPC.RustLord",DevComment="") ++GameplayTagList=(Tag="Token.NPC.ScubaJonesy",DevComment="") ++GameplayTagList=(Tag="Token.NPC.ShadowOps",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Sledgehammer",DevComment="") ++GameplayTagList=(Tag="Token.NPC.SpaceChimp",DevComment="") ++GameplayTagList=(Tag="Token.NPC.SunrisePalace",DevComment="") ++GameplayTagList=(Tag="Token.NPC.TeriyakiFishToon",DevComment="") ++GameplayTagList=(Tag="Token.NPC.Wrath",DevComment="") ++GameplayTagList=(Tag="Token.NPC.York",DevComment="") ++GameplayTagList=(Tag="Token.Rarity.Common",DevComment="") ++GameplayTagList=(Tag="Token.Rarity.Epic",DevComment="") ++GameplayTagList=(Tag="Token.Rarity.Legendary",DevComment="") ++GameplayTagList=(Tag="Token.Rarity.Rare",DevComment="") ++GameplayTagList=(Tag="Token.Rarity.Uncommon",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.01",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.02",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.03",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.04",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.05",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.06",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Ball.07",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W01",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W02",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W03",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W04",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W05",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W06",DevComment="") ++GameplayTagList=(Tag="Token.Stamina.Completed.W07",DevComment="") ++GameplayTagList=(Tag="Token.ThisWeekChallenge.Completed.W2",DevComment="") ++GameplayTagList=(Tag="Token.ThisWeekChallenge.Completed.W3",DevComment="") ++GameplayTagList=(Tag="Token.WeeklyChallenge.Completed",DevComment="") ++GameplayTagList=(Tag="Tooltip.Token.Ability.TeamLeadRequirements",DevComment="") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.Armor",DevComment="") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.Armor.Description",DevComment="") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.Armor.DisplayName",DevComment="") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.Armor.Value",DevComment="") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.BackpackSize",DevComment="Backpack Size") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.HarvestingTool",DevComment="Harvesting Tool Level") ++GameplayTagList=(Tag="Tooltip.Token.Attribute.StormShieldStorage",DevComment="Storm Shield Storage Size") ++GameplayTagList=(Tag="TrackingGroup.Athena.Item.FireworksMortar",DevComment="") ++GameplayTagList=(Tag="Trait.AllowRidingOnActor",DevComment="Allows certain activities like emoting while on moving bases") ++GameplayTagList=(Tag="Trait.Arsenic.Power.Type.Scream",DevComment="") ++GameplayTagList=(Tag="Trait.Arsenic.Power.Type.Secondary",DevComment="") ++GameplayTagList=(Tag="Trait.Backpack",DevComment="") ++GameplayTagList=(Tag="Trait.Boss",DevComment="") ++GameplayTagList=(Tag="Trait.Candy",DevComment="") ++GameplayTagList=(Tag="Trait.DamageImmune",DevComment="") ++GameplayTagList=(Tag="Trait.Environmental",DevComment="") ++GameplayTagList=(Tag="Trait.Fish.Big",DevComment="") ++GameplayTagList=(Tag="Trait.Fish.Small",DevComment="") ++GameplayTagList=(Tag="Trait.Foraged",DevComment="") ++GameplayTagList=(Tag="Trait.Fruit",DevComment="") ++GameplayTagList=(Tag="Trait.Fungus",DevComment="") ++GameplayTagList=(Tag="Trait.Grappler",DevComment="") ++GameplayTagList=(Tag="Trait.IgnorePull",DevComment="") ++GameplayTagList=(Tag="Trait.Immune.ShockwaveHammer.Launch.AutoTargetEnemy",DevComment="") ++GameplayTagList=(Tag="Trait.ImmuneToPhysicsTree",DevComment="This object is immune to damage from an object with the physics tree preset") ++GameplayTagList=(Tag="Trait.Impulse.Native",DevComment="When a GE Impluse Execution is executed and this tag is present, the native code will apply the impulse to the target.") ++GameplayTagList=(Tag="Trait.LowGrav",DevComment="") ++GameplayTagList=(Tag="Trait.Meat",DevComment="") ++GameplayTagList=(Tag="Trait.Papaya",DevComment="") ++GameplayTagList=(Tag="Trait.Power.Type.Jump",DevComment="") ++GameplayTagList=(Tag="Trait.Power.Type.Primary",DevComment="") ++GameplayTagList=(Tag="Trait.Power.Type.Secondary",DevComment="") ++GameplayTagList=(Tag="Trait.Power.Type.Utility",DevComment="") ++GameplayTagList=(Tag="Trait.QuestActor.Vehicle",DevComment="") ++GameplayTagList=(Tag="Trait.QuestVaultedItem",DevComment="") ++GameplayTagList=(Tag="Trait.Reinforced",DevComment="") ++GameplayTagList=(Tag="Trait.Restoration.Overshield",DevComment="") ++GameplayTagList=(Tag="Trait.Scoped",DevComment="") ++GameplayTagList=(Tag="Trait.Slurp",DevComment="") ++GameplayTagList=(Tag="Trait.Spicy",DevComment="") ++GameplayTagList=(Tag="Trait.Suppressed",DevComment="") ++GameplayTagList=(Tag="Trait.Thrown",DevComment="") ++GameplayTagList=(Tag="Trait.Veggie",DevComment="") ++GameplayTagList=(Tag="Trait.Vehicle",DevComment="Trait tag added to be used on all vehicle weapons") ++GameplayTagList=(Tag="Trait.Weight.VeryHeavy",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.CeilingDrop.HasHit",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.Drain.HasHit",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.SecretDoor.HasHit",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.Snare.HasHit",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.Teleport.HasHit",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.WallArrows.HasHit",DevComment="") ++GameplayTagList=(Tag="Trap.Starlight.WallArrows.Hit",DevComment="") ++GameplayTagList=(Tag="Trap.TeleportExit",DevComment="") ++GameplayTagList=(Tag="TurnTransition.Inplace.Allow",DevComment="") ++GameplayTagList=(Tag="UGC.EditSupport",DevComment="") ++GameplayTagList=(Tag="UI",DevComment="") ++GameplayTagList=(Tag="UI.Action",DevComment="") ++GameplayTagList=(Tag="UI.Action.Back",DevComment="") ++GameplayTagList=(Tag="UI.Action.Cancel",DevComment="") ++GameplayTagList=(Tag="UI.Action.Confirm",DevComment="") ++GameplayTagList=(Tag="UI.Action.FocusChat",DevComment="") ++GameplayTagList=(Tag="UI.Action.MainMenu",DevComment="") ++GameplayTagList=(Tag="UI.Action.NextTab",DevComment="") ++GameplayTagList=(Tag="UI.Action.OpenSocial",DevComment="") ++GameplayTagList=(Tag="UI.Action.PreviousTab",DevComment="") ++GameplayTagList=(Tag="UI.Action.SwitchProfile",DevComment="") ++GameplayTagList=(Tag="UI.Action.Tab",DevComment="") ++GameplayTagList=(Tag="UI.Action.Tab.Left",DevComment="") ++GameplayTagList=(Tag="UI.Action.Tab.Next",DevComment="") ++GameplayTagList=(Tag="UI.Bangs.EditSocialTags",DevComment="") ++GameplayTagList=(Tag="UI.Bangs.LookingForParty",DevComment="") ++GameplayTagList=(Tag="UI.CharacterPart.Dynamics.ForceReset.DuringEmote",DevComment="Force reset dynamics on this part during an emote in the UI") ++GameplayTagList=(Tag="UI.CharacterPart.Dynamics.ForceReset.PostEmote",DevComment="Force reset dynamics on this part after an emote in the UI") ++GameplayTagList=(Tag="UI.CharacterPart.Dynamics.ForceReset.PreEmote",DevComment="Force reset dynamics on this part before an emote in the UI") ++GameplayTagList=(Tag="UI.ContentOnDemand",DevComment="Tag to hide the ContentOnDemand widget anywhere (frontend, in-game or in-game shop/locker)") ++GameplayTagList=(Tag="UI.DataDownloadBar",DevComment="") ++GameplayTagList=(Tag="UI.GiftBox.HideRarity",DevComment="") ++GameplayTagList=(Tag="UI.Gifting.Widget.Winterfest",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.1h2hpickaxe",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.BKnight",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Brutus",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.BuildABrella",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Hoot",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Maya",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Midas",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Moewscles",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Pup",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.Sky",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.TnTinaCharacter",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.NewTech.TnTinaGlider",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.BattlePass",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.BattlePass.PurchaseLevel",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.ChallengeRewards",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.FortniteCrew",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.Gifting",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.ItemPresentation",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.ItemShop",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.Locker",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.Locker.LoadoutSelect",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.SpecialEvent",DevComment="") ++GameplayTagList=(Tag="UI.ItemPreview.SceneChanger.WinterQuest",DevComment="") ++GameplayTagList=(Tag="UI.Lobby.MatchmakingWidget",DevComment="") ++GameplayTagList=(Tag="UI.Pedestal.Test",DevComment="Tag to link test tab with test preview pedestal") ++GameplayTagList=(Tag="UI.Pedestal.Test.Background",DevComment="") ++GameplayTagList=(Tag="UI.Pedestal.Test.Focus",DevComment="") ++GameplayTagList=(Tag="UI.Pedestal.Test.Secondary.Background",DevComment="") ++GameplayTagList=(Tag="UI.Pedestal.Test.Secondary.Focus",DevComment="") ++GameplayTagList=(Tag="UI.PreviewRotator.BattlePass",DevComment="") ++GameplayTagList=(Tag="UI.PreviewRotator.RewardsScreen",DevComment="") ++GameplayTagList=(Tag="UI.PreviewRotator.SpecialEventRewards",DevComment="") ++GameplayTagList=(Tag="UI.State",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.BattleRoyale",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.Creative",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.Dev_CustomGame",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.ForcedEditIntro",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.ForcedIntro",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.ForcedPlaylist",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.Frontend",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.NightNight",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.Rejoin",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.Replay",DevComment="") ++GameplayTagList=(Tag="UI.State.Athena.Spectator",DevComment="") ++GameplayTagList=(Tag="UI.State.Startup",DevComment="") ++GameplayTagList=(Tag="UI.State.Startup.ForcedIntro",DevComment="") ++GameplayTagList=(Tag="UI.State.Startup.Login",DevComment="") ++GameplayTagList=(Tag="UI.State.Startup.NightNight",DevComment="") ++GameplayTagList=(Tag="UI.State.Startup.Rejoin",DevComment="") ++GameplayTagList=(Tag="UI.State.Startup.SubgameSelect",DevComment="") ++GameplayTagList=(Tag="UI.State.STW",DevComment="") ++GameplayTagList=(Tag="UI.State.STW.Cinematic",DevComment="") ++GameplayTagList=(Tag="UI.State.STW.Frontend",DevComment="") ++GameplayTagList=(Tag="UI.State.STW.InGame",DevComment="") ++GameplayTagList=(Tag="UI.State.STW.InGameCustom",DevComment="") ++GameplayTagList=(Tag="UI.State.STW.Rejoin",DevComment="") ++GameplayTagList=(Tag="UI.Tutorials.EditSocialTags",DevComment="") ++GameplayTagList=(Tag="UI.Tutorials.LikeIsland",DevComment="Indicates whether the player has seen the \"Like Island\" onboarding tooltip") ++GameplayTagList=(Tag="UI.Tutorials.LookingForParty",DevComment="") ++GameplayTagList=(Tag="UI.Tutorials.RebootRallyAvailable",DevComment="") ++GameplayTagList=(Tag="UI.Tutorials.RebootRallyUsers",DevComment="Indicates whether the player has seen the Reboot Rally onboarding tooltip") ++GameplayTagList=(Tag="UI.Tutorials.SeenLfgTab",DevComment="") ++GameplayTagList=(Tag="UI.Tutorials.SeenSidebarSupervisedSettings",DevComment="The player has seen the sidebar dismissible Tooltip") ++GameplayTagList=(Tag="UI.Tutorials.SeenVoiceChatPanelSupervisedSettings",DevComment="The player has seen the voice chat NON dismissible Tooltip") ++GameplayTagList=(Tag="Unlocks.DirectedAcquisition.Weapon.Ranged.SMG",DevComment="") ++GameplayTagList=(Tag="Unlocks.Promotion.Hero",DevComment="Unlock ability to promote STW Hero items") ++GameplayTagList=(Tag="Unlocks.Promotion.Survivor",DevComment="Unlock ability to promote STW Survivors and Leads") ++GameplayTagList=(Tag="Unlocks.Promotion.Trap",DevComment="Unlock ability to promote STW Trap Schematics") ++GameplayTagList=(Tag="Unlocks.Promotion.Weapon",DevComment="Unlock ability to promote STW Weapon Schematics") ++GameplayTagList=(Tag="Unlocks.RarityUpgrade.Weapon.SMG",DevComment="") ++GameplayTagList=(Tag="UraniumPOICamera",DevComment="") ++GameplayTagList=(Tag="ValetMods.BasicCar",DevComment="") ++GameplayTagList=(Tag="ValetMods.Semi",DevComment="") ++GameplayTagList=(Tag="ValetMods.Sport",DevComment="") ++GameplayTagList=(Tag="ValetMods.Taxi",DevComment="Mod tag for Taxi vehicle") ++GameplayTagList=(Tag="ValetMods.Truck",DevComment="") ++GameplayTagList=(Tag="Vehicle.AlwaysTakeVehicleCollisionDamage",DevComment="Vehicles with this tag should always take damage from being hit by other vehicles") ++GameplayTagList=(Tag="Vehicle.Antelope",DevComment="") ++GameplayTagList=(Tag="Vehicle.Biplane",DevComment="Vehicle tag for Biplane") ++GameplayTagList=(Tag="Vehicle.Cannon",DevComment="") ++GameplayTagList=(Tag="Vehicle.Foray",DevComment="Vehicle Tag for Foray Vehicle.") ++GameplayTagList=(Tag="Vehicle.Hoagie",DevComment="Other Vehicle tag for Hoagie.") ++GameplayTagList=(Tag="Vehicle.Hoverboard",DevComment="Vehicle tag for Hoverboard") ++GameplayTagList=(Tag="Vehicle.Meatball",DevComment="Meatball vehicle Tag") ++GameplayTagList=(Tag="Vehicle.MistyBop",DevComment="") ++GameplayTagList=(Tag="Vehicle.Mod.CowCatcher",DevComment="") ++GameplayTagList=(Tag="Vehicle.Mod.Mapping.Alpha",DevComment="Rename later.") ++GameplayTagList=(Tag="Vehicle.Mod.Receiver.CC.ABB",DevComment="") ++GameplayTagList=(Tag="Vehicle.Mod.Tire.Offroad",DevComment="") ++GameplayTagList=(Tag="Vehicle.Motorcycle",DevComment="") ++GameplayTagList=(Tag="Vehicle.MountedTurret",DevComment="vehicle tag for turret.") ++GameplayTagList=(Tag="Vehicle.MountedWaterCannon",DevComment="") ++GameplayTagList=(Tag="Vehicle.Nevada",DevComment="") ++GameplayTagList=(Tag="Vehicle.Octopus",DevComment="") ++GameplayTagList=(Tag="Vehicle.Ostrich",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.DriverCover",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Engine",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tire.Center.Driver",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tire.Center.Passenger",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tire.Front.Driver",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tire.Front.Passenger",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tire.Rear.Driver",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tire.Rear.Passenger",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tread.Left",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Tread.Right",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.TurretHatch.Left",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.TurretHatch.Right",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Window.Front.Center",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Window.Front.Driver",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Window.Front.Passenger",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Window.Rear.Center",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Window.Rear.Driver",DevComment="") ++GameplayTagList=(Tag="Vehicle.Part.Window.Rear.Passenger",DevComment="") ++GameplayTagList=(Tag="Vehicle.RoundSmall",DevComment="") ++GameplayTagList=(Tag="Vehicle.Seat.Open",DevComment="This seat of vehicle has no roof") ++GameplayTagList=(Tag="Vehicle.Seat.Roofed",DevComment="This seat of vehicle is with roof") ++GameplayTagList=(Tag="Vehicle.Tank",DevComment="Vehicle Tag for the Tank Vehicle") ++GameplayTagList=(Tag="Vehicle.TerrainType.Air",DevComment="Terrain type tag for air vehicles") ++GameplayTagList=(Tag="Vehicle.TerrainType.Air.Biplane",DevComment="Terrain type tag for Biplane") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land",DevComment="Terrain type tag for land vehicles") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.Baller",DevComment="Terrain type tag for Ballers") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.BasicCar",DevComment="Terrain type tag for Basic Cars") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.BasicTruck",DevComment="Terrain type tag for Basic Trucks") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.Driftboard",DevComment="Terrain type tag for Driftboards") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.SemiTruck",DevComment="Terrain type tag for Semi-Trucks") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.Sportscar",DevComment="Terrain type tag for Sportscars") ++GameplayTagList=(Tag="Vehicle.TerrainType.Land.Taxi",DevComment="Terrain type tag for Taxi") ++GameplayTagList=(Tag="Vehicle.TerrainType.Water",DevComment="Terrain type tag for water vehicles") ++GameplayTagList=(Tag="Vehicle.TerrainType.Water.MotorBoat",DevComment="Terrain type tag for MotorBoats") ++GameplayTagList=(Tag="Vehicle.TurnSlowdown",DevComment="") ++GameplayTagList=(Tag="Vehicle.Valet.ArmoredBattleBus",DevComment="") ++GameplayTagList=(Tag="Vehicle.Valet.BasicSedan",DevComment="Valet Vehicle tag for basic sedan") ++GameplayTagList=(Tag="Vehicle.Valet.Foray",DevComment="Vehicle tag for Foray") ++GameplayTagList=(Tag="Vehicle.Valet.Pickup",DevComment="Valet Vehicle tag for pickup truck") ++GameplayTagList=(Tag="Vehicle.Valet.Semi",DevComment="Valet vehicle tag for semi truck") ++GameplayTagList=(Tag="Vehicle.Valet.Semi.IO",DevComment="") ++GameplayTagList=(Tag="Vehicle.Valet.Sport",DevComment="Valet vehicle tag for sports car") ++GameplayTagList=(Tag="Vehicle.Valet.Taxi",DevComment="Valet vehicle tag for taxi cab") ++GameplayTagList=(Tag="VertigoPickaxe",DevComment="Tag for Vertigo Pickaxe") ++GameplayTagList=(Tag="Vote.Session.0001",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q1",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q2",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q3",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q1.A1",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q1.A2",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q1.A3",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q2.A1",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q2.A2",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q2.A3",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q3.A1",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q3.A2",DevComment="") ++GameplayTagList=(Tag="Vote.Session.0001.Q3.A3",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.HasHeat",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.PurchasedIncreaseWantedLevel",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.PurchasedReduceHeat",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.WantedLevel.0",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.WantedLevel.1",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.WantedLevel.2",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.WantedLevel.3",DevComment="") ++GameplayTagList=(Tag="WantedSystem.Player.WantedLevel.4",DevComment="") ++GameplayTagList=(Tag="Waterfall.EnvItem.Through",DevComment="Execute when player through waterfall.") ++GameplayTagList=(Tag="Wax.Hotel.Pacify.Added",DevComment="") ++GameplayTagList=(Tag="Wax.Hotel.Pacify.Removed",DevComment="") ++GameplayTagList=(Tag="Weapon.AllowsFlying",DevComment="") ++GameplayTagList=(Tag="Weapon.ChargeWeapon",DevComment="") ++GameplayTagList=(Tag="Weapon.EditTool",DevComment="") ++GameplayTagList=(Tag="Weapon.Gauntlet",DevComment="") ++GameplayTagList=(Tag="Weapon.HasDodgeAction",DevComment="") ++GameplayTagList=(Tag="Weapon.HasUseForFireIcon",DevComment="") ++GameplayTagList=(Tag="Weapon.IgnoresCancelPrimaryAbilityOnRelease",DevComment="Prevents CancelAbility from being called on trigger release. WARNING: This means that you will have to manually manage the cancelling/ending of primary fire ability! So use this functionality at your own risk!") ++GameplayTagList=(Tag="Weapon.LTM.End.Boots",DevComment="") ++GameplayTagList=(Tag="Weapon.Melee.InfinityBlade",DevComment="") ++GameplayTagList=(Tag="Weapon.Melee.Piercing.Spear.DisableInput",DevComment="") ++GameplayTagList=(Tag="Weapon.Melee.Piercing.Spear.EnableInput",DevComment="") ++GameplayTagList=(Tag="Weapon.Melee.Turbo.AxeMelee",DevComment="Turbos Melee Axe tag.") ++GameplayTagList=(Tag="Weapon.Melee.Turbo.AxeRanged",DevComment="Turbo Axe Throw") ++GameplayTagList=(Tag="Weapon.Melee.Turbo.AxeSlam",DevComment="Turbo Slam Attack tag") ++GameplayTagList=(Tag="Weapon.Meta.DoNotApplyCharms",DevComment="") ++GameplayTagList=(Tag="Weapon.Meta.DoNotApplyWraps",DevComment="") ++GameplayTagList=(Tag="Weapon.Meta.MiscWrapped",DevComment="use item wrap cosmetic from the misc slot") ++GameplayTagList=(Tag="Weapon.Meta.Scoped",DevComment="Scoped meta weapons") ++GameplayTagList=(Tag="Weapon.Permissions.AllowHarvestingTools",DevComment="Meant to allow blocking activation for Harvesting Tool weapon types") ++GameplayTagList=(Tag="Weapon.Ranged.AntivehicleTurret",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Assault.Infantry",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Assault.LMG",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Biplane",DevComment="Weapon tag for Biplane guns") ++GameplayTagList=(Tag="Weapon.Ranged.Boombox",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Bow",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Chaplin",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Chicago",DevComment="Chicago Weapon analytics tag.") ++GameplayTagList=(Tag="Weapon.Ranged.Crossbow.Explosive",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.FireworksMortar",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Grenade.Standard",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Grenade.Tnt",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Hamsterball",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Heal",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Heavy.Badger",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Heavy.Grape",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Heavy.ProximityMine",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Hippo",DevComment="Hippo Weapon analytics tag") ++GameplayTagList=(Tag="Weapon.Ranged.Indigo",DevComment="Weapon analytic tag for Indigo") ++GameplayTagList=(Tag="Weapon.Ranged.Milo.Launcher",DevComment="Milo weapon tag for launcher") ++GameplayTagList=(Tag="Weapon.Ranged.Milo.Rifle",DevComment="Milo Rifle weapon Tag") ++GameplayTagList=(Tag="Weapon.Ranged.PirateCannon",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Pistol.Flintlock",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Pistol.Flintlock.YeetKnock",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.SevenTurret",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Sniper.Bow",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.SplitArrow",DevComment="") ++GameplayTagList=(Tag="Weapon.Ranged.Trap.Spike",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.BandageBazooka",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.BoogieBomb",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.C4",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Chug",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Chug.VR",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Clinger",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Clinger.VR",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Crashpad",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Crashpad.VR",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Decoy",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Decoy.NoDropNoWepons",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Decoy.VR",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Dynamite",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Dynamite+",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Grappler",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.Grenade",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.IceGrenade",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.ImpulseGrenade",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.ProximityMine",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.ProximityMine.VR",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.ShieldBubble",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.ShockwaveGrenade+",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.SmokeGrenade",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.StinkBomb",DevComment="") ++GameplayTagList=(Tag="Weapon.RechargeAmmo.TastyStew",DevComment="") ++GameplayTagList=(Tag="Weapon.Reload.CameraActive",DevComment="") ++GameplayTagList=(Tag="Weapon.Reload.CameraTransitionOut",DevComment="") ++GameplayTagList=(Tag="Weapon.Thrown.ThrowingAxe",DevComment="") ++GameplayTagList=(Tag="Weaponset.ArtDeco",DevComment="") ++GameplayTagList=(Tag="WeaponSet.BlackMetal",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Boombox",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Fleming",DevComment="") ++GameplayTagList=(Tag="WeaponSet.HolidayFun",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Invasion",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Medieval",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Pirate",DevComment="") ++GameplayTagList=(Tag="WeaponSet.RetroSciFi",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Steampunk",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Stormking",DevComment="") ++GameplayTagList=(Tag="WeaponSet.Winter.2018",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.BottomCenter",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.BottomLeft",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.BottomRight",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.Left",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.Right",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.TopCenter",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.TopLeft",DevComment="") ++GameplayTagList=(Tag="WinterQuest.UI.IndicatorDirection.TopRight",DevComment="") + diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini new file mode 100644 index 00000000..550f1993 --- /dev/null +++ b/Config/DefaultInput.ini @@ -0,0 +1,86 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bShouldFlushPressedKeysOnViewportFocusLost=True +bEnableDynamicComponentInputBinding=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde ++ConsoleKeys=Caret + diff --git a/Config/HoloLens/HoloLensEngine.ini b/Config/HoloLens/HoloLensEngine.ini new file mode 100644 index 00000000..7b8b53f9 --- /dev/null +++ b/Config/HoloLens/HoloLensEngine.ini @@ -0,0 +1,31 @@ + + +[/Script/HoloLensPlatformEditor.HoloLensTargetSettings] +bBuildForEmulation=False +bBuildForDevice=True +bUseNameForLogo=True +bBuildForRetailWindowsStore=False +bAutoIncrementVersion=False +bShouldCreateAppInstaller=False +AppInstallerInstallationURL= +HoursBetweenUpdateChecks=0 +bEnablePIXProfiling=False +TileBackgroundColor=(B=64,G=0,R=0,A=255) +SplashScreenBackgroundColor=(B=64,G=0,R=0,A=255) ++PerCultureResources=(CultureId="",Strings=(PackageDisplayName="",PublisherDisplayName="",PackageDescription="",ApplicationDisplayName="",ApplicationDescription=""),Images=()) +TargetDeviceFamily=Windows.Holographic +MinimumPlatformVersion= +MaximumPlatformVersionTested=10.0.18362.0 +MaxTrianglesPerCubicMeter=500.000000 +SpatialMeshingVolumeSize=20.000000 +CompilerVersion=Default +Windows10SDKVersion=10.0.18362.0 ++CapabilityList=internetClientServer ++CapabilityList=privateNetworkClientServer ++Uap2CapabilityList=spatialPerception +bSetDefaultCapabilities=False +SpatializationPlugin= +ReverbPlugin= +OcclusionPlugin= +SoundCueCookQualityIndex=-1 + diff --git a/Config/Localization/Game_Compile.ini b/Config/Localization/Game_Compile.ini new file mode 100644 index 00000000..5a084b2f --- /dev/null +++ b/Config/Localization/Game_Compile.ini @@ -0,0 +1,14 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +ManifestName=Game.manifest +ArchiveName=Game.archive +ResourceName=Game.locres +bSkipSourceCheck=false +bValidateFormatPatterns=true +bValidateSafeWhitespace=false +CulturesToGenerate=en + +[GatherTextStep0] +CommandletClass=GenerateTextLocalizationResource + diff --git a/Config/Localization/Game_Export.ini b/Config/Localization/Game_Export.ini new file mode 100644 index 00000000..e067d0f2 --- /dev/null +++ b/Config/Localization/Game_Export.ini @@ -0,0 +1,15 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +CulturesToGenerate=en +ManifestName=Game.manifest +ArchiveName=Game.archive +PortableObjectName=Game.po + +[GatherTextStep0] +CommandletClass=InternationalizationExport +bExportLoc=true +LocalizedTextCollapseMode=ELocalizedTextCollapseMode::IdenticalTextIdAndSource +ShouldPersistCommentsOnExport=false +ShouldAddSourceLocationsAsComments=true + diff --git a/Config/Localization/Game_ExportDialogueScript.ini b/Config/Localization/Game_ExportDialogueScript.ini new file mode 100644 index 00000000..0a451f32 --- /dev/null +++ b/Config/Localization/Game_ExportDialogueScript.ini @@ -0,0 +1,11 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +CulturesToGenerate=en +ManifestName=Game.manifest +ArchiveName=Game.archive +DialogueScriptName=GameDialogue.csv + +[GatherTextStep0] +CommandletClass=ExportDialogueScript + diff --git a/Config/Localization/Game_Gather.ini b/Config/Localization/Game_Gather.ini new file mode 100644 index 00000000..509804d7 --- /dev/null +++ b/Config/Localization/Game_Gather.ini @@ -0,0 +1,22 @@ +[CommonSettings] +ManifestDependencies=../../../../../Games/UE_4.25/Engine/Content/Localization/Engine/Engine.manifest +ManifestDependencies=../../../../../Games/UE_4.25/Engine/Content/Localization/Editor/Editor.manifest +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +ManifestName=Game.manifest +ArchiveName=Game.archive +CulturesToGenerate=en + +[GatherTextStep0] +CommandletClass=GenerateGatherManifest + +[GatherTextStep1] +CommandletClass=GenerateGatherArchive + +[GatherTextStep2] +CommandletClass=GenerateTextLocalizationReport +bWordCountReport=true +WordCountReportName=Game.csv +bConflictReport=true +ConflictReportName=Game_Conflicts.txt + diff --git a/Config/Localization/Game_GenerateReports.ini b/Config/Localization/Game_GenerateReports.ini new file mode 100644 index 00000000..4749441f --- /dev/null +++ b/Config/Localization/Game_GenerateReports.ini @@ -0,0 +1,12 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +ManifestName=Game.manifest +ArchiveName=Game.archive +CulturesToGenerate=en + +[GatherTextStep0] +CommandletClass=GenerateTextLocalizationReport +bWordCountReport=true +WordCountReportName=Game.csv + diff --git a/Config/Localization/Game_Import.ini b/Config/Localization/Game_Import.ini new file mode 100644 index 00000000..a8416b85 --- /dev/null +++ b/Config/Localization/Game_Import.ini @@ -0,0 +1,13 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +CulturesToGenerate=en +ManifestName=Game.manifest +ArchiveName=Game.archive +PortableObjectName=Game.po + +[GatherTextStep0] +CommandletClass=InternationalizationExport +bImportLoc=true +LocalizedTextCollapseMode=ELocalizedTextCollapseMode::IdenticalTextIdAndSource + diff --git a/Config/Localization/Game_ImportDialogue.ini b/Config/Localization/Game_ImportDialogue.ini new file mode 100644 index 00000000..707dc23f --- /dev/null +++ b/Config/Localization/Game_ImportDialogue.ini @@ -0,0 +1,12 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +ManifestName=Game.manifest +ArchiveName=Game.archive +CulturesToGenerate=en + +[GatherTextStep0] +CommandletClass=ImportLocalizedDialogue +RawAudioPath= +ImportedDialogueFolder=ImportedDialogue +bImportNativeAsSource=false + diff --git a/Config/Localization/Game_ImportDialogueScript.ini b/Config/Localization/Game_ImportDialogueScript.ini new file mode 100644 index 00000000..960c703a --- /dev/null +++ b/Config/Localization/Game_ImportDialogueScript.ini @@ -0,0 +1,11 @@ +[CommonSettings] +SourcePath=Content/Localization/Game +DestinationPath=Content/Localization/Game +CulturesToGenerate=en +ManifestName=Game.manifest +ArchiveName=Game.archive +DialogueScriptName=GameDialogue.csv + +[GatherTextStep0] +CommandletClass=ImportDialogueScript + diff --git a/Content/2dAssets/Fortnite_World_Tex.uasset b/Content/2dAssets/Fortnite_World_Tex.uasset new file mode 100644 index 00000000..e4d31472 Binary files /dev/null and b/Content/2dAssets/Fortnite_World_Tex.uasset differ diff --git a/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_CMF.uasset b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_CMF.uasset new file mode 100644 index 00000000..e0ab4e56 Binary files /dev/null and b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_CMF.uasset differ diff --git a/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_CMM.uasset b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_CMM.uasset new file mode 100644 index 00000000..84d31470 Binary files /dev/null and b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_CMM.uasset differ diff --git a/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_Loop_CMF.uasset b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_Loop_CMF.uasset new file mode 100644 index 00000000..f8059325 Binary files /dev/null and b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_Loop_CMF.uasset differ diff --git a/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_Loop_CMM.uasset b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_Loop_CMM.uasset new file mode 100644 index 00000000..88e191a9 Binary files /dev/null and b/Content/Animation/Game/MainPlayer/Emotes/Boogie_Down/Emote_Boogie_Down_Loop_CMM.uasset differ diff --git a/Content/Animation/Settings/FortniteAnimBoneCompressionSettings.uasset b/Content/Animation/Settings/FortniteAnimBoneCompressionSettings.uasset new file mode 100644 index 00000000..966e3fe8 Binary files /dev/null and b/Content/Animation/Settings/FortniteAnimBoneCompressionSettings.uasset differ diff --git a/Content/Athena/Heroes/Hero_LightningDragon.uasset b/Content/Athena/Heroes/Hero_LightningDragon.uasset new file mode 100644 index 00000000..93176197 Binary files /dev/null and b/Content/Athena/Heroes/Hero_LightningDragon.uasset differ diff --git a/Content/Athena/Heroes/Meshes/Bodies/CP_Body_GreenNinja_Street.uasset b/Content/Athena/Heroes/Meshes/Bodies/CP_Body_GreenNinja_Street.uasset new file mode 100644 index 00000000..293d4278 Binary files /dev/null and b/Content/Athena/Heroes/Meshes/Bodies/CP_Body_GreenNinja_Street.uasset differ diff --git a/Content/Athena/Heroes/Meshes/Heads/F_Med_Head1_ATH.uasset b/Content/Athena/Heroes/Meshes/Heads/F_Med_Head1_ATH.uasset new file mode 100644 index 00000000..c5e5b406 Binary files /dev/null and b/Content/Athena/Heroes/Meshes/Heads/F_Med_Head1_ATH.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Backpacks/BID_STWHeroNoDefaultBackpack.uasset b/Content/Athena/Items/Cosmetics/Backpacks/BID_STWHeroNoDefaultBackpack.uasset new file mode 100644 index 00000000..41260876 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Backpacks/BID_STWHeroNoDefaultBackpack.uasset differ diff --git a/Content/Athena/Items/Cosmetics/BattleBuses/BBID_DefaultBus.uasset b/Content/Athena/Items/Cosmetics/BattleBuses/BBID_DefaultBus.uasset new file mode 100644 index 00000000..1ebd2610 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/BattleBuses/BBID_DefaultBus.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Characters/CID_A_024_Athena_Commando_F_Skirmish_QW2BQ.uasset b/Content/Athena/Items/Cosmetics/Characters/CID_A_024_Athena_Commando_F_Skirmish_QW2BQ.uasset new file mode 100644 index 00000000..d35b7048 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Characters/CID_A_024_Athena_Commando_F_Skirmish_QW2BQ.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Characters/CID_A_477_Athena_Commando_F_Handlebar.uasset b/Content/Athena/Items/Cosmetics/Characters/CID_A_477_Athena_Commando_F_Handlebar.uasset new file mode 100644 index 00000000..d4e55280 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Characters/CID_A_477_Athena_Commando_F_Handlebar.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Characters/CID_Random.uasset b/Content/Athena/Items/Cosmetics/Characters/CID_Random.uasset new file mode 100644 index 00000000..8a355f8e Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Characters/CID_Random.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Characters/NewDataAsset.uasset b/Content/Athena/Items/Cosmetics/Characters/NewDataAsset.uasset new file mode 100644 index 00000000..06046b2b Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Characters/NewDataAsset.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Contrails/DefaultContrail.uasset b/Content/Athena/Items/Cosmetics/Contrails/DefaultContrail.uasset new file mode 100644 index 00000000..df1839fa Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Contrails/DefaultContrail.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Contrails/Trails_Random.uasset b/Content/Athena/Items/Cosmetics/Contrails/Trails_Random.uasset new file mode 100644 index 00000000..502e8d0e Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Contrails/Trails_Random.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Gliders/DefaultGlider.uasset b/Content/Athena/Items/Cosmetics/Gliders/DefaultGlider.uasset new file mode 100644 index 00000000..c781979d Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Gliders/DefaultGlider.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Gliders/Glider_Random.uasset b/Content/Athena/Items/Cosmetics/Gliders/Glider_Random.uasset new file mode 100644 index 00000000..8a946872 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Gliders/Glider_Random.uasset differ diff --git a/Content/Athena/Items/Cosmetics/ItemWraps/Wrap_379_Pliant.uasset b/Content/Athena/Items/Cosmetics/ItemWraps/Wrap_379_Pliant.uasset new file mode 100644 index 00000000..bb88f9d1 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/ItemWraps/Wrap_379_Pliant.uasset differ diff --git a/Content/Athena/Items/Cosmetics/ItemWraps/Wrap_Random.uasset b/Content/Athena/Items/Cosmetics/ItemWraps/Wrap_Random.uasset new file mode 100644 index 00000000..9d811249 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/ItemWraps/Wrap_Random.uasset differ diff --git a/Content/Athena/Items/Cosmetics/LoadingScreens/LSID_Random.uasset b/Content/Athena/Items/Cosmetics/LoadingScreens/LSID_Random.uasset new file mode 100644 index 00000000..17a05d29 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/LoadingScreens/LSID_Random.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Metadata/CosmeticFilterTagDataRow.uasset b/Content/Athena/Items/Cosmetics/Metadata/CosmeticFilterTagDataRow.uasset new file mode 100644 index 00000000..4454a311 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Metadata/CosmeticFilterTagDataRow.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Metadata/CosmeticMarkupTable.uasset b/Content/Athena/Items/Cosmetics/Metadata/CosmeticMarkupTable.uasset new file mode 100644 index 00000000..8e939942 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Metadata/CosmeticMarkupTable.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.uasset b/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.uasset new file mode 100644 index 00000000..486fbfab Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Metadata/CosmeticUserFacingTags.uasset b/Content/Athena/Items/Cosmetics/Metadata/CosmeticUserFacingTags.uasset new file mode 100644 index 00000000..38206cce Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Metadata/CosmeticUserFacingTags.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Metadata/FortCosmeticItemFilterTable.uasset b/Content/Athena/Items/Cosmetics/Metadata/FortCosmeticItemFilterTable.uasset new file mode 100644 index 00000000..0fd720c8 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Metadata/FortCosmeticItemFilterTable.uasset differ diff --git a/Content/Athena/Items/Cosmetics/MusicPacks/MusicPack_TBD_Torikago.uasset b/Content/Athena/Items/Cosmetics/MusicPacks/MusicPack_TBD_Torikago.uasset new file mode 100644 index 00000000..6d728c92 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/MusicPacks/MusicPack_TBD_Torikago.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Pickaxes/DefaultPickaxe.uasset b/Content/Athena/Items/Cosmetics/Pickaxes/DefaultPickaxe.uasset new file mode 100644 index 00000000..5777bd28 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Pickaxes/DefaultPickaxe.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_Random.uasset b/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_Random.uasset new file mode 100644 index 00000000..cce78c71 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_Random.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Series/CreatorCollabSeries.uasset b/Content/Athena/Items/Cosmetics/Series/CreatorCollabSeries.uasset new file mode 100644 index 00000000..67339e08 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Series/CreatorCollabSeries.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Series/MarvelSeries.uasset b/Content/Athena/Items/Cosmetics/Series/MarvelSeries.uasset new file mode 100644 index 00000000..5d737060 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Series/MarvelSeries.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Series/PlatformSeries.uasset b/Content/Athena/Items/Cosmetics/Series/PlatformSeries.uasset new file mode 100644 index 00000000..f66fb716 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Series/PlatformSeries.uasset differ diff --git a/Content/Athena/Items/Cosmetics/Series/ShadowSeries.uasset b/Content/Athena/Items/Cosmetics/Series/ShadowSeries.uasset new file mode 100644 index 00000000..7cb20e80 Binary files /dev/null and b/Content/Athena/Items/Cosmetics/Series/ShadowSeries.uasset differ diff --git a/Content/Athena/Items/Weapons/AthenaMeleeWeapons.uasset b/Content/Athena/Items/Weapons/AthenaMeleeWeapons.uasset new file mode 100644 index 00000000..54464106 Binary files /dev/null and b/Content/Athena/Items/Weapons/AthenaMeleeWeapons.uasset differ diff --git a/Content/Athena/Items/Weapons/AthenaRangedWeapons.uasset b/Content/Athena/Items/Weapons/AthenaRangedWeapons.uasset new file mode 100644 index 00000000..ce3e3102 Binary files /dev/null and b/Content/Athena/Items/Weapons/AthenaRangedWeapons.uasset differ diff --git a/Content/Athena/Items/Weapons/AthenaRangedWeapons_ShootingModel2.uasset b/Content/Athena/Items/Weapons/AthenaRangedWeapons_ShootingModel2.uasset new file mode 100644 index 00000000..9d5303ae Binary files /dev/null and b/Content/Athena/Items/Weapons/AthenaRangedWeapons_ShootingModel2.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Generic-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Generic-S.png new file mode 100644 index 00000000..f76a95ae Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Generic-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Generic-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Generic-S.uasset new file mode 100644 index 00000000..2c8ca9f7 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Generic-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Generic.png b/Content/Athena/Prototype/Textures/T_Placeholder_Generic.png new file mode 100644 index 00000000..942e9c07 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Generic.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Generic.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Generic.uasset new file mode 100644 index 00000000..124f6ece Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Generic.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling-S.png new file mode 100644 index 00000000..17ef4bdc Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling-S.uasset new file mode 100644 index 00000000..df0d5a19 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling.png new file mode 100644 index 00000000..dbba869c Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling.uasset new file mode 100644 index 00000000..8827182a Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_BackBling.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote-S.png new file mode 100644 index 00000000..aa4f17a0 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote-S.uasset new file mode 100644 index 00000000..bfa396bf Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote.png new file mode 100644 index 00000000..94a03053 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote.uasset new file mode 100644 index 00000000..9420cb5f Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emote.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emoticon-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emoticon-S.png new file mode 100644 index 00000000..84f007c3 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emoticon-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emoticon-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emoticon-S.uasset new file mode 100644 index 00000000..a11714a1 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Emoticon-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider-S.png new file mode 100644 index 00000000..ce972bb6 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider-S.uasset new file mode 100644 index 00000000..d343e834 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider.png new file mode 100644 index 00000000..055a8716 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider.uasset new file mode 100644 index 00000000..7d5e63aa Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Glider.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_LoadingScreen-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_LoadingScreen-S.png new file mode 100644 index 00000000..c0248f3b Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_LoadingScreen-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_LoadingScreen-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_LoadingScreen-S.uasset new file mode 100644 index 00000000..37d9deb4 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_LoadingScreen-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Music-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Music-S.png new file mode 100644 index 00000000..1c8f1f26 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Music-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Music-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Music-S.uasset new file mode 100644 index 00000000..ae908554 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Music-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit-S.png new file mode 100644 index 00000000..25d04d52 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit-S.uasset new file mode 100644 index 00000000..44f441ef Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit.png new file mode 100644 index 00000000..5577e8c2 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit.uasset new file mode 100644 index 00000000..128f685a Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Outfit.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe-S.png new file mode 100644 index 00000000..92403caa Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe-S.uasset new file mode 100644 index 00000000..f5f38739 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe.png new file mode 100644 index 00000000..1e26c4d3 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe.uasset new file mode 100644 index 00000000..77173323 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Pickaxe.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Spray-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Spray-S.png new file mode 100644 index 00000000..a4ce956a Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Spray-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Spray-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Spray-S.uasset new file mode 100644 index 00000000..bc1351eb Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Spray-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Style.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Style.png new file mode 100644 index 00000000..111d43a1 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Style.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Style.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Style.uasset new file mode 100644 index 00000000..81b69fd8 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Style.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap-S.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap-S.png new file mode 100644 index 00000000..b953b044 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap-S.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap-S.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap-S.uasset new file mode 100644 index 00000000..e49657f0 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap-S.uasset differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap.png b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap.png new file mode 100644 index 00000000..786246af Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap.png differ diff --git a/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap.uasset b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap.uasset new file mode 100644 index 00000000..43b21649 Binary files /dev/null and b/Content/Athena/Prototype/Textures/T_Placeholder_Item_Wrap.uasset differ diff --git a/Content/Characters/CharacterParts/Backpacks/NoBackpack.uasset b/Content/Characters/CharacterParts/Backpacks/NoBackpack.uasset new file mode 100644 index 00000000..86e4e24c Binary files /dev/null and b/Content/Characters/CharacterParts/Backpacks/NoBackpack.uasset differ diff --git a/Content/Characters/CharacterParts/FaceAccessories/CP_F_Reference.uasset b/Content/Characters/CharacterParts/FaceAccessories/CP_F_Reference.uasset new file mode 100644 index 00000000..4e98e335 Binary files /dev/null and b/Content/Characters/CharacterParts/FaceAccessories/CP_F_Reference.uasset differ diff --git a/Content/Characters/CharacterParts/Female/Medium/Bodies/CP_Athena_Body_F_Ref.uasset b/Content/Characters/CharacterParts/Female/Medium/Bodies/CP_Athena_Body_F_Ref.uasset new file mode 100644 index 00000000..de8d3b69 Binary files /dev/null and b/Content/Characters/CharacterParts/Female/Medium/Bodies/CP_Athena_Body_F_Ref.uasset differ diff --git a/Content/Characters/CharacterParts/Female/Medium/Heads/CP_Head_Ref.uasset b/Content/Characters/CharacterParts/Female/Medium/Heads/CP_Head_Ref.uasset new file mode 100644 index 00000000..848e0bb1 Binary files /dev/null and b/Content/Characters/CharacterParts/Female/Medium/Heads/CP_Head_Ref.uasset differ diff --git a/Content/Characters/CharacterParts/Hats/Hat_F_Commando_08_V01.uasset b/Content/Characters/CharacterParts/Hats/Hat_F_Commando_08_V01.uasset new file mode 100644 index 00000000..5a7932f8 Binary files /dev/null and b/Content/Characters/CharacterParts/Hats/Hat_F_Commando_08_V01.uasset differ diff --git a/Content/Characters/CharacterParts/Hats/Hat_M_Commando_02_V1.uasset b/Content/Characters/CharacterParts/Hats/Hat_M_Commando_02_V1.uasset new file mode 100644 index 00000000..7c5d35e3 Binary files /dev/null and b/Content/Characters/CharacterParts/Hats/Hat_M_Commando_02_V1.uasset differ diff --git a/Content/Characters/CharacterParts/Hats/Ramirez_Glasses.uasset b/Content/Characters/CharacterParts/Hats/Ramirez_Glasses.uasset new file mode 100644 index 00000000..b2c09bad Binary files /dev/null and b/Content/Characters/CharacterParts/Hats/Ramirez_Glasses.uasset differ diff --git a/Content/Characters/CharacterParts/Male/Medium/Bodies/CP_Athena_Body_M_Blue.uasset b/Content/Characters/CharacterParts/Male/Medium/Bodies/CP_Athena_Body_M_Blue.uasset new file mode 100644 index 00000000..f49b51b1 Binary files /dev/null and b/Content/Characters/CharacterParts/Male/Medium/Bodies/CP_Athena_Body_M_Blue.uasset differ diff --git a/Content/Characters/CharacterParts/Male/Medium/Heads/CP_Athena_Head_M_Med_Blue.uasset b/Content/Characters/CharacterParts/Male/Medium/Heads/CP_Athena_Head_M_Med_Blue.uasset new file mode 100644 index 00000000..5f99abbb Binary files /dev/null and b/Content/Characters/CharacterParts/Male/Medium/Heads/CP_Athena_Head_M_Med_Blue.uasset differ diff --git a/Content/Characters/CharacterParts/Male/Small/Bodies/CP_Body_Outlander_025_PIrateOutlander.uasset b/Content/Characters/CharacterParts/Male/Small/Bodies/CP_Body_Outlander_025_PIrateOutlander.uasset new file mode 100644 index 00000000..6827dcc7 Binary files /dev/null and b/Content/Characters/CharacterParts/Male/Small/Bodies/CP_Body_Outlander_025_PIrateOutlander.uasset differ diff --git a/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_Export_Skeleton.uasset b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_Export_Skeleton.uasset new file mode 100644 index 00000000..5cd69d23 Binary files /dev/null and b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_Export_Skeleton.uasset differ diff --git a/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_Export_Skeleton_AnimBP.uasset b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_Export_Skeleton_AnimBP.uasset new file mode 100644 index 00000000..34ba3bde Binary files /dev/null and b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_Export_Skeleton_AnimBP.uasset differ diff --git a/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_FacialPose_Export.uasset b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_FacialPose_Export.uasset new file mode 100644 index 00000000..6953711f Binary files /dev/null and b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_FacialPose_Export.uasset differ diff --git a/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_FacialPose_Export_PoseAsset.uasset b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_FacialPose_Export_PoseAsset.uasset new file mode 100644 index 00000000..07096fdb Binary files /dev/null and b/Content/Characters/Player/Common/Fortnite_Base_Head/Fortnite_Base_Head_FacialPose_Export_PoseAsset.uasset differ diff --git a/Content/Characters/Player/Common/LODSettings/Medium_Player_LODSettings.uasset b/Content/Characters/Player/Common/LODSettings/Medium_Player_LODSettings.uasset new file mode 100644 index 00000000..53808b34 Binary files /dev/null and b/Content/Characters/Player/Common/LODSettings/Medium_Player_LODSettings.uasset differ diff --git a/Content/Characters/Player/DataTables/CharacterAttributesPlayer.uasset b/Content/Characters/Player/DataTables/CharacterAttributesPlayer.uasset new file mode 100644 index 00000000..fcdec9db Binary files /dev/null and b/Content/Characters/Player/DataTables/CharacterAttributesPlayer.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton.uasset b/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton.uasset new file mode 100644 index 00000000..9b57ebcc Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton.uexp b/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton.uexp new file mode 100644 index 00000000..9c650a63 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton.uexp differ diff --git a/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton_Skeleton.uasset b/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton_Skeleton.uasset new file mode 100644 index 00000000..a2363b8b Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Base/SK_M_Female_Base_Skeleton_Skeleton.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_MED_Soldier_01_CapeColliders.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_MED_Soldier_01_CapeColliders.uasset new file mode 100644 index 00000000..eb923f9c Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_MED_Soldier_01_CapeColliders.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset new file mode 100644 index 00000000..bc3c0a87 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_ShadowVolumes.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_ShadowVolumes.uasset new file mode 100644 index 00000000..e40fd850 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_ShadowVolumes.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_Skeleton.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_Skeleton.uasset new file mode 100644 index 00000000..245b26cb Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_Skeleton.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_Skeleton_AnimBP.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_Skeleton_AnimBP.uasset new file mode 100644 index 00000000..1e62c4ca Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01_Skeleton_AnimBP.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/F_MED_Commando_TV20_m.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/F_MED_Commando_TV20_m.uasset new file mode 100644 index 00000000..727cc08e Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/F_MED_Commando_TV20_m.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_D.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_D.uasset new file mode 100644 index 00000000..5ea02519 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_D.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_E.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_E.uasset new file mode 100644 index 00000000..32d18ae8 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_E.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_N.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_N.uasset new file mode 100644 index 00000000..5de65573 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_N.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_S.uasset b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_S.uasset new file mode 100644 index 00000000..3bc5fda3 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Textures/T_Skull_Brite_Eclipse_Body_S.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_AnimBP_Child_Stands.uasset b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_AnimBP_Child_Stands.uasset new file mode 100644 index 00000000..6586e859 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_AnimBP_Child_Stands.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_Facial_Pose_Export_Stands.uasset b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_Facial_Pose_Export_Stands.uasset new file mode 100644 index 00000000..749f4051 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_Facial_Pose_Export_Stands.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_PoseAsset_Stands.uasset b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_PoseAsset_Stands.uasset new file mode 100644 index 00000000..61c60cd8 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_01_PoseAsset_Stands.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_02_Stands.uasset b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_02_Stands.uasset new file mode 100644 index 00000000..74abe7ca Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Heads/F_MED_ASN_Starfish_Head_01/Meshes/F_MED_ASN_Starfish_Head_02_Stands.uasset differ diff --git a/Content/Characters/Player/Female/Medium/Heads/F_Med_Empty_Head_01/Meshes/F_Med_Empty_Head_01.uasset b/Content/Characters/Player/Female/Medium/Heads/F_Med_Empty_Head_01/Meshes/F_Med_Empty_Head_01.uasset new file mode 100644 index 00000000..99c847a8 Binary files /dev/null and b/Content/Characters/Player/Female/Medium/Heads/F_Med_Empty_Head_01/Meshes/F_Med_Empty_Head_01.uasset differ diff --git a/Content/Characters/Player/Fortress_CharacterShader/T_Fort_Character_AllBlack.uasset b/Content/Characters/Player/Fortress_CharacterShader/T_Fort_Character_AllBlack.uasset new file mode 100644 index 00000000..dee48c18 Binary files /dev/null and b/Content/Characters/Player/Fortress_CharacterShader/T_Fort_Character_AllBlack.uasset differ diff --git a/Content/Characters/Player/Fortress_CharacterShader/T_Fort_Character_AllBlack_M.uasset b/Content/Characters/Player/Fortress_CharacterShader/T_Fort_Character_AllBlack_M.uasset new file mode 100644 index 00000000..44ca5df7 Binary files /dev/null and b/Content/Characters/Player/Fortress_CharacterShader/T_Fort_Character_AllBlack_M.uasset differ diff --git a/Content/Characters/Player/Male/Large/Bodies/M_Large_Comet/Meshes/M_LRG_Comet.uasset b/Content/Characters/Player/Male/Large/Bodies/M_Large_Comet/Meshes/M_LRG_Comet.uasset new file mode 100644 index 00000000..9feb6cc3 Binary files /dev/null and b/Content/Characters/Player/Male/Large/Bodies/M_Large_Comet/Meshes/M_LRG_Comet.uasset differ diff --git a/Content/Characters/Player/Male/Large/Bodies/M_Large_Comet/Meshes/M_LRG_Comet_Skeleton.uasset b/Content/Characters/Player/Male/Large/Bodies/M_Large_Comet/Meshes/M_LRG_Comet_Skeleton.uasset new file mode 100644 index 00000000..5f1da641 Binary files /dev/null and b/Content/Characters/Player/Male/Large/Bodies/M_Large_Comet/Meshes/M_LRG_Comet_Skeleton.uasset differ diff --git a/Content/Characters/Player/Male/Male_Avg_Base/Fortnite_M_Avg_Player.uasset b/Content/Characters/Player/Male/Male_Avg_Base/Fortnite_M_Avg_Player.uasset new file mode 100644 index 00000000..b511ebb0 Binary files /dev/null and b/Content/Characters/Player/Male/Male_Avg_Base/Fortnite_M_Avg_Player.uasset differ diff --git a/Content/Characters/Player/Male/Male_Avg_Base/Fortnite_M_Avg_Player_Skeleton.uasset b/Content/Characters/Player/Male/Male_Avg_Base/Fortnite_M_Avg_Player_Skeleton.uasset new file mode 100644 index 00000000..d35cc4fe Binary files /dev/null and b/Content/Characters/Player/Male/Male_Avg_Base/Fortnite_M_Avg_Player_Skeleton.uasset differ diff --git a/Content/Characters/Player/Male/Medium/Base/SK_M_MALE_Base_Skeleton.uasset b/Content/Characters/Player/Male/Medium/Base/SK_M_MALE_Base_Skeleton.uasset new file mode 100644 index 00000000..09327e73 Binary files /dev/null and b/Content/Characters/Player/Male/Medium/Base/SK_M_MALE_Base_Skeleton.uasset differ diff --git a/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Skins/CV_04/Textures/M_Med_Soldier_Body_01_CV04_D.uasset b/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Skins/CV_04/Textures/M_Med_Soldier_Body_01_CV04_D.uasset new file mode 100644 index 00000000..f3e09d47 Binary files /dev/null and b/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Skins/CV_04/Textures/M_Med_Soldier_Body_01_CV04_D.uasset differ diff --git a/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Skins/Galaxy/Materials/M_MED_HIS_Sparks_Head_Celestial.uasset b/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Skins/Galaxy/Materials/M_MED_HIS_Sparks_Head_Celestial.uasset new file mode 100644 index 00000000..23171628 Binary files /dev/null and b/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Skins/Galaxy/Materials/M_MED_HIS_Sparks_Head_Celestial.uasset differ diff --git a/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Textures/M_Med_Soldier_Body_04_N.uasset b/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Textures/M_Med_Soldier_Body_04_N.uasset new file mode 100644 index 00000000..e9ea8b3e Binary files /dev/null and b/Content/Characters/Player/Male/Medium/Bodies/M_Med_Soldier_04/Textures/M_Med_Soldier_Body_04_N.uasset differ diff --git a/Content/Characters/Player/Male/Medium/Heads/M_MED_Tomcat_Head/Meshes/Male_Medium_Tomcat_Head.uasset b/Content/Characters/Player/Male/Medium/Heads/M_MED_Tomcat_Head/Meshes/Male_Medium_Tomcat_Head.uasset new file mode 100644 index 00000000..7c73c24b Binary files /dev/null and b/Content/Characters/Player/Male/Medium/Heads/M_MED_Tomcat_Head/Meshes/Male_Medium_Tomcat_Head.uasset differ diff --git a/Content/Global/Textures/Default/Mask_and_Gradients/T_Scratch_Mask.uasset b/Content/Global/Textures/Default/Mask_and_Gradients/T_Scratch_Mask.uasset new file mode 100644 index 00000000..6d35de36 Binary files /dev/null and b/Content/Global/Textures/Default/Mask_and_Gradients/T_Scratch_Mask.uasset differ diff --git a/Content/Items/Datatables/BaseItemRating.uasset b/Content/Items/Datatables/BaseItemRating.uasset new file mode 100644 index 00000000..217826ff Binary files /dev/null and b/Content/Items/Datatables/BaseItemRating.uasset differ diff --git a/Content/Items/Datatables/LootLevelData.uasset b/Content/Items/Datatables/LootLevelData.uasset new file mode 100644 index 00000000..5fec57b1 Binary files /dev/null and b/Content/Items/Datatables/LootLevelData.uasset differ diff --git a/Content/Items/Datatables/TransmogSacrificeData.uasset b/Content/Items/Datatables/TransmogSacrificeData.uasset new file mode 100644 index 00000000..052af0fc Binary files /dev/null and b/Content/Items/Datatables/TransmogSacrificeData.uasset differ diff --git a/Content/Packages/Fortress_SharedMaterials/Anime_Master_Material/M_MASTER_AnimeShading.uasset b/Content/Packages/Fortress_SharedMaterials/Anime_Master_Material/M_MASTER_AnimeShading.uasset new file mode 100644 index 00000000..e42498f9 Binary files /dev/null and b/Content/Packages/Fortress_SharedMaterials/Anime_Master_Material/M_MASTER_AnimeShading.uasset differ diff --git a/Content/Packages/Fortress_SharedMaterials/Base_Material/M_FN_Character_MASTER.uasset b/Content/Packages/Fortress_SharedMaterials/Base_Material/M_FN_Character_MASTER.uasset new file mode 100644 index 00000000..2362269b Binary files /dev/null and b/Content/Packages/Fortress_SharedMaterials/Base_Material/M_FN_Character_MASTER.uasset differ diff --git a/Content/Packages/Fortress_SharedMaterials/Base_Material/M_FN_Character_Wrap_MASTER.uasset b/Content/Packages/Fortress_SharedMaterials/Base_Material/M_FN_Character_Wrap_MASTER.uasset new file mode 100644 index 00000000..88ff6661 Binary files /dev/null and b/Content/Packages/Fortress_SharedMaterials/Base_Material/M_FN_Character_Wrap_MASTER.uasset differ diff --git a/Content/Packages/Fortress_SharedMaterials/Textures/SS_HeroSkin_02.uasset b/Content/Packages/Fortress_SharedMaterials/Textures/SS_HeroSkin_02.uasset new file mode 100644 index 00000000..85c31e26 Binary files /dev/null and b/Content/Packages/Fortress_SharedMaterials/Textures/SS_HeroSkin_02.uasset differ diff --git a/Content/Sounds/Attenuation/Athena_Glider_Thrust_Conc.uasset b/Content/Sounds/Attenuation/Athena_Glider_Thrust_Conc.uasset new file mode 100644 index 00000000..b7cd55ad Binary files /dev/null and b/Content/Sounds/Attenuation/Athena_Glider_Thrust_Conc.uasset differ diff --git a/Content/Sounds/Attenuation/Fort_Emotes_Athena_Att.uasset b/Content/Sounds/Attenuation/Fort_Emotes_Athena_Att.uasset new file mode 100644 index 00000000..31287ab3 Binary files /dev/null and b/Content/Sounds/Attenuation/Fort_Emotes_Athena_Att.uasset differ diff --git a/Content/Sounds/Attenuation/Fort_Player_Glider_OpenClose_Athena_Att.uasset b/Content/Sounds/Attenuation/Fort_Player_Glider_OpenClose_Athena_Att.uasset new file mode 100644 index 00000000..82c3c544 Binary files /dev/null and b/Content/Sounds/Attenuation/Fort_Player_Glider_OpenClose_Athena_Att.uasset differ diff --git a/Content/Sounds/Attenuation/Fort_Player_Glider_Thrust_Athena_Att.uasset b/Content/Sounds/Attenuation/Fort_Player_Glider_Thrust_Athena_Att.uasset new file mode 100644 index 00000000..0988c8c6 Binary files /dev/null and b/Content/Sounds/Attenuation/Fort_Player_Glider_Thrust_Athena_Att.uasset differ diff --git a/Content/Sounds/Attenuation/Fort_Weapon_Melee_Close_Athena_Att.uasset b/Content/Sounds/Attenuation/Fort_Weapon_Melee_Close_Athena_Att.uasset new file mode 100644 index 00000000..c2465d73 Binary files /dev/null and b/Content/Sounds/Attenuation/Fort_Weapon_Melee_Close_Athena_Att.uasset differ diff --git a/Content/Sounds/Attenuation/Fort_Weapon_Melee_Medium_Athena_Att.uasset b/Content/Sounds/Attenuation/Fort_Weapon_Melee_Medium_Athena_Att.uasset new file mode 100644 index 00000000..a14730d4 Binary files /dev/null and b/Content/Sounds/Attenuation/Fort_Weapon_Melee_Medium_Athena_Att.uasset differ diff --git a/Content/Sounds/Attenuation/Fort_Weapon_Pickaxe_Player_Impact_Att.uasset b/Content/Sounds/Attenuation/Fort_Weapon_Pickaxe_Player_Impact_Att.uasset new file mode 100644 index 00000000..082c200f Binary files /dev/null and b/Content/Sounds/Attenuation/Fort_Weapon_Pickaxe_Player_Impact_Att.uasset differ diff --git a/Content/Sounds/Concurrency_Rules/Athena_EmoteMusic_1P_Conc.uasset b/Content/Sounds/Concurrency_Rules/Athena_EmoteMusic_1P_Conc.uasset new file mode 100644 index 00000000..905d94b4 Binary files /dev/null and b/Content/Sounds/Concurrency_Rules/Athena_EmoteMusic_1P_Conc.uasset differ diff --git a/Content/Sounds/Concurrency_Rules/Athena_EmoteMusic_Conc.uasset b/Content/Sounds/Concurrency_Rules/Athena_EmoteMusic_Conc.uasset new file mode 100644 index 00000000..2adb4777 Binary files /dev/null and b/Content/Sounds/Concurrency_Rules/Athena_EmoteMusic_Conc.uasset differ diff --git a/Content/Sounds/Concurrency_Rules/Athena_GliderOpenClose_Conc.uasset b/Content/Sounds/Concurrency_Rules/Athena_GliderOpenClose_Conc.uasset new file mode 100644 index 00000000..9e51ee1a Binary files /dev/null and b/Content/Sounds/Concurrency_Rules/Athena_GliderOpenClose_Conc.uasset differ diff --git a/Content/Sounds/Concurrency_Rules/Player_Melee_Weapon_Conc.uasset b/Content/Sounds/Concurrency_Rules/Player_Melee_Weapon_Conc.uasset new file mode 100644 index 00000000..d90337cc Binary files /dev/null and b/Content/Sounds/Concurrency_Rules/Player_Melee_Weapon_Conc.uasset differ diff --git a/Content/Sounds/Effects/Fort_SubmixPreset_MasterComp_BR.uasset b/Content/Sounds/Effects/Fort_SubmixPreset_MasterComp_BR.uasset new file mode 100644 index 00000000..a3c8c738 Binary files /dev/null and b/Content/Sounds/Effects/Fort_SubmixPreset_MasterComp_BR.uasset differ diff --git a/Content/Sounds/Effects/Fort_SubmixPreset_MasterLimiter_BR.uasset b/Content/Sounds/Effects/Fort_SubmixPreset_MasterLimiter_BR.uasset new file mode 100644 index 00000000..0aab8277 Binary files /dev/null and b/Content/Sounds/Effects/Fort_SubmixPreset_MasterLimiter_BR.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Character.uasset b/Content/Sounds/FortSoundClassesAndModes/Character.uasset new file mode 100644 index 00000000..76310024 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Character.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Cinematics_InGameVideo.uasset b/Content/Sounds/FortSoundClassesAndModes/Cinematics_InGameVideo.uasset new file mode 100644 index 00000000..fec5eede Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Cinematics_InGameVideo.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/ClassName.uasset b/Content/Sounds/FortSoundClassesAndModes/ClassName.uasset new file mode 100644 index 00000000..e47a59c7 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/ClassName.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_01_VOIP.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_01_VOIP.uasset new file mode 100644 index 00000000..96274c57 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_01_VOIP.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_01_VOIP_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_01_VOIP_MixMod.uasset new file mode 100644 index 00000000..0b7bcacf Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_01_VOIP_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_02_Critical_Announcements.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_02_Critical_Announcements.uasset new file mode 100644 index 00000000..63899ae7 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_02_Critical_Announcements.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_03_Player_Health_Related.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_03_Player_Health_Related.uasset new file mode 100644 index 00000000..80a50601 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_03_Player_Health_Related.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_04_Normal_Announcements.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_04_Normal_Announcements.uasset new file mode 100644 index 00000000..4671141f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_04_Normal_Announcements.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_05_Close_Enemy.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_05_Close_Enemy.uasset new file mode 100644 index 00000000..85764bb6 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_05_Close_Enemy.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_06_Player_Attacks_Abilities.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_06_Player_Attacks_Abilities.uasset new file mode 100644 index 00000000..34d8e9b6 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_06_Player_Attacks_Abilities.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_07_Close_Enemy_Attacks_Abilities.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_07_Close_Enemy_Attacks_Abilities.uasset new file mode 100644 index 00000000..1f80411e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_07_Close_Enemy_Attacks_Abilities.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_08_Player_Searching_looting_crafting.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_08_Player_Searching_looting_crafting.uasset new file mode 100644 index 00000000..d2523bc9 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_08_Player_Searching_looting_crafting.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_09_Player_Enemy_Movement.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_09_Player_Enemy_Movement.uasset new file mode 100644 index 00000000..4662b780 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_09_Player_Enemy_Movement.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_10_Storm_Related.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_10_Storm_Related.uasset new file mode 100644 index 00000000..b533c47b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_10_Storm_Related.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_11_Base_music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_11_Base_music.uasset new file mode 100644 index 00000000..3be54503 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_11_Base_music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_12_Base_Ambient.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_12_Base_Ambient.uasset new file mode 100644 index 00000000..8b5526b2 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_12_Base_Ambient.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_MixMod.uasset new file mode 100644 index 00000000..f8cfaa57 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_Receive_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_Receive_MixMod.uasset new file mode 100644 index 00000000..1482729f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_Receive_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_Receive_Shield_Break_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_Receive_Shield_Break_MixMod.uasset new file mode 100644 index 00000000..8c742a90 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotification_Receive_Shield_Break_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_PSM.uasset new file mode 100644 index 00000000..33cfbc63 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_Receive_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_Receive_PSM.uasset new file mode 100644 index 00000000..18fbb6b2 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_Receive_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_Receive_Shield_Break_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_Receive_Shield_Break_PSM.uasset new file mode 100644 index 00000000..b4e4f6b3 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_2DHitNotifications_Receive_Shield_Break_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Abilities_SFX_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Abilities_SFX_Music.uasset new file mode 100644 index 00000000..99ee4f5a Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Abilities_SFX_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Athena.uasset new file mode 100644 index 00000000..3e220812 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOcean_Athena_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOcean_Athena_MixMod.uasset new file mode 100644 index 00000000..a682a87f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOcean_Athena_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOutdoors_Athena_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOutdoors_Athena_MixMod.uasset new file mode 100644 index 00000000..2c44d75a Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOutdoors_Athena_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOutdoors_Athena_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOutdoors_Athena_PSM.uasset new file mode 100644 index 00000000..bd4a5eff Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_DuckOutdoors_Athena_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Indoors_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Indoors_Athena.uasset new file mode 100644 index 00000000..0206a77f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Indoors_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_InteriorSpace_Duck_TileMix_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_InteriorSpace_Duck_TileMix_PSM.uasset new file mode 100644 index 00000000..50867c21 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_InteriorSpace_Duck_TileMix_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_NonLoop.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_NonLoop.uasset new file mode 100644 index 00000000..6f6928ef Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_NonLoop.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_Athena.uasset new file mode 100644 index 00000000..afc1a43e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_DuckOcean_Athena_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_DuckOcean_Athena_PSM.uasset new file mode 100644 index 00000000..402a390c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_DuckOcean_Athena_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_Weather_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_Weather_Athena.uasset new file mode 100644 index 00000000..1d885e4f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Outdoors_Weather_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Props_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Props_Athena.uasset new file mode 100644 index 00000000..e0145a8d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Props_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_TileMix.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_TileMix.uasset new file mode 100644 index 00000000..5a02d163 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_TileMix.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Trans_TileMix.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Trans_TileMix.uasset new file mode 100644 index 00000000..3ac0ea80 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Trans_TileMix.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Water.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Water.uasset new file mode 100644 index 00000000..b14ea879 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Ambient_Water.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Application_InFocus.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Application_InFocus.uasset new file mode 100644 index 00000000..54788765 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Application_InFocus.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Application_TrayNotification.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Application_TrayNotification.uasset new file mode 100644 index 00000000..3161f5cd Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Application_TrayNotification.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Athena_SFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Athena_SFX.uasset new file mode 100644 index 00000000..c82acf9d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Athena_SFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Atlas_Sounds_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Atlas_Sounds_PSM.uasset new file mode 100644 index 00000000..f7c2e702 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Atlas_Sounds_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_AudioGameplayVolumes.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_AudioGameplayVolumes.uasset new file mode 100644 index 00000000..cf5d3800 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_AudioGameplayVolumes.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BattleBus.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BattleBus.uasset new file mode 100644 index 00000000..e27a44a5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BattleBus.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight.uasset new file mode 100644 index 00000000..1447e304 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Foley.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Foley.uasset new file mode 100644 index 00000000..5af0d7b8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Foley.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_LTM_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_LTM_MixMod.uasset new file mode 100644 index 00000000..b1943192 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_LTM_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_LTM_Stinger.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_LTM_Stinger.uasset new file mode 100644 index 00000000..b897aedd Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_LTM_Stinger.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Music.uasset new file mode 100644 index 00000000..d89fb10b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_PlayerCritical.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_PlayerCritical.uasset new file mode 100644 index 00000000..49179506 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_PlayerCritical.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_PlayerCritical_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_PlayerCritical_Duck_MixMod.uasset new file mode 100644 index 00000000..88264345 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_PlayerCritical_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Projctile_Distant.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Projctile_Distant.uasset new file mode 100644 index 00000000..598607d7 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Projctile_Distant.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Projectile.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Projectile.uasset new file mode 100644 index 00000000..481a9dee Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_Projectile.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_SFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_SFX.uasset new file mode 100644 index 00000000..3d98af90 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_SFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_SFX_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_SFX_Duck_MixMod.uasset new file mode 100644 index 00000000..da6dd038 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_SFX_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_VOX.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_VOX.uasset new file mode 100644 index 00000000..3bbfefba Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_VOX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_VOX_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_VOX_Duck_MixMod.uasset new file mode 100644 index 00000000..aea86730 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BossFight_VOX_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_BulletImpact_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_BulletImpact_MixMod.uasset new file mode 100644 index 00000000..d8fcfe10 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_BulletImpact_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Cine_DuckIngame.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Cine_DuckIngame.uasset new file mode 100644 index 00000000..51de03f3 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Cine_DuckIngame.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Cine_DuckMusicSFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Cine_DuckMusicSFX.uasset new file mode 100644 index 00000000..4eca1214 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Cine_DuckMusicSFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Cinematics_InGame_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Cinematics_InGame_PSM.uasset new file mode 100644 index 00000000..e650a46e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Cinematics_InGame_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Cinematics_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Cinematics_PSM.uasset new file mode 100644 index 00000000..538a139d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Cinematics_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Combat_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Combat_Music.uasset new file mode 100644 index 00000000..00f1daf2 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Combat_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Consumables_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Consumables_Athena.uasset new file mode 100644 index 00000000..60171fca Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Consumables_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Creative_Ambient_TileMix.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Creative_Ambient_TileMix.uasset new file mode 100644 index 00000000..20fd8bb1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Creative_Ambient_TileMix.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic.uasset new file mode 100644 index 00000000..8ebabe75 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic_1P_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic_1P_PSM.uasset new file mode 100644 index 00000000..3cc6218a Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic_1P_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic_3P.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic_3P.uasset new file mode 100644 index 00000000..ccedc941 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_DanceMusic_3P.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Doors.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Doors.uasset new file mode 100644 index 00000000..5c055260 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Doors.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_DuckDefaultImpactSoundsQuieter_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_DuckDefaultImpactSoundsQuieter_MixMod.uasset new file mode 100644 index 00000000..739bd68f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_DuckDefaultImpactSoundsQuieter_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_DuckDefaultImpactSounds_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_DuckDefaultImpactSounds_MixMod.uasset new file mode 100644 index 00000000..ac2757e8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_DuckDefaultImpactSounds_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_DuckTileMix_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_DuckTileMix_MixMod.uasset new file mode 100644 index 00000000..e0ccd638 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_DuckTileMix_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Duck_NormalLobbyMusic.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Duck_NormalLobbyMusic.uasset new file mode 100644 index 00000000..4dc84475 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Duck_NormalLobbyMusic.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ElementalDamage_Player_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ElementalDamage_Player_PSM.uasset new file mode 100644 index 00000000..6a9c5464 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ElementalDamage_Player_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_EnemiesAreAttacking_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_EnemiesAreAttacking_MixMod.uasset new file mode 100644 index 00000000..f4b23784 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_EnemiesAreAttacking_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_EnemyFootstep_MixMod_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_EnemyFootstep_MixMod_Athena.uasset new file mode 100644 index 00000000..47a37a72 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_EnemyFootstep_MixMod_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_EnemyGlider_MixMod_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_EnemyGlider_MixMod_Athena.uasset new file mode 100644 index 00000000..62c23d5c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_EnemyGlider_MixMod_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Attack_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Attack_PSM.uasset new file mode 100644 index 00000000..4c98e2fb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Attack_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Bullet_Impact_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Bullet_Impact_PSM.uasset new file mode 100644 index 00000000..179b557c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Bullet_Impact_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_CriticalImpact_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_CriticalImpact_PSM.uasset new file mode 100644 index 00000000..2983cf38 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_CriticalImpact_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Duck_Normal_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Duck_Normal_MixMod.uasset new file mode 100644 index 00000000..910a234c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Duck_Normal_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Normal_Activity.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Normal_Activity.uasset new file mode 100644 index 00000000..a46df00d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Normal_Activity.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Spawn.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Spawn.uasset new file mode 100644 index 00000000..1b7a015b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_Spawn.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_SuperAttack_Duck_Normal_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_SuperAttack_Duck_Normal_PSM.uasset new file mode 100644 index 00000000..141bf128 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Enemy_SuperAttack_Duck_Normal_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Gameplay.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Gameplay.uasset new file mode 100644 index 00000000..a54ba0db Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Gameplay.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Master.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Master.uasset new file mode 100644 index 00000000..63c8daee Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Master.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Music.uasset new file mode 100644 index 00000000..ab859f03 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_PSM.uasset new file mode 100644 index 00000000..77149926 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted.uasset new file mode 100644 index 00000000..718203a4 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted_2D.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted_2D.uasset new file mode 100644 index 00000000..9851c2ee Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted_2D.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted_3D.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted_3D.uasset new file mode 100644 index 00000000..d8d1dc43 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_Scripted_3D.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_VO_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_VO_PSM.uasset new file mode 100644 index 00000000..6846721b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_VO_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Events_VO_Scripted.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_VO_Scripted.uasset new file mode 100644 index 00000000..8111f3d3 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Events_VO_Scripted.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Exploration_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Exploration_Music.uasset new file mode 100644 index 00000000..f2e17a72 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Exploration_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives.uasset new file mode 100644 index 00000000..3d3241b9 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_Duck_MixMod.uasset new file mode 100644 index 00000000..8a0f5696 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_PSM.uasset new file mode 100644 index 00000000..28afc2fe Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_Surround_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_Surround_PSM.uasset new file mode 100644 index 00000000..38d46042 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Large_Surround_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium.uasset new file mode 100644 index 00000000..cac34e22 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium_Duck_MixMod.uasset new file mode 100644 index 00000000..0310fde8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium_PSM.uasset new file mode 100644 index 00000000..f12fa769 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Explosives_Medium_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna.uasset new file mode 100644 index 00000000..46d90863 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_BodyFoley.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_BodyFoley.uasset new file mode 100644 index 00000000..644f575f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_BodyFoley.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Steps_Normal.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Steps_Normal.uasset new file mode 100644 index 00000000..0976f888 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Steps_Normal.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Steps_Tactical.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Steps_Tactical.uasset new file mode 100644 index 00000000..fd3a387e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Steps_Tactical.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Vocalization_Normal.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Vocalization_Normal.uasset new file mode 100644 index 00000000..365f1fd1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Vocalization_Normal.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Vocalization_Tactical.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Vocalization_Tactical.uasset new file mode 100644 index 00000000..099e4dd1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Fauna_Vocalization_Tactical.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets.uasset new file mode 100644 index 00000000..4a20e4f1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_BackBlings.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_BackBlings.uasset new file mode 100644 index 00000000..8ad01f12 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_BackBlings.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Creative.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Creative.uasset new file mode 100644 index 00000000..3b5934ea Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Creative.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Items.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Items.uasset new file mode 100644 index 00000000..b19c3d88 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Items.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_LTM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_LTM.uasset new file mode 100644 index 00000000..128dc4dc Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_LTM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Pet.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Pet.uasset new file mode 100644 index 00000000..e5b405b0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_Pet.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_StW.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_StW.uasset new file mode 100644 index 00000000..b6e0552e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gadgets_StW.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_GliderInAir_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_GliderInAir_MixMod.uasset new file mode 100644 index 00000000..f06eb944 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_GliderInAir_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders.uasset new file mode 100644 index 00000000..056d11d6 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_InAir.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_InAir.uasset new file mode 100644 index 00000000..a54494c5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_InAir.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_InAir_1P.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_InAir_1P.uasset new file mode 100644 index 00000000..2409990d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_InAir_1P.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_Land.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_Land.uasset new file mode 100644 index 00000000..72e1c26e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_Land.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_Music.uasset new file mode 100644 index 00000000..7e3a6e42 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Gliders_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Hideables_PhoneBooth.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Hideables_PhoneBooth.uasset new file mode 100644 index 00000000..0af53230 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Hideables_PhoneBooth.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Hideables_Tunnel_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Hideables_Tunnel_MixMod.uasset new file mode 100644 index 00000000..8ba74d80 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Hideables_Tunnel_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds.uasset new file mode 100644 index 00000000..b359dddf Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Close.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Close.uasset new file mode 100644 index 00000000..129cd5e9 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Close.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Default_Close.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Default_Close.uasset new file mode 100644 index 00000000..baaaea3d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Default_Close.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Distant.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Distant.uasset new file mode 100644 index 00000000..459eb08d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_Distant.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_DuckDefaultQuieter_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_DuckDefaultQuieter_PSM.uasset new file mode 100644 index 00000000..6a5da251 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_DuckDefaultQuieter_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_DuckDefault_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_DuckDefault_PSM.uasset new file mode 100644 index 00000000..c5a2eecf Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ImpactSounds_DuckDefault_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Impact_Player_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Impact_Player_PSM.uasset new file mode 100644 index 00000000..ba85edd5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Impact_Player_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_IncreaseWeaponMeleeImpact_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_IncreaseWeaponMeleeImpact_MixMod.uasset new file mode 100644 index 00000000..27c0b693 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_IncreaseWeaponMeleeImpact_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Large_Creatures.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Large_Creatures.uasset new file mode 100644 index 00000000..b8f484c5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Large_Creatures.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Large_Loot_Drop_Collect_Suppress.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Large_Loot_Drop_Collect_Suppress.uasset new file mode 100644 index 00000000..26e6ac9e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Large_Loot_Drop_Collect_Suppress.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld.uasset new file mode 100644 index 00000000..2a5d7003 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Loudspeakers.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Loudspeakers.uasset new file mode 100644 index 00000000..4fa0f376 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Loudspeakers.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Music.uasset new file mode 100644 index 00000000..7644414c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Theater.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Theater.uasset new file mode 100644 index 00000000..d0a33d2b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Papaya_Theater.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Risky_Active.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Risky_Active.uasset new file mode 100644 index 00000000..27e95a34 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_InWorld_Risky_Active.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Live_Raisin.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_Raisin.uasset new file mode 100644 index 00000000..0d662ba4 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Live_Raisin.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Lobby_MusicPacks.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Lobby_MusicPacks.uasset new file mode 100644 index 00000000..a0e338f9 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Lobby_MusicPacks.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Loot_Drop_Collect_Suppress.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Loot_Drop_Collect_Suppress.uasset new file mode 100644 index 00000000..5f2485e0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Loot_Drop_Collect_Suppress.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Loot_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Loot_Duck_MixMod.uasset new file mode 100644 index 00000000..46f86f39 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Loot_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_LowHealth_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_LowHealth_MixMod.uasset new file mode 100644 index 00000000..cb3e1c84 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_LowHealth_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_LowHealth_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_LowHealth_PSM.uasset new file mode 100644 index 00000000..3be91292 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_LowHealth_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_IngameSounds.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_IngameSounds.uasset new file mode 100644 index 00000000..40a14111 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_IngameSounds.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_Ingame_Important_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_Ingame_Important_MixMod.uasset new file mode 100644 index 00000000..b599835b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_Ingame_Important_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_Ingame_Important_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_Ingame_Important_PSM.uasset new file mode 100644 index 00000000..be8e0add Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Mission_Ingame_Important_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music.uasset new file mode 100644 index 00000000..71272a2a Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_MusicStinger_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_MusicStinger_MixMod.uasset new file mode 100644 index 00000000..4bb54cab Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_MusicStinger_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Boss_Stinger.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Boss_Stinger.uasset new file mode 100644 index 00000000..ef7a2a55 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Boss_Stinger.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Day_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Day_PSM.uasset new file mode 100644 index 00000000..b14e41ca Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Day_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_SFX_Voice.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_SFX_Voice.uasset new file mode 100644 index 00000000..558daa17 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_SFX_Voice.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_BossFight_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_BossFight_Music.uasset new file mode 100644 index 00000000..e2894cff Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_BossFight_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_Combat_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_Combat_Music.uasset new file mode 100644 index 00000000..e1cd72cb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_Combat_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_PointSourceMusic.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_PointSourceMusic.uasset new file mode 100644 index 00000000..31f7b101 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_PointSourceMusic.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_Quest_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_Quest_Music.uasset new file mode 100644 index 00000000..5e5db137 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Decrease_with_Quest_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote.uasset new file mode 100644 index 00000000..9ce5c9a5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_1P_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_1P_PSM.uasset new file mode 100644 index 00000000..fbc915ff Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_1P_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_3P.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_3P.uasset new file mode 100644 index 00000000..83700bc4 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_3P.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_MixMod.uasset new file mode 100644 index 00000000..461e6459 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Emote_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Gameplay.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Gameplay.uasset new file mode 100644 index 00000000..05e52031 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Gameplay.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Glider_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Glider_MixMod.uasset new file mode 100644 index 00000000..398521c8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Glider_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_LTM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_LTM.uasset new file mode 100644 index 00000000..93f20eb4 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_LTM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Menu_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Menu_PSM.uasset new file mode 100644 index 00000000..c96b77a6 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Menu_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_MissionCritical_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_MissionCritical_PSM.uasset new file mode 100644 index 00000000..fe99737d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_MissionCritical_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_NPC.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_NPC.uasset new file mode 100644 index 00000000..5a0b2e1e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_NPC.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_POI_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_POI_MixMod.uasset new file mode 100644 index 00000000..6f1f4147 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_POI_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_POI_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_POI_PSM.uasset new file mode 100644 index 00000000..860bf5b1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_POI_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Quest_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Quest_MixMod.uasset new file mode 100644 index 00000000..485c3658 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Quest_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Quest_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Quest_PSM.uasset new file mode 100644 index 00000000..b583593b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Quest_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Skydive_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Skydive_MixMod.uasset new file mode 100644 index 00000000..ed2aa8da Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Skydive_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Skydive_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Skydive_PSM.uasset new file mode 100644 index 00000000..828aa746 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Skydive_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Stinger_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Stinger_PSM.uasset new file mode 100644 index 00000000..aa707deb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Music_Stinger_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Physics.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Physics.uasset new file mode 100644 index 00000000..90f452cb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Physics.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Physics_Impact.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Physics_Impact.uasset new file mode 100644 index 00000000..c6ab821c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Physics_Impact.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Physics_Looping.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Physics_Looping.uasset new file mode 100644 index 00000000..5efd24d5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Physics_Looping.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_MixMod.uasset new file mode 100644 index 00000000..2cd63516 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_PSM.uasset new file mode 100644 index 00000000..c0c38844 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_UI.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_UI.uasset new file mode 100644 index 00000000..08978460 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Construction_UI.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley.uasset new file mode 100644 index 00000000..7714d5dc Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_1P_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_1P_Athena.uasset new file mode 100644 index 00000000..24cc64b5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_1P_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_3P_Enemy_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_3P_Enemy_Athena.uasset new file mode 100644 index 00000000..16025d67 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_3P_Enemy_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_3P_Friendly_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_3P_Friendly_Athena.uasset new file mode 100644 index 00000000..115c3aa3 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_3P_Friendly_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_Athena.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_Athena.uasset new file mode 100644 index 00000000..3cdcd3be Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Foley_Athena.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Duck_LowMed_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Duck_LowMed_MixMod.uasset new file mode 100644 index 00000000..e5971203 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Duck_LowMed_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_High_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_High_PSM.uasset new file mode 100644 index 00000000..b066da04 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_High_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Low_Collect.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Low_Collect.uasset new file mode 100644 index 00000000..50a86340 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Low_Collect.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Low_Drop.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Low_Drop.uasset new file mode 100644 index 00000000..5ac84741 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Player_Loot_Low_Drop.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_PointSource_Music_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_PointSource_Music_PSM.uasset new file mode 100644 index 00000000..0055f6bb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_PointSource_Music_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_ProjectileEffects.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_ProjectileEffects.uasset new file mode 100644 index 00000000..9d182498 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_ProjectileEffects.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_QuestEvent_Callout.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_QuestEvent_Callout.uasset new file mode 100644 index 00000000..701c0aa8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_QuestEvent_Callout.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_QuestEvent_Duck_MenuMusic_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_QuestEvent_Duck_MenuMusic_MixMod.uasset new file mode 100644 index 00000000..6fb4e977 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_QuestEvent_Duck_MenuMusic_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_QuestVO_Ingame_DuckSFXAndMusic_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_QuestVO_Ingame_DuckSFXAndMusic_MixMod.uasset new file mode 100644 index 00000000..908eecc5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_QuestVO_Ingame_DuckSFXAndMusic_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Quest_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Quest_Music.uasset new file mode 100644 index 00000000..ddeac67c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Quest_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Quests.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Quests.uasset new file mode 100644 index 00000000..3a7df0a5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Quests.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_Decrease_Muisc_Mixmod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_Decrease_Muisc_Mixmod.uasset new file mode 100644 index 00000000..1cad2a44 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_Decrease_Muisc_Mixmod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_InGame.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_InGame.uasset new file mode 100644 index 00000000..0262f33c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_InGame.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_InGame_ExcludeFromFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_InGame_ExcludeFromFX.uasset new file mode 100644 index 00000000..6b600909 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_SFX_InGame_ExcludeFromFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Foley.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Foley.uasset new file mode 100644 index 00000000..eb7e1c4b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Foley.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Reward_Loop_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Reward_Loop_PSM.uasset new file mode 100644 index 00000000..7733fcc5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Reward_Loop_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Reward_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Reward_PSM.uasset new file mode 100644 index 00000000..581792c6 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Searching_Reward_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Smasher.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Smasher.uasset new file mode 100644 index 00000000..66ec3dfc Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Smasher.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Smasher_Critical.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Smasher_Critical.uasset new file mode 100644 index 00000000..ac1c5579 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Smasher_Critical.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Storms_Duck_Ambient.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Storms_Duck_Ambient.uasset new file mode 100644 index 00000000..ac15e4e6 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Storms_Duck_Ambient.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Storms_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Storms_PSM.uasset new file mode 100644 index 00000000..1d40a0af Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Storms_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_SubMenu_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_SubMenu_Music.uasset new file mode 100644 index 00000000..28b704f8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_SubMenu_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Suppress_NormalMusicAndHealth_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Suppress_NormalMusicAndHealth_MixMod.uasset new file mode 100644 index 00000000..5191b7a0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Suppress_NormalMusicAndHealth_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Suppress_NormalMusic_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Suppress_NormalMusic_MixMod.uasset new file mode 100644 index 00000000..675ba8f1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Suppress_NormalMusic_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Taker.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Taker.uasset new file mode 100644 index 00000000..bab506f0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Taker.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Taker_Duck_NormalMusic_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Taker_Duck_NormalMusic_MixMod.uasset new file mode 100644 index 00000000..9784cff8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Taker_Duck_NormalMusic_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Trans_Suppress_NormalMusic_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Trans_Suppress_NormalMusic_MixMod.uasset new file mode 100644 index 00000000..f72117a2 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Trans_Suppress_NormalMusic_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Traps.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Traps.uasset new file mode 100644 index 00000000..fe5f9d63 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Traps.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Traps_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Traps_Duck_MixMod.uasset new file mode 100644 index 00000000..14e64d4e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Traps_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Traps_Placement.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Traps_Placement.uasset new file mode 100644 index 00000000..98030669 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Traps_Placement.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_TrayNotifications_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_TrayNotifications_MixMod.uasset new file mode 100644 index 00000000..6320bc86 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_TrayNotifications_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Troll.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Troll.uasset new file mode 100644 index 00000000..c6465664 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Troll.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI.uasset new file mode 100644 index 00000000..cd8b345b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Creative_Important.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Creative_Important.uasset new file mode 100644 index 00000000..bcee91ac Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Creative_Important.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Creative_Important_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Creative_Important_Duck_MixMod.uasset new file mode 100644 index 00000000..21cf110b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Creative_Important_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_EODScore_Mix.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_EODScore_Mix.uasset new file mode 100644 index 00000000..17f536af Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_EODScore_Mix.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_EODScore_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_EODScore_PSM.uasset new file mode 100644 index 00000000..4c5bf6ab Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_EODScore_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_HomeBase_DuckMusic_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_HomeBase_DuckMusic_MixMod.uasset new file mode 100644 index 00000000..2cd7db92 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_HomeBase_DuckMusic_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_HomeBase_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_HomeBase_PSM.uasset new file mode 100644 index 00000000..75c2fc14 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_HomeBase_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Important.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Important.uasset new file mode 100644 index 00000000..2083001f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Important.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Important_Duck_Music_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Important_Duck_Music_MixMod.uasset new file mode 100644 index 00000000..455f98e0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Important_Duck_Music_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGame.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGame.uasset new file mode 100644 index 00000000..b32f5909 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGame.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGameScore_Mix.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGameScore_Mix.uasset new file mode 100644 index 00000000..e34e715c Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGameScore_Mix.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGameScore_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGameScore_PSM.uasset new file mode 100644 index 00000000..65fc2868 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_InGameScore_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Timer.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Timer.uasset new file mode 100644 index 00000000..69e007b2 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_UI_Timer.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Announcement_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Announcement_MixMod.uasset new file mode 100644 index 00000000..77876008 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Announcement_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Announcement_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Announcement_PSM.uasset new file mode 100644 index 00000000..dcf92a29 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Announcement_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_AI.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_AI.uasset new file mode 100644 index 00000000..3ed4bea8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_AI.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_Henchmen.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_Henchmen.uasset new file mode 100644 index 00000000..000c8457 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_Henchmen.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_HenchmenBoss.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_HenchmenBoss.uasset new file mode 100644 index 00000000..58bed53e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_HenchmenBoss.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_QuestGiver.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_QuestGiver.uasset new file mode 100644 index 00000000..2962541a Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_QuestGiver.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_Wildlife.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_Wildlife.uasset new file mode 100644 index 00000000..93ab29d1 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_BR_Wildlife.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Event.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Event.uasset new file mode 100644 index 00000000..d3eb2187 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Event.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC.uasset new file mode 100644 index 00000000..7d30c598 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC_BR.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC_BR.uasset new file mode 100644 index 00000000..1fcd0ccd Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC_BR.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC_STW.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC_STW.uasset new file mode 100644 index 00000000..94b84162 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_NPC_STW.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Players_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Players_MixMod.uasset new file mode 100644 index 00000000..85aa3aea Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Players_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Players_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Players_PSM.uasset new file mode 100644 index 00000000..e4063594 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Players_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Ray_Announcement_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Ray_Announcement_PSM.uasset new file mode 100644 index 00000000..46fa8d9e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_Ray_Announcement_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_STW_Enemy.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_STW_Enemy.uasset new file mode 100644 index 00000000..0df931b4 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_STW_Enemy.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VO_STW_Friendly.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_STW_Friendly.uasset new file mode 100644 index 00000000..85e9a965 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VO_STW_Friendly.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles.uasset new file mode 100644 index 00000000..700c06e4 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Boost_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Boost_Music.uasset new file mode 100644 index 00000000..7e4e9cd5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Boost_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Engine.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Engine.uasset new file mode 100644 index 00000000..f96d3e25 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Engine.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Exterior.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Exterior.uasset new file mode 100644 index 00000000..fa8089c0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Exterior.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Interior.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Interior.uasset new file mode 100644 index 00000000..00eb6d8f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Vehicles_Interior.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VictoryRoyale_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VictoryRoyale_MixMod.uasset new file mode 100644 index 00000000..755941bf Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VictoryRoyale_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_VictoryRoyale_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_VictoryRoyale_PSM.uasset new file mode 100644 index 00000000..29653aab Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_VictoryRoyale_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Victory_Music_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Victory_Music_PSM.uasset new file mode 100644 index 00000000..a06e29e7 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Victory_Music_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_WarEffort_Funding.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_WarEffort_Funding.uasset new file mode 100644 index 00000000..390a262f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_WarEffort_Funding.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Water.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Water.uasset new file mode 100644 index 00000000..cb914933 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Water.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Ambient.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Ambient.uasset new file mode 100644 index 00000000..6f15a422 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Ambient.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Ambient_DuckGlobal_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Ambient_DuckGlobal_PSM.uasset new file mode 100644 index 00000000..29336365 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Ambient_DuckGlobal_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Water_CameraFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_CameraFX.uasset new file mode 100644 index 00000000..c4edfd59 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_CameraFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Interaction.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Interaction.uasset new file mode 100644 index 00000000..c9989c43 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Water_Interaction.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee.uasset new file mode 100644 index 00000000..7ee6502b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_BiggerDuck_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_BiggerDuck_PSM.uasset new file mode 100644 index 00000000..ac2d9bf9 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_BiggerDuck_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_Duck_MixMod.uasset new file mode 100644 index 00000000..bfd4918b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_PSM.uasset new file mode 100644 index 00000000..50bc70a9 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponMelee_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponProjectile.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponProjectile.uasset new file mode 100644 index 00000000..42403793 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_WeaponProjectile.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_CriticalDuck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_CriticalDuck_MixMod.uasset new file mode 100644 index 00000000..5dbffa62 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_CriticalDuck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_DegradeDuck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_DegradeDuck_MixMod.uasset new file mode 100644 index 00000000..a028b5bb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_DegradeDuck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Duck_FirstPerson_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Duck_FirstPerson_MixMod.uasset new file mode 100644 index 00000000..53ee53b0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Duck_FirstPerson_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Duck_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Duck_MixMod.uasset new file mode 100644 index 00000000..6ecd7845 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Duck_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Foley.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Foley.uasset new file mode 100644 index 00000000..c1e81d76 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Foley.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_LowAmmoCritical_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_LowAmmoCritical_PSM.uasset new file mode 100644 index 00000000..4e95810e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_LowAmmoCritical_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_LowAmmoDegraded_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_LowAmmoDegraded_PSM.uasset new file mode 100644 index 00000000..cf5d91fb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_LowAmmoDegraded_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_ADS_Scope_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_ADS_Scope_MixMod.uasset new file mode 100644 index 00000000..3e8339db Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_ADS_Scope_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_ADS_Scope_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_ADS_Scope_PSM.uasset new file mode 100644 index 00000000..dfa95553 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_ADS_Scope_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_Distant.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_Distant.uasset new file mode 100644 index 00000000..07e94053 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_Distant.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_FirstPerson_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_FirstPerson_PSM.uasset new file mode 100644 index 00000000..81da2053 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_FirstPerson_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_PSM.uasset new file mode 100644 index 00000000..67ccbcef Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapon_Projectile_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Fort_Weapons.uasset b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapons.uasset new file mode 100644 index 00000000..6b2ef23e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Fort_Weapons.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience.uasset new file mode 100644 index 00000000..7a8ddecb Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_InBuilding_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_InBuilding_PSM.uasset new file mode 100644 index 00000000..fef536af Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_InBuilding_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_OpenSpace_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_OpenSpace_PSM.uasset new file mode 100644 index 00000000..49b1d268 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_OpenSpace_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_Underground_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_Underground_PSM.uasset new file mode 100644 index 00000000..a0b31fcf Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_Ambience_Underground_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAllAmbience.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAllAmbience.uasset new file mode 100644 index 00000000..290b48a5 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAllAmbience.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_InBuilding.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_InBuilding.uasset new file mode 100644 index 00000000..15ccd71f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_InBuilding.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_OpenSpace.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_OpenSpace.uasset new file mode 100644 index 00000000..4a58c874 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_OpenSpace.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_Underground.uasset b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_Underground.uasset new file mode 100644 index 00000000..b25d3733 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Foundations_DuckAmbience_Underground.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Frontend_SFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Frontend_SFX.uasset new file mode 100644 index 00000000..7df49972 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Frontend_SFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Master.uasset b/Content/Sounds/FortSoundClassesAndModes/Master.uasset new file mode 100644 index 00000000..add42fe3 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Master.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/MediaCinematics.uasset b/Content/Sounds/FortSoundClassesAndModes/MediaCinematics.uasset new file mode 100644 index 00000000..5eb05c8d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/MediaCinematics.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Mole_Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Mole_Music.uasset new file mode 100644 index 00000000..570fdf3e Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Mole_Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Mole_SFX.uasset b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX.uasset new file mode 100644 index 00000000..cbc808f2 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Actions.uasset b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Actions.uasset new file mode 100644 index 00000000..ede4d779 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Actions.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Ambient.uasset b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Ambient.uasset new file mode 100644 index 00000000..7221a622 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Ambient.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Footsteps.uasset b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Footsteps.uasset new file mode 100644 index 00000000..cad04d96 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_Footsteps.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_UI.uasset b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_UI.uasset new file mode 100644 index 00000000..95232a06 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Mole_SFX_UI.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Music.uasset b/Content/Sounds/FortSoundClassesAndModes/Music.uasset new file mode 100644 index 00000000..04967130 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Music.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/PictureInPicture.uasset b/Content/Sounds/FortSoundClassesAndModes/PictureInPicture.uasset new file mode 100644 index 00000000..c7b20f67 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/PictureInPicture.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/PictureInPicture_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/PictureInPicture_MixMod.uasset new file mode 100644 index 00000000..7133c67f Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/PictureInPicture_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Player_ElementalDamage_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Player_ElementalDamage_MixMod.uasset new file mode 100644 index 00000000..6a5b80a0 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Player_ElementalDamage_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Player_Impact_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Player_Impact_MixMod.uasset new file mode 100644 index 00000000..cb9b0072 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Player_Impact_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Player_Impact_POI_Music_MixMod.uasset b/Content/Sounds/FortSoundClassesAndModes/Player_Impact_POI_Music_MixMod.uasset new file mode 100644 index 00000000..03a1a536 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Player_Impact_POI_Music_MixMod.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/RenderedCinematics.uasset b/Content/Sounds/FortSoundClassesAndModes/RenderedCinematics.uasset new file mode 100644 index 00000000..c6313683 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/RenderedCinematics.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/RiskyReels_Event_Screen_SoundMix.uasset b/Content/Sounds/FortSoundClassesAndModes/RiskyReels_Event_Screen_SoundMix.uasset new file mode 100644 index 00000000..7878ca1d Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/RiskyReels_Event_Screen_SoundMix.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/SFX.uasset b/Content/Sounds/FortSoundClassesAndModes/SFX.uasset new file mode 100644 index 00000000..0d6f4b3b Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/SFX.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Trans_DuckFoundations_PSM.uasset b/Content/Sounds/FortSoundClassesAndModes/Trans_DuckFoundations_PSM.uasset new file mode 100644 index 00000000..f6948a20 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Trans_DuckFoundations_PSM.uasset differ diff --git a/Content/Sounds/FortSoundClassesAndModes/Voice.uasset b/Content/Sounds/FortSoundClassesAndModes/Voice.uasset new file mode 100644 index 00000000..17225ac8 Binary files /dev/null and b/Content/Sounds/FortSoundClassesAndModes/Voice.uasset differ diff --git a/Content/Sounds/Fort_GamePlay_Sounds/Loot/Collect_Loot.uasset b/Content/Sounds/Fort_GamePlay_Sounds/Loot/Collect_Loot.uasset new file mode 100644 index 00000000..d40fb122 Binary files /dev/null and b/Content/Sounds/Fort_GamePlay_Sounds/Loot/Collect_Loot.uasset differ diff --git a/Content/Sounds/Fort_Music/Dance/dance_soldier_03.uasset b/Content/Sounds/Fort_Music/Dance/dance_soldier_03.uasset new file mode 100644 index 00000000..2c25af62 Binary files /dev/null and b/Content/Sounds/Fort_Music/Dance/dance_soldier_03.uasset differ diff --git a/Content/Sounds/Fort_Music/Dance/ninja_dance_01.uasset b/Content/Sounds/Fort_Music/Dance/ninja_dance_01.uasset new file mode 100644 index 00000000..7abc5a5b Binary files /dev/null and b/Content/Sounds/Fort_Music/Dance/ninja_dance_01.uasset differ diff --git a/Content/Sounds/Fort_Music/Dance/outlander_dance01.uasset b/Content/Sounds/Fort_Music/Dance/outlander_dance01.uasset new file mode 100644 index 00000000..fb524865 Binary files /dev/null and b/Content/Sounds/Fort_Music/Dance/outlander_dance01.uasset differ diff --git a/Content/Sounds/Fort_Test_Sounds/5_seconds_silence.uasset b/Content/Sounds/Fort_Test_Sounds/5_seconds_silence.uasset new file mode 100644 index 00000000..369fc803 Binary files /dev/null and b/Content/Sounds/Fort_Test_Sounds/5_seconds_silence.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Exit_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Exit_PSM.uasset new file mode 100644 index 00000000..517b2958 Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Exit_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_End_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_End_PSM.uasset new file mode 100644 index 00000000..ab77b754 Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_End_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_Main_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_Main_PSM.uasset new file mode 100644 index 00000000..40c02d3e Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_Main_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_Start_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_Start_PSM.uasset new file mode 100644 index 00000000..079e6f7f Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Phase_Start_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_VO_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_VO_PSM.uasset new file mode 100644 index 00000000..1ae95b70 Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_VO_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Warp_Amb_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Warp_Amb_PSM.uasset new file mode 100644 index 00000000..6ecad9af Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Warp_Amb_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Warp_Weapon_PSM.uasset b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Warp_Weapon_PSM.uasset new file mode 100644 index 00000000..7aeafeb7 Binary files /dev/null and b/Content/Sounds/LTMs/BR/Fort_LTM_BR_InGame_Warp_Weapon_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Ambience.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Ambience.uasset new file mode 100644 index 00000000..77fc385c Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Ambience.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Master.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Master.uasset new file mode 100644 index 00000000..b3298ff9 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Master.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_End_AMB_3D.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_End_AMB_3D.uasset new file mode 100644 index 00000000..d7a5f1ec Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_End_AMB_3D.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_End_Amb_2D_PSM.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_End_Amb_2D_PSM.uasset new file mode 100644 index 00000000..942351a5 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_End_Amb_2D_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Main_Amb_2D_PSM.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Main_Amb_2D_PSM.uasset new file mode 100644 index 00000000..c2cef30c Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Main_Amb_2D_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Main_Amb_3D.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Main_Amb_3D.uasset new file mode 100644 index 00000000..d3546892 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Main_Amb_3D.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Start_Amb_2D_PSM.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Start_Amb_2D_PSM.uasset new file mode 100644 index 00000000..39473820 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Start_Amb_2D_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Start_Amb_3D.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Start_Amb_3D.uasset new file mode 100644 index 00000000..4021422a Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Phase_Start_Amb_3D.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Item.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Item.uasset new file mode 100644 index 00000000..16848dd6 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Item.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Item_1P.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Item_1P.uasset new file mode 100644 index 00000000..8b87f4f5 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Item_1P.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Weapon.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Weapon.uasset new file mode 100644 index 00000000..a27db01c Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Weapon.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Weapon_1P.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Weapon_1P.uasset new file mode 100644 index 00000000..dc69cb49 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Special_Weapon_1P.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_VO.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_VO.uasset new file mode 100644 index 00000000..c9b39cbd Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_VO.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp.uasset new file mode 100644 index 00000000..7bac5d25 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp_1P_PSM.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp_1P_PSM.uasset new file mode 100644 index 00000000..d4bf4225 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp_1P_PSM.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp_3P.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp_3P.uasset new file mode 100644 index 00000000..a7dd092c Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_LTM_BR_InGame_Warp_3P.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_VictoryRoyaleLTM_MixMod.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_VictoryRoyaleLTM_MixMod.uasset new file mode 100644 index 00000000..d21b2415 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_VictoryRoyaleLTM_MixMod.uasset differ diff --git a/Content/Sounds/LTMs/BR/SoundClasses/Fort_VictoryRoyaleLTM_PSM.uasset b/Content/Sounds/LTMs/BR/SoundClasses/Fort_VictoryRoyaleLTM_PSM.uasset new file mode 100644 index 00000000..c767b989 Binary files /dev/null and b/Content/Sounds/LTMs/BR/SoundClasses/Fort_VictoryRoyaleLTM_PSM.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_AudioAnalysis_Music.uasset b/Content/Sounds/SubMixes/Fortnite_AudioAnalysis_Music.uasset new file mode 100644 index 00000000..3fe693dd Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_AudioAnalysis_Music.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_AudioAnalysis_Music_Submix.uasset b/Content/Sounds/SubMixes/Fortnite_AudioAnalysis_Music_Submix.uasset new file mode 100644 index 00000000..505772da Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_AudioAnalysis_Music_Submix.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_BaseDefaultSubmix.uasset b/Content/Sounds/SubMixes/Fortnite_BaseDefaultSubmix.uasset new file mode 100644 index 00000000..0c7228b2 Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_BaseDefaultSubmix.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_InGameSFX_Submix.uasset b/Content/Sounds/SubMixes/Fortnite_InGameSFX_Submix.uasset new file mode 100644 index 00000000..dc69a644 Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_InGameSFX_Submix.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_LicensedContent.uasset b/Content/Sounds/SubMixes/Fortnite_LicensedContent.uasset new file mode 100644 index 00000000..170d37fa Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_LicensedContent.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_LicensedContentSubmix.uasset b/Content/Sounds/SubMixes/Fortnite_LicensedContentSubmix.uasset new file mode 100644 index 00000000..7efe954d Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_LicensedContentSubmix.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_LicensedContent_2D.uasset b/Content/Sounds/SubMixes/Fortnite_LicensedContent_2D.uasset new file mode 100644 index 00000000..498b0e96 Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_LicensedContent_2D.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_MasterAudioAnalysisSubmix.uasset b/Content/Sounds/SubMixes/Fortnite_MasterAudioAnalysisSubmix.uasset new file mode 100644 index 00000000..af06cae9 Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_MasterAudioAnalysisSubmix.uasset differ diff --git a/Content/Sounds/SubMixes/Fortnite_MasterSubMixBus.uasset b/Content/Sounds/SubMixes/Fortnite_MasterSubMixBus.uasset new file mode 100644 index 00000000..396974e0 Binary files /dev/null and b/Content/Sounds/SubMixes/Fortnite_MasterSubMixBus.uasset differ diff --git a/Content/Sounds/SubMixes/Sidechain/DynProc_Sidechain_VOAudioBus.uasset b/Content/Sounds/SubMixes/Sidechain/DynProc_Sidechain_VOAudioBus.uasset new file mode 100644 index 00000000..e4ef179e Binary files /dev/null and b/Content/Sounds/SubMixes/Sidechain/DynProc_Sidechain_VOAudioBus.uasset differ diff --git a/Content/Splash/EdSplash.bmp b/Content/Splash/EdSplash.bmp new file mode 100644 index 00000000..cee0ac58 Binary files /dev/null and b/Content/Splash/EdSplash.bmp differ diff --git a/Content/Splash/Splash.bmp b/Content/Splash/Splash.bmp new file mode 100644 index 00000000..cee0ac58 Binary files /dev/null and b/Content/Splash/Splash.bmp differ diff --git a/Content/UI/Fort_HUD/UI_Radar_EnemyDot_White.uasset b/Content/UI/Fort_HUD/UI_Radar_EnemyDot_White.uasset new file mode 100644 index 00000000..f595b2a7 Binary files /dev/null and b/Content/UI/Fort_HUD/UI_Radar_EnemyDot_White.uasset differ diff --git a/Content/UI/Foundation/Input/CosmeticPreviewActionDataTable.uasset b/Content/UI/Foundation/Input/CosmeticPreviewActionDataTable.uasset new file mode 100644 index 00000000..239267bd Binary files /dev/null and b/Content/UI/Foundation/Input/CosmeticPreviewActionDataTable.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-Disagree-L.uasset b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-Disagree-L.uasset new file mode 100644 index 00000000..7f14a8c5 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-Disagree-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-Disagree.uasset b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-Disagree.uasset new file mode 100644 index 00000000..df6759c4 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-Disagree.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-KPopDance03-L.uasset b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-KPopDance03-L.uasset new file mode 100644 index 00000000..e32f70ed Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-KPopDance03-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-KPopDance03.uasset b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-KPopDance03.uasset new file mode 100644 index 00000000..54df53eb Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Emotes/T-Icon-Emotes-E-KPopDance03.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Icon-GreenNinja_Street-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Icon-GreenNinja_Street-L.uasset new file mode 100644 index 00000000..1739152e Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Icon-GreenNinja_Street-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Icon-GreenNinja_Street.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Icon-GreenNinja_Street.uasset new file mode 100644 index 00000000..37356ec5 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Icon-GreenNinja_Street.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-001-Athena-Commando-F-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-001-Athena-Commando-F-L.uasset new file mode 100644 index 00000000..8bbbb795 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-001-Athena-Commando-F-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-001-Athena-Commando-F.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-001-Athena-Commando-F.uasset new file mode 100644 index 00000000..7f8faa0e Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-001-Athena-Commando-F.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-477-Athena-Commando-F-SpaceSuit-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-477-Athena-Commando-F-SpaceSuit-L.uasset new file mode 100644 index 00000000..059aafc0 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-477-Athena-Commando-F-SpaceSuit-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-477-Athena-Commando-F-SpaceSuit.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-477-Athena-Commando-F-SpaceSuit.uasset new file mode 100644 index 00000000..e2b9361b Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-477-Athena-Commando-F-SpaceSuit.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-806-Athena-Commando-F-GreenJacket-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-806-Athena-Commando-F-GreenJacket-L.uasset new file mode 100644 index 00000000..4b468fbf Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-806-Athena-Commando-F-GreenJacket-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-806-Athena-Commando-F-GreenJacket.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-806-Athena-Commando-F-GreenJacket.uasset new file mode 100644 index 00000000..ac81e166 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-806-Athena-Commando-F-GreenJacket.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-171-Athena-Commando-F-Stands-D-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-171-Athena-Commando-F-Stands-D-L.uasset new file mode 100644 index 00000000..b8266a14 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-171-Athena-Commando-F-Stands-D-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-171-Athena-Commando-F-Stands-D.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-171-Athena-Commando-F-Stands-D.uasset new file mode 100644 index 00000000..c522df67 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-171-Athena-Commando-F-Stands-D.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-453-Athena-Commando-F-FuzzyBearSummer-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-453-Athena-Commando-F-FuzzyBearSummer-L.uasset new file mode 100644 index 00000000..6e36108d Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-453-Athena-Commando-F-FuzzyBearSummer-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-453-Athena-Commando-F-FuzzyBearSummer.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-453-Athena-Commando-F-FuzzyBearSummer.uasset new file mode 100644 index 00000000..ad5993c4 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-453-Athena-Commando-F-FuzzyBearSummer.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-477-Athena-Commando-F-Handlebar-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-477-Athena-Commando-F-Handlebar-L.uasset new file mode 100644 index 00000000..dbb92156 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-477-Athena-Commando-F-Handlebar-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-477-Athena-Commando-F-Handlebar.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-477-Athena-Commando-F-Handlebar.uasset new file mode 100644 index 00000000..30edd055 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Athena/Soldier/T-Soldier-HID-A-477-Athena-Commando-F-Handlebar.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Soldier-HID-806-Athena-Commando-F-GreenJacket-Shadow.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Soldier-HID-806-Athena-Commando-F-GreenJacket-Shadow.uasset new file mode 100644 index 00000000..0ab6fc8e Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Soldier-HID-806-Athena-Commando-F-GreenJacket-Shadow.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-A-171-F-Stands-D-White.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-A-171-F-Stands-D-White.uasset new file mode 100644 index 00000000..faeacb75 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-A-171-F-Stands-D-White.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-A-453-F-FuzzyBearSummer-HoodDown.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-A-453-F-FuzzyBearSummer-HoodDown.uasset new file mode 100644 index 00000000..e4402aac Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-A-453-F-FuzzyBearSummer-HoodDown.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-F-SpaceBunny-Red-L.uasset b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-F-SpaceBunny-Red-L.uasset new file mode 100644 index 00000000..7f47a65f Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Heroes/Variants/T-Variant-F-SpaceBunny-Red-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.uasset b/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.uasset new file mode 100644 index 00000000..d13141a7 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Weapons/Items/T-Icon-Pickaxes-PickaxeMarkII-L.uasset b/Content/UI/Foundation/Textures/Icons/Weapons/Items/T-Icon-Pickaxes-PickaxeMarkII-L.uasset new file mode 100644 index 00000000..c07cb765 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Weapons/Items/T-Icon-Pickaxes-PickaxeMarkII-L.uasset differ diff --git a/Content/UI/Foundation/Textures/Icons/Weapons/Items/T-Icon-Pickaxes-PickaxeMarkII.uasset b/Content/UI/Foundation/Textures/Icons/Weapons/Items/T-Icon-Pickaxes-PickaxeMarkII.uasset new file mode 100644 index 00000000..692f3312 Binary files /dev/null and b/Content/UI/Foundation/Textures/Icons/Weapons/Items/T-Icon-Pickaxes-PickaxeMarkII.uasset differ diff --git a/Content/Weapons/FORT_Melee/Materials/MI_FORT_Melee_Pick_Celestial.uasset b/Content/Weapons/FORT_Melee/Materials/MI_FORT_Melee_Pick_Celestial.uasset new file mode 100644 index 00000000..6bf2105b Binary files /dev/null and b/Content/Weapons/FORT_Melee/Materials/MI_FORT_Melee_Pick_Celestial.uasset differ diff --git a/FortniteGame.png b/FortniteGame.png new file mode 100644 index 00000000..acab44a6 Binary files /dev/null and b/FortniteGame.png differ diff --git a/FortniteGame.uproject b/FortniteGame.uproject new file mode 100644 index 00000000..cef62b66 --- /dev/null +++ b/FortniteGame.uproject @@ -0,0 +1,57 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.1", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "FortniteGame", + "Type": "Runtime", + "LoadingPhase": "Default", + "AdditionalDependencies": [ + "OnlineSubsystemMcp", + "CoreUObject", + "Engine", + "GameplayAbilities", + "ReplicationGraph" + ] + }, + { + "Name": "FortniteEditor", + "Type": "Editor", + "LoadingPhase": "PostEngineInit", + "AdditionalDependencies": [ + "OnlineSubsystemMcp", + "CoreUObject", + "Engine" + ] + } + ], + "Plugins": [ + { + "Name": "GameplayAbilities", + "Enabled": true + }, + { + "Name": "ReplicationGraph", + "Enabled": true + }, + { + "Name": "AppleARKit", + "Enabled": true, + "SupportedTargetPlatforms": [ + "IOS", + "Win64", + "Mac" + ] + }, + { + "Name": "SoundCueTemplates", + "Enabled": true + }, + { + "Name": "FortSoundCueTemplates", + "Enabled": false + } + ] +} \ No newline at end of file diff --git a/Plugins/ACLPlugin/ACLPlugin.uplugin b/Plugins/ACLPlugin/ACLPlugin.uplugin new file mode 100644 index 00000000..f1711fb9 --- /dev/null +++ b/Plugins/ACLPlugin/ACLPlugin.uplugin @@ -0,0 +1,31 @@ +{ + "FileVersion": 3, + "Version": 20007, + "VersionName": "2.0.7", + "FriendlyName": "Animation Compression Library", + "Description": "Use the Animation Compression Library (ACL) to compress AnimSequences.", + "Category": "Animation", + "CreatedBy": "Nicholas Frechette", + "CreatedByURL": "https://nfrechette.github.io/", + "DocsURL": "https://github.com/nfrechette/acl-ue4-plugin", + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/993b51e29e9a48d0b3fb449645b82cb6", + "SupportURL": "https://github.com/nfrechette/acl-ue4-plugin", + "EnabledByDefault": true, + "CanContainContent": true, + "IsBetaVersion": false, + "EngineVersion": "5.1.0", + "Modules": [ + { + "Name": "ACLPlugin", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit", + "BlacklistPlatforms": [] + }, + { + "Name": "ACLPluginEditor", + "Type" : "Editor", + "LoadingPhase" : "Default", + "BlacklistPlatforms": [] + } + ] +} diff --git a/Plugins/ACLPlugin/Config/FilterPlugin.ini b/Plugins/ACLPlugin/Config/FilterPlugin.ini new file mode 100644 index 00000000..9ae7b592 --- /dev/null +++ b/Plugins/ACLPlugin/Config/FilterPlugin.ini @@ -0,0 +1,6 @@ +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. + +[FilterPlugin] +/LICENSE +/README.md diff --git a/Plugins/ACLPlugin/Content/ACLAnimBoneCompressionSettings.uasset b/Plugins/ACLPlugin/Content/ACLAnimBoneCompressionSettings.uasset new file mode 100644 index 00000000..a8027d5f Binary files /dev/null and b/Plugins/ACLPlugin/Content/ACLAnimBoneCompressionSettings.uasset differ diff --git a/Plugins/ACLPlugin/Content/ACLAnimCurveCompressionSettings.uasset b/Plugins/ACLPlugin/Content/ACLAnimCurveCompressionSettings.uasset new file mode 100644 index 00000000..c2c48798 Binary files /dev/null and b/Plugins/ACLPlugin/Content/ACLAnimCurveCompressionSettings.uasset differ diff --git a/Plugins/ACLPlugin/Extras/stat_parser.py b/Plugins/ACLPlugin/Extras/stat_parser.py new file mode 100644 index 00000000..454abd40 --- /dev/null +++ b/Plugins/ACLPlugin/Extras/stat_parser.py @@ -0,0 +1,638 @@ +import multiprocessing +import numpy +import os +import platform +import queue +import time +import sys + +# This script depends on a SJSON parsing package: +# https://pypi.python.org/pypi/SJSON/1.1.0 +# https://shelter13.net/projects/SJSON/ +# https://bitbucket.org/Anteru/sjson/src +import sjson + + +def parse_argv(): + options = {} + options['stats'] = "" + options['acl_stats'] = "" + options['ue4_stats'] = "" + options['dual_stat_inputs'] = False + options['csv_summary'] = False + options['csv_error'] = False + options['csv_kr'] = False + options['num_threads'] = 1 + + for i in range(1, len(sys.argv)): + value = sys.argv[i] + + # TODO: Strip trailing '/' or '\' + if value.startswith('-stats='): + options['stats'] = value[7:].replace('"', '') + + # TODO: Strip trailing '/' or '\' + if value.startswith('-acl='): + options['acl_stats'] = value[5:].replace('"', '') + + # TODO: Strip trailing '/' or '\' + if value.startswith('-ue4='): + options['ue4_stats'] = value[5:].replace('"', '') + + if value == '-csv_summary': + options['csv_summary'] = True + + if value == '-csv_error': + options['csv_error'] = True + + if value == '-csv_kr': + options['csv_kr'] = True + + if value.startswith('-parallel='): + options['num_threads'] = int(value[len('-parallel='):].replace('"', '')) + + has_stats_dir = (not options['stats'] == None) and (not len(options['stats']) == 0) + has_acl_stats_dir = (not options['acl_stats'] == None) and (not len(options['acl_stats']) == 0) + has_ue4_stats_dir = (not options['ue4_stats'] == None) and (not len(options['ue4_stats']) == 0) + if not has_stats_dir and not has_acl_stats_dir and not has_ue4_stats_dir: + print('A stats input directory must be provided either with `-stats=` or with both `-acl=` and `-ue4=`') + print_usage() + sys.exit(1) + + if has_stats_dir and (has_acl_stats_dir or has_ue4_stats_dir): + print('`-stats=` cannot be used with `-acl=` or `-ue4=`') + print_usage() + sys.exit(1) + + if not has_stats_dir and not (has_acl_stats_dir and has_ue4_stats_dir): + print('Both `-acl=` and `-ue4=` must be provided together') + print_usage() + sys.exit(1) + + options['dual_stat_inputs'] = has_acl_stats_dir and has_ue4_stats_dir + + if has_acl_stats_dir and has_ue4_stats_dir: + if not os.path.exists(options['acl_stats']) or not os.path.isdir(options['acl_stats']): + print('ACL stats input directory not found: {}'.format(options['acl_stats'])) + print_usage() + sys.exit(1) + + if not os.path.exists(options['ue4_stats']) or not os.path.isdir(options['ue4_stats']): + print('UE4 stats input directory not found: {}'.format(options['ue4_stats'])) + print_usage() + sys.exit(1) + else: + if not os.path.exists(options['stats']) or not os.path.isdir(options['stats']): + print('Stats input directory not found: {}'.format(options['stats'])) + print_usage() + sys.exit(1) + + if options['num_threads'] <= 0: + print('-parallel switch argument must be greater than 0') + print_usage() + sys.exit(1) + + return options + +def print_usage(): + print('Usage: python stat_parser.py [-stats=] [-acl=] [-ue4=] [-csv_summary] [-csv_error] [-csv_kr] [-parallel=]') + +def bytes_to_mb(size_in_bytes): + return size_in_bytes / (1024.0 * 1024.0) + +def bytes_to_kb(size_in_bytes): + return size_in_bytes / 1024.0 + +def format_elapsed_time(elapsed_time): + hours, rem = divmod(elapsed_time, 3600) + minutes, seconds = divmod(rem, 60) + return '{:0>2}h {:0>2}m {:05.2f}s'.format(int(hours), int(minutes), seconds) + +def sanitize_csv_entry(entry): + return entry.replace(', ', ' ').replace(',', '_') + +def output_csv_summary(stat_dir, merged_stats): + csv_filename = os.path.join(stat_dir, 'stats_summary.csv') + print('Generating CSV file {} ...'.format(csv_filename)) + file = open(csv_filename, 'w') + + stat_acl, stat_auto = merged_stats[0] + header = 'Clip Name, Raw Size' + if 'ue4_auto' in stat_auto: + header += ', Auto Size, Auto Ratio, Auto UE4 Error, Auto ACL Error' + if 'ue4_acl' in stat_acl: + header += ', ACL Size, ACL Ratio, ACL UE4 Error, ACL ACL Error' + print(header, file = file) + + for (stat_acl, stat_auto) in merged_stats: + clip_name = stat_acl['clip_name'] + raw_size = stat_acl['acl_raw_size'] + csv_line = '{}, {}'.format(clip_name, raw_size) + + if 'ue4_auto' in stat_auto: + auto_size = stat_auto['ue4_auto']['compressed_size'] + auto_ratio = stat_auto['ue4_auto']['acl_compression_ratio'] + auto_ue4_error = stat_auto['ue4_auto']['ue4_max_error'] + auto_acl_error = stat_auto['ue4_auto']['acl_max_error'] + csv_line += ', {}, {}, {}, {}'.format(auto_size, auto_ratio, auto_ue4_error, auto_acl_error) + + if 'ue4_acl' in stat_acl: + acl_size = stat_acl['ue4_acl']['compressed_size'] + acl_ratio = stat_acl['ue4_acl']['acl_compression_ratio'] + acl_ue4_error = stat_acl['ue4_acl']['ue4_max_error'] + acl_acl_error = stat_acl['ue4_acl']['acl_max_error'] + csv_line += ', {}, {}, {}, {}'.format(acl_size, acl_ratio, acl_ue4_error, acl_acl_error) + + print(csv_line, file = file) + + file.close() + +def output_csv_error(stat_dir, merged_stats): + stat_acl, stat_auto = merged_stats[0] + if 'ue4_auto' in stat_auto and 'error_per_frame_and_bone' in stat_auto['ue4_auto']: + csv_filename = os.path.join(stat_dir, 'stats_ue4_auto_error.csv') + print('Generating CSV file {} ...'.format(csv_filename)) + file = open(csv_filename, 'w') + + print('Clip Name, Key Frame, Bone Index, Error', file = file) + + for (_, stat_auto) in merged_stats: + name = stat_auto['clip_name'] + key_frame = 0 + for frame_errors in stat_auto['ue4_auto']['error_per_frame_and_bone']: + bone_index = 0 + for bone_error in frame_errors: + print('{}, {}, {}, {}'.format(name, key_frame, bone_index, bone_error), file = file) + bone_index += 1 + + key_frame += 1 + + file.close() + + if 'ue4_acl' in stat_acl and 'error_per_frame_and_bone' in stat_acl['ue4_acl']: + csv_filename = os.path.join(stat_dir, 'stats_ue4_acl_error.csv') + print('Generating CSV file {} ...'.format(csv_filename)) + file = open(csv_filename, 'w') + + print('Clip Name, Key Frame, Bone Index, Error', file = file) + + for (stat_acl, _) in merged_stats: + name = stat_acl['clip_name'] + key_frame = 0 + for frame_errors in stat_acl['ue4_acl']['error_per_frame_and_bone']: + bone_index = 0 + for bone_error in frame_errors: + print('{}, {}, {}, {}'.format(name, key_frame, bone_index, bone_error), file = file) + bone_index += 1 + + key_frame += 1 + + file.close() + +def output_csv_kr(stat_dir, clip_drop_rates, pose_drop_rates, track_drop_rates): + csv_filename = os.path.join(stat_dir, 'stats_kr.csv') + print('Generating CSV file {} ...'.format(csv_filename)) + file = open(csv_filename, 'w') + + print('Dropped Per Clip, Dropped Per Pose, Dropped Per Track', file = file) + + num_rows = max([len(clip_drop_rates), len(pose_drop_rates), len(track_drop_rates)]) + values = [('', '', '')] * num_rows + + for i in range(len(clip_drop_rates)): + clip_rate = clip_drop_rates[i] + _, pose_rate, track_rate = values[i] + values[i] = (clip_rate, pose_rate, track_rate) + + for i in range(len(pose_drop_rates)): + pose_rate = pose_drop_rates[i] + clip_rate, _, track_rate = values[i] + values[i] = (clip_rate, pose_rate, track_rate) + + for i in range(len(track_drop_rates)): + track_rate = track_drop_rates[i] + clip_rate, pose_rate, _ = values[i] + values[i] = (clip_rate, pose_rate, track_rate) + + for (clip_rate, pose_rate, track_rate) in values: + print('{}, {}, {}'.format(clip_rate, pose_rate, track_rate), file = file) + + file.close() + +def print_progress(iteration, total, prefix='', suffix='', decimals = 1, bar_length = 40): + # Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console + # With minor tweaks + """ + Call in a loop to create terminal progress bar + @params: + iteration - Required : current iteration (Int) + total - Required : total iterations (Int) + prefix - Optional : prefix string (Str) + suffix - Optional : suffix string (Str) + decimals - Optional : positive number of decimals in percent complete (Int) + bar_length - Optional : character length of bar (Int) + """ + str_format = "{0:." + str(decimals) + "f}" + percents = str_format.format(100 * (iteration / float(total))) + filled_length = int(round(bar_length * iteration / float(total))) + bar = '█' * filled_length + '-' * (bar_length - filled_length) + + # We need to clear any previous line we might have to ensure we have no visual artifacts + # Note that if this function is called too quickly, the text might flicker + terminal_width = 80 + sys.stdout.write('{}\r'.format(' ' * terminal_width)) + sys.stdout.flush() + + sys.stdout.write('%s |%s| %s%s %s\r' % (prefix, bar, percents, '%', suffix)), + sys.stdout.flush() + + if iteration == total: + sys.stdout.write('\n') + +def append_stats(permutation, clip_stats, run_stats, aggregate_results): + key = run_stats['desc'] + if not key in aggregate_results: + run_total_stats = {} + run_total_stats['desc'] = key + run_total_stats['total_raw_size'] = 0 + run_total_stats['total_compressed_size'] = 0 + run_total_stats['total_compression_time'] = 0.0 + run_total_stats['acl_max_error'] = 0.0 + run_total_stats['ue4_max_error'] = 0.0 + run_total_stats['num_runs'] = 0 + aggregate_results[key] = run_total_stats + + run_total_stats = aggregate_results[key] + run_total_stats['total_raw_size'] += clip_stats['acl_raw_size'] + run_total_stats['total_compressed_size'] += run_stats['compressed_size'] + run_total_stats['total_compression_time'] += run_stats['compression_time'] + run_total_stats['acl_max_error'] = max(run_stats['acl_max_error'], run_total_stats['acl_max_error']) + run_total_stats['ue4_max_error'] = max(run_stats['ue4_max_error'], run_total_stats['ue4_max_error']) + run_total_stats['num_runs'] += 1 + + if not permutation in aggregate_results: + permutation_stats = {} + permutation_stats['total_raw_size'] = 0 + permutation_stats['total_compressed_size'] = 0 + permutation_stats['total_compression_time'] = 0.0 + permutation_stats['acl_max_error'] = 0.0 + permutation_stats['ue4_max_error'] = 0.0 + permutation_stats['num_runs'] = 0 + permutation_stats['worst_error'] = -1.0 + permutation_stats['worst_entry'] = None + aggregate_results[permutation] = permutation_stats + + permutation_stats = aggregate_results[permutation] + permutation_stats['total_raw_size'] += clip_stats['acl_raw_size'] + permutation_stats['total_compressed_size'] += run_stats['compressed_size'] + permutation_stats['total_compression_time'] += run_stats['compression_time'] + permutation_stats['acl_max_error'] = max(run_stats['acl_max_error'], permutation_stats['acl_max_error']) + permutation_stats['ue4_max_error'] = max(run_stats['ue4_max_error'], permutation_stats['ue4_max_error']) + permutation_stats['num_runs'] += 1 + if run_stats['acl_max_error'] > permutation_stats['worst_error']: + permutation_stats['worst_error'] = run_stats['acl_max_error'] + permutation_stats['worst_entry'] = clip_stats + +def do_parse_stats(options, stat_queue, result_queue): + try: + stats = [] + acl_error_values = [] + ue4_error_values = [] + ue4_keyreduction_data = {} + ue4_keyreduction_data['drop_rates'] = [] + ue4_keyreduction_data['pose_drop_rates'] = [] + ue4_keyreduction_data['track_drop_rates'] = [] + acl_compression_times = [] + ue4_compression_times = [] + + while True: + stat_filename = stat_queue.get() + if stat_filename is None: + break + + if platform.system() == 'Windows': + filename = '\\\\?\\{}'.format(stat_filename) # Long path prefix + else: + filename = stat_filename + + with open(filename, 'r') as file: + try: + file_data = sjson.loads(file.read()) + if 'error' in file_data: + print('{} [{}]'.format(file_data['error'], stat_filename)) + continue + + file_data['filename'] = stat_filename + file_data['clip_name'] = os.path.splitext(os.path.basename(stat_filename))[0].replace('_stats', '') + + if not options['csv_error']: + # The sjson lib doesn't always return numbers as floats, sometimes as int but numpy doesn't like that + if 'ue4_acl' in file_data and 'error_per_frame_and_bone' in file_data['ue4_acl']: + for frame_error_values in file_data['ue4_acl']['error_per_frame_and_bone']: + acl_error_values.extend([float(v) for v in frame_error_values]) + file_data['ue4_acl']['error_per_frame_and_bone'] = [] + + if 'ue4_auto' in file_data and 'error_per_frame_and_bone' in file_data['ue4_auto']: + for frame_error_values in file_data['ue4_auto']['error_per_frame_and_bone']: + ue4_error_values.extend([float(v) for v in frame_error_values]) + file_data['ue4_auto']['error_per_frame_and_bone'] = [] + + if 'ue4_acl' in file_data: + acl_compression_times.append(file_data['ue4_acl']['compression_time']) + + if 'ue4_auto' in file_data: + ue4_compression_times.append(file_data['ue4_auto']['compression_time']) + + if 'ue4_keyreduction' in file_data: + num_animated_keys = float(file_data['ue4_keyreduction']['total_num_animated_keys']) + if num_animated_keys > 2.001: + drop_rate = float(file_data['ue4_keyreduction']['total_num_dropped_animated_keys']) / num_animated_keys + ue4_keyreduction_data['drop_rates'].append(drop_rate) + ue4_keyreduction_data['pose_drop_rates'].extend([float(v) for v in file_data['ue4_keyreduction']['dropped_pose_keys']]) + ue4_keyreduction_data['track_drop_rates'].extend([float(v) for v in file_data['ue4_keyreduction']['dropped_track_keys']]) + else: + ue4_keyreduction_data['drop_rates'].append(0.0) + ue4_keyreduction_data['pose_drop_rates'].extend([0.0 for v in file_data['ue4_keyreduction']['dropped_pose_keys']]) + ue4_keyreduction_data['track_drop_rates'].extend([0.0 for v in file_data['ue4_keyreduction']['dropped_track_keys']]) + + stats.append(file_data) + except sjson.ParseException: + print('Failed to parse SJSON file: {}'.format(stat_filename)) + + result_queue.put(('progress', stat_filename)) + + results = {} + results['stats'] = stats + results['acl_error_values'] = acl_error_values + results['ue4_error_values'] = ue4_error_values + results['ue4_keyreduction_data'] = ue4_keyreduction_data + results['acl_compression_times'] = acl_compression_times + results['ue4_compression_times'] = ue4_compression_times + + result_queue.put(('done', results)) + except KeyboardInterrupt: + print('Interrupted') + +def parallel_parse_stats(options, stat_files, label): + stat_queue = multiprocessing.Queue() + for stat_filename in stat_files: + stat_queue.put(stat_filename) + + # Add a marker to terminate the jobs + for i in range(options['num_threads']): + stat_queue.put(None) + + result_queue = multiprocessing.Queue() + + jobs = [ multiprocessing.Process(target = do_parse_stats, args = (options, stat_queue, result_queue)) for _i in range(options['num_threads']) ] + for job in jobs: + job.start() + + if options['dual_stat_inputs']: + label = ' {}'.format(label) + else: + label = '' # No need for a label if we parse both together + + num_stat_files = len(stat_files) + num_stat_file_processed = 0 + stats = [] + print_progress(num_stat_file_processed, len(stat_files), 'Aggregating{} results:'.format(label), '{} / {}'.format(num_stat_file_processed, num_stat_files)) + try: + while True: + try: + (msg, data) = result_queue.get(True, 1.0) + if msg == 'progress': + num_stat_file_processed += 1 + print_progress(num_stat_file_processed, len(stat_files), 'Aggregating{} results:'.format(label), '{} / {}'.format(num_stat_file_processed, num_stat_files)) + elif msg == 'done': + stats.append(data) + except queue.Empty: + all_jobs_done = True + for job in jobs: + if job.is_alive(): + all_jobs_done = False + + if all_jobs_done: + break + except KeyboardInterrupt: + sys.exit(1) + + return stats + +def get_stat_files(options): + if options['dual_stat_inputs']: + acl_stat_files = [] + ue4_stat_files = [] + + for (dirpath, dirnames, filenames) in os.walk(options['acl_stats']): + for filename in filenames: + if not filename.endswith('.sjson'): + continue + + stat_filename = os.path.join(dirpath, filename) + acl_stat_files.append(stat_filename) + + for (dirpath, dirnames, filenames) in os.walk(options['ue4_stats']): + for filename in filenames: + if not filename.endswith('.sjson'): + continue + + stat_filename = os.path.join(dirpath, filename) + ue4_stat_files.append(stat_filename) + + acl_file_set = set([os.path.basename(file) for file in acl_stat_files]) + ue4_file_set = set([os.path.basename(file) for file in ue4_stat_files]) + if len(acl_file_set.intersection(ue4_file_set)) != len(acl_stat_files): + print('The input files for ACL and UE4 do not match, some are missing in one or the other') + sys.exit(1) + + return (acl_stat_files, ue4_stat_files) + else: + stat_files = [] + + for (dirpath, dirnames, filenames) in os.walk(options['stats']): + for filename in filenames: + if not filename.endswith('.sjson'): + continue + + stat_filename = os.path.join(dirpath, filename) + stat_files.append(stat_filename) + + return (stat_files, stat_files) + +def percentile_rank(values, value): + return (values < value).mean() * 100.0 + +if __name__ == "__main__": + options = parse_argv() + + acl_stat_files, ue4_stat_files = get_stat_files(options) + + if len(acl_stat_files) == 0: + print('No input clips found') + sys.exit(0) + + aggregating_start_time = time.clock() + + acl_stats = parallel_parse_stats(options, acl_stat_files, 'ACL') + if options['dual_stat_inputs']: + ue4_stats = parallel_parse_stats(options, ue4_stat_files, 'UE4 Auto') + else: + ue4_stats = acl_stats + + acl_error_values = numpy.array([]) + acl_compression_times = numpy.array([]) + for result in acl_stats: + acl_error_values = numpy.append(acl_error_values, result['acl_error_values']) + acl_compression_times = numpy.append(acl_compression_times, result['acl_compression_times']) + + ue4_error_values = numpy.array([]) + ue4_compression_times = numpy.array([]) + for result in ue4_stats: + ue4_error_values = numpy.append(ue4_error_values, result['ue4_error_values']) + ue4_compression_times = numpy.append(ue4_compression_times, result['ue4_compression_times']) + + clip_drop_rates = numpy.array([]) + pose_drop_rates = numpy.array([]) + track_drop_rates = numpy.array([]) + for result in acl_stats: + clip_drop_rates = numpy.append(clip_drop_rates, result['ue4_keyreduction_data']['drop_rates']) + pose_drop_rates = numpy.append(pose_drop_rates, result['ue4_keyreduction_data']['pose_drop_rates']) + track_drop_rates = numpy.append(track_drop_rates, result['ue4_keyreduction_data']['track_drop_rates']) + clip_drop_rates = numpy.sort(clip_drop_rates) + pose_drop_rates = numpy.sort(pose_drop_rates) + track_drop_rates = numpy.sort(track_drop_rates) + + # Flatten our stats into a list and strip the error values + acl_stats = [ stat for result in acl_stats for stat in result['stats'] ] + ue4_stats = [ stat for result in ue4_stats for stat in result['stats'] ] + + # Sort out stats by clip name so we can zip them in pairs + acl_stats.sort(key=lambda stat: stat['clip_name']) + ue4_stats.sort(key=lambda stat: stat['clip_name']) + + merged_stats = list(zip(acl_stats, ue4_stats)) + + aggregating_end_time = time.clock() + print('Parsed stats in {}'.format(format_elapsed_time(aggregating_end_time - aggregating_start_time))) + + if options['csv_summary']: + output_csv_summary(os.getcwd(), merged_stats) + + if options['csv_error']: + output_csv_error(os.getcwd(), merged_stats) + + if options['csv_kr'] and len(clip_drop_rates) > 0: + output_csv_kr(os.getcwd(), clip_drop_rates, pose_drop_rates, track_drop_rates) + + print() + print('Stats per run type:') + aggregate_results = {} + num_acl_size_wins = 0 + num_acl_accuracy_wins = 0 + num_acl_speed_wins = 0 + num_acl_wins = 0 + num_acl_auto_wins = 0 + for (stat_acl, stat_auto) in merged_stats: + if 'ue4_auto' in stat_auto: + ue4_auto = stat_auto['ue4_auto'] + ue4_auto['desc'] = '{} {} {}'.format(ue4_auto['algorithm_name'], ue4_auto['rotation_format'], ue4_auto['translation_format']) + append_stats('ue4_auto', stat_auto, ue4_auto, aggregate_results) + + if 'ue4_acl' in stat_acl: + ue4_acl = stat_acl['ue4_acl'] + ue4_acl['desc'] = ue4_acl['algorithm_name'] + append_stats('ue4_acl', stat_acl, ue4_acl, aggregate_results) + + if 'ue4_keyreduction' in stat_acl: + ue4_keyreduction = stat_acl['ue4_keyreduction'] + ue4_keyreduction['desc'] = ue4_keyreduction['algorithm_name'] + append_stats('ue4_keyreduction', stat_acl, ue4_keyreduction, aggregate_results) + + if 'ue4_auto' in stat_auto and 'ue4_acl' in stat_acl: + ue4_auto = stat_auto['ue4_auto'] + ue4_acl = stat_acl['ue4_acl'] + if ue4_acl['compressed_size'] < ue4_auto['compressed_size']: + num_acl_size_wins += 1 + if ue4_acl['ue4_max_error'] < ue4_auto['ue4_max_error']: + num_acl_accuracy_wins += 1 + if ue4_acl['compression_time'] < ue4_auto['compression_time']: + num_acl_speed_wins += 1 + if ue4_acl['compressed_size'] < ue4_auto['compressed_size'] and ue4_acl['ue4_max_error'] < ue4_auto['ue4_max_error'] and ue4_acl['compression_time'] < ue4_auto['compression_time']: + num_acl_wins += 1 + + lowers_error = ue4_acl['ue4_max_error'] < ue4_auto['ue4_max_error']; + saved_size = int(ue4_auto['compressed_size']) - int(ue4_acl['compressed_size']) + lowers_size = ue4_acl['compressed_size'] < ue4_auto['compressed_size']; + error_under_threshold = float(ue4_acl['ue4_max_error']) <= 0.1; + + # keep it if it we want to force the error below the threshold and it reduces error + # or if has an acceptable error and saves space + # or if saves the same amount and an acceptable error that is lower than the previous best + reduces_error_below_threshold = lowers_error and error_under_threshold; + has_acceptable_error_and_saves_space = error_under_threshold and saved_size > 0; + lowers_error_and_saves_same_or_better = error_under_threshold and lowers_error and saved_size >= 0; + if reduces_error_below_threshold or has_acceptable_error_and_saves_space or lowers_error_and_saves_same_or_better: + num_acl_auto_wins += 1 + + print() + raw_size = 0.0 + if 'ue4_auto' in aggregate_results: + ue4_auto = aggregate_results['ue4_auto'] + raw_size = ue4_auto['total_raw_size'] + ratio = float(ue4_auto['total_raw_size']) / float(ue4_auto['total_compressed_size']) + print('Total Automatic Compression:') + print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [UE4: {:.4f}, ACL: {:.4f}]'.format(bytes_to_mb(ue4_auto['total_compressed_size']), format_elapsed_time(ue4_auto['total_compression_time']), ratio, ue4_auto['ue4_max_error'], ue4_auto['acl_max_error'])) + print('Least accurate: {} Ratio: {:.2f}, Error: {:.4f}'.format(ue4_auto['worst_entry']['clip_name'], ue4_auto['worst_entry']['ue4_auto']['acl_compression_ratio'], ue4_auto['worst_entry']['ue4_auto']['acl_max_error'])) + print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(raw_size) / ue4_auto['total_compression_time'])) + print('Compression time 50, 85, 99th percentile: {:.3f}, {:.3f}, {:.3f} seconds'.format(numpy.percentile(ue4_compression_times, 50.0), numpy.percentile(ue4_compression_times, 85.0), numpy.percentile(ue4_compression_times, 99.0))) + if len(ue4_error_values) > 0: + print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(ue4_error_values, 99.0))) + print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(ue4_error_values, 0.01))) + print() + + if 'ue4_acl' in aggregate_results: + ue4_acl = aggregate_results['ue4_acl'] + raw_size = ue4_acl['total_raw_size'] + ratio = float(ue4_acl['total_raw_size']) / float(ue4_acl['total_compressed_size']) + print('Total ACL Compression:') + print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [UE4: {:.4f}, ACL: {:.4f}]'.format(bytes_to_mb(ue4_acl['total_compressed_size']), format_elapsed_time(ue4_acl['total_compression_time']), ratio, ue4_acl['ue4_max_error'], ue4_acl['acl_max_error'])) + print('Least accurate: {} Ratio: {:.2f}, Error: {:.4f}'.format(ue4_acl['worst_entry']['clip_name'], ue4_acl['worst_entry']['ue4_acl']['acl_compression_ratio'], ue4_acl['worst_entry']['ue4_acl']['acl_max_error'])) + print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(raw_size) / ue4_acl['total_compression_time'])) + print('Compression time 50, 85, 99th percentile: {:.3f}, {:.3f}, {:.3f} seconds'.format(numpy.percentile(acl_compression_times, 50.0), numpy.percentile(acl_compression_times, 85.0), numpy.percentile(acl_compression_times, 99.0))) + if len(acl_error_values) > 0: + print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(acl_error_values, 99.0))) + print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(acl_error_values, 0.01))) + print() + + if 'ue4_keyreduction' in aggregate_results: + ue4_keyreduction = aggregate_results['ue4_keyreduction'] + raw_size = ue4_keyreduction['total_raw_size'] + ratio = float(ue4_keyreduction['total_raw_size']) / float(ue4_keyreduction['total_compressed_size']) + print('Total Key Reduction Compression:') + print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [UE4: {:.4f}, ACL: {:.4f}]'.format(bytes_to_mb(ue4_keyreduction['total_compressed_size']), format_elapsed_time(ue4_keyreduction['total_compression_time']), ratio, ue4_keyreduction['ue4_max_error'], ue4_keyreduction['acl_max_error'])) + print('Least accurate: {} Ratio: {:.2f}, Error: {:.4f}'.format(ue4_keyreduction['worst_entry']['clip_name'], ue4_keyreduction['worst_entry']['ue4_keyreduction']['acl_compression_ratio'], ue4_keyreduction['worst_entry']['ue4_keyreduction']['acl_max_error'])) + print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(raw_size) / ue4_keyreduction['total_compression_time'])) + #print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(acl_error_values, 99.0))) + #print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(acl_error_values, 0.01))) + print() + + num_clips = float(len(acl_stat_files)) + print('Raw size: {:.2f} MB'.format(bytes_to_mb(raw_size))) + print('ACL was smaller for {} clips ({:.2f} %)'.format(num_acl_size_wins, float(num_acl_size_wins) / num_clips * 100.0)) + print('ACL was more accurate for {} clips ({:.2f} %)'.format(num_acl_accuracy_wins, float(num_acl_accuracy_wins) / num_clips * 100.0)) + print('ACL has faster compression for {} clips ({:.2f} %)'.format(num_acl_speed_wins, float(num_acl_speed_wins) / num_clips * 100.0)) + print('ACL was smaller, better, faster for {} clips ({:.2f} %)'.format(num_acl_wins, float(num_acl_wins) / num_clips * 100.0)) + print('ACL won with simulated auto {} clips ({:.2f} %)'.format(num_acl_auto_wins, float(num_acl_auto_wins) / num_clips * 100.0)) + + if len(clip_drop_rates) > 0: + print() + print('Key reduction clip avg drop rate: {:.2f} %'.format(numpy.average(clip_drop_rates) * 100.0)) + print('Key reduction clip 50th percentile drop rate: {:.2f} %'.format(numpy.percentile(clip_drop_rates, 50.0) * 100.0)) + print('Key reduction clip 90th percentile drop rate: {:.2f} %'.format(numpy.percentile(clip_drop_rates, 90.0) * 100.0)) + print('Key reduction pose avg drop rate: {:.2f} %'.format(numpy.average(pose_drop_rates) * 100.0)) + print('Key reduction pose 50th percentile drop rate: {:.2f} %'.format(numpy.percentile(pose_drop_rates, 50.0) * 100.0)) + print('Key reduction pose 90th percentile drop rate: {:.2f} %'.format(numpy.percentile(pose_drop_rates, 90.0) * 100.0)) + print('Key reduction track avg drop rate: {:.2f} %'.format(numpy.average(track_drop_rates) * 100.0)) + print('Key reduction track 50th percentile drop rate: {:.2f} %'.format(numpy.percentile(track_drop_rates, 50.0) * 100.0)) + print('Key reduction track 90th percentile drop rate: {:.2f} %'.format(numpy.percentile(track_drop_rates, 90.0) * 100.0)) diff --git a/Plugins/ACLPlugin/Extras/tally_anim_csv.py b/Plugins/ACLPlugin/Extras/tally_anim_csv.py new file mode 100644 index 00000000..aa1b975f --- /dev/null +++ b/Plugins/ACLPlugin/Extras/tally_anim_csv.py @@ -0,0 +1,64 @@ +import csv +import sys + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print('Invalid usage: python {}'.format(' '.join(sys.argv))) + print('Usage: python tally_anim_csv.py ') + sys.exit(1) + + csv_filename = sys.argv[1] + + rows = None + + # Read our CSV file and cache our rows + with open(csv_filename, newline='') as csv_file: + reader = csv.reader(csv_file) + rows = list(reader) + + if not rows or len(rows) == 0: + print('No CSV data found!') + sys.exit(1) + + header_row = rows[0] + if header_row[-1] == 'Animation/Total/ExtractPoseFromAnimData': + print('This CSV file has already been processed!') + sys.exit(1) + + # Process our rows + # First append our new header to the first row as the last column + + num_columns = len(header_row) + num_rows = len(rows) + header_row.append('Animation/Total/ExtractPoseFromAnimData') + print('Found {} columns and {} rows to process ...'.format(num_columns, num_rows)) + + # Process every row and add the sum totals to our last column + # Skip the first row, it contains our header + # Skip the last two rows, it contains other things + for row_idx in range(1, num_rows - 2): + row = rows[row_idx] + + total_decomp_time = 0.0 + for column_idx in range(num_columns): + header_name = header_row[column_idx] + if header_name.endswith('ExtractPoseFromAnimData'): + try: + value = float(row[column_idx]) + except: + value = 0.0 + total_decomp_time += value + + # By default, everything is in milliseconds + row.append(str(total_decomp_time)) + + # The second to last row must match our header + rows[-2].append('Animation/Total/ExtractPoseFromAnimData') + + # Write out our modified rows over the same file + with open(csv_filename, 'w', newline='') as csv_file: + writer = csv.writer(csv_file, quoting=csv.QUOTE_MINIMAL) + writer.writerows(rows) + + print('Done!') diff --git a/Plugins/ACLPlugin/LICENSE b/Plugins/ACLPlugin/LICENSE new file mode 100644 index 00000000..56c93514 --- /dev/null +++ b/Plugins/ACLPlugin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Nicholas Frechette + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Plugins/ACLPlugin/README.md b/Plugins/ACLPlugin/README.md new file mode 100644 index 00000000..60376570 --- /dev/null +++ b/Plugins/ACLPlugin/README.md @@ -0,0 +1,7 @@ +# ACL Plugin + +This plugin integrates the [Animation Compression Library](https://github.com/nfrechette/acl) within Unreal Engine 4. It is suitable for all your animations and features a low memory footprint, high accuracy, and very fast compression and decompression. + +See [here](https://github.com/nfrechette/acl-ue4-plugin) for details! + +Note: the plugin releases on GitHub and on the Unreal Marketplace remove a few development only dependencies not required at runtime or within the editor. diff --git a/Plugins/ACLPlugin/Resources/Icon128.png b/Plugins/ACLPlugin/Resources/Icon128.png new file mode 100644 index 00000000..d9b42bb4 Binary files /dev/null and b/Plugins/ACLPlugin/Resources/Icon128.png differ diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/ACLPlugin.Build.cs b/Plugins/ACLPlugin/Source/ACLPlugin/ACLPlugin.Build.cs new file mode 100644 index 00000000..69e71974 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/ACLPlugin.Build.cs @@ -0,0 +1,45 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class ACLPlugin : ModuleRules + { + public ACLPlugin(ReadOnlyTargetRules Target) : base(Target) + { + string ACLSDKDir = Path.GetFullPath(Path.Combine(ModuleDirectory, "../ThirdParty")); + + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //OptimizeCode = CodeOptimization.Never; + //bUseUnity = false; + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PublicIncludePaths.Add(Path.Combine(ACLSDKDir, "acl/includes")); + PublicIncludePaths.Add(Path.Combine(ACLSDKDir, "acl/external/rtm/includes")); + + PrivateIncludePaths.Add("ACLPlugin/Private"); + + PublicDependencyModuleNames.Add("Core"); + PublicDependencyModuleNames.Add("CoreUObject"); + PublicDependencyModuleNames.Add("Engine"); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.Add("DesktopPlatform"); + PrivateDependencyModuleNames.Add("UnrealEd"); + + PublicIncludePaths.Add(Path.Combine(ACLSDKDir, "acl/external/sjson-cpp/includes")); + } + + if (Target.Platform == UnrealTargetPlatform.Linux) + { + // There appears to be a bug when cross-compiling Linux under Windows where the clang tool-chain used + // isn't fully C++11 compliant. The standard specifies that when the 'cinttypes' header is included + // the format macros are always defined unlike C which requires the following macro to be defined first. + // This fix should be required for UE 4.20 and earlier versions. + PrivateDefinitions.Add("__STDC_FORMAT_MACROS"); + } + } + } +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACL.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACL.h new file mode 100644 index 00000000..10ba90de --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACL.h @@ -0,0 +1,52 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "AnimBoneCompressionCodec_ACLBase.h" +#include "AnimBoneCompressionCodec_ACL.generated.h" + +/** The default codec implementation for ACL support with the minimal set of exposed features for ease of use. */ +UCLASS(MinimalAPI, config = Engine, meta = (DisplayName = "Anim Compress ACL")) +class UAnimBoneCompressionCodec_ACL : public UAnimBoneCompressionCodec_ACLBase +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditAnywhere, Category = "ACL Options", Instanced, meta = (EditInline)) + UAnimBoneCompressionCodec* SafetyFallbackCodec; + +#if WITH_EDITORONLY_DATA + /** The error threshold after which we fallback on a safer encoding. */ + UPROPERTY(EditAnywhere, Category = "ACL Options", meta = (ClampMin = "0")) + float SafetyFallbackThreshold; + + /** The skeletal meshes used to estimate the skinning deformation during compression. */ + UPROPERTY(EditAnywhere, Category = "ACL Options") + TArray OptimizationTargets; + + ////////////////////////////////////////////////////////////////////////// + // UObject implementation + virtual void PostInitProperties() override; + virtual void GetPreloadDependencies(TArray& OutDeps) override; + + // UAnimBoneCompressionCodec implementation + virtual bool IsCodecValid() const override; + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + virtual void PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) override; +#else + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + // UAnimBoneCompressionCodec_ACLBase implementation + virtual void GetCompressionSettings(acl::compression_settings& OutSettings) const override; + virtual TArray GetOptimizationTargets() const override { return OptimizationTargets; } + virtual ACLSafetyFallbackResult ExecuteSafetyFallback(acl::iallocator& Allocator, const acl::compression_settings& Settings, const acl::track_array_qvvf& RawClip, const acl::track_array_qvvf& BaseClip, const acl::compressed_tracks& CompressedClipData, const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult); +#endif + + // UAnimBoneCompressionCodec implementation + virtual UAnimBoneCompressionCodec* GetCodec(const FString& DDCHandle); + virtual void DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const override; + virtual void DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLBase.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLBase.h new file mode 100644 index 00000000..3a6f80ef --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLBase.h @@ -0,0 +1,87 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Animation/AnimBoneCompressionCodec.h" + +#include "ACLImpl.h" + +#if WITH_EDITORONLY_DATA +#include +#include +#include +#include +#endif + +#include + +#include "AnimBoneCompressionCodec_ACLBase.generated.h" + +/** An enum that represents the result of attempting to use a safety fallback codec. */ +enum class ACLSafetyFallbackResult +{ + Success, // Safety fallback is used and compressed fine + Failure, // Safety fallback is used but failed to compress + Ignored, // No safety fallback used +}; + +struct FACLCompressedAnimData final : public ICompressedAnimData +{ + /** Holds the compressed_tracks instance */ + TArrayView CompressedByteStream; + + const acl::compressed_tracks* GetCompressedTracks() const { return acl::make_compressed_tracks(CompressedByteStream.GetData()); } + + // ICompressedAnimData implementation + virtual void Bind(const TArrayView BulkData) override { CompressedByteStream = BulkData; } + virtual int64 GetApproxCompressedSize() const override { return CompressedByteStream.Num(); } + virtual bool IsValid() const override; +}; + +/** The base codec implementation for ACL support. */ +UCLASS(abstract, MinimalAPI) +class UAnimBoneCompressionCodec_ACLBase : public UAnimBoneCompressionCodec +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** The compression level to use. Higher levels will be slower to compress but yield a lower memory footprint. */ + UPROPERTY(EditAnywhere, Category = "ACL Options") + TEnumAsByte CompressionLevel; + + /** The default virtual vertex distance for normal bones. */ + UPROPERTY(EditAnywhere, Category = "ACL Options", meta = (ClampMin = "0")) + float DefaultVirtualVertexDistance; + + /** The virtual vertex distance for bones that requires extra accuracy. */ + UPROPERTY(EditAnywhere, Category = "ACL Options", meta = (ClampMin = "0")) + float SafeVirtualVertexDistance; + + /** The error threshold to use when optimizing and compressing the animation sequence. */ + UPROPERTY(EditAnywhere, Category = "ACL Options", meta = (ClampMin = "0")) + float ErrorThreshold; + + // UAnimBoneCompressionCodec implementation + virtual bool Compress(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + virtual void PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) override; +#else + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + // Our implementation + virtual bool UseDatabase() const { return false; } + virtual void RegisterWithDatabase(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) {} + virtual void GetCompressionSettings(acl::compression_settings& OutSettings) const PURE_VIRTUAL(UAnimBoneCompressionCodec_ACLBase::GetCompressionSettings, ); + virtual TArray GetOptimizationTargets() const { return TArray(); } + virtual ACLSafetyFallbackResult ExecuteSafetyFallback(acl::iallocator& Allocator, const acl::compression_settings& Settings, const acl::track_array_qvvf& RawClip, const acl::track_array_qvvf& BaseClip, const acl::compressed_tracks& CompressedClipData, const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult); +#endif + + // UAnimBoneCompressionCodec implementation + virtual TUniquePtr AllocateAnimData() const override; + virtual void ByteSwapIn(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryReader& MemoryStream) const override; + virtual void ByteSwapOut(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryWriter& MemoryStream) const override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLCustom.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLCustom.h new file mode 100644 index 00000000..5b04898d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLCustom.h @@ -0,0 +1,62 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "AnimBoneCompressionCodec_ACLBase.h" +#include "AnimBoneCompressionCodec_ACLCustom.generated.h" + +/** The custom codec implementation for ACL support with all features supported. */ +UCLASS(MinimalAPI, config = Engine, meta = (DisplayName = "Anim Compress ACL Custom")) +class UAnimBoneCompressionCodec_ACLCustom : public UAnimBoneCompressionCodec_ACLBase +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** The rotation format to use. */ + UPROPERTY(EditAnywhere, Category = Clip) + TEnumAsByte RotationFormat; + + /** The translation format to use. */ + UPROPERTY(EditAnywhere, Category = Clip) + TEnumAsByte TranslationFormat; + + /** The scale format to use. */ + UPROPERTY(EditAnywhere, Category = Clip) + TEnumAsByte ScaleFormat; + + /** The threshold used to detect constant rotation tracks. */ + UPROPERTY(EditAnywhere, Category = Clip, meta = (ClampMin = "0")) + float ConstantRotationThresholdAngle; + + /** The threshold used to detect constant translation tracks. */ + UPROPERTY(EditAnywhere, Category = Clip, meta = (ClampMin = "0")) + float ConstantTranslationThreshold; + + /** The threshold used to detect constant scale tracks. */ + UPROPERTY(EditAnywhere, Category = Clip, meta = (ClampMin = "0")) + float ConstantScaleThreshold; + + /** The skeletal meshes used to estimate the skinning deformation during compression. */ + UPROPERTY(EditAnywhere, Category = "ACL Options") + TArray OptimizationTargets; + + ////////////////////////////////////////////////////////////////////////// + + // UAnimBoneCompressionCodec implementation +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + virtual void PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) override; +#else + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + // UAnimBoneCompressionCodec_ACLBase implementation + virtual void GetCompressionSettings(acl::compression_settings& OutSettings) const override; + virtual TArray GetOptimizationTargets() const override { return OptimizationTargets; } +#endif + + // UAnimBoneCompressionCodec implementation + virtual void DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const override; + virtual void DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLDatabase.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLDatabase.h new file mode 100644 index 00000000..7acb5f4d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLDatabase.h @@ -0,0 +1,96 @@ +#pragma once + +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "ACLImpl.h" + +#include + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "AnimationCompressionLibraryDatabase.h" +#include "AnimBoneCompressionCodec_ACLBase.h" +#include "AnimBoneCompressionCodec_ACLDatabase.generated.h" + +struct FACLDatabaseCompressedAnimData final : public ICompressedAnimData +{ + /** Maps the compressed_tracks instance. Used in cooked build only. */ + TArrayView CompressedByteStream; + + /** Maps the database context instance. Used in cooked build only. */ + acl::database_context* DatabaseContext = nullptr; + + /** The codec instance that owns us. */ + const class UAnimBoneCompressionCodec_ACLDatabase* Codec = nullptr; + + /** The sequence name hash that owns this data. */ + uint32 SequenceNameHash = 0; + +#if WITH_EDITORONLY_DATA + /** Holds the compressed_tracks instance for the anim sequence */ + TArray CompressedClip; +#endif + +#if WITH_EDITORONLY_DATA + const acl::compressed_tracks* GetCompressedTracks() const { return acl::make_compressed_tracks(CompressedClip.GetData()); } +#else + const acl::compressed_tracks* GetCompressedTracks() const { return acl::make_compressed_tracks(CompressedByteStream.GetData()); } +#endif + + // ICompressedAnimData implementation +#if WITH_EDITORONLY_DATA + virtual ~FACLDatabaseCompressedAnimData() override; +#endif + + virtual void SerializeCompressedData(FArchive& Ar) override; + virtual void Bind(const TArrayView BulkData) override; + virtual int64 GetApproxCompressedSize() const override; + virtual bool IsValid() const override; +}; + +/** The default database codec implementation for ACL support with the minimal set of exposed features for ease of use. */ +UCLASS(MinimalAPI, config = Engine, meta = (DisplayName = "Anim Compress ACL Database")) +class UAnimBoneCompressionCodec_ACLDatabase : public UAnimBoneCompressionCodec_ACLBase +{ + GENERATED_UCLASS_BODY() + + /** The database asset that will hold the compressed animation data. */ + UPROPERTY(EditAnywhere, Category = "ACL Options") + UAnimationCompressionLibraryDatabase* DatabaseAsset; + +#if WITH_EDITORONLY_DATA + /** The skeletal meshes used to estimate the skinning deformation during compression. */ + UPROPERTY(EditAnywhere, Category = "ACL Options") + TArray OptimizationTargets; + + ////////////////////////////////////////////////////////////////////////// + // UObject + virtual void GetPreloadDependencies(TArray& OutDeps) override; + +#if ENGINE_MAJOR_VERSION >= 5 + virtual void PreSave(FObjectPreSaveContext SaveContext) override; +#else + virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; +#endif + + // UAnimBoneCompressionCodec implementation +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + virtual void PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) override; +#else + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + // UAnimBoneCompressionCodec_ACLBase implementation + virtual bool UseDatabase() const override { return true; } + virtual void RegisterWithDatabase(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; + virtual void GetCompressionSettings(acl::compression_settings& OutSettings) const override; + virtual TArray GetOptimizationTargets() const override { return OptimizationTargets; } +#endif + + // UAnimBoneCompressionCodec implementation + virtual TUniquePtr AllocateAnimData() const override; + virtual void ByteSwapIn(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryReader& MemoryStream) const override; + virtual void ByteSwapOut(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryWriter& MemoryStream) const override; + virtual void DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const override; + virtual void DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLSafe.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLSafe.h new file mode 100644 index 00000000..c538dfed --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimBoneCompressionCodec_ACLSafe.h @@ -0,0 +1,31 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "AnimBoneCompressionCodec_ACLBase.h" +#include "AnimBoneCompressionCodec_ACLSafe.generated.h" + +/** The default codec implementation for ACL support with the minimal set of exposed features for ease of use. */ +UCLASS(MinimalAPI, config = Engine, meta = (DisplayName = "Anim Compress ACL Safe")) +class UAnimBoneCompressionCodec_ACLSafe : public UAnimBoneCompressionCodec_ACLBase +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + // UAnimBoneCompressionCodec implementation +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + virtual void PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) override; +#else + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + // UAnimBoneCompressionCodec_ACLBase implementation + virtual void GetCompressionSettings(acl::compression_settings& OutSettings) const override; +#endif + + // UAnimBoneCompressionCodec implementation + virtual void DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const override; + virtual void DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimCurveCompressionCodec_ACL.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimCurveCompressionCodec_ACL.h new file mode 100644 index 00000000..24648f76 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimCurveCompressionCodec_ACL.h @@ -0,0 +1,40 @@ +#pragma once + +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "ACLImpl.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "AnimCurveCompressionCodec_ACL.generated.h" + +/** The default codec implementation for ACL curve compression support with the minimal set of exposed features for ease of use. */ +UCLASS(MinimalAPI, config = Engine, meta = (DisplayName = "ACL Curves")) +class UAnimCurveCompressionCodec_ACL : public UAnimCurveCompressionCodec +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** The curve precision to target when compressing the animation curves. */ + UPROPERTY(EditAnywhere, Category = "ACL Options", meta = (ClampMin = "0")) + float CurvePrecision; + + /** The mesh deformation precision to target when compressing morph target animation curves. */ + UPROPERTY(EditAnywhere, Category = "ACL Options", meta = (ClampMin = "0", EditCondition = "MorphTargetSource != nullptr")) + float MorphTargetPositionPrecision; + + /** The skeletal mesh used to estimate the morph target deformation during compression. */ + UPROPERTY(EditAnywhere, Category = "ACL Options") + class USkeletalMesh* MorphTargetSource; + + ////////////////////////////////////////////////////////////////////////// + // UAnimCurveCompressionCodec implementation + virtual void PopulateDDCKey(FArchive& Ar) override; + virtual bool Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) override; +#endif + + // UAnimCurveCompressionCodec implementation + virtual void DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimationCompressionLibraryDatabase.h b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimationCompressionLibraryDatabase.h new file mode 100644 index 00000000..c00a0204 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Classes/AnimationCompressionLibraryDatabase.h @@ -0,0 +1,209 @@ +#pragma once + +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "ACLImpl.h" + +#include +#include + +#include "CoreMinimal.h" +#include "Containers/Ticker.h" +#include "PerPlatformProperties.h" +#include "Engine/LatentActionManager.h" +#include "Serialization/BulkData.h" +#include "UObject/ObjectMacros.h" +#include "AnimationCompressionLibraryDatabase.generated.h" + +// Aliases to work with tickers +#if ENGINE_MAJOR_VERSION >= 5 +using FTickerType = FTSTicker; +using FTickerDelegateHandleType = FTSTicker::FDelegateHandle; +#else +using FTickerType = FTicker; +using FTickerDelegateHandleType = FDelegateHandle; +#endif + +/** An enum to represent the ACL visual fidelity level. */ +UENUM() +enum class ACLVisualFidelity : uint8 +{ + Highest UMETA(DisplayName = "Highest"), + Medium UMETA(DisplayName = "Medium"), + Lowest UMETA(DisplayName = "Lowest"), +}; + +/** An enum to represent the result of latent visual fidelity change requests. */ +UENUM(BlueprintType) +enum class ACLVisualFidelityChangeResult : uint8 +{ + Dispatched, + Completed, + Failed, +}; + +/** Represents a pending visual fidelity change request */ +struct FFidelityChangeRequest +{ + ACLVisualFidelityChangeResult* Result; // Optional, present when triggered from a blueprint + + uint32 RequestID; + ACLVisualFidelity Fidelity; + bool bIsInProgress; +}; + +/** An ACL database object references several UAnimSequence instances that it contains. */ +UCLASS(MinimalAPI, config = Engine, meta = (DisplayName = "ACL Database")) +class UAnimationCompressionLibraryDatabase : public UObject +{ + GENERATED_UCLASS_BODY() + +private: + /** The raw binary data for our compressed database and anim sequences. Present only in cooked builds. */ + UPROPERTY() + TArray CookedCompressedBytes; + + /** Stores a mapping for each anim sequence, where its compressed data lives in our compressed buffer. Each 64 bit value is split into 32 bits: (Hash << 32) | Offset. Present only in cooked builds. */ + UPROPERTY() + TArray CookedAnimSequenceMappings; + + /** Bulk data that we'll stream. Present only in cooked builds. */ + FByteBulkData CookedBulkData; + + /** + * The database decompression context object. Bound to the compressed database instance. + * PAIN AND MISERY + * UObjects don't support alignment above 16, see FUObjectAllocator::AllocateUObject(..). + * TUniquePtr uses operator new but versions prior to UE 4.27 do not have overrides for the + * aligned versions and thus do not support alignment above 8/16 depending on the allocator + * as it uses DEFAULT_ALIGNMENT. And even with UE 4.27, aligned overrides aren't supported by MSVC. + * Using a custom deleter and malloc/free could work but it would be ugly and require an extra memory access. + * std::aligned_storage cannot be used because UE4 emulates an old incorrect behavior that does not + * support extended alignment, see _DISABLE_EXTENDED_ALIGNED_STORAGE. + * To avoid this issue, we reserve enough space here and handle alignment manually. + * GetDatabaseContext() returns a reference to the type we need. + */ + uint8 DatabaseContextBuffer[sizeof(acl::database_context) + alignof(acl::database_context)]; + + /** The streamer instance used by the database context. Only used in cooked builds. */ + TUniquePtr DatabaseStreamer; + + /** The current visual fidelity level. */ + ACLVisualFidelity CurrentVisualFidelity; + + /** The next fidelity change request ID. Always increments. */ + uint32 NextFidelityChangeRequestID; + + /** A queue of visual fidelity change requests. */ + TArray FidelityChangeRequests; + + /** The handle to the fidelity update ticket. */ + FTickerDelegateHandleType FidelityUpdateTickerHandle; + +#if WITH_EDITORONLY_DATA + /** What percentage of the key frames should remain in the anim sequences. */ + UPROPERTY(VisibleAnywhere, Category = "Database") + float HighestImportanceProportion; + + /** What percentage of the key frames should be moved to the database. Medium importance key frames are moved second. */ + UPROPERTY(EditAnywhere, Category = "Database", meta = (ClampMin = "0", ClampMax = "1")) + float MediumImportanceProportion; + + /** What percentage of the key frames should be moved to the database. Least important key frames are moved first. */ + UPROPERTY(EditAnywhere, Category = "Database", meta = (ClampMin = "0", ClampMax = "1")) + float LowestImportanceProportion; + + /** Whether or not to strip the lowest importance tier entirely from disk. Stripping the lowest tier means that the visual fidelity of Highest and Medium are equivalent. */ + UPROPERTY(EditAnywhere, Category = "Database") + FPerPlatformBool StripLowestImportanceTier; +#endif + + /** The maximum size in KiloBytes of streaming requests. Setting this to 0 will force tiers to load in a single request regardless of their size. */ + UPROPERTY(EditAnywhere, Category = "Database", meta = (ClampMin = "4", ClampMax = "1048576")) + uint32 MaxStreamRequestSizeKB; + +#if WITH_EDITORONLY_DATA + /** The level of quality to preview with the database when decompressing in the editor. */ + UPROPERTY(EditAnywhere, Transient, Category = "Debug") + ACLVisualFidelity PreviewVisualFidelity; + + /** Editor only, transient, preview version of 'CookedCompressedBytes'. */ + TArray PreviewCompressedBytes; + + /** Editor only, transient, preview version of 'CookedAnimSequenceMappings'. */ + TArray PreviewAnimSequenceMappings; + + /** Editor only, transient, preview version of 'CookedBulkData'. */ + TArray PreviewBulkData; + + /** Editor only, transient, preview version of 'DatabaseStreamer'. */ + TUniquePtr PreviewDatabaseStreamer; + + /** The anim sequences contained within the database. Built manually from the asset UI, content browser, or with a commandlet. */ + UPROPERTY(VisibleAnywhere, Category = "Metadata") + TArray AnimSequences; + +public: + ////////////////////////////////////////////////////////////////////////// + // UObject implementation + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + +#if ENGINE_MAJOR_VERSION >= 5 + virtual void PreSave(FObjectPreSaveContext SaveContext) override; +#else + virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; +#endif + + /** Updates the internal list of anim sequences that reference this database. Returns whether or not anything changed. */ + ACLPLUGIN_API bool UpdateReferencingAnimSequenceList(); +#endif + +public: + // UObject implementation + virtual void BeginDestroy() override; + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; + + /** Initiate a latent database change in visual fidelity by streaming in/out as necessary. */ + void SetVisualFidelity(ACLVisualFidelity VisualFidelity); + + /** Retrieves the current visual fidelity level. */ + ACLVisualFidelity GetVisualFidelity() const { return CurrentVisualFidelity; } + + /** Initiate a latent database change in quality by streaming in/out as necessary. */ + UFUNCTION(BlueprintCallable, Category = "Animation|ACL", meta = (DisplayName = "Set Database Visual Fidelity", WorldContext="WorldContextObject", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs="Result")) + static void SetVisualFidelity(UObject* WorldContextObject, FLatentActionInfo LatentInfo, UAnimationCompressionLibraryDatabase* DatabaseAsset, ACLVisualFidelityChangeResult& Result, ACLVisualFidelity VisualFidelity = ACLVisualFidelity::Highest); + + UFUNCTION(BlueprintCallable, Category = "Animation|ACL", meta = (DisplayName = "Get Database Visual Fidelity")) + static ACLVisualFidelity GetVisualFidelity(UAnimationCompressionLibraryDatabase* DatabaseAsset); + +private: +#if WITH_EDITORONLY_DATA + /** Builds our database and its related mappings as well as the new anim sequence data. */ + void BuildDatabase(TArray& OutCompressedBytes, TArray& OutAnimSequenceMappings, TArray& OutBulkData, bool bStripLowestTier = false) const; + + /** Updates the internal preview state and optionally builds the database when requested. */ + void UpdatePreviewState(bool bBuildDatabase); +#endif + + /** Shared implementation between C++ and blueprint interfaces. Returns the request ID necessary to abort or ~0 if no change is required. */ + uint32 SetVisualFidelityImpl(ACLVisualFidelity VisualFidelity, ACLVisualFidelityChangeResult* OutResult); + + /** Cancels an ongoing visual fidelity request. Only supported if the request hasn't completed. Will not update the ACLVisualFidelityChangeResult of the request. */ + void CancelVisualFidelityRequestImpl(uint32 RequestID); + + /** Core ticker update function to update our visual fidelity state. */ + bool UpdateVisualFidelityTicker(float DeltaTime); + + /** Returns a reference to the properly aligned acl::database_context. */ + acl::database_context& GetDatabaseContext() + { + uint8* AlignedDatabaseContextBuffer = Align(&DatabaseContextBuffer[0], alignof(acl::database_context)); + return *reinterpret_cast*>(AlignedDatabaseContextBuffer); + } + + friend class FACLPlugin; + friend class FSetDatabaseVisualFidelityAction; + friend class UAnimBoneCompressionCodec_ACLDatabase; + friend struct FACLDatabaseCompressedAnimData; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLDecompressionImpl.h b/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLDecompressionImpl.h new file mode 100644 index 00000000..6a382a56 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLDecompressionImpl.h @@ -0,0 +1,245 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" + +#include "ACLImpl.h" + +#include +#include + +constexpr acl::sample_rounding_policy get_rounding_policy(EAnimInterpolationType InterpType) { return InterpType == EAnimInterpolationType::Step ? acl::sample_rounding_policy::floor : acl::sample_rounding_policy::none; } + +/* + * The FTransform type does not support setting the members directly from vector types + * so we derive from it and expose that functionality. + */ +struct FACLTransform final : public FTransform +{ + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL SetRotationRaw(rtm::quatf_arg0 Rotation_) + { +#if PLATFORM_ENABLE_VECTORINTRINSICS + Rotation = Rotation_; +#else + rtm::quat_store(Rotation_, &Rotation.X); +#endif + } + + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL SetTranslationRaw(rtm::vector4f_arg0 Translation_) + { +#if PLATFORM_ENABLE_VECTORINTRINSICS + Translation = VectorSet_W0(Translation_); +#else + rtm::vector_store3(Translation_, &Translation.X); +#endif + } + + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL SetScale3DRaw(rtm::vector4f_arg0 Scale_) + { +#if PLATFORM_ENABLE_VECTORINTRINSICS + Scale3D = VectorSet_W0(Scale_); +#else + rtm::vector_store3(Scale_, &Scale3D.X); +#endif + } +}; + +/** These 3 indices map into the output Atom array. */ +struct FAtomIndices +{ + uint16 Rotation; + uint16 Translation; + uint16 Scale; +}; + +/* + * Output pose writer that can selectively skip certain tracks. + */ +struct FUE4OutputWriter final : public acl::track_writer +{ + // Raw pointer for performance reasons, caller is responsible for ensuring data is valid + FACLTransform* Atoms; + const FAtomIndices* TrackToAtomsMap; + + FUE4OutputWriter(TArrayView& Atoms_, const FAtomIndices* TrackToAtomsMap_) + : Atoms(static_cast(Atoms_.GetData())) + , TrackToAtomsMap(TrackToAtomsMap_) + {} + + ////////////////////////////////////////////////////////////////////////// + // Override the OutputWriter behavior + FORCEINLINE_DEBUGGABLE bool skip_track_rotation(uint32_t BoneIndex) const { return TrackToAtomsMap[BoneIndex].Rotation == 0xFFFF; } + FORCEINLINE_DEBUGGABLE bool skip_track_translation(uint32_t BoneIndex) const { return TrackToAtomsMap[BoneIndex].Translation == 0xFFFF; } + FORCEINLINE_DEBUGGABLE bool skip_track_scale(uint32_t BoneIndex) const { return TrackToAtomsMap[BoneIndex].Scale == 0xFFFF; } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a quaternion rotation value for a specified bone index + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_rotation(uint32_t BoneIndex, rtm::quatf_arg0 Rotation) + { + const uint32 AtomIndex = TrackToAtomsMap[BoneIndex].Rotation; + + FACLTransform& BoneAtom = Atoms[AtomIndex]; + BoneAtom.SetRotationRaw(Rotation); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a translation value for a specified bone index + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_translation(uint32_t BoneIndex, rtm::vector4f_arg0 Translation) + { + const uint32 AtomIndex = TrackToAtomsMap[BoneIndex].Translation; + + FACLTransform& BoneAtom = Atoms[AtomIndex]; + BoneAtom.SetTranslationRaw(Translation); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a scale value for a specified bone index + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_scale(uint32_t BoneIndex, rtm::vector4f_arg0 Scale) + { + const uint32 AtomIndex = TrackToAtomsMap[BoneIndex].Scale; + + FACLTransform& BoneAtom = Atoms[AtomIndex]; + BoneAtom.SetScale3DRaw(Scale); + } +}; + +/* +* Output track writer for a single track. +*/ +struct UE4OutputTrackWriter final : public acl::track_writer +{ + // Raw reference for performance reasons, caller is responsible for ensuring data is valid + FACLTransform& Atom; + + explicit UE4OutputTrackWriter(FTransform& Atom_) + : Atom(static_cast(Atom_)) + {} + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a quaternion rotation value for a specified bone index + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_rotation(uint32_t BoneIndex, rtm::quatf_arg0 Rotation) + { + Atom.SetRotationRaw(Rotation); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a translation value for a specified bone index + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_translation(uint32_t BoneIndex, rtm::vector4f_arg0 Translation) + { + Atom.SetTranslationRaw(Translation); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a scale value for a specified bone index + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_scale(uint32_t BoneIndex, rtm::vector4f_arg0 Scale) + { + Atom.SetScale3DRaw(Scale); + } +}; + +template +FORCEINLINE_DEBUGGABLE void DecompressBone(FAnimSequenceDecompressionContext& DecompContext, ACLContextType& ACLContext, int32 TrackIndex, FTransform& OutAtom) +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + const float Time = DecompContext.GetEvaluationTime(); +#else + const float Time = DecompContext.Time; +#endif + + ACLContext.seek(Time, get_rounding_policy(DecompContext.Interpolation)); + + UE4OutputTrackWriter Writer(OutAtom); + ACLContext.decompress_track(TrackIndex, Writer); +} + +template +FORCEINLINE_DEBUGGABLE void DecompressPose(FAnimSequenceDecompressionContext& DecompContext, ACLContextType& ACLContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + const float Time = DecompContext.GetEvaluationTime(); +#else + const float Time = DecompContext.Time; +#endif + + // Seek first, we'll start prefetching ahead right away + ACLContext.seek(Time, get_rounding_policy(DecompContext.Interpolation)); + + const acl::compressed_tracks* CompressedClipData = ACLContext.get_compressed_tracks(); + const int32 TrackCount = CompressedClipData->get_num_tracks(); + + // TODO: Allocate this with padding and use SIMD to set everything to 0xFF + FAtomIndices* TrackToAtomsMap = new(FMemStack::Get()) FAtomIndices[TrackCount]; + FMemory::Memset(TrackToAtomsMap, 0xFF, sizeof(FAtomIndices) * TrackCount); + + // TODO: We should only need 1x uint16 atom index for each track/bone index + // and we need 3 bits to tell whether we care about the rot/trans/scale + // The rot and scale pairs are often the same array, skip the scale iteration and set both flags while + // iterating on the rotation pairs. This will reduce the mapping size by 2 bytes if we use 4 bytes. + // All reads will be aligned, can we pack further with 1x uint16 and 1x uint8 and do unaligned loads? + // Need to double check what the ASM looks like on x64 and ARM first to make sure it's good + // Maybe having two arrays side by side is better with uint16/uint8? Or having 1 bitset array? + // Ultimately, when we load these indices, they will be in the L1 since we write them here just before + // we use them during decompression. Optimizing for quick loading/unpacking it best. + +#if DO_CHECK + int32 MinAtomIndex = OutAtoms.Num(); + int32 MaxAtomIndex = -1; + int32 MinTrackIndex = INT_MAX; + int32 MaxTrackIndex = -1; +#endif + + for (const BoneTrackPair& Pair : RotationPairs) + { + TrackToAtomsMap[Pair.TrackIndex].Rotation = (uint16)Pair.AtomIndex; + +#if DO_CHECK + MinAtomIndex = FMath::Min(MinAtomIndex, Pair.AtomIndex); + MaxAtomIndex = FMath::Max(MaxAtomIndex, Pair.AtomIndex); + MinTrackIndex = FMath::Min(MinTrackIndex, Pair.TrackIndex); + MaxTrackIndex = FMath::Max(MaxTrackIndex, Pair.TrackIndex); +#endif + } + + for (const BoneTrackPair& Pair : TranslationPairs) + { + TrackToAtomsMap[Pair.TrackIndex].Translation = (uint16)Pair.AtomIndex; + +#if DO_CHECK + MinAtomIndex = FMath::Min(MinAtomIndex, Pair.AtomIndex); + MaxAtomIndex = FMath::Max(MaxAtomIndex, Pair.AtomIndex); + MinTrackIndex = FMath::Min(MinTrackIndex, Pair.TrackIndex); + MaxTrackIndex = FMath::Max(MaxTrackIndex, Pair.TrackIndex); +#endif + } + + const acl::acl_impl::tracks_header& TracksHeader = acl::acl_impl::get_tracks_header(*CompressedClipData); + if (TracksHeader.get_has_scale()) + { + for (const BoneTrackPair& Pair : ScalePairs) + { + TrackToAtomsMap[Pair.TrackIndex].Scale = (uint16)Pair.AtomIndex; + +#if DO_CHECK + MinAtomIndex = FMath::Min(MinAtomIndex, Pair.AtomIndex); + MaxAtomIndex = FMath::Max(MaxAtomIndex, Pair.AtomIndex); + MinTrackIndex = FMath::Min(MinTrackIndex, Pair.TrackIndex); + MaxTrackIndex = FMath::Max(MaxTrackIndex, Pair.TrackIndex); +#endif + } + } + +#if DO_CHECK + // Only assert once for performance reasons, when we write the pose, we won't perform the checks + checkf(OutAtoms.IsValidIndex(MinAtomIndex), TEXT("Invalid atom index: %d"), MinAtomIndex); + checkf(OutAtoms.IsValidIndex(MaxAtomIndex), TEXT("Invalid atom index: %d"), MaxAtomIndex); + checkf(MinTrackIndex >= 0, TEXT("Invalid track index: %d"), MinTrackIndex); + checkf(MaxTrackIndex < TrackCount, TEXT("Invalid track index: %d"), MaxTrackIndex); +#endif + + // We will decompress the whole pose even if we only care about a smaller subset of bone tracks. + // This ensures we read the compressed pose data once, linearly. + + FUE4OutputWriter PoseWriter(OutAtoms, TrackToAtomsMap); + ACLContext.decompress_tracks(PoseWriter); +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLImpl.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLImpl.cpp new file mode 100644 index 00000000..680eee0f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLImpl.cpp @@ -0,0 +1,263 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "ACLImpl.h" + +#if WITH_EDITOR +#include "AnimationCompression.h" +#include "AnimationUtils.h" +#include "Animation/AnimCompressionTypes.h" + +acl::rotation_format8 GetRotationFormat(ACLRotationFormat Format) +{ + switch (Format) + { + default: + case ACLRF_Quat_128: return acl::rotation_format8::quatf_full; + case ACLRF_QuatDropW_96: return acl::rotation_format8::quatf_drop_w_full; + case ACLRF_QuatDropW_Variable: return acl::rotation_format8::quatf_drop_w_variable; + } +} + +acl::vector_format8 GetVectorFormat(ACLVectorFormat Format) +{ + switch (Format) + { + default: + case ACLVF_Vector3_96: return acl::vector_format8::vector3f_full; + case ACLVF_Vector3_Variable: return acl::vector_format8::vector3f_variable; + } +} + +acl::compression_level8 GetCompressionLevel(ACLCompressionLevel Level) +{ + switch (Level) + { + default: + case ACLCL_Lowest: return acl::compression_level8::lowest; + case ACLCL_Low: return acl::compression_level8::low; + case ACLCL_Medium: return acl::compression_level8::medium; + case ACLCL_High: return acl::compression_level8::high; + case ACLCL_Highest: return acl::compression_level8::highest; + } +} + +static int32 FindAnimationTrackIndex(const FCompressibleAnimData& CompressibleAnimData, int32 BoneIndex) +{ + const TArray& TrackToSkelMap = CompressibleAnimData.TrackToSkeletonMapTable; + if (BoneIndex != INDEX_NONE) + { + for (int32 TrackIndex = 0; TrackIndex < TrackToSkelMap.Num(); ++TrackIndex) + { + const FTrackToSkeletonMap& TrackToSkeleton = TrackToSkelMap[TrackIndex]; + if (TrackToSkeleton.BoneTreeIndex == BoneIndex) + return TrackIndex; + } + } + + return INDEX_NONE; +} + +static bool IsAdditiveBakedIntoRaw(const FCompressibleAnimData& CompressibleAnimData) +{ + if (!CompressibleAnimData.bIsValidAdditive) + { + return true; // Sequences that aren't additive don't need baking as they are already baked + } + + if (CompressibleAnimData.RawAnimationData.Num() == 0) + { + return true; // Sequences with no raw data don't need baking and we can't tell if they are baked or not so assume that they are, it doesn't matter + } + + if (CompressibleAnimData.AdditiveBaseAnimationData.Num() != 0) + { + return true; // Sequence has raw data and additive base data, it is baked + } + + return false; // Sequence has raw data but no additive base data, it isn't baked +} + +acl::track_array_qvvf BuildACLTransformTrackArray(ACLAllocator& AllocatorImpl, const FCompressibleAnimData& CompressibleAnimData, float DefaultVirtualVertexDistance, float SafeVirtualVertexDistance, bool bBuildAdditiveBase) +{ + const bool bIsAdditive = bBuildAdditiveBase ? false : CompressibleAnimData.bIsValidAdditive; + const bool bIsAdditiveBakedIntoRaw = IsAdditiveBakedIntoRaw(CompressibleAnimData); + check(!bIsAdditive || bIsAdditiveBakedIntoRaw); + if (bIsAdditive && !bIsAdditiveBakedIntoRaw) + { + UE_LOG(LogAnimationCompression, Fatal, TEXT("Animation sequence is additive but it is not baked into the raw data, this is not supported. [%s]"), *CompressibleAnimData.FullName); + return acl::track_array_qvvf(); + } + + // Ordinary non additive sequences only contain raw data returned by GetRawAnimationData(). + // + // Additive sequences have their raw data baked with the raw additive base already taken out. At runtime, + // the additive sequence data returned by GetRawAnimationData() gets applied on top of the + // additive base sequence whose's data is returned by GetAdditiveBaseAnimationData(). In practice, + // at runtime the additive base might also be compressed and thus there might be some added noise but + // it typically isn't visible. + // The baked additive data should have the same number of frames as the current sequence but if we only care about a specific frame, + // we use it. When this happens, the additive base will contain that single frame repeated over and over: an animated static pose. + // To avoid wasting memory, we just grab the first frame. + + const TArray& RawTracks = bBuildAdditiveBase ? CompressibleAnimData.AdditiveBaseAnimationData : CompressibleAnimData.RawAnimationData; + const uint32 NumSamples = GetNumSamples(CompressibleAnimData); + const bool bIsStaticPose = NumSamples <= 1 || CompressibleAnimData.SequenceLength < 0.0001f; + const float SampleRate = bIsStaticPose ? 30.0f : (float(NumSamples - 1) / CompressibleAnimData.SequenceLength); + const int32 NumBones = CompressibleAnimData.BoneData.Num(); + const int32 NumUETracks = CompressibleAnimData.TrackToSkeletonMapTable.Num(); + + // Additive animations have 0,0,0 scale as the default since we add it + const FRawAnimTrackVector3 UE4DefaultScale(bIsAdditive ? 0.0f : 1.0f); + const rtm::vector4f ACLDefaultScale = rtm::vector_set(bIsAdditive ? 0.0f : 1.0f); + + // We need to make sure to allocate enough ACL tracks. It is very common to have a skeleton with a number of bones + // and to have anim sequences that use that skeleton that have fewer tracks. This might happen if bones are added + // and when this happens, we'll populate the bind pose for that missing track. A less common case can happen where + // a sequence has compressed tracks for bones that no longer exist. We still need to compress this unused data to + // ensure the track indices remain in sync with the CompressedTrackToSkeletonMapTable. + const int32 NumInputACLTracks = FMath::Max(NumBones, NumUETracks); + + acl::track_array_qvvf Tracks(AllocatorImpl, NumInputACLTracks); + Tracks.set_name(acl::string(AllocatorImpl, TCHAR_TO_ANSI(*CompressibleAnimData.FullName))); + + TBitArray<> PopulatedUETracks(false, NumUETracks); + + // Populate all our track based on our skeleton, even if some end up stripped + for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const FBoneData& UE4Bone = CompressibleAnimData.BoneData[BoneIndex]; + + acl::track_desc_transformf Desc; + + // We use a higher virtual vertex distance when bones have a socket attached or are keyed end effectors (IK, hand, camera, etc) + Desc.shell_distance = (UE4Bone.bHasSocket || UE4Bone.bKeyEndEffector) ? SafeVirtualVertexDistance : DefaultVirtualVertexDistance; + + const int32 ParentBoneIndex = UE4Bone.GetParent(); + Desc.parent_index = ParentBoneIndex >= 0 ? ParentBoneIndex : acl::k_invalid_track_index; + + const int32 UETrackIndex = FindAnimationTrackIndex(CompressibleAnimData, BoneIndex); + + // We output bone data in UE track order. If a track isn't present, we will use the bind pose and strip it from the + // compressed stream. + if (UETrackIndex >= 0) + { + PopulatedUETracks[UETrackIndex] = true; + Desc.output_index = UETrackIndex; + } + else + { + Desc.output_index = acl::k_invalid_track_index; + } + + acl::track_qvvf Track = acl::track_qvvf::make_reserve(Desc, AllocatorImpl, NumSamples, SampleRate); + Track.set_name(acl::string(AllocatorImpl, TCHAR_TO_ANSI(*UE4Bone.Name.ToString()))); + + if (UETrackIndex >= 0) + { + // We have a track for this bone, use it + const FRawAnimSequenceTrack& RawTrack = RawTracks[UETrackIndex]; + + for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const FRawAnimTrackQuat& RotationSample = RawTrack.RotKeys.Num() == 1 ? RawTrack.RotKeys[0] : RawTrack.RotKeys[SampleIndex]; + Track[SampleIndex].rotation = UEQuatToACL(RotationSample); + + const FRawAnimTrackVector3& TranslationSample = RawTrack.PosKeys.Num() == 1 ? RawTrack.PosKeys[0] : RawTrack.PosKeys[SampleIndex]; + Track[SampleIndex].translation = UEVector3ToACL(TranslationSample); + + const FRawAnimTrackVector3& ScaleSample = RawTrack.ScaleKeys.Num() == 0 ? UE4DefaultScale : (RawTrack.ScaleKeys.Num() == 1 ? RawTrack.ScaleKeys[0] : RawTrack.ScaleKeys[SampleIndex]); + Track[SampleIndex].scale = UEVector3ToACL(ScaleSample); + } + } + else + { + // No track data for this bone, it must be new. Use the bind pose instead. + // Additive animations have the identity with 0 scale as their bind pose. + rtm::qvvf BindTransform; + if (bIsAdditive) + { + BindTransform = rtm::qvv_identity(); + BindTransform.scale = ACLDefaultScale; + } + else + { + BindTransform = rtm::qvv_set(UEQuatToACL(UE4Bone.Orientation), UEVector3ToACL(UE4Bone.Position), ACLDefaultScale); + } + + for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + Track[SampleIndex] = BindTransform; + } + } + + Tracks[BoneIndex] = MoveTemp(Track); + } + + // If we have leftover tracks that do not map to any bone in our skeleton, compress them anyway to keep indices consistent + // TODO: Can we just set them to identity to strip them? Is it possible for them to be used at runtime? + int32 ACLTrackIndex = NumBones; // Start inserting at the end + for (int32 UETrackIndex = 0; UETrackIndex < NumUETracks; ++UETrackIndex) + { + if (PopulatedUETracks[UETrackIndex]) + { + // This track has been populated, skip it + continue; + } + + // This track has no corresponding skeleton bone, add it anyway + acl::track_desc_transformf Desc; + + // Without a bone, we have no way of knowing if we require special treatment + Desc.shell_distance = DefaultVirtualVertexDistance; + + // Without a bone, no way to know if we have a parent, assume we are a root bone + Desc.parent_index = acl::k_invalid_track_index; + + // Output index is our UE track index + Desc.output_index = UETrackIndex; + + acl::track_qvvf Track = acl::track_qvvf::make_reserve(Desc, AllocatorImpl, NumSamples, SampleRate); + + // We have raw track data, use it + const FRawAnimSequenceTrack& RawTrack = RawTracks[UETrackIndex]; + + for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const FRawAnimTrackQuat& RotationSample = RawTrack.RotKeys.Num() == 1 ? RawTrack.RotKeys[0] : RawTrack.RotKeys[SampleIndex]; + Track[SampleIndex].rotation = UEQuatToACL(RotationSample); + + const FRawAnimTrackVector3& TranslationSample = RawTrack.PosKeys.Num() == 1 ? RawTrack.PosKeys[0] : RawTrack.PosKeys[SampleIndex]; + Track[SampleIndex].translation = UEVector3ToACL(TranslationSample); + + const FRawAnimTrackVector3& ScaleSample = RawTrack.ScaleKeys.Num() == 0 ? UE4DefaultScale : (RawTrack.ScaleKeys.Num() == 1 ? RawTrack.ScaleKeys[0] : RawTrack.ScaleKeys[SampleIndex]); + Track[SampleIndex].scale = UEVector3ToACL(ScaleSample); + } + + // TODO: Should we warn the user about this? We are compressing extra data that might not be otherwise usable... + + // Add our extra track + Tracks[ACLTrackIndex] = MoveTemp(Track); + ACLTrackIndex++; + } + + return Tracks; +} + +uint32 GetNumSamples(const FCompressibleAnimData& CompressibleAnimData) +{ +#if ENGINE_MAJOR_VERSION >= 5 + return CompressibleAnimData.NumberOfKeys; +#else + return CompressibleAnimData.NumFrames; +#endif +} + +float GetSequenceLength(const UAnimSequence& AnimSeq) +{ +#if ENGINE_MAJOR_VERSION >= 5 + return AnimSeq.GetDataModel()->GetPlayLength(); +#else + return AnimSeq.SequenceLength; +#endif +} +#endif // WITH_EDITOR diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLPluginModule.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLPluginModule.cpp new file mode 100644 index 00000000..e5534f0f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/ACLPluginModule.cpp @@ -0,0 +1,395 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "IACLPluginModule.h" +#include "Modules/ModuleManager.h" + +// Enable console commands only in development builds when logging is enabled +#define WITH_ACL_CONSOLE_COMMANDS (!UE_BUILD_SHIPPING && !UE_BUILD_TEST && !NO_LOGGING) + +#if WITH_ACL_CONSOLE_COMMANDS +#include "AnimationCompressionLibraryDatabase.h" +#include "AnimBoneCompressionCodec_ACLDatabase.h" + +#include "AnimationCompression.h" +#include "Animation/AnimBoneCompressionCodec.h" +#include "Animation/AnimBoneCompressionSettings.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "Animation/AnimCurveCompressionSettings.h" +#include "HAL/IConsoleManager.h" +#include "UObject/UObjectIterator.h" +#endif + +#if WITH_EDITORONLY_DATA +#include "EditorDatabaseMonitor.h" +#endif + +ACLAllocator ACLAllocatorImpl; + +class FACLPlugin final : public IACLPlugin +{ +private: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +#if WITH_ACL_CONSOLE_COMMANDS + // Console commands + void ListCodecs(const TArray& Args); + void ListAnimSequences(const TArray& Args); + void SetDatabaseVisualFidelity(const TArray& Args); + + TArray ConsoleCommands; +#endif + +#if WITH_EDITORONLY_DATA + void OnPostEngineInit(); +#endif +}; + +IMPLEMENT_MODULE(FACLPlugin, ACLPlugin) + +////////////////////////////////////////////////////////////////////////// + +#if WITH_ACL_CONSOLE_COMMANDS +template +static TArray GetObjectInstancesSorted() +{ + TArray Results; + + for (TObjectIterator It; It; ++It) + { + Results.Add(*It); + } + + struct FCompareObjectNames + { + FORCEINLINE bool operator()(const ClassType& Lhs, const ClassType& Rhs) const + { + return Lhs.GetPathName().Compare(Rhs.GetPathName()) < 0; + } + }; + Results.Sort(FCompareObjectNames()); + + return Results; +} + +static double BytesToKB(SIZE_T NumBytes) +{ + return (double)NumBytes / 1024.0; +} + +static double BytesToMB(SIZE_T NumBytes) +{ + return (double)NumBytes / (1024.0 * 1024.0); +} + +template +static double Percentage(SizeType Part, SizeType Whole) +{ + return Whole != 0 ? (((double)Part / (double)Whole) * 100.0) : 0.0; +} + +static SIZE_T GetCompressedBoneSize(const FCompressedAnimSequence& CompressedData) +{ + SIZE_T Size = CompressedData.CompressedTrackToSkeletonMapTable.GetAllocatedSize(); + if (CompressedData.CompressedDataStructure) + { + Size += CompressedData.CompressedDataStructure->GetApproxCompressedSize(); + } + return Size; +} + +static SIZE_T GetCompressedCurveSize(const FCompressedAnimSequence& CompressedData) +{ + SIZE_T Size = CompressedData.CompressedCurveNames.GetAllocatedSize(); + Size += CompressedData.CompressedCurveByteStream.GetAllocatedSize(); + return Size; +} + +void FACLPlugin::ListCodecs(const TArray& Args) +{ + // Turn off log times to make diffing easier + TGuardValue DisableLogTimes(GPrintLogTimes, ELogTimes::None); + + // Make sure to log everything + const ELogVerbosity::Type OldVerbosity = LogAnimationCompression.GetVerbosity(); + LogAnimationCompression.SetVerbosity(ELogVerbosity::All); + + const TArray BoneSettings = GetObjectInstancesSorted(); + const TArray BoneCodecs = GetObjectInstancesSorted(); + const TArray CurveSettings = GetObjectInstancesSorted(); + const TArray CurveCodecs = GetObjectInstancesSorted(); + const TArray AnimSequences = GetObjectInstancesSorted(); + const TArray Databases = GetObjectInstancesSorted(); + + UE_LOG(LogAnimationCompression, Log, TEXT("===== Bone Compression Setting Assets =====")); + for (const UAnimBoneCompressionSettings* Settings : BoneSettings) + { + int32 NumReferences = 0; + SIZE_T TotalSize = 0; + SIZE_T UsedSize = 0; + for (const UAnimSequence* AnimSeq : AnimSequences) + { + const SIZE_T Size = GetCompressedBoneSize(AnimSeq->CompressedData); + if (AnimSeq->BoneCompressionSettings == Settings) + { + NumReferences++; + UsedSize += Size; + } + TotalSize += Size; + } + + UE_LOG(LogAnimationCompression, Log, TEXT("%s ..."), *Settings->GetPathName()); + UE_LOG(LogAnimationCompression, Log, TEXT(" used by %d / %d (%.1f %%) anim sequences"), NumReferences, AnimSequences.Num(), Percentage(NumReferences, AnimSequences.Num())); + UE_LOG(LogAnimationCompression, Log, TEXT(" uses %.2f MB / %.2f MB (%.1f %%)"), BytesToMB(UsedSize), BytesToMB(TotalSize), Percentage(UsedSize, TotalSize)); + } + + UE_LOG(LogAnimationCompression, Log, TEXT("===== Bone Compression Codecs =====")); + for (const UAnimBoneCompressionCodec* Codec : BoneCodecs) + { + int32 NumReferences = 0; + SIZE_T TotalSize = 0; + SIZE_T UsedSize = 0; + for (const UAnimSequence* AnimSeq : AnimSequences) + { + const SIZE_T Size = GetCompressedBoneSize(AnimSeq->CompressedData); + if (AnimSeq->CompressedData.BoneCompressionCodec == Codec) + { + NumReferences++; + UsedSize += Size; + } + TotalSize += Size; + } + + if (Codec->Description.IsEmpty()) + { + UE_LOG(LogAnimationCompression, Log, TEXT("%s ..."), *Codec->GetPathName()); + } + else + { + UE_LOG(LogAnimationCompression, Log, TEXT("%s (%s) ..."), *Codec->GetPathName(), *Codec->Description); + } + + UE_LOG(LogAnimationCompression, Log, TEXT(" used by %d / %d (%.1f %%) anim sequences"), NumReferences, AnimSequences.Num(), Percentage(NumReferences, AnimSequences.Num())); + UE_LOG(LogAnimationCompression, Log, TEXT(" uses %.2f MB / %.2f MB (%.1f %%)"), BytesToMB(UsedSize), BytesToMB(TotalSize), Percentage(UsedSize, TotalSize)); + } + + UE_LOG(LogAnimationCompression, Log, TEXT("===== Curve Compression Setting Assets =====")); + for (const UAnimCurveCompressionSettings* Settings : CurveSettings) + { + int32 NumReferences = 0; + SIZE_T TotalSize = 0; + SIZE_T UsedSize = 0; + for (const UAnimSequence* AnimSeq : AnimSequences) + { + const SIZE_T Size = GetCompressedCurveSize(AnimSeq->CompressedData); + if (AnimSeq->CurveCompressionSettings == Settings) + { + NumReferences++; + UsedSize += Size; + } + TotalSize += Size; + } + + UE_LOG(LogAnimationCompression, Log, TEXT("%s ..."), *Settings->GetPathName()); + UE_LOG(LogAnimationCompression, Log, TEXT(" used by %d / %d (%.1f %%) anim sequences"), NumReferences, AnimSequences.Num(), Percentage(NumReferences, AnimSequences.Num())); + UE_LOG(LogAnimationCompression, Log, TEXT(" uses %.2f MB / %.2f MB (%.1f %%)"), BytesToMB(UsedSize), BytesToMB(TotalSize), Percentage(UsedSize, TotalSize)); + } + + UE_LOG(LogAnimationCompression, Log, TEXT("===== Curve Compression Codecs =====")); + for (const UAnimCurveCompressionCodec* Codec : CurveCodecs) + { + int32 NumReferences = 0; + SIZE_T TotalSize = 0; + SIZE_T UsedSize = 0; + for (const UAnimSequence* AnimSeq : AnimSequences) + { + const SIZE_T Size = GetCompressedCurveSize(AnimSeq->CompressedData); + if (AnimSeq->CompressedData.CurveCompressionCodec == Codec) + { + NumReferences++; + UsedSize += Size; + } + TotalSize += Size; + } + + UE_LOG(LogAnimationCompression, Log, TEXT("%s ..."), *Codec->GetPathName()); + UE_LOG(LogAnimationCompression, Log, TEXT(" used by %d / %d (%.1f %%) anim sequences"), NumReferences, AnimSequences.Num(), Percentage(NumReferences, AnimSequences.Num())); + UE_LOG(LogAnimationCompression, Log, TEXT(" uses %.2f MB / %.2f MB (%.1f %%)"), BytesToMB(UsedSize), BytesToMB(TotalSize), Percentage(UsedSize, TotalSize)); + } + + UE_LOG(LogAnimationCompression, Log, TEXT("===== Animation Compression Library Database Assets =====")); + for (const UAnimationCompressionLibraryDatabase* Database : Databases) + { + int32 NumReferences = 0; + for (const UAnimSequence* AnimSeq : AnimSequences) + { + UAnimBoneCompressionCodec_ACLDatabase* Codec = Cast(AnimSeq->CompressedData.BoneCompressionCodec); + if (Codec != nullptr && Codec->DatabaseAsset == Database) + { + NumReferences++; + } + } + + const acl::compressed_database* CompressedDatabase = acl::make_compressed_database(Database->CookedCompressedBytes.GetData()); + + const uint32 DatabaseTotalSize = CompressedDatabase != nullptr ? CompressedDatabase->get_total_size() : 0; + const uint32 DatabaseSize = CompressedDatabase != nullptr ? CompressedDatabase->get_size() : 0; + const uint32 DatabaseBulkDataSizeMedium = CompressedDatabase != nullptr ? CompressedDatabase->get_bulk_data_size(acl::quality_tier::medium_importance) : 0; + const uint32 DatabaseBulkDataSizeLow = CompressedDatabase != nullptr ? CompressedDatabase->get_bulk_data_size(acl::quality_tier::lowest_importance) : 0; + const uint32 DatabaseBulkDataSize = DatabaseBulkDataSizeMedium + DatabaseBulkDataSizeLow; + const uint32 SequencesSize = Database->CookedCompressedBytes.Num() - DatabaseSize; // CompressedBytes contains the DB metadata and the sequences but not the bulk data + + UE_LOG(LogAnimationCompression, Log, TEXT("%s ..."), *Database->GetPathName()); + UE_LOG(LogAnimationCompression, Log, TEXT(" used by %d / %d (%.1f %%) anim sequences"), NumReferences, AnimSequences.Num(), Percentage(NumReferences, AnimSequences.Num())); + UE_LOG(LogAnimationCompression, Log, TEXT(" sequences use %.2f MB"), BytesToMB(SequencesSize)); + UE_LOG(LogAnimationCompression, Log, TEXT(" database uses %.2f MB (%.2f MB streamable)"), BytesToMB(DatabaseTotalSize), BytesToMB(DatabaseBulkDataSize)); + } + + LogAnimationCompression.SetVerbosity(OldVerbosity); +} + +void FACLPlugin::ListAnimSequences(const TArray& Args) +{ + // Turn off log times to make diffing easier + TGuardValue DisableLogTimes(GPrintLogTimes, ELogTimes::None); + + // Make sure to log everything + const ELogVerbosity::Type OldVerbosity = LogAnimationCompression.GetVerbosity(); + LogAnimationCompression.SetVerbosity(ELogVerbosity::All); + + const TArray AnimSequences = GetObjectInstancesSorted(); + + SIZE_T BoneDataTotalSize = 0; + SIZE_T CurveDataTotalSize = 0; + + UE_LOG(LogAnimationCompression, Log, TEXT("===== Anim Sequence Assets =====")); + for (const UAnimSequence* AnimSeq : AnimSequences) + { + UE_LOG(LogAnimationCompression, Log, TEXT("%s ..."), *AnimSeq->GetPathName()); + + if (AnimSeq->CompressedData.BoneCompressionCodec->Description.IsEmpty()) + { + UE_LOG(LogAnimationCompression, Log, TEXT(" uses bone codec %s"), *AnimSeq->CompressedData.BoneCompressionCodec->GetPathName()); + } + else + { + UE_LOG(LogAnimationCompression, Log, TEXT(" uses bone codec %s (%s)"), *AnimSeq->CompressedData.BoneCompressionCodec->GetPathName(), *AnimSeq->CompressedData.BoneCompressionCodec->Description); + } + + const SIZE_T BoneDataSize = GetCompressedBoneSize(AnimSeq->CompressedData); + UE_LOG(LogAnimationCompression, Log, TEXT(" has %.2f KB of bone data"), BytesToKB(BoneDataSize)); + +#if WITH_EDITORONLY_DATA + if (AnimSeq->CompressedData.CompressedDataStructure) + { + UE_LOG(LogAnimationCompression, Log, TEXT(" has a bone error of %.4f cm"), AnimSeq->CompressedData.CompressedDataStructure->BoneCompressionErrorStats.MaxError); + } +#endif + + UE_LOG(LogAnimationCompression, Log, TEXT(" uses curve codec %s"), *AnimSeq->CompressedData.CurveCompressionCodec->GetPathName()); + + const SIZE_T CurveDataSize = GetCompressedCurveSize(AnimSeq->CompressedData); + UE_LOG(LogAnimationCompression, Log, TEXT(" has %.2f KB of curve data"), BytesToKB(CurveDataSize)); + + BoneDataTotalSize += BoneDataSize; + CurveDataTotalSize += CurveDataSize; + } + + UE_LOG(LogAnimationCompression, Log, TEXT("Total bone data size: %.2f MB"), BytesToMB(BoneDataTotalSize)); + UE_LOG(LogAnimationCompression, Log, TEXT("Total curve data size: %.2f MB"), BytesToMB(CurveDataTotalSize)); + + LogAnimationCompression.SetVerbosity(OldVerbosity); +} + +void FACLPlugin::SetDatabaseVisualFidelity(const TArray& Args) +{ + // Make sure to log everything + const ELogVerbosity::Type OldVerbosity = LogAnimationCompression.GetVerbosity(); + LogAnimationCompression.SetVerbosity(ELogVerbosity::All); + + ACLVisualFidelity Fidelity = ACLVisualFidelity::Highest; + if (Args.Contains(TEXT("Highest"))) + { + Fidelity = ACLVisualFidelity::Highest; + } + else if (Args.Contains(TEXT("Medium"))) + { + Fidelity = ACLVisualFidelity::Medium; + } + else if (Args.Contains(TEXT("Lowest"))) + { + Fidelity = ACLVisualFidelity::Lowest; + } + else if (Args.Num() != 0) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("Invalid visual fidelity: %s"), *Args[0]); + } + + const TArray DatabaseAssets = GetObjectInstancesSorted(); + for (UAnimationCompressionLibraryDatabase* DatabaseAsset : DatabaseAssets) + { + DatabaseAsset->SetVisualFidelity(Fidelity); + } + + LogAnimationCompression.SetVerbosity(OldVerbosity); +} +#endif + +#if WITH_EDITORONLY_DATA +void FACLPlugin::OnPostEngineInit() +{ + EditorDatabaseMonitor::RegisterMonitor(); +} +#endif + +void FACLPlugin::StartupModule() +{ +#if WITH_ACL_CONSOLE_COMMANDS + if (!IsRunningCommandlet()) + { + ConsoleCommands.Add(IConsoleManager::Get().RegisterConsoleCommand( + TEXT("ACL.ListCodecs"), + TEXT("Dumps statistics about animation codecs to the log."), + FConsoleCommandWithArgsDelegate::CreateRaw(this, &FACLPlugin::ListCodecs), + ECVF_Default + )); + + ConsoleCommands.Add(IConsoleManager::Get().RegisterConsoleCommand( + TEXT("ACL.ListAnimSequences"), + TEXT("Dumps statistics about animation sequences to the log."), + FConsoleCommandWithArgsDelegate::CreateRaw(this, &FACLPlugin::ListAnimSequences), + ECVF_Default + )); + + ConsoleCommands.Add(IConsoleManager::Get().RegisterConsoleCommand( + TEXT("ACL.SetDatabaseVisualFidelity"), + TEXT("Sets the visual fidelity of all ACL databases. Argument: Highest (default if no argument is provided), Medium, Lowest"), + FConsoleCommandWithArgsDelegate::CreateRaw(this, &FACLPlugin::SetDatabaseVisualFidelity), + ECVF_Default + )); + } +#endif + +#if WITH_EDITORONLY_DATA + FCoreDelegates::OnPostEngineInit.AddRaw(this, &FACLPlugin::OnPostEngineInit); +#endif +} + +void FACLPlugin::ShutdownModule() +{ +#if WITH_EDITORONLY_DATA + FCoreDelegates::OnPostEngineInit.RemoveAll(this); + + EditorDatabaseMonitor::UnregisterMonitor(); +#endif + +#if WITH_ACL_CONSOLE_COMMANDS + for (IConsoleObject* Cmd : ConsoleCommands) + { + IConsoleManager::Get().UnregisterConsoleObject(Cmd); + } + ConsoleCommands.Empty(); +#endif +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACL.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACL.cpp new file mode 100644 index 00000000..5a578abf --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACL.cpp @@ -0,0 +1,162 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "AnimBoneCompressionCodec_ACL.h" + +#if WITH_EDITORONLY_DATA +#include "AnimBoneCompressionCodec_ACLSafe.h" +#include "Rendering/SkeletalMeshModel.h" + +#include "ACLImpl.h" + +#include +#include +#endif // WITH_EDITORONLY_DATA + +#include "ACLDecompressionImpl.h" + +UAnimBoneCompressionCodec_ACL::UAnimBoneCompressionCodec_ACL(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +#if WITH_EDITORONLY_DATA + , SafetyFallbackThreshold(1.0f) // 1cm, should be very rarely exceeded +#endif +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimBoneCompressionCodec_ACL::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!IsTemplate()) + { + // Ensure we are never null + SafetyFallbackCodec = NewObject(this, NAME_None, RF_Public); + } +} + +void UAnimBoneCompressionCodec_ACL::GetPreloadDependencies(TArray& OutDeps) +{ + Super::GetPreloadDependencies(OutDeps); + + if (SafetyFallbackCodec != nullptr) + { + OutDeps.Add(SafetyFallbackCodec); + } +} + +bool UAnimBoneCompressionCodec_ACL::IsCodecValid() const +{ + if (!Super::IsCodecValid()) + { + return false; + } + + return SafetyFallbackCodec != nullptr ? SafetyFallbackCodec->IsCodecValid() : true; +} + +void UAnimBoneCompressionCodec_ACL::GetCompressionSettings(acl::compression_settings& OutSettings) const +{ + OutSettings = acl::get_default_compression_settings(); + + OutSettings.level = GetCompressionLevel(CompressionLevel); +} + +ACLSafetyFallbackResult UAnimBoneCompressionCodec_ACL::ExecuteSafetyFallback(acl::iallocator& Allocator, const acl::compression_settings& Settings, const acl::track_array_qvvf& RawClip, const acl::track_array_qvvf& BaseClip, const acl::compressed_tracks& CompressedClipData, const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) +{ + if (SafetyFallbackCodec != nullptr && SafetyFallbackThreshold > 0.0f) + { + checkSlow(CompressedClipData.is_valid(true).empty()); + + acl::decompression_context Context; + Context.initialize(CompressedClipData); + + const acl::track_error TrackError = acl::calculate_compression_error(Allocator, RawClip, Context, *Settings.error_metric, BaseClip); + if (TrackError.error >= SafetyFallbackThreshold) + { + UE_LOG(LogAnimationCompression, Verbose, TEXT("ACL Animation compressed size: %u bytes [%s]"), CompressedClipData.get_size(), *CompressibleAnimData.FullName); + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL Animation error is too high, a safe fallback will be used instead: %.4f cm [%s]"), TrackError.error, *CompressibleAnimData.FullName); + + // Just use the safety fallback + return SafetyFallbackCodec->Compress(CompressibleAnimData, OutResult) ? ACLSafetyFallbackResult::Success : ACLSafetyFallbackResult::Failure; + } + } + + return ACLSafetyFallbackResult::Ignored; +} + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UAnimBoneCompressionCodec_ACL::PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) +#else +void UAnimBoneCompressionCodec_ACL::PopulateDDCKey(FArchive& Ar) +#endif +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + Super::PopulateDDCKey(KeyArgs, Ar); +#else + Super::PopulateDDCKey(Ar); +#endif + + acl::compression_settings Settings; + GetCompressionSettings(Settings); + + uint32 ForceRebuildVersion = 1; + uint32 SettingsHash = Settings.get_hash(); + + Ar << SafetyFallbackThreshold << ForceRebuildVersion << SettingsHash; + + for (USkeletalMesh* SkelMesh : OptimizationTargets) + { + FSkeletalMeshModel* MeshModel = SkelMesh != nullptr ? SkelMesh->GetImportedModel() : nullptr; + if (MeshModel != nullptr) + { + Ar << MeshModel->SkeletalMeshModelGUID; + } + } + + if (SafetyFallbackCodec != nullptr) + { +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + SafetyFallbackCodec->PopulateDDCKey(KeyArgs, Ar); +#else + SafetyFallbackCodec->PopulateDDCKey(Ar); +#endif + } +} +#endif // WITH_EDITORONLY_DATA + +UAnimBoneCompressionCodec* UAnimBoneCompressionCodec_ACL::GetCodec(const FString& DDCHandle) +{ + const FString ThisHandle = GetCodecDDCHandle(); + UAnimBoneCompressionCodec* CodecMatch = ThisHandle == DDCHandle ? this : nullptr; + + if (CodecMatch == nullptr && SafetyFallbackCodec != nullptr) + { + CodecMatch = SafetyFallbackCodec->GetCodec(DDCHandle); + } + + return CodecMatch; +} + +void UAnimBoneCompressionCodec_ACL::DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const +{ + const FACLCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + acl::decompression_context ACLContext; + ACLContext.initialize(*CompressedClipData); + + ::DecompressPose(DecompContext, ACLContext, RotationPairs, TranslationPairs, ScalePairs, OutAtoms); +} + +void UAnimBoneCompressionCodec_ACL::DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const +{ + const FACLCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + acl::decompression_context ACLContext; + ACLContext.initialize(*CompressedClipData); + + ::DecompressBone(DecompContext, ACLContext, TrackIndex, OutAtom); +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLBase.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLBase.cpp new file mode 100644 index 00000000..a989f243 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLBase.cpp @@ -0,0 +1,363 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "AnimBoneCompressionCodec_ACLBase.h" +#include "Serialization/MemoryWriter.h" +#include "Serialization/MemoryReader.h" + +#if WITH_EDITORONLY_DATA +#include "AnimBoneCompressionCodec_ACLSafe.h" +#include "Animation/AnimationSettings.h" +#include "Rendering/SkeletalMeshModel.h" + +#include "ACLImpl.h" + +#include +#include +#include +#include +#endif // WITH_EDITORONLY_DATA + +#include + +bool FACLCompressedAnimData::IsValid() const +{ + if (CompressedByteStream.Num() == 0) + { + return false; + } + + const acl::compressed_tracks* CompressedClipData = acl::make_compressed_tracks(CompressedByteStream.GetData()); + return CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty(); +} + +UAnimBoneCompressionCodec_ACLBase::UAnimBoneCompressionCodec_ACLBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + CompressionLevel = ACLCL_Medium; + + // We use a higher virtual vertex distance when bones have a socket attached or are keyed end effectors (IK, hand, camera, etc) + // We use 100cm instead of 3cm. UE 4 usually uses 50cm (END_EFFECTOR_DUMMY_BONE_LENGTH_SOCKET) but + // we use a higher value anyway due to the fact that ACL has no error compensation and it is more aggressive. + DefaultVirtualVertexDistance = 3.0f; // 3cm, suitable for ordinary characters + SafeVirtualVertexDistance = 100.0f; // 100cm + + ErrorThreshold = 0.01f; // 0.01cm, conservative enough for cinematographic quality +#endif // WITH_EDITORONLY_DATA +} + +#if WITH_EDITORONLY_DATA +static void AppendMaxVertexDistances(USkeletalMesh* OptimizationTarget, TMap& BoneMaxVertexDistanceMap) +{ +#if (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 27) || ENGINE_MAJOR_VERSION >= 5 + USkeleton* Skeleton = OptimizationTarget != nullptr ? OptimizationTarget->GetSkeleton() : nullptr; +#else + USkeleton* Skeleton = OptimizationTarget != nullptr ? OptimizationTarget->Skeleton : nullptr; +#endif + + if (Skeleton == nullptr) + { + return; // No data to work with + } + + const FSkeletalMeshModel* MeshModel = OptimizationTarget->GetImportedModel(); + if (MeshModel == nullptr || MeshModel->LODModels.Num() == 0) + { + return; // No data to work with + } + + const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); + const TArray& RefSkeletonPose = RefSkeleton.GetRefBonePose(); + const uint32 NumBones = RefSkeletonPose.Num(); + + TArray RefSkeletonObjectSpacePose; + RefSkeletonObjectSpacePose.AddUninitialized(NumBones); + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const int32 ParentBoneIndex = RefSkeleton.GetParentIndex(BoneIndex); + if (ParentBoneIndex != INDEX_NONE) + { + RefSkeletonObjectSpacePose[BoneIndex] = RefSkeletonPose[BoneIndex] * RefSkeletonObjectSpacePose[ParentBoneIndex]; + } + else + { + RefSkeletonObjectSpacePose[BoneIndex] = RefSkeletonPose[BoneIndex]; + } + } + + // Iterate over every vertex and track which one is the most distant for every bone + TArray MostDistantVertexDistancePerBone; + MostDistantVertexDistancePerBone.AddZeroed(NumBones); + + const uint32 NumSections = MeshModel->LODModels[0].Sections.Num(); + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + const FSkelMeshSection& Section = MeshModel->LODModels[0].Sections[SectionIndex]; + const uint32 NumVertices = Section.SoftVertices.Num(); + + for (uint32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + { + const FSoftSkinVertex& VertexInfo = Section.SoftVertices[VertexIndex]; + const FVector& VertexPosition = UEVector3Cast(VertexInfo.Position); + + for (uint32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex) + { + if (VertexInfo.InfluenceWeights[InfluenceIndex] != 0) + { + const uint32 SectionBoneIndex = VertexInfo.InfluenceBones[InfluenceIndex]; + const uint32 BoneIndex = Section.BoneMap[SectionBoneIndex]; + + const FTransform& BoneTransform = RefSkeletonObjectSpacePose[BoneIndex]; + const FVector BoneTranslation = UEVector3Cast(BoneTransform.GetTranslation()); + + const float VertexDistanceToBone = FVector::Distance(VertexPosition, BoneTranslation); + + float& MostDistantVertexDistance = MostDistantVertexDistancePerBone[BoneIndex]; + MostDistantVertexDistance = FMath::Max(MostDistantVertexDistance, VertexDistanceToBone); + } + } + } + } + + // Store the results in a map by bone name since the optimizing target might use a different + // skeleton mapping. + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const FName BoneName = RefSkeleton.GetBoneName(BoneIndex); + const float MostDistantVertexDistance = MostDistantVertexDistancePerBone[BoneIndex]; + + float& BoneMaxVertexDistance = BoneMaxVertexDistanceMap.FindOrAdd(BoneName, 0.0f); + BoneMaxVertexDistance = FMath::Max(BoneMaxVertexDistance, MostDistantVertexDistance); + } +} + +static void PopulateShellDistanceFromOptimizationTargets(const FCompressibleAnimData& CompressibleAnimData, const TArray& OptimizationTargets, acl::track_array_qvvf& ACLTracks) +{ + // For each bone, get the furtest vertex distance + TMap BoneMaxVertexDistanceMap; + for (USkeletalMesh* OptimizationTarget : OptimizationTargets) + { + AppendMaxVertexDistances(OptimizationTarget, BoneMaxVertexDistanceMap); + } + + const uint32 NumBones = ACLTracks.get_num_tracks(); + for (uint32 ACLBoneIndex = 0; ACLBoneIndex < NumBones; ++ACLBoneIndex) + { + acl::track_qvvf& ACLTrack = ACLTracks[ACLBoneIndex]; + const FName BoneName(ACLTrack.get_name().c_str()); + + const float* MostDistantVertexDistance = BoneMaxVertexDistanceMap.Find(BoneName); + if (MostDistantVertexDistance == nullptr || *MostDistantVertexDistance <= 0.0F) + { + continue; // No skinned vertices for this bone, skipping + } + + const FBoneData& UE4Bone = CompressibleAnimData.BoneData[ACLBoneIndex]; + + acl::track_desc_transformf& Desc = ACLTrack.get_description(); + + // We set our shell distance to the most distant vertex distance. + // This ensures that we measure the error where that vertex lies. + // Together with the precision value, all vertices skinned to this bone + // will be guaranteed to have an error smaller or equal to the precision + // threshold used. + if (UE4Bone.bHasSocket || UE4Bone.bKeyEndEffector) + { + // Bones that have sockets or are key end effectors require extra precision, make sure + // that our shell distance is at least what we ask of it regardless of the skinning + // information. + Desc.shell_distance = FMath::Max(Desc.shell_distance, *MostDistantVertexDistance); + } + else + { + // This could be higher or lower than the default value used by ordinary bones. + // This thus taylors the shell distance to the visual mesh. + Desc.shell_distance = *MostDistantVertexDistance; + } + } +} + +bool UAnimBoneCompressionCodec_ACLBase::Compress(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) +{ + acl::track_array_qvvf ACLTracks = BuildACLTransformTrackArray(ACLAllocatorImpl, CompressibleAnimData, DefaultVirtualVertexDistance, SafeVirtualVertexDistance, false); + + acl::track_array_qvvf ACLBaseTracks; + if (CompressibleAnimData.bIsValidAdditive) + ACLBaseTracks = BuildACLTransformTrackArray(ACLAllocatorImpl, CompressibleAnimData, DefaultVirtualVertexDistance, SafeVirtualVertexDistance, true); + + UE_LOG(LogAnimationCompression, Verbose, TEXT("ACL Animation raw size: %u bytes [%s]"), ACLTracks.get_raw_size(), *CompressibleAnimData.FullName); + + // If we have an optimization target, use it + TArray OptimizationTargets = GetOptimizationTargets(); + if (OptimizationTargets.Num() != 0) + { + PopulateShellDistanceFromOptimizationTargets(CompressibleAnimData, OptimizationTargets, ACLTracks); + } + + // Set our error threshold + for (acl::track_qvvf& Track : ACLTracks) + { + Track.get_description().precision = ErrorThreshold; + } + + // Override track settings if we need to + if (IsA()) + { + // Disable constant rotation track detection + for (acl::track_qvvf& Track : ACLTracks) + { + Track.get_description().constant_rotation_threshold_angle = 0.0f; + } + } + + acl::compression_settings Settings; + GetCompressionSettings(Settings); + + constexpr acl::additive_clip_format8 AdditiveFormat = acl::additive_clip_format8::additive1; + + acl::qvvf_transform_error_metric DefaultErrorMetric; + acl::additive_qvvf_transform_error_metric AdditiveErrorMetric; + if (!ACLBaseTracks.is_empty()) + { + Settings.error_metric = &AdditiveErrorMetric; + } + else + { + Settings.error_metric = &DefaultErrorMetric; + } + + const bool bUseStreamingDatabase = UseDatabase(); + + if (bUseStreamingDatabase) + { + Settings.enable_database_support = true; + } + + acl::output_stats Stats; + acl::compressed_tracks* CompressedTracks = nullptr; + const acl::error_result CompressionResult = acl::compress_track_list(ACLAllocatorImpl, ACLTracks, Settings, ACLBaseTracks, AdditiveFormat, CompressedTracks, Stats); + + // Make sure if we managed to compress, that the error is acceptable and if it isn't, re-compress again with safer settings + // This should be VERY rare with the default threshold + if (CompressionResult.empty()) + { + const ACLSafetyFallbackResult FallbackResult = ExecuteSafetyFallback(ACLAllocatorImpl, Settings, ACLTracks, ACLBaseTracks, *CompressedTracks, CompressibleAnimData, OutResult); + if (FallbackResult != ACLSafetyFallbackResult::Ignored) + { + ACLAllocatorImpl.deallocate(CompressedTracks, CompressedTracks->get_size()); + CompressedTracks = nullptr; + + return FallbackResult == ACLSafetyFallbackResult::Success; + } + } + + if (!CompressionResult.empty()) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL failed to compress clip: %s [%s]"), ANSI_TO_TCHAR(CompressionResult.c_str()), *CompressibleAnimData.FullName); + return false; + } + + checkSlow(CompressedTracks->is_valid(true).empty()); + + const uint32 CompressedClipDataSize = CompressedTracks->get_size(); + + OutResult.CompressedByteStream.Empty(CompressedClipDataSize); + OutResult.CompressedByteStream.AddUninitialized(CompressedClipDataSize); + FMemory::Memcpy(OutResult.CompressedByteStream.GetData(), CompressedTracks, CompressedClipDataSize); + + OutResult.Codec = this; + + OutResult.AnimData = AllocateAnimData(); + +#if ENGINE_MAJOR_VERSION >= 5 + OutResult.AnimData->CompressedNumberOfKeys = GetNumSamples(CompressibleAnimData); +#else + OutResult.AnimData->CompressedNumberOfFrames = GetNumSamples(CompressibleAnimData); +#endif + +#if !NO_LOGGING + { + // Use debug settings in case codec picked is the fallback + acl::decompression_context Context; + Context.initialize(*CompressedTracks); + + const acl::track_error TrackError = acl::calculate_compression_error(ACLAllocatorImpl, ACLTracks, Context, *Settings.error_metric, ACLBaseTracks); + + UE_LOG(LogAnimationCompression, Verbose, TEXT("ACL Animation compressed size: %u bytes [%s]"), CompressedClipDataSize, *CompressibleAnimData.FullName); + UE_LOG(LogAnimationCompression, Verbose, TEXT("ACL Animation error: %.4f cm (bone %u @ %.3f) [%s]"), TrackError.error, TrackError.index, TrackError.sample_time, *CompressibleAnimData.FullName); + } +#endif + + ACLAllocatorImpl.deallocate(CompressedTracks, CompressedClipDataSize); + + if (bUseStreamingDatabase) + { + RegisterWithDatabase(CompressibleAnimData, OutResult); + } + + // Bind our compressed sequence data buffer + OutResult.AnimData->Bind(OutResult.CompressedByteStream); + + return true; +} + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UAnimBoneCompressionCodec_ACLBase::PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) +#else +void UAnimBoneCompressionCodec_ACLBase::PopulateDDCKey(FArchive& Ar) +#endif +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + Super::PopulateDDCKey(KeyArgs, Ar); +#else + Super::PopulateDDCKey(Ar); +#endif + + uint32 ForceRebuildVersion = 4; + + Ar << ForceRebuildVersion << DefaultVirtualVertexDistance << SafeVirtualVertexDistance << ErrorThreshold; + Ar << CompressionLevel; + + // Add the end effector match name list since if it changes, we need to re-compress + const TArray& KeyEndEffectorsMatchNameArray = UAnimationSettings::Get()->KeyEndEffectorsMatchNameArray; + for (const FString& MatchName : KeyEndEffectorsMatchNameArray) + { + uint32 MatchNameHash = GetTypeHash(MatchName); + Ar << MatchNameHash; + } +} + +ACLSafetyFallbackResult UAnimBoneCompressionCodec_ACLBase::ExecuteSafetyFallback(acl::iallocator& Allocator, const acl::compression_settings& Settings, const acl::track_array_qvvf& RawClip, const acl::track_array_qvvf& BaseClip, const acl::compressed_tracks& CompressedClipData, const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) +{ + return ACLSafetyFallbackResult::Ignored; +} +#endif + +TUniquePtr UAnimBoneCompressionCodec_ACLBase::AllocateAnimData() const +{ + return MakeUnique(); +} + +void UAnimBoneCompressionCodec_ACLBase::ByteSwapIn(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryReader& MemoryStream) const +{ +#if !PLATFORM_LITTLE_ENDIAN +#error "ACL does not currently support big-endian platforms" +#endif + + // ByteSwapIn(..) is called on load + + // TODO: ACL does not support byte swapping + MemoryStream.Serialize(CompressedData.GetData(), CompressedData.Num()); +} + +void UAnimBoneCompressionCodec_ACLBase::ByteSwapOut(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryWriter& MemoryStream) const +{ +#if !PLATFORM_LITTLE_ENDIAN +#error "ACL does not currently support big-endian platforms" +#endif + + // ByteSwapOut(..) is called on save, during cooking, or when counting memory + + // TODO: ACL does not support byte swapping + MemoryStream.Serialize(CompressedData.GetData(), CompressedData.Num()); +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLCustom.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLCustom.cpp new file mode 100644 index 00000000..9d6fd5f4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLCustom.cpp @@ -0,0 +1,90 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "AnimBoneCompressionCodec_ACLCustom.h" + +#include "ACLDecompressionImpl.h" + +#if WITH_EDITORONLY_DATA +#include "Rendering/SkeletalMeshModel.h" + +#include +#endif + +UAnimBoneCompressionCodec_ACLCustom::UAnimBoneCompressionCodec_ACLCustom(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + RotationFormat = ACLRotationFormat::ACLRF_QuatDropW_Variable; + TranslationFormat = ACLVectorFormat::ACLVF_Vector3_Variable; + ScaleFormat = ACLVectorFormat::ACLVF_Vector3_Variable; + + ConstantRotationThresholdAngle = 0.00284714461f; // The smallest angle a float32 can represent in a quaternion is 0.000690533954 so we use a value just slightly larger + ConstantTranslationThreshold = 0.001f; // 0.001cm, very conservative to be safe + ConstantScaleThreshold = 0.00001f; // Very small value to be safe since scale is sensitive +#endif // WITH_EDITORONLY_DATA +} + +#if WITH_EDITORONLY_DATA +void UAnimBoneCompressionCodec_ACLCustom::GetCompressionSettings(acl::compression_settings& OutSettings) const +{ + OutSettings = acl::compression_settings(); + OutSettings.rotation_format = GetRotationFormat(RotationFormat); + OutSettings.translation_format = GetVectorFormat(TranslationFormat); + OutSettings.scale_format = GetVectorFormat(ScaleFormat); + OutSettings.level = GetCompressionLevel(CompressionLevel); +} + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UAnimBoneCompressionCodec_ACLCustom::PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) +#else +void UAnimBoneCompressionCodec_ACLCustom::PopulateDDCKey(FArchive& Ar) +#endif +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + Super::PopulateDDCKey(KeyArgs, Ar); +#else + Super::PopulateDDCKey(Ar); +#endif + + acl::compression_settings Settings; + GetCompressionSettings(Settings); + + uint32 ForceRebuildVersion = 1; + uint32 SettingsHash = Settings.get_hash(); + + Ar << ForceRebuildVersion << SettingsHash; + + for (USkeletalMesh* SkelMesh : OptimizationTargets) + { + FSkeletalMeshModel* MeshModel = SkelMesh != nullptr ? SkelMesh->GetImportedModel() : nullptr; + if (MeshModel != nullptr) + { + Ar << MeshModel->SkeletalMeshModelGUID; + } + } +} +#endif // WITH_EDITORONLY_DATA + +void UAnimBoneCompressionCodec_ACLCustom::DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const +{ + const FACLCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + acl::decompression_context ACLContext; + ACLContext.initialize(*CompressedClipData); + + ::DecompressPose(DecompContext, ACLContext, RotationPairs, TranslationPairs, ScalePairs, OutAtoms); +} + +void UAnimBoneCompressionCodec_ACLCustom::DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const +{ + const FACLCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + acl::decompression_context ACLContext; + ACLContext.initialize(*CompressedClipData); + + ::DecompressBone(DecompContext, ACLContext, TrackIndex, OutAtom); +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLDatabase.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLDatabase.cpp new file mode 100644 index 00000000..389d08c6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLDatabase.cpp @@ -0,0 +1,373 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "AnimBoneCompressionCodec_ACLDatabase.h" + +#include "Algo/BinarySearch.h" + +#if WITH_EDITORONLY_DATA +#include "Animation/AnimBoneCompressionSettings.h" +#include "Rendering/SkeletalMeshModel.h" + +#if ENGINE_MAJOR_VERSION >= 5 +#include "UObject/ObjectSaveContext.h" +#endif + +#include "ACLImpl.h" +#include "EditorDatabaseMonitor.h" + +#include +#include +#include +#include +#endif // WITH_EDITORONLY_DATA + +#include "ACLDecompressionImpl.h" + +void FACLDatabaseCompressedAnimData::SerializeCompressedData(FArchive& Ar) +{ + ICompressedAnimData::SerializeCompressedData(Ar); + + Ar << SequenceNameHash; + +#if WITH_EDITORONLY_DATA + if (!Ar.IsFilterEditorOnly()) + { + Ar << CompressedClip; + } +#endif +} + +void FACLDatabaseCompressedAnimData::Bind(const TArrayView BulkData) +{ + check(BulkData.Num() == 0); // Should always be empty + +#if WITH_EDITORONLY_DATA + // We have fresh new compressed data which means either we ran compression or we loaded from the DDC + // We can't tell which is which so mark the database as being potentially dirty + EditorDatabaseMonitor::MarkDirty(Codec->DatabaseAsset); +#else + // In a cooked build, we lookup our anim sequence and database from the database asset + // We search by the sequence hash which lives in the top 32 bits of each entry + const int32 SequenceIndex = Algo::BinarySearchBy(Codec->DatabaseAsset->CookedAnimSequenceMappings, SequenceNameHash, [](uint64 InValue) { return uint32(InValue >> 32); }); + if (SequenceIndex != INDEX_NONE) + { + const uint32 CompressedClipOffset = uint32(Codec->DatabaseAsset->CookedAnimSequenceMappings[SequenceIndex]); // Truncate top 32 bits + uint8* CompressedBytes = Codec->DatabaseAsset->CookedCompressedBytes.GetData() + CompressedClipOffset; + + const acl::compressed_tracks* CompressedClipData = acl::make_compressed_tracks(CompressedBytes); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + const uint32 CompressedSize = CompressedClipData->get_size(); + + CompressedByteStream = TArrayView(CompressedBytes, CompressedSize); + DatabaseContext = &Codec->DatabaseAsset->GetDatabaseContext(); + } + else + { + // This sequence doesn't live in the database, the mapping must be stale + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL Database mapping is stale. [0x%X] should be contained but isn't."), SequenceNameHash); + + // Since we have no sequence data, decompression will yield a T-pose + } +#endif +} + +int64 FACLDatabaseCompressedAnimData::GetApproxCompressedSize() const +{ +#if WITH_EDITORONLY_DATA + return CompressedClip.Num(); +#else + return CompressedByteStream.Num(); +#endif +} + +bool FACLDatabaseCompressedAnimData::IsValid() const +{ + const acl::compressed_tracks* CompressedClipData = GetCompressedTracks(); + if (CompressedClipData == nullptr || CompressedClipData->is_valid(false).any()) + { + return false; + } + +#if !WITH_EDITORONLY_DATA + if (DatabaseContext == nullptr) + { + return false; + } +#endif + + return true; +} + +#if WITH_EDITORONLY_DATA +FACLDatabaseCompressedAnimData::~FACLDatabaseCompressedAnimData() +{ + // Our compressed data is being destroyed which means either we are unloading or we are about to have + // new compressed data. If the new codec isn't an ACL instance we have no way of knowing so assume the + // database is dirty and double check. + EditorDatabaseMonitor::MarkDirty(Codec->DatabaseAsset); +} +#endif + +UAnimBoneCompressionCodec_ACLDatabase::UAnimBoneCompressionCodec_ACLDatabase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , DatabaseAsset(nullptr) +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimBoneCompressionCodec_ACLDatabase::GetPreloadDependencies(TArray& OutDeps) +{ + Super::GetPreloadDependencies(OutDeps); + + // We preload the database asset because we need it loaded during Serialize to lookup the proper sequence data + if (DatabaseAsset != nullptr) + { + OutDeps.Add(DatabaseAsset); + } +} + +#if ENGINE_MAJOR_VERSION >= 5 +void UAnimBoneCompressionCodec_ACLDatabase::PreSave(FObjectPreSaveContext SaveContext) +#else +void UAnimBoneCompressionCodec_ACLDatabase::PreSave(const class ITargetPlatform* TargetPlatform) +#endif +{ +#if ENGINE_MAJOR_VERSION >= 5 + Super::PreSave(SaveContext); +#else + Super::PreSave(TargetPlatform); +#endif + + UAnimBoneCompressionSettings* Settings = Cast(GetOuter()); + if (Settings != nullptr && Settings->Codecs.Num() != 1) + { + UE_LOG(LogAnimationCompression, Error, TEXT("ACL database codec must be the only codec in its parent bone compression settings asset. [%s]"), *Settings->GetPathName()); + } +} + +void UAnimBoneCompressionCodec_ACLDatabase::RegisterWithDatabase(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) +{ + // After we are done compressing our animation sequence, it will contain the necessary metadata needed to build our + // streaming database. The anim sequence will contain every sample and it will be used as-is in the editor where we + // show the highest quality by default. + // + // However, the anim sequence data that we just compressed will not be used in a cooked build. When we build our + // database, the sequence data will be modified since we'll remove key frames from it. Its hash will change. + // The new compressed data will live in the database asset next to the compressed database data. This has the benefit + // that every compressed clip and the database now live in the same region of virtual memory, reducing the TLB miss + // rate (when large pages are used on console and mobile since multiple clips fit within a page) and when we do miss + // the TLB, it will be cheaper since most of the mapping levels are shared. + // + // To that end, the data we just compressed will not be serialized in cooked builds, it only lives in the DDC and in memory + // while in the editor. To be able to find our new sequence data at runtime, we compute the hash from the sequence name. + + FACLDatabaseCompressedAnimData& AnimData = static_cast(*OutResult.AnimData); + + // Store the sequence full name's hash since we need it in cooked builds to find our data + AnimData.SequenceNameHash = GetTypeHash(CompressibleAnimData.FullName); + + // Copy the sequence data + AnimData.CompressedClip = OutResult.CompressedByteStream; + + // When we have a database, the compressed sequence data lives in the database, zero out the compressed byte buffer + // since we handle the data manually + OutResult.CompressedByteStream.Empty(0); +} + +void UAnimBoneCompressionCodec_ACLDatabase::GetCompressionSettings(acl::compression_settings& OutSettings) const +{ + OutSettings = acl::get_default_compression_settings(); + + OutSettings.level = GetCompressionLevel(CompressionLevel); +} + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UAnimBoneCompressionCodec_ACLDatabase::PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) +#else +void UAnimBoneCompressionCodec_ACLDatabase::PopulateDDCKey(FArchive& Ar) +#endif +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + Super::PopulateDDCKey(KeyArgs, Ar); +#else + Super::PopulateDDCKey(Ar); +#endif + + acl::compression_settings Settings; + GetCompressionSettings(Settings); + + uint32 ForceRebuildVersion = 4; + uint32 SettingsHash = Settings.get_hash(); + + Ar << ForceRebuildVersion << SettingsHash; + + for (USkeletalMesh* SkelMesh : OptimizationTargets) + { + FSkeletalMeshModel* MeshModel = SkelMesh != nullptr ? SkelMesh->GetImportedModel() : nullptr; + if (MeshModel != nullptr) + { + Ar << MeshModel->SkeletalMeshModelGUID; + } + } +} +#endif // WITH_EDITORONLY_DATA + +TUniquePtr UAnimBoneCompressionCodec_ACLDatabase::AllocateAnimData() const +{ + TUniquePtr AnimData = MakeUnique(); + + AnimData->Codec = const_cast(this); + + return AnimData; +} + +void UAnimBoneCompressionCodec_ACLDatabase::ByteSwapIn(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryReader& MemoryStream) const +{ +#if !PLATFORM_LITTLE_ENDIAN +#error "ACL does not currently support big-endian platforms" +#endif + + // ByteSwapIn(..) is called on load + + // TODO: ACL does not support byte swapping + + // Because we manage the memory manually, the compressed data should always be empty + check(CompressedData.Num() == 0); +} + +void UAnimBoneCompressionCodec_ACLDatabase::ByteSwapOut(ICompressedAnimData& AnimData, TArrayView CompressedData, FMemoryWriter& MemoryStream) const +{ +#if !PLATFORM_LITTLE_ENDIAN +#error "ACL does not currently support big-endian platforms" +#endif + + // ByteSwapOut(..) is called on save, during cooking, or when counting memory + + // TODO: ACL does not support byte swapping + +#if WITH_EDITORONLY_DATA + // In the editor, if we are saving or cooking, the output should be empty since we manage the memory manually. + // The real editor data lives in FACLDatabaseCompressedAnimData::CompressedClip + // + // Sadly, we have no way of knowing if we are counting memory from here and as such we'll contribute no size. + // For a true memory report, it is best to run it with cooked data anyway. + check(CompressedData.Num() == 0); +#else + // With cooked data, we are never saving unless it is to count memory. + // Since the actual sequence data lives in the database, its size will be tracked there. + // We'll do nothing here to avoid counting twice. +#endif +} + +void UAnimBoneCompressionCodec_ACLDatabase::DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const +{ + const FACLDatabaseCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + + acl::decompression_context ACLContext; + +#if WITH_EDITORONLY_DATA + acl::database_context* DatabaseContext = DatabaseAsset != nullptr ? &DatabaseAsset->GetDatabaseContext() : nullptr; + if (DatabaseContext != nullptr && DatabaseContext->is_initialized()) + { + // We are previewing, use the database and the anim sequence data contained within it + + // Lookup our anim sequence from the database asset + // We search by the sequence hash which lives in the top 32 bits of each entry + const int32 SequenceIndex = Algo::BinarySearchBy(DatabaseAsset->PreviewAnimSequenceMappings, AnimData.SequenceNameHash, [](uint64 InValue) { return uint32(InValue >> 32); }); + if (SequenceIndex != INDEX_NONE) + { + const uint32 CompressedClipOffset = uint32(DatabaseAsset->PreviewAnimSequenceMappings[SequenceIndex]); // Truncate top 32 bits + const uint8* CompressedBytes = DatabaseAsset->PreviewCompressedBytes.GetData() + CompressedClipOffset; + + const acl::compressed_tracks* CompressedClipData = acl::make_compressed_tracks(CompressedBytes); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + ACLContext.initialize(*CompressedClipData, *DatabaseContext); + } + } + + if (!ACLContext.is_initialized()) + { + // No preview or we live updated things and the monitor hasn't caught up yet + // Use the full quality that lives in the anim sequence + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + ACLContext.initialize(*CompressedClipData); + } +#else + if (AnimData.CompressedByteStream.Num() == 0) + { + return; // Our mapping must have been stale + } + + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + if (AnimData.DatabaseContext == nullptr || !ACLContext.initialize(*CompressedClipData, *AnimData.DatabaseContext)) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL failed initialize decompression context, database won't be used")); + + ACLContext.initialize(*CompressedClipData); + } +#endif + + ::DecompressPose(DecompContext, ACLContext, RotationPairs, TranslationPairs, ScalePairs, OutAtoms); +} + +void UAnimBoneCompressionCodec_ACLDatabase::DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const +{ + const FACLDatabaseCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + + acl::decompression_context ACLContext; + +#if WITH_EDITORONLY_DATA + acl::database_context* DatabaseContext = DatabaseAsset != nullptr ? &DatabaseAsset->GetDatabaseContext() : nullptr; + if (DatabaseContext != nullptr && DatabaseContext->is_initialized()) + { + // We are previewing, use the database and the anim sequence data contained within it + + // Lookup our anim sequence from the database asset + // We search by the sequence hash which lives in the top 32 bits of each entry + const int32 SequenceIndex = Algo::BinarySearchBy(DatabaseAsset->PreviewAnimSequenceMappings, AnimData.SequenceNameHash, [](uint64 InValue) { return uint32(InValue >> 32); }); + if (SequenceIndex != INDEX_NONE) + { + const uint32 CompressedClipOffset = uint32(DatabaseAsset->PreviewAnimSequenceMappings[SequenceIndex]); // Truncate top 32 bits + const uint8* CompressedBytes = DatabaseAsset->PreviewCompressedBytes.GetData() + CompressedClipOffset; + + const acl::compressed_tracks* CompressedClipData = acl::make_compressed_tracks(CompressedBytes); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + ACLContext.initialize(*CompressedClipData, *DatabaseContext); + } + } + + if (!ACLContext.is_initialized()) + { + // No preview or we live updated things and the monitor hasn't caught up yet + // Use the full quality that lives in the anim sequence + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + ACLContext.initialize(*CompressedClipData); + } +#else + if (AnimData.CompressedByteStream.Num() == 0) + { + return; // Our mapping must have been stale + } + + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + if (AnimData.DatabaseContext == nullptr || !ACLContext.initialize(*CompressedClipData, *AnimData.DatabaseContext)) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL failed initialize decompression context, database won't be used")); + + ACLContext.initialize(*CompressedClipData); + } +#endif + + ::DecompressBone(DecompContext, ACLContext, TrackIndex, OutAtom); +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLSafe.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLSafe.cpp new file mode 100644 index 00000000..7c6d2a44 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimBoneCompressionCodec_ACLSafe.cpp @@ -0,0 +1,70 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "AnimBoneCompressionCodec_ACLSafe.h" + +#include "ACLDecompressionImpl.h" + +#if WITH_EDITORONLY_DATA +#include +#endif + +UAnimBoneCompressionCodec_ACLSafe::UAnimBoneCompressionCodec_ACLSafe(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimBoneCompressionCodec_ACLSafe::GetCompressionSettings(acl::compression_settings& OutSettings) const +{ + OutSettings = acl::get_default_compression_settings(); + + // Fallback to full precision rotations + OutSettings.rotation_format = acl::rotation_format8::quatf_full; +} + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UAnimBoneCompressionCodec_ACLSafe::PopulateDDCKey(const UE::Anim::Compression::FAnimDDCKeyArgs& KeyArgs, FArchive& Ar) +#else +void UAnimBoneCompressionCodec_ACLSafe::PopulateDDCKey(FArchive& Ar) +#endif +{ +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + Super::PopulateDDCKey(KeyArgs, Ar); +#else + Super::PopulateDDCKey(Ar); +#endif + + acl::compression_settings Settings; + GetCompressionSettings(Settings); + + uint32 ForceRebuildVersion = 1; + uint32 SettingsHash = Settings.get_hash(); + + Ar << DefaultVirtualVertexDistance << SafeVirtualVertexDistance + << ForceRebuildVersion << SettingsHash; +} +#endif // WITH_EDITORONLY_DATA + +void UAnimBoneCompressionCodec_ACLSafe::DecompressPose(FAnimSequenceDecompressionContext& DecompContext, const BoneTrackArray& RotationPairs, const BoneTrackArray& TranslationPairs, const BoneTrackArray& ScalePairs, TArrayView& OutAtoms) const +{ + const FACLCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + acl::decompression_context ACLContext; + ACLContext.initialize(*CompressedClipData); + + ::DecompressPose(DecompContext, ACLContext, RotationPairs, TranslationPairs, ScalePairs, OutAtoms); +} + +void UAnimBoneCompressionCodec_ACLSafe::DecompressBone(FAnimSequenceDecompressionContext& DecompContext, int32 TrackIndex, FTransform& OutAtom) const +{ + const FACLCompressedAnimData& AnimData = static_cast(DecompContext.CompressedAnimData); + const acl::compressed_tracks* CompressedClipData = AnimData.GetCompressedTracks(); + check(CompressedClipData != nullptr && CompressedClipData->is_valid(false).empty()); + + acl::decompression_context ACLContext; + ACLContext.initialize(*CompressedClipData); + + ::DecompressBone(DecompContext, ACLContext, TrackIndex, OutAtom); +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimCurveCompressionCodec_ACL.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimCurveCompressionCodec_ACL.cpp new file mode 100644 index 00000000..aa5f4bed --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimCurveCompressionCodec_ACL.cpp @@ -0,0 +1,323 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "AnimCurveCompressionCodec_ACL.h" + +#include "ACLImpl.h" + +#if WITH_EDITORONLY_DATA +#include "AnimationCompression.h" +#include "Animation/MorphTarget.h" +#include "Rendering/SkeletalMeshModel.h" + +#include +#include +#include +#include +#endif // WITH_EDITOR + +#include + +UAnimCurveCompressionCodec_ACL::UAnimCurveCompressionCodec_ACL(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + CurvePrecision = 0.001f; + MorphTargetPositionPrecision = 0.01f; // 0.01cm, conservative enough for cinematographic quality +#endif +} + +#if WITH_EDITORONLY_DATA +void UAnimCurveCompressionCodec_ACL::PopulateDDCKey(FArchive& Ar) +{ + Super::PopulateDDCKey(Ar); + + Ar << CurvePrecision; + Ar << MorphTargetPositionPrecision; + + if (MorphTargetSource != nullptr) + { + FSkeletalMeshModel* MeshModel = MorphTargetSource->GetImportedModel(); + if (MeshModel != nullptr) + { + Ar << MeshModel->SkeletalMeshModelGUID; + } + } + + uint32 ForceRebuildVersion = 2; + Ar << ForceRebuildVersion; + + acl::compression_settings Settings; + uint32 SettingsHash = Settings.get_hash(); + Ar << SettingsHash; +} + +static const TArray& GetRawCurves(const FCompressibleAnimData& AnimSeq) +{ +#if ENGINE_MAJOR_VERSION >= 5 + return AnimSeq.RawFloatCurves; +#else + return AnimSeq.RawCurveData.FloatCurves; +#endif +} + +// For each curve, returns its largest position delta if the curve is for a morph target, 0.0 otherwise +static TArray GetMorphTargetMaxPositionDeltas(const FCompressibleAnimData& AnimSeq, const USkeletalMesh* MorphTargetSource) +{ + const TArray& RawCurves = GetRawCurves(AnimSeq); + const int32 NumCurves = RawCurves.Num(); + + TArray MorphTargetMaxPositionDeltas; + MorphTargetMaxPositionDeltas.AddZeroed(NumCurves); + + if (MorphTargetSource == nullptr) + { + return MorphTargetMaxPositionDeltas; + } + + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + float MaxDeltaPosition = 0.0f; + const FFloatCurve& Curve = RawCurves[CurveIndex]; + + UMorphTarget* Target = MorphTargetSource->FindMorphTarget(Curve.Name.DisplayName); + if (Target != nullptr) + { + // This curve drives a morph target, find the largest displacement it can have + const int32 LODIndex = 0; + int32 NumDeltas = 0; + const FMorphTargetDelta* Deltas = Target->GetMorphTargetDelta(LODIndex, NumDeltas); + for (int32 DeltaIndex = 0; DeltaIndex < NumDeltas; ++DeltaIndex) + { + const FMorphTargetDelta& Delta = Deltas[DeltaIndex]; + MaxDeltaPosition = FMath::Max(MaxDeltaPosition, Delta.PositionDelta.Size()); + } + } + + MorphTargetMaxPositionDeltas[CurveIndex] = MaxDeltaPosition; + } + + return MorphTargetMaxPositionDeltas; +} + +bool UAnimCurveCompressionCodec_ACL::Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) +{ + const TArray MorphTargetMaxPositionDeltas = GetMorphTargetMaxPositionDeltas(AnimSeq, MorphTargetSource); + const TArray& RawCurves = GetRawCurves(AnimSeq); + + const int32 NumCurves = RawCurves.Num(); + if (NumCurves == 0) + { + // Nothing to compress + OutResult.CompressedBytes.Empty(0); + OutResult.Codec = this; + return true; + } + + const int32 NumSamples = GetNumSamples(AnimSeq); + const float SequenceLength = AnimSeq.SequenceLength; + + const bool bIsStaticPose = NumSamples <= 1 || SequenceLength < 0.0001f; + const float SampleRate = bIsStaticPose ? 30.0f : (float(NumSamples - 1) / SequenceLength); + const float InvSampleRate = 1.0f / SampleRate; + + acl::track_array_float1f Tracks(ACLAllocatorImpl, NumCurves); + + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = RawCurves[CurveIndex]; + const float MaxPositionDelta = MorphTargetMaxPositionDeltas[CurveIndex]; + + // If our curve drives a morph target, we use a different precision value with world space units. + // This is much easier to tune and control: 0.1mm precision is clear. + // In order to this this, we must convert that precision value into a value that makes sense for the curve + // since the animated blend weight doesn't have any units: it's a scaling factor. + // The morph target math is like this for every vertex: result vtx = ref vtx + (target vtx - ref vtx) * blend weight + // (target vtx - ref vtx) is our deformation difference (or delta) and we scale it between 0.0 and 1.0 with our blend weight. + // At 0.0, the resulting vertex is 100% the reference vertex. + // At 1.0, the resulting vertex is 100% the target vertex. + // This can thus be re-written as follow: result vtx = ref vtx + vtx delta * blend weight + // From this, it follows that any error we introduce into the blend weight will impact the delta linearly. + // If our delta measures 1 meter, an error of 10% translates into 0.1 meter. + // If our delta measures 1 cm, an error of 10% translates into 0.1 cm. + // Thus, for a given error quantity, a larger delta means a larger resulting difference from the original value. + // If the delta is zero, any error is irrelevant as it will have no measurable impact. + // By dividing the precision value we want with the delta length, we can control how much precision our blend weight needs. + // If we want 0.01 cm precision and our largest vertex displacement is 3 cm, the blend weight precision needs to be: + // 0.01 cm / 3.00 cm = 0.0033 (with the units cancelling out just like we need) + // Another way to think about it is that every 0.0033 increment of the blend weight results in an increment of 0.01 cm + // when our displacement delta is 3 cm. + // 0.01 cm / 50.00 cm = 0.0002 (if our delta increases, we need to retain more blend weight precision) + // 0.01 cm / 1.00 cm = 0.01 + // Each blend weight curve will drive a different target position for many vertices and this way, we can specify + // a single value for the world space precision we want to achieve for every vertex and every blend weight curve + // will end up with the precision value it needs. + // + // If our curve doesn't drive a morph target, we use the supplied CurvePrecision instead. + + const float Precision = MaxPositionDelta > 0.0f ? (MorphTargetPositionPrecision / MaxPositionDelta) : CurvePrecision; + + acl::track_desc_scalarf Desc; + Desc.output_index = CurveIndex; + Desc.precision = Precision; + + acl::track_float1f Track = acl::track_float1f::make_reserve(Desc, ACLAllocatorImpl, NumSamples, SampleRate); + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const float SampleTime = FMath::Clamp(SampleIndex * InvSampleRate, 0.0f, SequenceLength); + const float SampleValue = Curve.FloatCurve.Eval(SampleTime); + + Track[SampleIndex] = SampleValue; + } + + Tracks[CurveIndex] = MoveTemp(Track); + } + + acl::compression_settings Settings; + + acl::compressed_tracks* CompressedTracks = nullptr; + acl::output_stats Stats; + const acl::error_result CompressionResult = acl::compress_track_list(ACLAllocatorImpl, Tracks, Settings, CompressedTracks, Stats); + + if (CompressionResult.any()) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL failed to compress curves: %s [%s]"), ANSI_TO_TCHAR(CompressionResult.c_str()), *AnimSeq.FullName); + return false; + } + + checkSlow(CompressedTracks->is_valid(true).empty()); + + const uint32 CompressedDataSize = CompressedTracks->get_size(); + + OutResult.CompressedBytes.Empty(CompressedDataSize); + OutResult.CompressedBytes.AddUninitialized(CompressedDataSize); + FMemory::Memcpy(OutResult.CompressedBytes.GetData(), CompressedTracks, CompressedDataSize); + + OutResult.Codec = this; + +#if !NO_LOGGING + { + acl::decompression_context Context; + Context.initialize(*CompressedTracks); + const acl::track_error Error = acl::calculate_compression_error(ACLAllocatorImpl, Tracks, Context); + + UE_LOG(LogAnimationCompression, Verbose, TEXT("ACL Curves compressed size: %u bytes [%s]"), CompressedDataSize, *AnimSeq.FullName); + UE_LOG(LogAnimationCompression, Verbose, TEXT("ACL Curves error: %.4f (curve %u @ %.3f) [%s]"), Error.error, Error.index, Error.sample_time, *AnimSeq.FullName); + } +#endif + + ACLAllocatorImpl.deallocate(CompressedTracks, CompressedDataSize); + return true; +} +#endif // WITH_EDITORONLY_DATA + +struct UE4CurveDecompressionSettings final : public acl::decompression_settings +{ + static constexpr bool is_track_type_supported(acl::track_type8 type) { return type == acl::track_type8::float1f; } + + // Only support our latest version + static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::latest; } + +#if UE_BUILD_SHIPPING + // Shipping builds do not need safety checks, by then the game has been tested enough + // Only data corruption could cause a safety check to fail + // We keep this disabled regardless because it is generally better to output a T-pose than to have a + // potential crash. Corruption can happen and it would be unfortunate if a demo or playtest failed + // as a result of a crash that we can otherwise recover from. + //static constexpr bool skip_initialize_safety_checks() { return true; } +#endif +}; + +struct UE4CurveWriter final : public acl::track_writer +{ + const TArray& CompressedCurveNames; + FBlendedCurve& Curves; + + UE4CurveWriter(const TArray& CompressedCurveNames_, FBlendedCurve& Curves_) + : CompressedCurveNames(CompressedCurveNames_) + , Curves(Curves_) + { + } + + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_float1(uint32_t TrackIndex, rtm::scalarf_arg0 Value) + { + const FSmartName& CurveName = CompressedCurveNames[TrackIndex]; + if (Curves.IsEnabled(CurveName.UID)) + { + Curves.Set(CurveName.UID, rtm::scalar_cast(Value)); + } + } +}; + +void UAnimCurveCompressionCodec_ACL::DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +{ + const TArray& CompressedCurveNames = AnimSeq.CompressedCurveNames; + const int32 NumCurves = CompressedCurveNames.Num(); + + if (NumCurves == 0) + { + return; + } + + const acl::compressed_tracks* CompressedTracks = acl::make_compressed_tracks(AnimSeq.CompressedCurveByteStream.GetData()); + check(CompressedTracks != nullptr && CompressedTracks->is_valid(false).empty()); + + acl::decompression_context Context; + Context.initialize(*CompressedTracks); + Context.seek(CurrentTime, acl::sample_rounding_policy::none); + + UE4CurveWriter TrackWriter(CompressedCurveNames, Curves); + Context.decompress_tracks(TrackWriter); +} + +struct UE4ScalarCurveWriter final : public acl::track_writer +{ + float SampleValue; + + UE4ScalarCurveWriter() + : SampleValue(0.0f) + { + } + + FORCEINLINE_DEBUGGABLE void RTM_SIMD_CALL write_float1(uint32_t /*TrackIndex*/, rtm::scalarf_arg0 Value) + { + SampleValue = rtm::scalar_cast(Value); + } +}; + +float UAnimCurveCompressionCodec_ACL::DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +{ + const TArray& CompressedCurveNames = AnimSeq.CompressedCurveNames; + const int32 NumCurves = CompressedCurveNames.Num(); + + if (NumCurves == 0) + { + return 0.0f; + } + + const acl::compressed_tracks* CompressedTracks = acl::make_compressed_tracks(AnimSeq.CompressedCurveByteStream.GetData()); + check(CompressedTracks != nullptr && CompressedTracks->is_valid(false).empty()); + + acl::decompression_context Context; + Context.initialize(*CompressedTracks); + Context.seek(CurrentTime, acl::sample_rounding_policy::none); + + int32 TrackIndex = -1; + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + if (CompressedCurveNames[CurveIndex].UID == CurveUID) + { + TrackIndex = CurveIndex; + break; + } + } + + if (TrackIndex < 0) + { + return 0.0f; // Track not found + } + + UE4ScalarCurveWriter TrackWriter; + Context.decompress_track(TrackIndex, TrackWriter); + + return TrackWriter.SampleValue; +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimationCompressionLibraryDatabase.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimationCompressionLibraryDatabase.cpp new file mode 100644 index 00000000..6a818575 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/AnimationCompressionLibraryDatabase.cpp @@ -0,0 +1,1139 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "AnimationCompressionLibraryDatabase.h" +#include "AnimBoneCompressionCodec_ACLDatabase.h" + +#include "UE4DatabaseStreamer.h" + +#include "LatentActions.h" +#include "Containers/Ticker.h" +#include "Engine/Engine.h" + +#include "ACLImpl.h" + +#if WITH_EDITORONLY_DATA +#include "Animation/AnimBoneCompressionSettings.h" +#include "PlatformInfo.h" +#include "Interfaces/ITargetPlatform.h" +#include "UObject/UObjectIterator.h" + +#if ENGINE_MAJOR_VERSION >= 5 +#include "UObject/ObjectSaveContext.h" +#endif + +#include "UE4DatabasePreviewStreamer.h" + +#include +#endif // WITH_EDITORONLY_DATA + +const TCHAR* VisualFidelityToString(ACLVisualFidelity Fidelity) +{ + switch (Fidelity) + { + case ACLVisualFidelity::Highest: + return TEXT("Highest"); + case ACLVisualFidelity::Medium: + return TEXT("Medium"); + case ACLVisualFidelity::Lowest: + return TEXT("Lowest"); + default: + return TEXT("Unknown visual fidelity"); + } +} + +UAnimationCompressionLibraryDatabase::UAnimationCompressionLibraryDatabase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurrentVisualFidelity(ACLVisualFidelity::Lowest) + , NextFidelityChangeRequestID(0) +#if WITH_EDITORONLY_DATA + , HighestImportanceProportion(0.5f) // The rest remains in the anim sequences + , MediumImportanceProportion(0.0f) // No medium quality tier by default + , LowestImportanceProportion(0.5f) // By default we move 50% of the key frames to the database + , StripLowestImportanceTier(false) // By default we don't strip the lowest tier +#endif + , MaxStreamRequestSizeKB(1024) // By default we stream 1 MB (1 chunk) at a time +#if WITH_EDITORONLY_DATA + // By default, in the editor we preview the full quality. + // Our database context won't be used until we need to build the database for preview if we change this value. + , PreviewVisualFidelity(ACLVisualFidelity::Highest) +#endif +{ + // Make sure to initialize our context properly + new (&GetDatabaseContext()) acl::database_context(); +} + +#if WITH_EDITORONLY_DATA +void UAnimationCompressionLibraryDatabase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property == nullptr) + { + return; // Safety check + } + + const FName ChangedPropertyName = PropertyChangedEvent.Property->GetFName(); + + bool bBuildDatabaseForPreview = false; + bool bUpdateStreaming = false; + + if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(UAnimationCompressionLibraryDatabase, LowestImportanceProportion)) + { + // Clamp our medium importance propertion + MediumImportanceProportion = FMath::Clamp(MediumImportanceProportion, 0.0f, FMath::Clamp(1.0f - LowestImportanceProportion, 0.0f, 1.0f)); + + // Update our high importance proportion to reflect what remains + HighestImportanceProportion = FMath::Clamp(1.0f - LowestImportanceProportion - MediumImportanceProportion, 0.0f, 1.0f); + + // Only update our preview state if we are setting the final value to avoid slowing down the UI when using the slider + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + bBuildDatabaseForPreview = PreviewDatabaseStreamer != nullptr; // If we already had a preview database, we need to rebuild it + } + } + else if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(UAnimationCompressionLibraryDatabase, MediumImportanceProportion)) + { + // Clamp our medium importance propertion + LowestImportanceProportion = FMath::Clamp(LowestImportanceProportion, 0.0f, FMath::Clamp(1.0f - MediumImportanceProportion, 0.0f, 1.0f)); + + // Update our high importance proportion to reflect what remains + HighestImportanceProportion = FMath::Clamp(1.0f - LowestImportanceProportion - MediumImportanceProportion, 0.0f, 1.0f); + + // Only update our preview state if we are setting the final value to avoid slowing down the UI when using the slider + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + bBuildDatabaseForPreview = PreviewDatabaseStreamer != nullptr; // If we already had a preview database, we need to rebuild it + } + } + else if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(UAnimationCompressionLibraryDatabase, PreviewVisualFidelity)) + { + // Our preview state changed, check if we need to generate our database + bBuildDatabaseForPreview = PreviewDatabaseStreamer == nullptr; // We didn't have a preview database, create one now + bUpdateStreaming = true; + } + + if (bBuildDatabaseForPreview || bUpdateStreaming) + { + UpdatePreviewState(bBuildDatabaseForPreview); + } +} + +#if ENGINE_MAJOR_VERSION >= 5 +void UAnimationCompressionLibraryDatabase::PreSave(FObjectPreSaveContext SaveContext) +#else +void UAnimationCompressionLibraryDatabase::PreSave(const ITargetPlatform* TargetPlatform) +#endif +{ +#if ENGINE_MAJOR_VERSION >= 5 + Super::PreSave(SaveContext); + + const ITargetPlatform* TargetPlatform = SaveContext.GetTargetPlatform(); +#else + Super::PreSave(TargetPlatform); +#endif + + // Clear any stale cooked data we might have + CookedCompressedBytes.Empty(0); + CookedAnimSequenceMappings.Empty(0); + CookedBulkData.RemoveBulkData(); + + if (TargetPlatform != nullptr && TargetPlatform->RequiresCookedData()) + { +#if ENGINE_MAJOR_VERSION >= 5 + const bool bStripLowestTier = StripLowestImportanceTier.GetValueForPlatform(*TargetPlatform->IniPlatformName()); +#else + const bool bStripLowestTier = StripLowestImportanceTier.GetValueForPlatformIdentifiers( + TargetPlatform->GetPlatformInfo().PlatformGroupName, + TargetPlatform->GetPlatformInfo().VanillaPlatformName); +#endif + + TArray BulkData; + BuildDatabase(CookedCompressedBytes, CookedAnimSequenceMappings, BulkData, bStripLowestTier); + + CookedBulkData.Lock(LOCK_READ_WRITE); + { + const uint32 BulkDataSize = BulkData.Num(); + void* BulkDataToSave = CookedBulkData.Realloc(BulkDataSize); + FMemory::Memcpy(BulkDataToSave, BulkData.GetData(), BulkDataSize); + } + CookedBulkData.Unlock(); + } +} + +void UAnimationCompressionLibraryDatabase::BuildDatabase(TArray& OutCompressedBytes, TArray& OutAnimSequenceMappings, TArray& OutBulkData, bool bStripLowestTier) const +{ + // Clear any stale data we might have + OutCompressedBytes.Empty(0); + OutAnimSequenceMappings.Empty(0); + OutBulkData.Empty(0); + + // We are cooking or previewing, iterate over every animation sequence that references this database + // and merge them together into our final database instance. Note that the mapping could + // be stale and we must double check. + + // Gather the sequences we need to merge, these are already sorted by FName by construction + TArray CookedSequences; + CookedSequences.Empty(AnimSequences.Num()); + + // Because we use a hash at runtime in a cooked build to retrieve our clip data, we must ensure that the hash value is unique + TSet SequenceHashes; + SequenceHashes.Empty(AnimSequences.Num()); + + for (UAnimSequence* AnimSeq : AnimSequences) + { + UAnimBoneCompressionCodec_ACLDatabase* DatabaseCodec = Cast(AnimSeq->CompressedData.BoneCompressionCodec); + if (DatabaseCodec == nullptr || DatabaseCodec->DatabaseAsset != this) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL Database mapping is stale. '%s' no longer references it. [%s]"), *AnimSeq->GetPathName(), *GetPathName()); + continue; + } + + // Update our compressed data to match the current settings + // We might need to revert to non-frame stripped data or the codec might have changed + // forcing us to recompress + // In practice we'll load directly from the DDC and it should be fast + const bool bAsyncCompression = false; + const bool bAllowAlternateCompressor = false; + const bool bOutput = false; + + FRequestAnimCompressionParams Params(bAsyncCompression, bAllowAlternateCompressor, bOutput); + Params.bPerformFrameStripping = false; + + AnimSeq->RequestAnimCompression(Params); + + const FACLDatabaseCompressedAnimData& AnimData = static_cast(*AnimSeq->CompressedData.CompressedDataStructure); + if (!AnimData.IsValid()) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("Cannot include a invalid sequence '%s' in the ACL database. [%s]"), *AnimSeq->GetPathName(), *GetPathName()); + continue; + } + + bool bIsAlreadyInSet = false; + SequenceHashes.Add(AnimData.SequenceNameHash, &bIsAlreadyInSet); + if (bIsAlreadyInSet) + { + // Our anim sequence has a hash that we've seen already which means an anim sequence has been duplicated + // and lives in a separate asset with identical data. + // We'll skip it since we'll be able to re-use the same data at runtime. + continue; + } + + CookedSequences.Add(AnimSeq); + } + + if (CookedSequences.Num() == 0) + { + return; // Nothing to cook + } + + TArray ACLCompressedTracks; + for (const UAnimSequence* AnimSeq : CookedSequences) + { + const FACLDatabaseCompressedAnimData& AnimData = static_cast(*AnimSeq->CompressedData.CompressedDataStructure); + ACLCompressedTracks.Add(AnimData.GetCompressedTracks()); + } + + const int32 NumSequences = ACLCompressedTracks.Num(); + + acl::compression_database_settings Settings; // Use defaults + Settings.low_importance_tier_proportion = LowestImportanceProportion; + Settings.medium_importance_tier_proportion = MediumImportanceProportion; + + TArray ACLDBCompressedTracks; + ACLDBCompressedTracks.AddZeroed(NumSequences); + + acl::compressed_database* MergedDB = nullptr; + acl::error_result MergeResult = acl::build_database(ACLAllocatorImpl, Settings, ACLCompressedTracks.GetData(), NumSequences, ACLDBCompressedTracks.GetData(), MergedDB); + + if (MergeResult.any()) + { + // Free our duplicate compressed clips + UE_LOG(LogAnimationCompression, Error, TEXT("ACL failed to merge databases: %s [%s]"), ANSI_TO_TCHAR(MergeResult.c_str()), *GetPathName()); + return; + } + +#if DO_GUARD_SLOW + // Sanity check that the database is properly constructed + { + checkSlow(MergedDB->is_valid(true).empty()); + + acl::database_context DebugDatabaseContext; + const bool ContextInitResult = DebugDatabaseContext.initialize(ACLAllocatorImpl, *MergedDB); + checkf(ContextInitResult, TEXT("ACL failed to initialize the database context")); + + for (const acl::compressed_tracks* CompressedTracks : ACLDBCompressedTracks) + { + checkSlow(CompressedTracks->is_valid(true).empty()); + checkSlow(MergedDB->contains(*CompressedTracks)); + checkSlow(DebugDatabaseContext.contains(*CompressedTracks)); + } +} +#endif + + // Split our database to serialize the bulk data separately + acl::compressed_database* SplitDB = nullptr; + uint8* SplitDBBulkDataMedium = nullptr; + uint8* SplitDBBulkDataLow = nullptr; + const acl::error_result SplitResult = acl::split_database_bulk_data(ACLAllocatorImpl, *MergedDB, SplitDB, SplitDBBulkDataMedium, SplitDBBulkDataLow); + + // Free the merged instance we no longer need + ACLAllocatorImpl.deallocate(MergedDB, MergedDB->get_size()); + MergedDB = nullptr; + + if (SplitResult.any()) + { + // Free our clip copies + for (acl::compressed_tracks* CompressedTracks : ACLDBCompressedTracks) + { + ACLAllocatorImpl.deallocate(CompressedTracks, CompressedTracks->get_size()); + } + + UE_LOG(LogAnimationCompression, Error, TEXT("ACL failed to split database: %s [%s]"), ANSI_TO_TCHAR(SplitResult.c_str()), *GetPathName()); + return; + } + + checkSlow(SplitDB->is_valid(true).empty()); + + const uint32 BulkDataSizeMedium = SplitDB->get_bulk_data_size(acl::quality_tier::medium_importance); + const uint32 BulkDataSizeLow = SplitDB->get_bulk_data_size(acl::quality_tier::lowest_importance); + uint32 BulkDataEffectiveSizeLow = BulkDataSizeLow; + + // Strip our lowest tier if requested and if it contains data + if (bStripLowestTier && SplitDB->has_bulk_data(acl::quality_tier::lowest_importance)) + { + acl::compressed_database* StrippedDB = nullptr; + const acl::error_result StripResult = acl::strip_database_quality_tier(ACLAllocatorImpl, *SplitDB, acl::quality_tier::lowest_importance, StrippedDB); + + if (StripResult.any()) + { + // We failed to strip our tier but the split database is still usable, don't fail anything + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL failed to strip lowest database tier: %s [%s]"), ANSI_TO_TCHAR(StripResult.c_str()), *GetPathName()); + } + else + { + // Medium tier shouldn't have changed + check(BulkDataSizeMedium == StrippedDB->get_bulk_data_size(acl::quality_tier::medium_importance)); + + // Free the split instance we no longer need + ACLAllocatorImpl.deallocate(SplitDB, SplitDB->get_size()); + + // Use the stripped database instead + SplitDB = StrippedDB; + + // We no longer have the lowest tier + BulkDataEffectiveSizeLow = 0; + } + } + + const uint32 CompressedDatabaseSize = SplitDB->get_size(); + + // Our compressed sequences follow the database in memory, aligned to 16 bytes + uint32 CompressedSequenceOffset = acl::align_to(CompressedDatabaseSize, 16); + + // Write our our cooked offset mappings + // We use an array for simplicity. UE4 doesn't support serializing a TMap or TSortedMap and so instead + // we store an array of sorted FNames hashes and offsets. We'll use binary search to find our + // index at runtime in O(logN) in the sorted names array, and read the offset we need in the other. + // TODO: Use perfect hashing to bring it to O(1) + + SIZE_T TotalSizeSeqOld = 0; + SIZE_T TotalSizeSeqNew = 0; + + OutAnimSequenceMappings.Empty(NumSequences); + for (int32 MappingIndex = 0; MappingIndex < NumSequences; ++MappingIndex) + { + UAnimSequence* AnimSeq = CookedSequences[MappingIndex]; + const acl::compressed_tracks* CompressedTracks = ACLDBCompressedTracks[MappingIndex]; + FACLDatabaseCompressedAnimData& AnimData = static_cast(*AnimSeq->CompressedData.CompressedDataStructure); + + // Align our sequence to 16 bytes + CompressedSequenceOffset = acl::align_to(CompressedSequenceOffset, 16); + + // Add our mapping + OutAnimSequenceMappings.Add((uint64(AnimData.SequenceNameHash) << 32) | uint64(CompressedSequenceOffset)); + + // Increment our offset but don't align since we don't want to add unnecesary padding at the end of the last sequence + CompressedSequenceOffset += CompressedTracks->get_size(); + + TotalSizeSeqOld += ACLCompressedTracks[MappingIndex]->get_size(); + TotalSizeSeqNew += CompressedTracks->get_size(); + } + + auto BytesToMB = [](SIZE_T NumBytes) { return (double)NumBytes / (1024.0 * 1024.0); }; + + UE_LOG(LogAnimationCompression, Log, TEXT("ACL DB [%s] Sequences (%u) went from %.2f MB -> %.2f MB. DB is %.2f MB"), + *GetPathName(), NumSequences, BytesToMB(TotalSizeSeqOld), BytesToMB(TotalSizeSeqNew), BytesToMB(SplitDB->get_total_size())); + UE_LOG(LogAnimationCompression, Log, TEXT(" DB metadata is %.2f MB"), BytesToMB(SplitDB->get_size())); + UE_LOG(LogAnimationCompression, Log, TEXT(" DB medium tier is %.2f MB"), BytesToMB(BulkDataSizeMedium)); + UE_LOG(LogAnimationCompression, Log, TEXT(" DB lowest tier is %.2f MB%s"), BytesToMB(BulkDataSizeLow), bStripLowestTier ? TEXT(" (stripped)") : TEXT("")); + + // Make sure to sort our array, it'll be sorted by hash first since it lives in the top bits + OutAnimSequenceMappings.Sort(); + + // Our full buffer size is our resulting offset + const uint32 CompressedBytesSize = CompressedSequenceOffset; + + // Copy our database + OutCompressedBytes.Empty(CompressedBytesSize); + OutCompressedBytes.AddZeroed(CompressedBytesSize); + FMemory::Memcpy(OutCompressedBytes.GetData(), SplitDB, CompressedDatabaseSize); + + // Copy our compressed clips + CompressedSequenceOffset = acl::align_to(CompressedDatabaseSize, 16); // Reset + + for (const acl::compressed_tracks* CompressedTracks : ACLDBCompressedTracks) + { + // Align our sequence to 16 bytes + CompressedSequenceOffset = acl::align_to(CompressedSequenceOffset, 16); + + // Copy our data + FMemory::Memcpy(OutCompressedBytes.GetData() + CompressedSequenceOffset, CompressedTracks, CompressedTracks->get_size()); + + // Increment our offset but don't align since we don't want to add unnecesary padding at the end of the last sequence + CompressedSequenceOffset += CompressedTracks->get_size(); + } + + // Copy our bulk data + uint32 BulkDataSize = BulkDataSizeMedium; + if (!bStripLowestTier && BulkDataEffectiveSizeLow != 0) + { + BulkDataSize += BulkDataEffectiveSizeLow; + } + + OutBulkData.Empty(BulkDataSize); + OutBulkData.AddZeroed(BulkDataSize); + FMemory::Memcpy(OutBulkData.GetData(), SplitDBBulkDataMedium, BulkDataSizeMedium); + + if (!bStripLowestTier && BulkDataEffectiveSizeLow != 0) + { + FMemory::Memcpy(OutBulkData.GetData() + BulkDataSizeMedium, SplitDBBulkDataLow, BulkDataEffectiveSizeLow); + } + + // Free the split instance we no longer need + ACLAllocatorImpl.deallocate(SplitDB, CompressedDatabaseSize); + ACLAllocatorImpl.deallocate(SplitDBBulkDataMedium, BulkDataSizeMedium); + ACLAllocatorImpl.deallocate(SplitDBBulkDataLow, BulkDataSizeLow); + + // Free our clip copies + for (acl::compressed_tracks* CompressedTracks : ACLDBCompressedTracks) + { + ACLAllocatorImpl.deallocate(CompressedTracks, CompressedTracks->get_size()); + } +} + +void UAnimationCompressionLibraryDatabase::UpdatePreviewState(bool bBuildDatabase) +{ + acl::database_context& DatabaseContext = GetDatabaseContext(); + + // Check if we need to build/rebuild our preview database + if (bBuildDatabase) + { + // Create a temporary database now so we can preview our animations at the desired quality + PreviewDatabaseStreamer.Reset(); + DatabaseContext.reset(); + + BuildDatabase(PreviewCompressedBytes, PreviewAnimSequenceMappings, PreviewBulkData); + + if (PreviewCompressedBytes.Num() != 0) + { + // Our database was built, initialize what we need to be able to use it + const acl::compressed_database* CompressedDatabase = acl::make_compressed_database(PreviewCompressedBytes.GetData()); + check(CompressedDatabase != nullptr && CompressedDatabase->is_valid(false).empty()); + + PreviewDatabaseStreamer = MakeUnique(*CompressedDatabase, PreviewBulkData); + + const bool ContextInitResult = DatabaseContext.initialize(ACLAllocatorImpl, *CompressedDatabase, *PreviewDatabaseStreamer, *PreviewDatabaseStreamer); + checkf(ContextInitResult, TEXT("ACL failed to initialize the database context")); + + // New fidelity is lowest + CurrentVisualFidelity = ACLVisualFidelity::Lowest; + } + } + + // Perform any streaming request as a result of our new preview/database state + if (DatabaseContext.is_initialized()) + { + SetVisualFidelity(PreviewVisualFidelity); + } +} + +bool UAnimationCompressionLibraryDatabase::UpdateReferencingAnimSequenceList() +{ + // Grab every anim sequence that references us + TArray ReferencingAnimSequences; + for (TObjectIterator It; It; ++It) + { + UAnimSequence* AnimSeq = *It; + if (AnimSeq->GetOutermost() == GetTransientPackage()) + { + continue; + } + + UAnimBoneCompressionSettings* Settings = AnimSeq->BoneCompressionSettings; + if (Settings == nullptr || Settings->Codecs.Num() != 1) + { + continue; + } + + UAnimBoneCompressionCodec_ACLDatabase* DatabaseCodec = Cast(Settings->Codecs[0]); + if (DatabaseCodec != nullptr && DatabaseCodec->DatabaseAsset == this) + { + ReferencingAnimSequences.Add(AnimSeq); + } + } + + // Sort our anim sequences by path name to ensure predictable and consistent results + struct FCompareObjectNames + { + FORCEINLINE bool operator()(const UAnimSequence& Lhs, const UAnimSequence& Rhs) const + { + return Lhs.GetPathName().Compare(Rhs.GetPathName()) < 0; + } + }; + ReferencingAnimSequences.Sort(FCompareObjectNames()); + + // Check if the list is any different to avoid marking as dirty if we aren't + const int32 NumSeqs = ReferencingAnimSequences.Num(); + bool bIsDirty = false; + if (AnimSequences.Num() == NumSeqs) + { + for (int32 SeqIdx = 0; SeqIdx < NumSeqs; ++SeqIdx) + { + if (AnimSequences[SeqIdx] != ReferencingAnimSequences[SeqIdx]) + { + // Sorted arrays do not match, we are dirty + bIsDirty = true; + break; + } + } + } + else + { + // Count differs, dirty for sure + bIsDirty = true; + } + + if (bIsDirty) + { + // Swap our content since we are new + Swap(AnimSequences, ReferencingAnimSequences); + + MarkPackageDirty(); + + // If we were previewing, update our database + if (PreviewDatabaseStreamer) + { + UpdatePreviewState(true); + } + } + + return bIsDirty; +} +#endif + +static void FailAllRequests(const TArray& Requests) +{ + for (const FFidelityChangeRequest& Request : Requests) + { + if (Request.Result != nullptr) + { + *Request.Result = ACLVisualFidelityChangeResult::Failed; + } + } +} + +void UAnimationCompressionLibraryDatabase::BeginDestroy() +{ + Super::BeginDestroy(); + + // Remove our ticker if we have one + if (FidelityUpdateTickerHandle.IsValid()) + { + FTickerType::GetCoreTicker().RemoveTicker(FidelityUpdateTickerHandle); + FidelityUpdateTickerHandle.Reset(); + } + + // If we have visual fidelity changes in progress, cancel them + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + + acl::database_context& DatabaseContext = GetDatabaseContext(); + + if (DatabaseStreamer) + { + // Wait for any pending IO requests + UE4DatabaseStreamer* Streamer = (UE4DatabaseStreamer*)DatabaseStreamer.Release(); + Streamer->WaitForStreamingToComplete(); + + // Reset our context to make sure it no longer references the streamer + DatabaseContext.reset(); + + // Free our streamer, it is no longer needed + delete Streamer; + } + +#if WITH_EDITORONLY_DATA + if (PreviewDatabaseStreamer) + { + // Reset our context to make sure it no longer references the streamer + DatabaseContext.reset(); + + // Free our streamer, it is no longer needed + PreviewDatabaseStreamer.Reset(); + } +#endif + + // Manually run the destructor since the container is opaque + DatabaseContext.~database_context(); +} + +void UAnimationCompressionLibraryDatabase::PostLoad() +{ + Super::PostLoad(); + + if (CookedCompressedBytes.Num() != 0) + { + const acl::compressed_database* CompressedDatabase = acl::make_compressed_database(CookedCompressedBytes.GetData()); + check(CompressedDatabase != nullptr && CompressedDatabase->is_valid(false).empty()); + + DatabaseStreamer = MakeUnique(*CompressedDatabase, CookedBulkData); + + const bool ContextInitResult = GetDatabaseContext().initialize(ACLAllocatorImpl, *CompressedDatabase, *DatabaseStreamer, *DatabaseStreamer); + checkf(ContextInitResult, TEXT("ACL failed to initialize the database context")); + } +} + +void UAnimationCompressionLibraryDatabase::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + bool bCooked = Ar.IsCooking(); + Ar << bCooked; + + // Serialize the cooked bulk data ONLY if we are cooking. + if (bCooked) + { + CookedBulkData.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload); + CookedBulkData.Serialize(Ar, this, INDEX_NONE, false); + } + else if (Ar.IsCountingMemory()) + { + acl::database_context& DatabaseContext = GetDatabaseContext(); + + // When counting memory, also track the streamed in data. + if (DatabaseContext.is_initialized() && DatabaseStreamer) + { + const uint8* MediumTierData = DatabaseStreamer->get_bulk_data(acl::quality_tier::medium_importance); + if (MediumTierData != nullptr) + { + const uint32 MediumTierSize = DatabaseContext.get_compressed_database()->get_bulk_data_size(acl::quality_tier::medium_importance); + + // Avoid touching the actual tier data, use a temporary array since the content itself doesn't matter + TArray Tmp; + Tmp.Empty(MediumTierSize); + Tmp.AddZeroed(MediumTierSize); + + Ar << Tmp; + } + + const uint8* LowestTierData = DatabaseStreamer->get_bulk_data(acl::quality_tier::lowest_importance); + if (LowestTierData != nullptr) + { + const uint32 LowestTierSize = DatabaseContext.get_compressed_database()->get_bulk_data_size(acl::quality_tier::lowest_importance); + + // Avoid touching the actual tier data, use a temporary array since the content itself doesn't matter + TArray Tmp; + Tmp.Empty(LowestTierSize); + Tmp.AddZeroed(LowestTierSize); + + Ar << Tmp; + } + } + } +} + +void UAnimationCompressionLibraryDatabase::SetVisualFidelity(ACLVisualFidelity VisualFidelity) +{ + SetVisualFidelityImpl(VisualFidelity, nullptr); +} + +uint32 UAnimationCompressionLibraryDatabase::SetVisualFidelityImpl(ACLVisualFidelity VisualFidelity, ACLVisualFidelityChangeResult* OutResult) +{ + // Must execute on the main thread but must do so while animations aren't updating + check(IsInGameThread()); + + acl::database_context& DatabaseContext = GetDatabaseContext(); + +#if WITH_EDITORONLY_DATA + if (!DatabaseContext.is_initialized()) + { + // We are in the editor and we aren't previewing yet which means the highest quality is showing, build the database now + // so we can properly preview + UpdatePreviewState(true); + } +#endif + + check(DatabaseContext.is_initialized()); + + const bool bIsFirstRequest = FidelityChangeRequests.Num() == 0; + const ACLVisualFidelity FinalEffectiveFidelity = bIsFirstRequest ? CurrentVisualFidelity : FidelityChangeRequests.Last().Fidelity; + + // If the desired visual fidelity matches the final effective fidelity once all change requests have completed, we can ignore it + if (VisualFidelity == FinalEffectiveFidelity) + { + if (OutResult != nullptr) + { + *OutResult = ACLVisualFidelityChangeResult::Completed; + } + + return ~0U; + } + + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database is requesting visual fidelity %s [%s]"), VisualFidelityToString(VisualFidelity), *GetPathName()); + + const uint32 RequestID = NextFidelityChangeRequestID++; + + // Add our change requests + // To simplify handling, change requests only transition from one fidelity level to the next closest + // Large changes are split into simpler steps + switch (FinalEffectiveFidelity) + { + case ACLVisualFidelity::Highest: + switch (VisualFidelity) + { + case ACLVisualFidelity::Medium: + // From highest to medium we need a single change + FidelityChangeRequests.Add({ OutResult, RequestID, ACLVisualFidelity::Medium, false }); + break; + case ACLVisualFidelity::Lowest: + // From highest to lowest we need two changes + FidelityChangeRequests.Add({ nullptr, RequestID, ACLVisualFidelity::Medium, false }); + FidelityChangeRequests.Add({ OutResult, RequestID, ACLVisualFidelity::Lowest, false }); + break; + default: + checkf(false, TEXT("Unexpected visual fidelity value")); + break; + } + break; + case ACLVisualFidelity::Medium: + switch (VisualFidelity) + { + case ACLVisualFidelity::Highest: + // From medium to highest we need a single change + FidelityChangeRequests.Add({ OutResult, RequestID, ACLVisualFidelity::Highest, false }); + break; + case ACLVisualFidelity::Lowest: + // From medium to lowest we need a single change + FidelityChangeRequests.Add({ OutResult, RequestID, ACLVisualFidelity::Lowest, false }); + break; + default: + checkf(false, TEXT("Unexpected visual fidelity value")); + break; + } + break; + case ACLVisualFidelity::Lowest: + switch (VisualFidelity) + { + case ACLVisualFidelity::Highest: + // From lowest to highest we need two changes + FidelityChangeRequests.Add({ nullptr, RequestID, ACLVisualFidelity::Medium, false }); + FidelityChangeRequests.Add({ OutResult, RequestID, ACLVisualFidelity::Highest, false }); + break; + case ACLVisualFidelity::Medium: + // From lowest to medium we need a single change + FidelityChangeRequests.Add({ OutResult, RequestID, ACLVisualFidelity::Medium, false }); + break; + default: + checkf(false, TEXT("Unexpected visual fidelity value")); + break; + } + break; + } + + // If this is the first request, queue our ticker so we can start tracking our changes + if (bIsFirstRequest) + { + check(!FidelityUpdateTickerHandle.IsValid()); + auto UpdateVisualFidelity = [this](float DeltaTime) { return UpdateVisualFidelityTicker(DeltaTime); }; + FidelityUpdateTickerHandle = FTickerType::GetCoreTicker().AddTicker(TEXT("ACLDBStreamOut"), 0.0F, UpdateVisualFidelity); + } + + return RequestID; +} + +void UAnimationCompressionLibraryDatabase::CancelVisualFidelityRequestImpl(uint32 RequestID) +{ + // Must execute on the main thread + check(IsInGameThread()); + + // Nothing to do if the request ID isn't valid + if (RequestID == ~0U) + { + return; + } + + // Look for our request ID and cancel what we can + // Iterate in reverse order so we can remove elements as we go + for (int32 RequestIndex = FidelityChangeRequests.Num() - 1; RequestIndex >= 0; --RequestIndex) + { + FFidelityChangeRequest& Request = FidelityChangeRequests[RequestIndex]; + if (Request.RequestID != RequestID) + { + // Not our request, skip it + continue; + } + + // Found our request + if (Request.bIsInProgress) + { + // Our request is in progress but we can't cancel it + // The request will attempt to complete but the result pointer might no longer be valid then, clear it + Request.Result = nullptr; + } + else + { + // Our request hasn't started yet, remove it + FidelityChangeRequests.RemoveAt(RequestIndex); + } + } +} + +static void LogRequestResult(const UAnimationCompressionLibraryDatabase& Database, acl::database_stream_request_result Result) +{ + switch (Result) + { + default: + UE_LOG(LogAnimationCompression, Log, TEXT("Unknown ACL database stream request result: %u [%s]"), uint32(Result), *Database.GetPathName()); + break; + case acl::database_stream_request_result::done: + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database streaming is done [%s]"), *Database.GetPathName()); + break; + case acl::database_stream_request_result::dispatched: + break; + case acl::database_stream_request_result::streaming_in_progress: + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database streaming is already in progress [%s]"), *Database.GetPathName()); + break; + case acl::database_stream_request_result::context_not_initialized: + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database context not initialized [%s]"), *Database.GetPathName()); + break; + case acl::database_stream_request_result::invalid_database_tier: + UE_LOG(LogAnimationCompression, Log, TEXT("Specified ACL database tier is invalid [%s]"), *Database.GetPathName()); + break; + case acl::database_stream_request_result::no_free_streaming_requests: + UE_LOG(LogAnimationCompression, Log, TEXT("Failed to find a free ACL database streaming request [%s]"), *Database.GetPathName()); + break; + } +} + +static uint32 CalculateNumChunksToStream(const acl::compressed_database& Database, uint32 MaxStreamRequestSizeKB) +{ + if (MaxStreamRequestSizeKB == 0) + { + return ~0U; + } + + const uint32 MaxStreamRequestSize = MaxStreamRequestSizeKB * 1024; + const uint32 MaxChunkSize = Database.get_max_chunk_size(); + return FMath::Max(MaxStreamRequestSize / MaxChunkSize, 1); // Must stream at least one chunk +} + +/** +* Visual fidelity is updated through this latent ticker. Depending on the current database fidelity, +* we determine whether we need to stream in/out or do nothing. +* If a fidelity change is ongoing and a new value is requested, it will be queued. Subsequent changes +* at the same fidelity level simply update the parameters and do nothing. +* This greatly simplifies the level of control that ACL offers. A user can simply dictate the desired quality level +* and let the plugin determine what to do. +*/ +bool UAnimationCompressionLibraryDatabase::UpdateVisualFidelityTicker(float DeltaTime) +{ + acl::database_context& DatabaseContext = GetDatabaseContext(); + + check(DatabaseContext.is_initialized()); + + const uint32 NumChunksToStream = CalculateNumChunksToStream(*DatabaseContext.get_compressed_database(), MaxStreamRequestSizeKB); + + while (FidelityChangeRequests.Num() != 0) + { + FFidelityChangeRequest& Request = FidelityChangeRequests[0]; + check(Request.Fidelity != CurrentVisualFidelity); + + bool bIsRequestCompleted = false; + acl::database_stream_request_result Result = acl::database_stream_request_result::context_not_initialized; + + switch (CurrentVisualFidelity) + { + default: + case ACLVisualFidelity::Highest: + // We have highest fidelity + // The medium/lowest importance tiers are already streamed in + + checkf(Request.Fidelity == ACLVisualFidelity::Medium, TEXT("Unexpected visual fidelity value")); + if (Request.Fidelity != ACLVisualFidelity::Medium) + { + // Something wrong happened, ignore all change requests + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + } + else + { + // To reach medium quality, we need to stream out our lowest importance tier + Result = DatabaseContext.stream_out(acl::quality_tier::lowest_importance, NumChunksToStream); + LogRequestResult(*this, Result); + + switch (Result) + { + case acl::database_stream_request_result::done: + // We are done + bIsRequestCompleted = true; + break; + case acl::database_stream_request_result::dispatched: + // Streaming request has been dispatched, done for now + Request.bIsInProgress = true; + break; + case acl::database_stream_request_result::streaming_in_progress: + // Streaming is already in progress, done for now + checkf(Request.bIsInProgress, TEXT("Expected request to already be in progress")); + break; + default: + // Something wrong happened, ignore all change requests + checkf(false, TEXT("Something unexpected happened while ACL is streaming")); + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + break; + } + } + + // Done + break; + case ACLVisualFidelity::Medium: + // We have medium fidelity + // The medium importance tier is already streamed in + + checkf(Request.Fidelity == ACLVisualFidelity::Highest || Request.Fidelity == ACLVisualFidelity::Lowest, TEXT("Unexpected visual fidelity value")); + if (Request.Fidelity != ACLVisualFidelity::Highest && Request.Fidelity != ACLVisualFidelity::Lowest) + { + // Something wrong happened, ignore all change requests + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + } + else + { + if (Request.Fidelity == ACLVisualFidelity::Highest) + { + // To reach highest quality, we need to stream in our lowest importance tier + Result = DatabaseContext.stream_in(acl::quality_tier::lowest_importance, NumChunksToStream); + } + else + { + // To reach lowest quality, we need to stream out our medium importance tier + Result = DatabaseContext.stream_out(acl::quality_tier::medium_importance, NumChunksToStream); + } + + LogRequestResult(*this, Result); + + switch (Result) + { + case acl::database_stream_request_result::done: + // We are done + bIsRequestCompleted = true; + break; + case acl::database_stream_request_result::dispatched: + // Streaming request has been dispatched, done for now + Request.bIsInProgress = true; + break; + case acl::database_stream_request_result::streaming_in_progress: + // Streaming is already in progress, done for now + checkf(Request.bIsInProgress, TEXT("Expected request to already be in progress")); + break; + default: + // Something wrong happened, ignore all change requests + checkf(false, TEXT("Something unexpected happened while ACL is streaming")); + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + break; + } + } + + // Done + break; + case ACLVisualFidelity::Lowest: + // We have lowest fidelity + // Nothing is currently streamed in + + checkf(Request.Fidelity == ACLVisualFidelity::Medium, TEXT("Unexpected visual fidelity value")); + if (Request.Fidelity != ACLVisualFidelity::Medium) + { + // Something wrong happened, ignore all change requests + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + } + else + { + // To reach medium quality, we need to stream in our medium importance tier + Result = DatabaseContext.stream_in(acl::quality_tier::medium_importance, NumChunksToStream); + LogRequestResult(*this, Result); + + switch (Result) + { + case acl::database_stream_request_result::done: + // We are done + bIsRequestCompleted = true; + break; + case acl::database_stream_request_result::dispatched: + // Streaming request has been dispatched, done for now + Request.bIsInProgress = true; + break; + case acl::database_stream_request_result::streaming_in_progress: + // Streaming is already in progress, done for now + checkf(Request.bIsInProgress, TEXT("Expected request to already be in progress")); + break; + default: + // Something wrong happened, ignore all change requests + checkf(false, TEXT("Something unexpected happened while ACL is streaming")); + FailAllRequests(FidelityChangeRequests); + FidelityChangeRequests.Empty(); + break; + } + } + + // Done + break; + } + + // If we completed this request, consume it and loop again otherwise stop + if (bIsRequestCompleted) + { + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database is changing visual fidelity from %s to %s [%s]"), VisualFidelityToString(CurrentVisualFidelity), VisualFidelityToString(Request.Fidelity), *GetPathName()); + +#if WITH_EDITORONLY_DATA + // Make sure our preview fidelity matches what just changed + PreviewVisualFidelity = Request.Fidelity; +#endif + + CurrentVisualFidelity = Request.Fidelity; + if (Request.Result != nullptr) + { + *Request.Result = ACLVisualFidelityChangeResult::Completed; + } + + FidelityChangeRequests.RemoveAt(0); + } + else + { + break; + } + } + + const bool bHasMoreRequests = FidelityChangeRequests.Num() != 0; + if (!bHasMoreRequests) + { + // Clear our ticker handle, no longer needed + FidelityUpdateTickerHandle.Reset(); + } + + return bHasMoreRequests; // We need to fire again if we have more pending requests +} + +class FSetDatabaseVisualFidelityAction final : public FPendingLatentAction +{ +public: + FSetDatabaseVisualFidelityAction(UAnimationCompressionLibraryDatabase* DatabaseAsset_, ACLVisualFidelity VisualFidelity_, ACLVisualFidelityChangeResult& OutResult_, const FLatentActionInfo& LatentInfo_) + : DatabaseAsset(DatabaseAsset_) + , VisualFidelity(VisualFidelity_) + , OutResult(OutResult_) + , LatentInfo(LatentInfo_) + , bIsDispatched(false) + , RequestID(~0U) + { + } + + virtual void UpdateOperation(FLatentResponse& Response) override + { + // Must execute on the main thread + check(IsInGameThread()); + + if (DatabaseAsset == nullptr) + { + // No valid asset provided + OutResult = ACLVisualFidelityChangeResult::Failed; + Response.FinishAndTriggerIf(true, LatentInfo.ExecutionFunction, LatentInfo.Linkage, LatentInfo.CallbackTarget); + return; + } + + bool bDispatchedNow = false; + if (!bIsDispatched) + { + // Fire off our change request, will complete right away if no change is requested, otherwise we just dispatch + bIsDispatched = true; + bDispatchedNow = true; + OutResult = ACLVisualFidelityChangeResult::Dispatched; + RequestID = DatabaseAsset->SetVisualFidelityImpl(VisualFidelity, &OutResult); + } + + // We are done once our result is set + const bool bIsDone = OutResult != ACLVisualFidelityChangeResult::Dispatched; + if (bIsDone) + { + Response.FinishAndTriggerIf(bIsDone, LatentInfo.ExecutionFunction, LatentInfo.Linkage, LatentInfo.CallbackTarget); + } + else if (bDispatchedNow) + { + Response.TriggerLink(LatentInfo.ExecutionFunction, LatentInfo.Linkage, LatentInfo.CallbackTarget); + } + } + + virtual void NotifyObjectDestroyed() override + { + // Nothing we can do + } + + virtual void NotifyActionAborted() override + { + // Our request is being aborted, cancel what we can + if (bIsDispatched && RequestID != ~0U) + { + OutResult = ACLVisualFidelityChangeResult::Failed; + DatabaseAsset->CancelVisualFidelityRequestImpl(RequestID); + } + } + +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Updating ACL database visual fidelity"); + } +#endif + +private: + UAnimationCompressionLibraryDatabase* DatabaseAsset; + + ACLVisualFidelity VisualFidelity; + ACLVisualFidelityChangeResult& OutResult; + + FLatentActionInfo LatentInfo; + bool bIsDispatched; + uint32 RequestID; +}; + +void UAnimationCompressionLibraryDatabase::SetVisualFidelity(UObject* WorldContextObject, FLatentActionInfo LatentInfo, UAnimationCompressionLibraryDatabase* DatabaseAsset, ACLVisualFidelityChangeResult& Result, ACLVisualFidelity VisualFidelity) +{ + // Must execute on the main thread but must do so while animations aren't updating + check(IsInGameThread()); + + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) + { + FLatentActionManager& LatentManager = World->GetLatentActionManager(); + if (LatentManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr) + { + FSetDatabaseVisualFidelityAction* NewAction = new FSetDatabaseVisualFidelityAction(DatabaseAsset, VisualFidelity, Result, LatentInfo); + LatentManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction); + } + } +} + +ACLVisualFidelity UAnimationCompressionLibraryDatabase::GetVisualFidelity(UAnimationCompressionLibraryDatabase* DatabaseAsset) +{ + checkf(DatabaseAsset != nullptr, TEXT("Cannot query null ACL database asset")); + return DatabaseAsset != nullptr ? DatabaseAsset->CurrentVisualFidelity : ACLVisualFidelity::Lowest; +} diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/EditorDatabaseMonitor.cpp b/Plugins/ACLPlugin/Source/ACLPlugin/Private/EditorDatabaseMonitor.cpp new file mode 100644 index 00000000..a1c291e5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/EditorDatabaseMonitor.cpp @@ -0,0 +1,80 @@ +// Copyright 2021 Nicholas Frechette. All Rights Reserved. + +#if WITH_EDITORONLY_DATA +#include "EditorDatabaseMonitor.h" +#include "Containers/Ticker.h" +#include "UObject/WeakObjectPtrTemplates.h" + +namespace EditorDatabaseMonitor +{ + // Aliases to work with tickers +#if ENGINE_MAJOR_VERSION >= 5 + using FTickerType = FTSTicker; + using FTickerDelegateHandleType = FTSTicker::FDelegateHandle; +#else + using FTickerType = FTicker; + using FTickerDelegateHandleType = FDelegateHandle; +#endif + + static FTickerDelegateHandleType MonitorTickerHandle; + static TArray> DirtyDatabases; + static FCriticalSection DirtyDatabasesCS; + + static bool MonitorTicker(float DeltaTime) + { + // Copy our array with a quick swap to avoid holding the lock too long + TArray> DirtyDatabasesTmp; + { + FScopeLock Lock(&DirtyDatabasesCS); + Swap(DirtyDatabases, DirtyDatabasesTmp); + } + + // Iterate over our dirty databases and refresh any stale mappings we might have + for (TWeakObjectPtr& DatabasePtr : DirtyDatabasesTmp) + { + if (UAnimationCompressionLibraryDatabase* Database = DatabasePtr.Get()) + { + Database->UpdateReferencingAnimSequenceList(); + } + } + + const bool bFireTickerAgainAfterDelay = true; + return bFireTickerAgainAfterDelay; + } + + void RegisterMonitor() + { + if (MonitorTickerHandle.IsValid()) + { + return; // Already registered + } + + // Tick every 300ms + const float TickerDelay = 0.3F; + + MonitorTickerHandle = FTickerType::GetCoreTicker().AddTicker(TEXT("ACLEditorDatabaseMonitor"), TickerDelay, MonitorTicker); + } + + void UnregisterMonitor() + { + if (MonitorTickerHandle.IsValid()) + { + FTickerType::GetCoreTicker().RemoveTicker(MonitorTickerHandle); + MonitorTickerHandle.Reset(); + } + } + + void MarkDirty(UAnimationCompressionLibraryDatabase* Database) + { + if (Database == nullptr) + { + return; // Nothing to do + } + + // Add our database, we'll process it later + FScopeLock Lock(&DirtyDatabasesCS); + DirtyDatabases.AddUnique(Database); + } +} + +#endif diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/EditorDatabaseMonitor.h b/Plugins/ACLPlugin/Source/ACLPlugin/Private/EditorDatabaseMonitor.h new file mode 100644 index 00000000..c5fcf5fc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/EditorDatabaseMonitor.h @@ -0,0 +1,19 @@ +#pragma once + +// Copyright 2021 Nicholas Frechette. All Rights Reserved. + +#if WITH_EDITORONLY_DATA + +#include "CoreMinimal.h" +#include "AnimationCompressionLibraryDatabase.h" + +/** A central database monitor that ensures database instances have their mappings up to date. */ +namespace EditorDatabaseMonitor +{ + void RegisterMonitor(); + void UnregisterMonitor(); + + void MarkDirty(UAnimationCompressionLibraryDatabase* Database); +} + +#endif diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/UE4DatabasePreviewStreamer.h b/Plugins/ACLPlugin/Source/ACLPlugin/Private/UE4DatabasePreviewStreamer.h new file mode 100644 index 00000000..08c5220c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/UE4DatabasePreviewStreamer.h @@ -0,0 +1,57 @@ +#pragma once + +// Copyright 2021 Nicholas Frechette. All Rights Reserved. + +#include "AnimationCompression.h" +#include "CoreMinimal.h" +#include "HAL/UnrealMemory.h" + +#include "ACLImpl.h" + +#include +#include + + +/** A simple UE4 preview streamer. Everything is assumed to be in memory and no real streaming is done. */ +class UE4DatabasePreviewStreamer final : public acl::database_streamer +{ +public: + UE4DatabasePreviewStreamer(const acl::compressed_database& CompressedDatabase, const TArray& BulkData_) + : database_streamer(Requests, acl::k_num_database_tiers) + { + const uint32 BulkDataMediumSize = CompressedDatabase.get_bulk_data_size(acl::quality_tier::medium_importance); + + const uint8* BulkDataMedium = BulkData_.GetData(); + BulkData[0] = BulkDataMedium; + BulkData[1] = BulkDataMedium + BulkDataMediumSize; + } + + virtual bool is_initialized() const override { return true; } + + virtual const uint8_t* get_bulk_data(acl::quality_tier Tier) const override + { + checkf(Tier != acl::quality_tier::highest_importance, TEXT("Unexpected quality tier")); + const uint32 TierIndex = uint32(Tier) - 1; + return BulkData[TierIndex]; + } + + virtual void stream_in(uint32_t Offset, uint32_t Size, bool CanAllocateBulkData, acl::quality_tier Tier, acl::streaming_request_id RequestID) override + { + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database bulk data is streaming in!")); + complete(RequestID); + } + + virtual void stream_out(uint32_t Offset, uint32_t Size, bool CanDeallocateBulkData, acl::quality_tier Tier, acl::streaming_request_id RequestID) override + { + UE_LOG(LogAnimationCompression, Log, TEXT("ACL database bulk data is streaming out!")); + complete(RequestID); + } + +private: + UE4DatabasePreviewStreamer(const UE4DatabasePreviewStreamer&) = delete; + UE4DatabasePreviewStreamer& operator=(const UE4DatabasePreviewStreamer&) = delete; + + const uint8* BulkData[acl::k_num_database_tiers]; + + acl::streaming_request Requests[acl::k_num_database_tiers]; // One request per tier is enough +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Private/UE4DatabaseStreamer.h b/Plugins/ACLPlugin/Source/ACLPlugin/Private/UE4DatabaseStreamer.h new file mode 100644 index 00000000..930d97ab --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Private/UE4DatabaseStreamer.h @@ -0,0 +1,183 @@ +#pragma once + +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "AnimationCompression.h" +#include "CoreMinimal.h" +#include "HAL/UnrealMemory.h" + +#include "ACLImpl.h" + +#include + +// UE 4.25 doesn't expose its virtual memory management, see FPlatformMemory::FPlatformVirtualMemoryBlock +#define WITH_VMEM_MANAGEMENT 0 + +/** A simple async UE4 streamer. Memory is allocated on the first stream in request and deallocated on the last stream out request. */ +class UE4DatabaseStreamer final : public acl::database_streamer +{ +public: + UE4DatabaseStreamer(const acl::compressed_database& CompressedDatabase, FByteBulkData& StreamableBulkData_) + : database_streamer(Requests, acl::k_num_database_tiers) + , StreamableBulkData(StreamableBulkData_) + , PendingIORequest(nullptr) + { + BulkData[0] = BulkData[1] = nullptr; + + const uint32 BulkDataMediumSize = CompressedDatabase.get_bulk_data_size(acl::quality_tier::medium_importance); + const uint32 BulkDataLowSize = CompressedDatabase.get_bulk_data_size(acl::quality_tier::lowest_importance); + + BulkDataSize[0] = BulkDataMediumSize; + BulkDataSize[1] = BulkDataLowSize; + +#if WITH_VMEM_MANAGEMENT + // Allocate but don't commit the memory until we need it + // TODO: Commit right away if requested + + StreamedBulkDataBlock = FPlatformMemory::FPlatformVirtualMemoryBlock::AllocateVirtual(BulkDataTotalSize); + BulkDataPtr = static_cast(StreamedBulkDataBlock.GetVirtualPointer()); + bIsBulkDataCommited = false; +#endif + } + + virtual ~UE4DatabaseStreamer() + { + // If we have a stream in request, wait for it to complete and clear it + WaitForStreamingToComplete(); + +#if WITH_VMEM_MANAGEMENT + StreamedBulkDataBlock.FreeVirtual(); +#else + delete[] BulkData[0]; + delete[] BulkData[1]; +#endif + } + + virtual bool is_initialized() const override { return true; } + + virtual const uint8_t* get_bulk_data(acl::quality_tier Tier) const override + { + checkf(Tier != acl::quality_tier::highest_importance, TEXT("Unexpected quality tier")); + const uint32 TierIndex = uint32(Tier) - 1; + return BulkData[TierIndex]; + } + + virtual void stream_in(uint32_t Offset, uint32_t Size, bool CanAllocateBulkData, acl::quality_tier Tier, acl::streaming_request_id RequestID) override + { + const uint32 TierIndex = uint32(Tier) - 1; + const uint32 TierBulkDataSize = BulkDataSize[TierIndex]; + + checkf(Offset < TierBulkDataSize, TEXT("Steam offset is outside of the bulk data range")); + checkf(Size <= TierBulkDataSize, TEXT("Stream size is larger than the bulk data size")); + checkf(uint64(Offset) + uint64(Size) <= uint64(TierBulkDataSize), TEXT("Streaming request is outside of the bulk data range")); + checkf(Tier != acl::quality_tier::highest_importance, TEXT("Unexpected quality tier")); + + // If we already did a stream in request, wait for it to complete and clear it + WaitForStreamingToComplete(); + + FBulkDataIORequestCallBack AsyncFileCallBack = [this, RequestID](bool bWasCancelled, IBulkDataIORequest* Req) + { + UE_LOG(LogAnimationCompression, Log, TEXT("ACL completed the stream in request!")); + + // Tell ACL whether the streaming request was a success or not, this is thread safe + if (bWasCancelled) + this->cancel(RequestID); + else + this->complete(RequestID); + }; + + UE_LOG(LogAnimationCompression, Log, TEXT("ACL starting a new stream in request!")); + + // Allocate our bulk data buffer on the first stream in request + if (CanAllocateBulkData) + { + UE_LOG(LogAnimationCompression, Log, TEXT("ACL is allocating the database bulk data!")); + +#if WITH_VMEM_MANAGEMENT + check(!bIsBulkDataCommited); + StreamedBulkDataBlock.Commit(); + bIsBulkDataCommited = true; +#else + check(BulkData[TierIndex] == nullptr); + BulkData[TierIndex] = new uint8[TierBulkDataSize]; +#endif + } + + // Fire off our async streaming request + const uint32 BulkDataTierStartOffset = Tier == acl::quality_tier::medium_importance ? 0 : BulkDataSize[0]; + const uint64 BulkDataReadOffset = BulkDataTierStartOffset + Offset; + + uint8* StreamedBulkDataPtr = BulkData[TierIndex]; + uint8* DestBulkDataPtr = StreamedBulkDataPtr + Offset; + + PendingIORequest = StreamableBulkData.CreateStreamingRequest(BulkDataReadOffset, Size, AIOP_Low, &AsyncFileCallBack, DestBulkDataPtr); + if (PendingIORequest == nullptr) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("ACL failed to initiate database stream in request!")); + cancel(RequestID); + } + } + + virtual void stream_out(uint32_t Offset, uint32_t Size, bool CanDeallocateBulkData, acl::quality_tier Tier, acl::streaming_request_id RequestID) override + { + const uint32 TierIndex = uint32(Tier) - 1; + const uint32 TierBulkDataSize = BulkDataSize[TierIndex]; + + checkf(Offset < TierBulkDataSize, TEXT("Steam offset is outside of the bulk data range")); + checkf(Size <= TierBulkDataSize, TEXT("Stream size is larger than the bulk data size")); + checkf(uint64(Offset) + uint64(Size) <= uint64(TierBulkDataSize), TEXT("Streaming request is outside of the bulk data range")); + checkf(Tier != acl::quality_tier::highest_importance, TEXT("Unexpected quality tier")); + + // If we already did a stream in request, wait for it to complete and clear it + WaitForStreamingToComplete(); + + UE_LOG(LogAnimationCompression, Log, TEXT("ACL is streaming out a database!")); + + // Free our bulk data on the last stream out request + if (CanDeallocateBulkData) + { + // TODO: Make this optional? + UE_LOG(LogAnimationCompression, Log, TEXT("ACL is deallocating the database bulk data!")); + +#if WITH_VMEM_MANAGEMENT + check(bIsBulkDataCommited); + StreamedBulkDataBlock.Decommit(); + bIsBulkDataCommited = false; +#else + check(BulkData[TierIndex] != nullptr); + delete[] BulkData[TierIndex]; + BulkData[TierIndex] = nullptr; +#endif + } + + // Notify ACL that we streamed out the data, this is not thread safe and cannot run while animations are decompressing + complete(RequestID); + } + + void WaitForStreamingToComplete() + { + if (PendingIORequest != nullptr) + { + verify(PendingIORequest->WaitCompletion()); + delete PendingIORequest; + PendingIORequest = nullptr; + } + } + +private: + UE4DatabaseStreamer(const UE4DatabaseStreamer&) = delete; + UE4DatabaseStreamer& operator=(const UE4DatabaseStreamer&) = delete; + + FByteBulkData& StreamableBulkData; + uint8* BulkData[acl::k_num_database_tiers]; + uint32 BulkDataSize[acl::k_num_database_tiers]; + + IBulkDataIORequest* PendingIORequest; + + acl::streaming_request Requests[acl::k_num_database_tiers]; // One request per tier is enough + +#if WITH_VMEM_MANAGEMENT + FPlatformMemory::FPlatformVirtualMemoryBlock StreamedBulkDataBlock; + bool bIsBulkDataCommited; +#endif +}; diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Public/ACLImpl.h b/Plugins/ACLPlugin/Source/ACLPlugin/Public/ACLImpl.h new file mode 100644 index 00000000..7e186e77 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Public/ACLImpl.h @@ -0,0 +1,183 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Runtime/Launch/Resources/Version.h" + +#if DO_GUARD_SLOW + // ACL has a lot of asserts, only enabled in Debug + // This decision has been made because the extensive asserts add considerable overhead and most + // developers use a Development configuration for the editor. The ACL library runs extensive + // unit and regression tests on a very large number of clips which minimizes the risk of + // having a legitimate assert fire. + #define ACL_ON_ASSERT_CUSTOM + #define ACL_ASSERT(expression, format, ...) checkf(expression, TEXT(format), #__VA_ARGS__) +#endif + +// Enable popcount usage on PS4. +// We cannot use PLATFORM_ENABLE_POPCNT_INTRINSIC as that might be unsafe. +// UE uses that macro to determine if the GCC intrinsic is present, not if the instruction +// is supported. +// UE 5.1 removed the PS4 define and we can no longer rely on it +#if (ENGINE_MAJOR_VERSION <= 5 && ENGINE_MINOR_VERSION <= 0) && PLATFORM_PS4 + // Enable usage of popcount instruction + #define ACL_USE_POPCOUNT +#endif + +#include +#include +#include + +#include +#include +#include + +/** The ACL allocator implementation simply forwards to the default heap allocator. */ +class ACLAllocator final : public acl::iallocator +{ +public: + virtual void* allocate(size_t size, size_t alignment = acl::iallocator::k_default_alignment) + { + return GMalloc->Malloc(size, alignment); + } + + virtual void deallocate(void* ptr, size_t size) + { + GMalloc->Free(ptr); + } +}; + +extern ACLPLUGIN_API ACLAllocator ACLAllocatorImpl; + +/** RTM <-> UE conversion utilities and aliases. */ +#if ENGINE_MAJOR_VERSION >= 5 +inline FQuat RTM_SIMD_CALL UEQuatCast(const FQuat4f& Input) { return FQuat(Input.X, Input.Y, Input.Z, Input.W); } +inline FVector RTM_SIMD_CALL UEVector3Cast(const FVector3f& Input) { return FVector(Input.X, Input.Y, Input.Z); } +inline FVector RTM_SIMD_CALL UEVector3Cast(const FVector3d& Input) { return FVector(Input.X, Input.Y, Input.Z); } + +inline rtm::vector4f RTM_SIMD_CALL UEVector3ToACL(const FVector3f& Input) { return rtm::vector_set(Input.X, Input.Y, Input.Z); } +inline rtm::vector4f RTM_SIMD_CALL UEVector3ToACL(const FVector& Input) { return rtm::vector_cast(rtm::vector_set(Input.X, Input.Y, Input.Z)); } +inline FVector3f RTM_SIMD_CALL ACLVector3ToUE(rtm::vector4f_arg0 Input) { return FVector3f(rtm::vector_get_x(Input), rtm::vector_get_y(Input), rtm::vector_get_z(Input)); } + +inline rtm::quatf RTM_SIMD_CALL UEQuatToACL(const FQuat4f& Input) { return rtm::quat_set(Input.X, Input.Y, Input.Z, Input.W); } +inline rtm::quatf RTM_SIMD_CALL UEQuatToACL(const FQuat& Input) { return rtm::quat_cast(rtm::quat_set(Input.X, Input.Y, Input.Z, Input.W)); } +inline FQuat4f RTM_SIMD_CALL ACLQuatToUE(rtm::quatf_arg0 Input) { return FQuat4f(rtm::quat_get_x(Input), rtm::quat_get_y(Input), rtm::quat_get_z(Input), rtm::quat_get_w(Input)); } + +inline FTransform RTM_SIMD_CALL ACLTransformToUE(rtm::qvvf_arg0 Input) { return FTransform(UEQuatCast(ACLQuatToUE(Input.rotation)), UEVector3Cast(ACLVector3ToUE(Input.translation)), UEVector3Cast(ACLVector3ToUE(Input.scale))); } + +using FRawAnimTrackQuat = FQuat4f; +using FRawAnimTrackVector3 = FVector3f; +#else +inline FQuat RTM_SIMD_CALL UEQuatCast(const FQuat& Input) { return Input; } +inline FVector RTM_SIMD_CALL UEVector3Cast(const FVector& Input) { return Input; } + +inline rtm::vector4f RTM_SIMD_CALL UEVector3ToACL(const FVector& Input) { return rtm::vector_set(Input.X, Input.Y, Input.Z); } +inline FVector RTM_SIMD_CALL ACLVector3ToUE(rtm::vector4f_arg0 Input) { return FVector(rtm::vector_get_x(Input), rtm::vector_get_y(Input), rtm::vector_get_z(Input)); } + +inline rtm::quatf RTM_SIMD_CALL UEQuatToACL(const FQuat& Input) { return rtm::quat_set(Input.X, Input.Y, Input.Z, Input.W); } +inline FQuat RTM_SIMD_CALL ACLQuatToUE(rtm::quatf_arg0 Input) { return FQuat(rtm::quat_get_x(Input), rtm::quat_get_y(Input), rtm::quat_get_z(Input), rtm::quat_get_w(Input)); } + +inline FTransform RTM_SIMD_CALL ACLTransformToUE(rtm::qvvf_arg0 Input) { return FTransform(ACLQuatToUE(Input.rotation), ACLVector3ToUE(Input.translation), ACLVector3ToUE(Input.scale)); } + +using FRawAnimTrackQuat = FQuat; +using FRawAnimTrackVector3 = FVector; +#endif + +/** The decompression settings used by ACL */ +struct UE4DefaultDecompressionSettings : public acl::default_transform_decompression_settings +{ + // Only support our latest version + static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::latest; } + +#if UE_BUILD_SHIPPING + // Shipping builds do not need safety checks, by then the game has been tested enough + // Only data corruption could cause a safety check to fail + // We keep this disabled regardless because it is generally better to output a T-pose than to have a + // potential crash. Corruption can happen and it would be unfortunate if a demo or playtest failed + // as a result of a crash that we can otherwise recover from. + //static constexpr bool skip_initialize_safety_checks() { return true; } +#endif +}; + +struct UE4DebugDecompressionSettings : public acl::debug_transform_decompression_settings +{ + // Only support our latest version + static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::latest; } +}; + +// Same as debug settings for now since everything is allowed +using UE4CustomDecompressionSettings = UE4DebugDecompressionSettings; + +struct UE4SafeDecompressionSettings final : public UE4DefaultDecompressionSettings +{ + static constexpr bool is_rotation_format_supported(acl::rotation_format8 format) { return format == acl::rotation_format8::quatf_full; } + static constexpr acl::rotation_format8 get_rotation_format(acl::rotation_format8 /*format*/) { return acl::rotation_format8::quatf_full; } +}; + +struct UE4DefaultDatabaseSettings final : public acl::default_database_settings +{ + // Only support our latest version + static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::latest; } +}; + +struct UE4DefaultDBDecompressionSettings final : public UE4DefaultDecompressionSettings +{ + using database_settings_type = UE4DefaultDatabaseSettings; +}; + +using UE4DebugDatabaseSettings = acl::debug_database_settings; + +struct UE4DebugDBDecompressionSettings final : public UE4DebugDecompressionSettings +{ + using database_settings_type = UE4DebugDatabaseSettings; +}; + +/** UE4 equivalents for some ACL enums */ +/** An enum for ACL rotation formats. */ +UENUM() +enum ACLRotationFormat +{ + ACLRF_Quat_128 UMETA(DisplayName = "Quat Full Bit Rate"), + ACLRF_QuatDropW_96 UMETA(DisplayName = "Quat Drop W Full Bit Rate"), + ACLRF_QuatDropW_Variable UMETA(DisplayName = "Quat Drop W Variable Bit Rate"), +}; + +/** An enum for ACL Vector3 formats. */ +UENUM() +enum ACLVectorFormat +{ + ACLVF_Vector3_96 UMETA(DisplayName = "Vector3 Full Bit Rate"), + ACLVF_Vector3_Variable UMETA(DisplayName = "Vector3 Variable Bit Rate"), +}; + +/** An enum for ACL compression levels. */ +UENUM() +enum ACLCompressionLevel +{ + ACLCL_Lowest UMETA(DisplayName = "Lowest"), + ACLCL_Low UMETA(DisplayName = "Low"), + ACLCL_Medium UMETA(DisplayName = "Medium"), + ACLCL_High UMETA(DisplayName = "High"), + ACLCL_Highest UMETA(DisplayName = "Highest"), +}; + +/** Editor only utilities */ +#if WITH_EDITOR +#include +#include + +struct FCompressibleAnimData; +class UAnimSequence; + +ACLPLUGIN_API acl::rotation_format8 GetRotationFormat(ACLRotationFormat Format); +ACLPLUGIN_API acl::vector_format8 GetVectorFormat(ACLVectorFormat Format); +ACLPLUGIN_API acl::compression_level8 GetCompressionLevel(ACLCompressionLevel Level); + +ACLPLUGIN_API acl::track_array_qvvf BuildACLTransformTrackArray(ACLAllocator& AllocatorImpl, const FCompressibleAnimData& CompressibleAnimData, float DefaultVirtualVertexDistance, float SafeVirtualVertexDistance, bool bBuildAdditiveBase); + +/** Compatibility utilities */ +ACLPLUGIN_API uint32 GetNumSamples(const FCompressibleAnimData& CompressibleAnimData); +ACLPLUGIN_API float GetSequenceLength(const UAnimSequence& AnimSeq); +#endif // WITH_EDITOR diff --git a/Plugins/ACLPlugin/Source/ACLPlugin/Public/IACLPluginModule.h b/Plugins/ACLPlugin/Source/ACLPlugin/Public/IACLPluginModule.h new file mode 100644 index 00000000..2fea95e4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPlugin/Public/IACLPluginModule.h @@ -0,0 +1,22 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +/** The main ACL plugin module interface. */ +class IACLPlugin : public IModuleInterface +{ +public: + static inline IACLPlugin& Get() + { + return FModuleManager::LoadModuleChecked("ACLPlugin"); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ACLPlugin"); + } +}; diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/ACLPluginEditor.Build.cs b/Plugins/ACLPlugin/Source/ACLPluginEditor/ACLPluginEditor.Build.cs new file mode 100644 index 00000000..fb1c1515 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/ACLPluginEditor.Build.cs @@ -0,0 +1,35 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class ACLPluginEditor : ModuleRules + { + public ACLPluginEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //OptimizeCode = CodeOptimization.Never; + //bUseUnity = false; + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + + PrivateIncludePaths.Add("ACLPluginEditor/Private"); + + PublicDependencyModuleNames.Add("ACLPlugin"); + PublicDependencyModuleNames.Add("Core"); + PublicDependencyModuleNames.Add("CoreUObject"); + PublicDependencyModuleNames.Add("Engine"); + + if (Target.Version.MajorVersion >= 5) + { + PublicDependencyModuleNames.Add("AnimationDataController"); + } + + PrivateDependencyModuleNames.Add("EditorStyle"); + PrivateDependencyModuleNames.Add("Slate"); + PrivateDependencyModuleNames.Add("SlateCore"); + PrivateDependencyModuleNames.Add("UnrealEd"); + } + } +} diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/ACLDatabaseBuildCommandlet.h b/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/ACLDatabaseBuildCommandlet.h new file mode 100644 index 00000000..7311ffdc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/ACLDatabaseBuildCommandlet.h @@ -0,0 +1,18 @@ +#pragma once + +// Copyright 2021 Nicholas Frechette. All Rights Reserved. + +#include "Commandlets/Commandlet.h" +#include "ACLDatabaseBuildCommandlet.generated.h" + +/* + * This commandlet is used to update instances of UAnimationCompressionLibraryDatabase to ensure their mapping is up-to-date. + */ +UCLASS() +class UACLDatabaseBuildCommandlet : public UCommandlet +{ + GENERATED_UCLASS_BODY() + +public: + virtual int32 Main(const FString& Params) override; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/ACLStatsDumpCommandlet.h b/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/ACLStatsDumpCommandlet.h new file mode 100644 index 00000000..1182a9b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/ACLStatsDumpCommandlet.h @@ -0,0 +1,43 @@ +#pragma once + +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "Commandlets/Commandlet.h" +#include "ACLStatsDumpCommandlet.generated.h" + +/* + * This commandlet is used to extract and dump animation compression statistics. + * + * It supports the following arguments: -acl= -stats= -MasterTolerance= + * + * acl: This is the path to the input directory that contains the ACL SJSON animation clips. + * stats: This is the path to the output directory that will contain the extracted SJSON statistics. + * MasterTolerance: This is the master tolerance used by the UE4 Automatic compression algorithm. Defaults to 0.1cm. + */ +UCLASS() +class UACLStatsDumpCommandlet : public UCommandlet +{ + GENERATED_UCLASS_BODY() + +public: + virtual int32 Main(const FString& Params) override; + + FString ACLRawDir; + FString OutputDir; + + bool PerformExhaustiveDump; + bool PerformCompression; + bool PerformClipExtraction; + bool TryAutomaticCompression; + bool TryACLCompression; + bool TryKeyReductionRetarget; + bool TryKeyReduction; + bool ResumeTask; + bool SkipAdditiveClips; + + class UAnimBoneCompressionSettings* AutoCompressionSettings; + class UAnimBoneCompressionSettings* ACLCompressionSettings; + class UAnimBoneCompressionSettings* KeyReductionCompressionSettings; + class UAnimBoneCompressionCodec_ACL* ACLCodec; + class UAnimCompress_RemoveLinearKeys* KeyReductionCodec; +}; diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/AnimationCompressionLibraryDatabaseFactory.h b/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/AnimationCompressionLibraryDatabaseFactory.h new file mode 100644 index 00000000..d2e062f6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Classes/AnimationCompressionLibraryDatabaseFactory.h @@ -0,0 +1,17 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AnimationCompressionLibraryDatabaseFactory.generated.h" + +UCLASS(HideCategories = Object, MinimalAPI) +class UAnimationCompressionLibraryDatabaseFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLDatabaseBuildCommandlet.cpp b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLDatabaseBuildCommandlet.cpp new file mode 100644 index 00000000..19714f81 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLDatabaseBuildCommandlet.cpp @@ -0,0 +1,185 @@ +// Copyright 2021 Nicholas Frechette. All Rights Reserved. + +#include "ACLDatabaseBuildCommandlet.h" + +#include "AnimationCompressionLibraryDatabase.h" + +#include "AnimationCompression.h" +#include "AnimationUtils.h" +#include "FileHelpers.h" +#include "ISourceControlModule.h" +#include "SourceControlOperations.h" + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +#include "AssetRegistry/AssetRegistryModule.h" +#else +#include "AssetRegistryModule.h" +#endif + + +////////////////////////////////////////////////////////////////////////// +// Commandlet example inspired by: https://github.com/ue4plugins/CommandletPlugin +// To run the commandlet, add to the commandline: "$(SolutionDir)$(ProjectName).uproject" -run=/Script/ACLPluginEditor.ACLDatabaseBuild + +////////////////////////////////////////////////////////////////////////// + +UACLDatabaseBuildCommandlet::UACLDatabaseBuildCommandlet(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + IsClient = false; + IsServer = false; + IsEditor = true; + LogToConsole = true; + ShowErrorCount = true; +} + +int32 UACLDatabaseBuildCommandlet::Main(const FString& Params) +{ + TArray Tokens; + TArray Switches; + TMap ParamsMap; + UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamsMap); + + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + + TArray DatabaseAssets; + { + UE_LOG(LogAnimationCompression, Log, TEXT("Retrieving all ACL databases from current project ...")); + + FARFilter DatabaseFilter; + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + DatabaseFilter.ClassPaths.Add(UAnimationCompressionLibraryDatabase::StaticClass()->GetClassPathName()); +#else + DatabaseFilter.ClassNames.Add(UAnimationCompressionLibraryDatabase::StaticClass()->GetFName()); +#endif + + AssetRegistryModule.Get().GetAssets(DatabaseFilter, DatabaseAssets); + } + + if (DatabaseAssets.Num() == 0) + { + UE_LOG(LogAnimationCompression, Log, TEXT("Failed to find any ACL databases, done")); + return 0; + } + + TArray AnimSequenceAssets; + { + UE_LOG(LogAnimationCompression, Log, TEXT("Retrieving all animation sequences from current project ...")); + + FARFilter AnimSequenceFilter; + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + AnimSequenceFilter.ClassPaths.Add(UAnimSequence::StaticClass()->GetClassPathName()); +#else + AnimSequenceFilter.ClassNames.Add(UAnimSequence::StaticClass()->GetFName()); +#endif + + AssetRegistryModule.Get().GetAssets(AnimSequenceFilter, AnimSequenceAssets); + } + + if (AnimSequenceAssets.Num() == 0) + { + UE_LOG(LogAnimationCompression, Log, TEXT("Failed to find any animation sequences, done")); + return 0; + } + + { + UE_LOG(LogAnimationCompression, Log, TEXT("Loading %u animation sequences ..."), AnimSequenceAssets.Num()); + for (const FAssetData& Asset : AnimSequenceAssets) + { + UAnimSequence* AnimSeq = Cast(Asset.GetAsset()); + if (AnimSeq == nullptr) + { + UE_LOG(LogAnimationCompression, Log, TEXT("Failed to load animation sequence: %s"), *Asset.PackagePath.ToString()); + continue; + } + + // Make sure all our required dependencies are loaded + FAnimationUtils::EnsureAnimSequenceLoaded(*AnimSeq); + } + } + + { + UE_LOG(LogAnimationCompression, Log, TEXT("Loading %u ACL databases ..."), DatabaseAssets.Num()); + for (const FAssetData& Asset : DatabaseAssets) + { + UAnimationCompressionLibraryDatabase* Database = Cast(Asset.GetAsset()); + if (Database == nullptr) + { + UE_LOG(LogAnimationCompression, Log, TEXT("Failed to load ACL database: %s"), *Asset.PackagePath.ToString()); + continue; + } + } + } + + TArray DirtyDatabasePackages; + { + for (const FAssetData& Asset : DatabaseAssets) + { + UE_LOG(LogAnimationCompression, Log, TEXT("Building mapping for ACL database: %s ..."), *Asset.PackagePath.ToString()); + + UAnimationCompressionLibraryDatabase* Database = Cast(Asset.GetAsset()); + if (Database == nullptr) + { + continue; + } + + const bool bIsDirty = Database->UpdateReferencingAnimSequenceList(); + if (bIsDirty) + { + UE_LOG(LogAnimationCompression, Log, TEXT(" Mapping updated!")); + DirtyDatabasePackages.Add(Asset.GetPackage()); + } + } + } + + bool bFailedToSave = false; + { + if (ISourceControlModule::Get().IsEnabled()) + { + ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); + + TArray PackagesToSave; + for (UPackage* Package : DirtyDatabasePackages) + { + FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Package, EStateCacheUsage::Use); + if (SourceControlState->IsCheckedOutOther()) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("Package %s is already checked out by someone, will not check out"), *SourceControlState->GetFilename()); + } + else if (!SourceControlState->IsCurrent()) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("Package %s is not at head, will not check out"), *SourceControlState->GetFilename()); + } + else if (SourceControlState->CanCheckout()) + { + const ECommandResult::Type StatusResult = SourceControlProvider.Execute(ISourceControlOperation::Create(), Package); + if (StatusResult != ECommandResult::Succeeded) + { + UE_LOG(LogAnimationCompression, Log, TEXT("Package %s failed to check out"), *SourceControlState->GetFilename()); + bFailedToSave = true; + } + else + { + PackagesToSave.Add(Package); + } + } + else if (!SourceControlState->IsSourceControlled() || SourceControlState->CanEdit()) + { + PackagesToSave.Add(Package); + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); + ISourceControlModule::Get().QueueStatusUpdate(PackagesToSave); + } + else + { + // No source control, just try to save what we have + UEditorLoadingAndSavingUtils::SavePackages(DirtyDatabasePackages, true); + } + } + + return bFailedToSave ? 1 : 0; +} diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLPluginEditorModule.cpp b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLPluginEditorModule.cpp new file mode 100644 index 00000000..080d6b49 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLPluginEditorModule.cpp @@ -0,0 +1,61 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "IACLPluginEditorModule.h" +#include "Modules/ModuleManager.h" + +#include "AssetToolsModule.h" + +#include "AssetTypeActions_AnimationCompressionLibraryDatabase.h" + +class FACLPluginEditor final : public IACLPluginEditor +{ +private: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + void OnPostEngineInit(); + void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action); + + TArray> RegisteredAssetTypeActions; +}; + +IMPLEMENT_MODULE(FACLPluginEditor, ACLPluginEditor) + +////////////////////////////////////////////////////////////////////////// + +void FACLPluginEditor::StartupModule() +{ + FCoreDelegates::OnPostEngineInit.AddRaw(this, &FACLPluginEditor::OnPostEngineInit); +} + +void FACLPluginEditor::ShutdownModule() +{ + FCoreDelegates::OnPostEngineInit.RemoveAll(this); + + // Unregister our asset types + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); + for (int32 Index = 0; Index < RegisteredAssetTypeActions.Num(); ++Index) + { + AssetTools.UnregisterAssetTypeActions(RegisteredAssetTypeActions[Index].ToSharedRef()); + } + } + RegisteredAssetTypeActions.Empty(); +} + +void FACLPluginEditor::OnPostEngineInit() +{ + // Register our asset types + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_AnimationCompressionLibraryDatabase)); +} + +void FACLPluginEditor::RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + RegisteredAssetTypeActions.Add(Action); +} diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLStatsDumpCommandlet.cpp b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLStatsDumpCommandlet.cpp new file mode 100644 index 00000000..49ad7d41 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/ACLStatsDumpCommandlet.cpp @@ -0,0 +1,1522 @@ +// Copyright 2018 Nicholas Frechette. All Rights Reserved. + +#include "ACLStatsDumpCommandlet.h" + +#include "Runtime/Core/Public/HAL/FileManagerGeneric.h" +#include "Runtime/Core/Public/HAL/PlatformTime.h" +#include "Runtime/CoreUObject/Public/UObject/UObjectIterator.h" +#include "Runtime/Engine/Classes/Animation/AnimBoneCompressionSettings.h" +#include "Runtime/Engine/Classes/Animation/AnimCompress.h" +#include "Runtime/Engine/Classes/Animation/AnimCompress_RemoveLinearKeys.h" +#include "Runtime/Engine/Classes/Animation/Skeleton.h" +#include "Runtime/Engine/Public/AnimationCompression.h" +#include "Editor/UnrealEd/Public/PackageHelperFunctions.h" + +#if ENGINE_MAJOR_VERSION >= 5 +#include "AnimDataController.h" +#endif + +#include "AnimBoneCompressionCodec_ACL.h" +#include "ACLImpl.h" + +#include +#include + +#include // For create_output_track_mapping(..) +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// +// Commandlet example inspired by: https://github.com/ue4plugins/CommandletPlugin +// To run the commandlet, add to the commandline: "$(SolutionDir)$(ProjectName).uproject" -run=/Script/ACLPluginEditor.ACLStatsDump "-input=" "-output=" -compress +// +// Usage: +// -input=: If present all *acl.sjson files will be used as the input for the commandlet otherwise the current project is used +// -output=: The commandlet output will be written at the given path (stats or dumped clips) +// -compress: Commandlet will compress the input clips and output stats +// -extract: Commandlet will extract the input clips into output *acl.sjson clips +// -error: Enables the exhaustive error dumping +// -auto: Uses automatic compression +// -acl: Uses ACL compression +// -MasterTolerance=: The error threshold used by automatic compression +// -resume: If present, clip extraction or compression will continue where it left off +////////////////////////////////////////////////////////////////////////// + +class UE4SJSONStreamWriter final : public sjson::StreamWriter +{ +public: + UE4SJSONStreamWriter(FArchive* File_) + : File(File_) + {} + + virtual void write(const void* Buffer, size_t BufferSize) override + { + File->Serialize(const_cast(Buffer), BufferSize); + } + +private: + FArchive* File; +}; + +static const TCHAR* ReadACLClip(FFileManagerGeneric& FileManager, const FString& ACLClipPath, acl::iallocator& Allocator, acl::track_array_qvvf& OutTracks) +{ + FArchive* Reader = FileManager.CreateFileReader(*ACLClipPath); + const int64 Size = Reader->TotalSize(); + + // Allocate directly without a TArray to automatically manage the memory because some + // clips are larger than 2 GB + char* RawData = static_cast(GMalloc->Malloc(Size)); + + Reader->Serialize(RawData, Size); + Reader->Close(); + + if (ACLClipPath.EndsWith(TEXT(".acl"))) + { + acl::compressed_tracks* CompressedTracks = reinterpret_cast(RawData); + if (Size != CompressedTracks->get_size() || CompressedTracks->is_valid(true).any()) + { + GMalloc->Free(RawData); + return TEXT("Invalid binary ACL file provided"); + } + + const acl::error_result Result = acl::convert_track_list(Allocator, *CompressedTracks, OutTracks); + if (Result.any()) + { + GMalloc->Free(RawData); + return TEXT("Failed to convert input binary track list"); + } + } + else + { + acl::clip_reader ClipReader(Allocator, RawData, Size); + + if (ClipReader.get_file_type() != acl::sjson_file_type::raw_clip) + { + GMalloc->Free(RawData); + return TEXT("SJSON file isn't a raw clip"); + } + + acl::sjson_raw_clip RawClip; + if (!ClipReader.read_raw_clip(RawClip)) + { + GMalloc->Free(RawData); + return TEXT("Failed to read ACL raw clip from file"); + } + + OutTracks = MoveTemp(RawClip.track_list); + } + + GMalloc->Free(RawData); + return nullptr; +} + +static void ConvertSkeleton(const acl::track_array_qvvf& Tracks, USkeleton* UE4Skeleton) +{ + // Not terribly clean, we cast away the 'const' to modify the skeleton + FReferenceSkeleton& RefSkeleton = const_cast(UE4Skeleton->GetReferenceSkeleton()); + FReferenceSkeletonModifier SkeletonModifier(RefSkeleton, UE4Skeleton); + + for (const acl::track_qvvf& Track : Tracks) + { + const acl::track_desc_transformf& Desc = Track.get_description(); + + const FString BoneName = ANSI_TO_TCHAR(Track.get_name().c_str()); + + FMeshBoneInfo UE4Bone; + UE4Bone.Name = FName(*BoneName); + UE4Bone.ParentIndex = Desc.parent_index == acl::k_invalid_track_index ? INDEX_NONE : Desc.parent_index; + UE4Bone.ExportName = BoneName; + + SkeletonModifier.Add(UE4Bone, FTransform::Identity); + } + + // When our modifier is destroyed here, it will rebuild the skeleton +} + +static void ConvertClip(const acl::track_array_qvvf& Tracks, UAnimSequence* UE4Clip, USkeleton* UE4Skeleton) +{ + const int32 NumSamples = Tracks.get_num_samples_per_track(); + const float SequenceLength = FGenericPlatformMath::Max(Tracks.get_finite_duration(), MINIMUM_ANIMATION_LENGTH); + +#if ENGINE_MAJOR_VERSION >= 5 + const float SampleRate = Tracks.get_sample_rate(); + + // This is incorrect because the true sample rate can be fractional but UE doesn't support it + const uint32 FrameRate = FGenericPlatformMath::RoundToInt(SampleRate); + + IAnimationDataController& UE4ClipController = UE4Clip->GetController(); + UE4ClipController.SetFrameRate(FFrameRate(FrameRate, 1)); + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 2) + UE4ClipController.SetNumberOfFrames(FFrameNumber(NumSamples)); +#else + UE4ClipController.SetPlayLength(SequenceLength); +#endif + + // Ensure our frame rate update propagates first to avoid re-sampling below + UE4ClipController.NotifyPopulated(); + + UE4ClipController.OpenBracket(FText::FromString("Generating Animation Data")); + UE4ClipController.RemoveAllBoneTracks(); +#else + UE4Clip->SequenceLength = SequenceLength; + UE4Clip->SetRawNumberOfFrame(NumSamples); +#endif + + UE4Clip->SetSkeleton(UE4Skeleton); + + if (NumSamples != 0) + { + const uint32 NumBones = Tracks.get_num_tracks(); + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + + FRawAnimSequenceTrack RawTrack; + RawTrack.PosKeys.Empty(); + RawTrack.RotKeys.Empty(); + RawTrack.ScaleKeys.Empty(); + + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const FRawAnimTrackQuat Rotation = ACLQuatToUE(rtm::quat_normalize(Track[SampleIndex].rotation)); + RawTrack.RotKeys.Add(Rotation); + } + + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const FRawAnimTrackVector3 Translation = ACLVector3ToUE(Track[SampleIndex].translation); + RawTrack.PosKeys.Add(Translation); + } + + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const FRawAnimTrackVector3 Scale = ACLVector3ToUE(Track[SampleIndex].scale); + RawTrack.ScaleKeys.Add(Scale); + } + + const FName BoneName(Track.get_name().c_str()); + +#if ENGINE_MAJOR_VERSION >= 5 + UE4ClipController.AddBoneTrack(BoneName); + UE4ClipController.SetBoneTrackKeys(BoneName, RawTrack.PosKeys, RawTrack.RotKeys, RawTrack.ScaleKeys); +#else + UE4Clip->AddNewRawTrack(BoneName, &RawTrack); +#endif + } + } + +#if ENGINE_MAJOR_VERSION >= 5 + UE4ClipController.NotifyPopulated(); + UE4ClipController.CloseBracket(); +#else + UE4Clip->MarkRawDataAsModified(); + UE4Clip->PostProcessSequence(); +#endif +} + +static int32 GetAnimationTrackIndex(const int32 BoneIndex, const UAnimSequence* AnimSeq) +{ + if (BoneIndex == INDEX_NONE) + { + return INDEX_NONE; + } + + const TArray& TrackToSkelMap = AnimSeq->CompressedData.CompressedTrackToSkeletonMapTable; + for (int32 TrackIndex = 0; TrackIndex < TrackToSkelMap.Num(); ++TrackIndex) + { + const FTrackToSkeletonMap& TrackToSkeleton = TrackToSkelMap[TrackIndex]; + if (TrackToSkeleton.BoneTreeIndex == BoneIndex) + { + return TrackIndex; + } + } + + return INDEX_NONE; +} + +static void SampleUE4Clip(const acl::track_array_qvvf& Tracks, USkeleton* UE4Skeleton, const UAnimSequence* UE4Clip, float SampleTime, rtm::qvvf* LossyPoseTransforms) +{ + const FReferenceSkeleton& RefSkeleton = UE4Skeleton->GetReferenceSkeleton(); + const TArray& RefSkeletonPose = UE4Skeleton->GetRefLocalPoses(); + + const uint32 NumBones = Tracks.get_num_tracks(); + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + const FName BoneName(Track.get_name().c_str()); + const int32 BoneTreeIndex = RefSkeleton.FindBoneIndex(BoneName); + const int32 BoneTrackIndex = GetAnimationTrackIndex(BoneTreeIndex, UE4Clip); + + FTransform BoneTransform; + if (BoneTrackIndex != INDEX_NONE) + { +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + UE4Clip->GetBoneTransform(BoneTransform, BoneTrackIndex, double(SampleTime), false); +#else + UE4Clip->GetBoneTransform(BoneTransform, BoneTrackIndex, SampleTime, false); +#endif + } + else + { + BoneTransform = RefSkeletonPose[BoneTreeIndex]; + } + + const rtm::quatf Rotation = UEQuatToACL(BoneTransform.GetRotation()); + const rtm::vector4f Translation = UEVector3ToACL(BoneTransform.GetTranslation()); + const rtm::vector4f Scale = UEVector3ToACL(BoneTransform.GetScale3D()); + LossyPoseTransforms[BoneIndex] = rtm::qvv_set(Rotation, Translation, Scale); + } +} + +static bool UE4ClipHasScale(const UAnimSequence* UE4Clip) +{ +#if ENGINE_MAJOR_VERSION >= 5 + const TArray& Tracks = UE4Clip->GetDataModel()->GetBoneAnimationTracks(); + for (const FBoneAnimationTrack& Track : Tracks) + { + if (Track.InternalTrackData.ScaleKeys.Num() != 0) + { + return true; + } + } +#else + const TArray& Tracks = UE4Clip->GetRawAnimationData(); + for (const FRawAnimSequenceTrack& Track : Tracks) + { + if (Track.ScaleKeys.Num() != 0) + return true; + } +#endif + + return false; +} + +struct SimpleTransformWriter final : public acl::track_writer +{ + explicit SimpleTransformWriter(TArray& Transforms_) : Transforms(Transforms_) {} + + TArray& Transforms; + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a quaternion rotation value for a specified bone index. + void RTM_SIMD_CALL write_rotation(uint32_t TrackIndex, rtm::quatf_arg0 Rotation) + { + Transforms[TrackIndex].rotation = Rotation; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a translation value for a specified bone index. + void RTM_SIMD_CALL write_translation(uint32_t TrackIndex, rtm::vector4f_arg0 Translation) + { + Transforms[TrackIndex].translation = Translation; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a scale value for a specified bone index. + void RTM_SIMD_CALL write_scale(uint32_t TrackIndex, rtm::vector4f_arg0 Scale) + { + Transforms[TrackIndex].scale = Scale; + } +}; + +static void CalculateClipError(const acl::track_array_qvvf& Tracks, const UAnimSequence* UE4Clip, USkeleton* UE4Skeleton, uint32& OutWorstBone, float& OutMaxError, float& OutWorstSampleTime) +{ + // Use the ACL code if we can to calculate the error instead of approximating it with UE4. + UAnimBoneCompressionCodec_ACLBase* ACLCodec = Cast(UE4Clip->CompressedData.BoneCompressionCodec); + if (ACLCodec != nullptr) + { + const acl::compressed_tracks* CompressedClipData = acl::make_compressed_tracks(UE4Clip->CompressedData.CompressedByteStream.GetData()); + + const acl::qvvf_transform_error_metric ErrorMetric; + + // Use debug settings since we don't know the specific codec used + acl::decompression_context Context; + Context.initialize(*CompressedClipData); + const acl::track_error TrackError = acl::calculate_compression_error(ACLAllocatorImpl, Tracks, Context, ErrorMetric); + + OutWorstBone = TrackError.index; + OutMaxError = TrackError.error; + OutWorstSampleTime = TrackError.sample_time; + return; + } + + const uint32 NumBones = Tracks.get_num_tracks(); + const float ClipDuration = Tracks.get_duration(); + const float SampleRate = Tracks.get_sample_rate(); + const uint32 NumSamples = Tracks.get_num_samples_per_track(); + const bool HasScale = UE4ClipHasScale(UE4Clip); + + TArray RawLocalPoseTransforms; + TArray RawObjectPoseTransforms; + TArray LossyLocalPoseTransforms; + TArray LossyObjectPoseTransforms; + RawLocalPoseTransforms.AddUninitialized(NumBones); + RawObjectPoseTransforms.AddUninitialized(NumBones); + LossyLocalPoseTransforms.AddUninitialized(NumBones); + LossyObjectPoseTransforms.AddUninitialized(NumBones); + + uint32 WorstBone = acl::k_invalid_track_index; + float MaxError = 0.0f; + float WorstSampleTime = 0.0f; + + const acl::qvvf_transform_error_metric ErrorMetric; + SimpleTransformWriter RawWriter(RawLocalPoseTransforms); + + TArray ParentTransformIndices; + TArray SelfTransformIndices; + ParentTransformIndices.AddUninitialized(NumBones); + SelfTransformIndices.AddUninitialized(NumBones); + + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + const acl::track_desc_transformf& Desc = Track.get_description(); + + ParentTransformIndices[BoneIndex] = Desc.parent_index; + SelfTransformIndices[BoneIndex] = BoneIndex; + } + + acl::itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw; + local_to_object_space_args_raw.dirty_transform_indices = SelfTransformIndices.GetData(); + local_to_object_space_args_raw.num_dirty_transforms = NumBones; + local_to_object_space_args_raw.parent_transform_indices = ParentTransformIndices.GetData(); + local_to_object_space_args_raw.local_transforms = RawLocalPoseTransforms.GetData(); + local_to_object_space_args_raw.num_transforms = NumBones; + + acl::itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw; + local_to_object_space_args_lossy.local_transforms = LossyLocalPoseTransforms.GetData(); + + for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + // Sample our streams and calculate the error + const float SampleTime = rtm::scalar_min(float(SampleIndex) / SampleRate, ClipDuration); + + Tracks.sample_tracks(SampleTime, acl::sample_rounding_policy::none, RawWriter); + SampleUE4Clip(Tracks, UE4Skeleton, UE4Clip, SampleTime, LossyLocalPoseTransforms.GetData()); + + if (HasScale) + { + ErrorMetric.local_to_object_space(local_to_object_space_args_raw, RawObjectPoseTransforms.GetData()); + ErrorMetric.local_to_object_space(local_to_object_space_args_lossy, LossyObjectPoseTransforms.GetData()); + } + else + { + ErrorMetric.local_to_object_space_no_scale(local_to_object_space_args_raw, RawObjectPoseTransforms.GetData()); + ErrorMetric.local_to_object_space_no_scale(local_to_object_space_args_lossy, LossyObjectPoseTransforms.GetData()); + } + + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + const acl::track_desc_transformf& Desc = Track.get_description(); + + acl::itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = &RawObjectPoseTransforms[BoneIndex]; + calculate_error_args.transform1 = &LossyObjectPoseTransforms[BoneIndex]; + calculate_error_args.construct_sphere_shell(Desc.shell_distance); + + float Error; + if (HasScale) + { + Error = rtm::scalar_cast(ErrorMetric.calculate_error(calculate_error_args)); + } + else + { + Error = rtm::scalar_cast(ErrorMetric.calculate_error_no_scale(calculate_error_args)); + } + + if (Error > MaxError) + { + MaxError = Error; + WorstBone = BoneIndex; + WorstSampleTime = SampleTime; + } + } + } + + OutWorstBone = WorstBone; + OutMaxError = MaxError; + OutWorstSampleTime = WorstSampleTime; +} + +static void DumpClipDetailedError(const acl::track_array_qvvf& Tracks, UAnimSequence* UE4Clip, USkeleton* UE4Skeleton, sjson::ObjectWriter& Writer) +{ + const uint32 NumBones = Tracks.get_num_tracks(); + const float ClipDuration = Tracks.get_duration(); + const float SampleRate = Tracks.get_sample_rate(); + const uint32 NumSamples = Tracks.get_num_samples_per_track(); + const bool HasScale = UE4ClipHasScale(UE4Clip); + + TArray RawLocalPoseTransforms; + TArray RawObjectPoseTransforms; + TArray LossyLocalPoseTransforms; + TArray LossyObjectPoseTransforms; + RawLocalPoseTransforms.AddUninitialized(NumBones); + RawObjectPoseTransforms.AddUninitialized(NumBones); + LossyLocalPoseTransforms.AddUninitialized(NumBones); + LossyObjectPoseTransforms.AddUninitialized(NumBones); + + const acl::qvvf_transform_error_metric ErrorMetric; + + SimpleTransformWriter RawWriter(RawLocalPoseTransforms); + + TArray ParentTransformIndices; + TArray SelfTransformIndices; + ParentTransformIndices.AddUninitialized(NumBones); + SelfTransformIndices.AddUninitialized(NumBones); + + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + const acl::track_desc_transformf& Desc = Track.get_description(); + + ParentTransformIndices[BoneIndex] = Desc.parent_index; + SelfTransformIndices[BoneIndex] = BoneIndex; + } + + acl::itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw; + local_to_object_space_args_raw.dirty_transform_indices = SelfTransformIndices.GetData(); + local_to_object_space_args_raw.num_dirty_transforms = NumBones; + local_to_object_space_args_raw.parent_transform_indices = ParentTransformIndices.GetData(); + local_to_object_space_args_raw.local_transforms = RawLocalPoseTransforms.GetData(); + local_to_object_space_args_raw.num_transforms = NumBones; + + acl::itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw; + local_to_object_space_args_lossy.local_transforms = LossyLocalPoseTransforms.GetData(); + + // Use the ACL code if we can to calculate the error instead of approximating it with UE4. + UAnimBoneCompressionCodec_ACLBase* ACLCodec = Cast(UE4Clip->CompressedData.BoneCompressionCodec); + if (ACLCodec != nullptr) + { + uint32 NumOutputBones = 0; + uint32* OutputBoneMapping = acl::acl_impl::create_output_track_mapping(ACLAllocatorImpl, Tracks, NumOutputBones); + + TArray LossyRemappedLocalPoseTransforms; + LossyRemappedLocalPoseTransforms.AddUninitialized(NumBones); + + local_to_object_space_args_lossy.local_transforms = LossyRemappedLocalPoseTransforms.GetData(); + + const acl::compressed_tracks* CompressedClipData = acl::make_compressed_tracks(UE4Clip->CompressedData.CompressedByteStream.GetData()); + + acl::decompression_context Context; + Context.initialize(*CompressedClipData); + + Writer["error_per_frame_and_bone"] = [&](sjson::ArrayWriter& Writer) + { + SimpleTransformWriter PoseWriter(LossyLocalPoseTransforms); + + for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + // Sample our streams and calculate the error + const float SampleTime = rtm::scalar_min(float(SampleIndex) / SampleRate, ClipDuration); + + Tracks.sample_tracks(SampleTime, acl::sample_rounding_policy::none, RawWriter); + + Context.seek(SampleTime, acl::sample_rounding_policy::none); + Context.decompress_tracks(PoseWriter); + + // Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if + // the data is available + LossyRemappedLocalPoseTransforms = RawLocalPoseTransforms; + for (uint32 OutputIndex = 0; OutputIndex < NumOutputBones; ++OutputIndex) + { + const uint32 BoneIndex = OutputBoneMapping[OutputIndex]; + LossyRemappedLocalPoseTransforms[BoneIndex] = LossyLocalPoseTransforms[OutputIndex]; + } + + if (HasScale) + { + ErrorMetric.local_to_object_space(local_to_object_space_args_raw, RawObjectPoseTransforms.GetData()); + ErrorMetric.local_to_object_space(local_to_object_space_args_lossy, LossyObjectPoseTransforms.GetData()); + } + else + { + ErrorMetric.local_to_object_space_no_scale(local_to_object_space_args_raw, RawObjectPoseTransforms.GetData()); + ErrorMetric.local_to_object_space_no_scale(local_to_object_space_args_lossy, LossyObjectPoseTransforms.GetData()); + } + + Writer.push_newline(); + Writer.push([&](sjson::ArrayWriter& Writer) + { + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + const acl::track_desc_transformf& Desc = Track.get_description(); + + acl::itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = &RawObjectPoseTransforms[BoneIndex]; + calculate_error_args.transform1 = &LossyObjectPoseTransforms[BoneIndex]; + calculate_error_args.construct_sphere_shell(Desc.shell_distance); + + float Error; + if (HasScale) + Error = rtm::scalar_cast(ErrorMetric.calculate_error(calculate_error_args)); + else + Error = rtm::scalar_cast(ErrorMetric.calculate_error_no_scale(calculate_error_args)); + + Writer.push(Error); + } + }); + } + }; + + acl::deallocate_type_array(ACLAllocatorImpl, OutputBoneMapping, NumOutputBones); + return; + } + + Writer["error_per_frame_and_bone"] = [&](sjson::ArrayWriter& Writer) + { + for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + // Sample our streams and calculate the error + const float SampleTime = rtm::scalar_min(float(SampleIndex) / SampleRate, ClipDuration); + + Tracks.sample_tracks(SampleTime, acl::sample_rounding_policy::none, RawWriter); + SampleUE4Clip(Tracks, UE4Skeleton, UE4Clip, SampleTime, LossyLocalPoseTransforms.GetData()); + + if (HasScale) + { + ErrorMetric.local_to_object_space(local_to_object_space_args_raw, RawObjectPoseTransforms.GetData()); + ErrorMetric.local_to_object_space(local_to_object_space_args_lossy, LossyObjectPoseTransforms.GetData()); + } + else + { + ErrorMetric.local_to_object_space_no_scale(local_to_object_space_args_raw, RawObjectPoseTransforms.GetData()); + ErrorMetric.local_to_object_space_no_scale(local_to_object_space_args_lossy, LossyObjectPoseTransforms.GetData()); + } + + Writer.push_newline(); + Writer.push([&](sjson::ArrayWriter& Writer) + { + for (uint32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) + { + const acl::track_qvvf& Track = Tracks[BoneIndex]; + const acl::track_desc_transformf& Desc = Track.get_description(); + + acl::itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = &RawObjectPoseTransforms[BoneIndex]; + calculate_error_args.transform1 = &LossyObjectPoseTransforms[BoneIndex]; + calculate_error_args.construct_sphere_shell(Desc.shell_distance); + + float Error; + if (HasScale) + Error = rtm::scalar_cast(ErrorMetric.calculate_error(calculate_error_args)); + else + Error = rtm::scalar_cast(ErrorMetric.calculate_error_no_scale(calculate_error_args)); + + Writer.push(Error); + } + }); + } + }; +} + +struct FCompressionContext +{ + UAnimBoneCompressionSettings* AutoCompressor; + UAnimBoneCompressionSettings* ACLCompressor; + UAnimBoneCompressionSettings* KeyReductionCompressor; + + UAnimSequence* UE4Clip; + USkeleton* UE4Skeleton; + + acl::track_array_qvvf ACLTracks; + + uint32 ACLRawSize; + int32 UE4RawSize; +}; + +static FString GetCodecName(UAnimBoneCompressionCodec* Codec) +{ + if (Codec == nullptr) + { + return TEXT(""); + } + + if (Codec->Description.Len() > 0 && Codec->Description != TEXT("None")) + { + return Codec->Description; + } + + return Codec->GetClass()->GetName(); +} + +static void CompressWithUE4Auto(FCompressionContext& Context, bool PerformExhaustiveDump, sjson::Writer& Writer) +{ + // Force recompression and avoid the DDC + TGuardValue CompressGuard(Context.UE4Clip->CompressCommandletVersion, INDEX_NONE); + + const uint64 UE4StartTimeCycles = FPlatformTime::Cycles64(); + + Context.UE4Clip->BoneCompressionSettings = Context.AutoCompressor; + Context.UE4Clip->RequestSyncAnimRecompression(); + + const uint64 UE4EndTimeCycles = FPlatformTime::Cycles64(); + + const uint64 UE4ElapsedCycles = UE4EndTimeCycles - UE4StartTimeCycles; + const double UE4ElapsedTimeSec = FPlatformTime::ToSeconds64(UE4ElapsedCycles); + + if (Context.UE4Clip->IsCompressedDataValid()) + { + const bool bHasClipData = Context.UE4Clip->CompressedData.CompressedDataStructure != nullptr; + + AnimationErrorStats UE4ErrorStats; + uint32 WorstBone = INDEX_NONE; + float MaxError = 0.0f; + float WorstSampleTime = 0.0f; + + if (bHasClipData) + { + UE4ErrorStats = Context.UE4Clip->CompressedData.CompressedDataStructure->BoneCompressionErrorStats; + + CalculateClipError(Context.ACLTracks, Context.UE4Clip, Context.UE4Skeleton, WorstBone, MaxError, WorstSampleTime); + } + + const int32 CompressedSize = Context.UE4Clip->GetApproxCompressedSize(); + const double UE4CompressionRatio = double(Context.UE4RawSize) / double(CompressedSize); + const double ACLCompressionRatio = double(Context.ACLRawSize) / double(CompressedSize); + + Writer["ue4_auto"] = [&](sjson::ObjectWriter& Writer) + { + Writer["algorithm_name"] = TCHAR_TO_ANSI(*Context.UE4Clip->BoneCompressionSettings->GetClass()->GetName()); + Writer["codec_name"] = TCHAR_TO_ANSI(*GetCodecName(Context.UE4Clip->CompressedData.BoneCompressionCodec)); + Writer["compressed_size"] = CompressedSize; + Writer["ue4_compression_ratio"] = UE4CompressionRatio; + Writer["acl_compression_ratio"] = ACLCompressionRatio; + Writer["compression_time"] = UE4ElapsedTimeSec; + Writer["ue4_max_error"] = UE4ErrorStats.MaxError; + Writer["ue4_avg_error"] = UE4ErrorStats.AverageError; + Writer["ue4_worst_bone"] = UE4ErrorStats.MaxErrorBone; + Writer["ue4_worst_time"] = UE4ErrorStats.MaxErrorTime; + Writer["acl_max_error"] = MaxError; + Writer["acl_worst_bone"] = WorstBone; + Writer["acl_worst_time"] = WorstSampleTime; + + if (Context.UE4Clip->CompressedData.BoneCompressionCodec != nullptr + && Context.UE4Clip->CompressedData.BoneCompressionCodec->IsA() + && bHasClipData) + { + const FUECompressedAnimData& AnimData = static_cast(*Context.UE4Clip->CompressedData.CompressedDataStructure); + Writer["rotation_format"] = TCHAR_TO_ANSI(*FAnimationUtils::GetAnimationCompressionFormatString(AnimData.RotationCompressionFormat)); + Writer["translation_format"] = TCHAR_TO_ANSI(*FAnimationUtils::GetAnimationCompressionFormatString(AnimData.TranslationCompressionFormat)); + Writer["scale_format"] = TCHAR_TO_ANSI(*FAnimationUtils::GetAnimationCompressionFormatString(AnimData.ScaleCompressionFormat)); + } + + if (PerformExhaustiveDump && bHasClipData) + { + DumpClipDetailedError(Context.ACLTracks, Context.UE4Clip, Context.UE4Skeleton, Writer); + } + }; + } + else + { + Writer["error"] = "failed to compress UE4 clip"; + } +} + +static void CompressWithACL(FCompressionContext& Context, bool PerformExhaustiveDump, sjson::Writer& Writer) +{ + // Force recompression and avoid the DDC + TGuardValue CompressGuard(Context.UE4Clip->CompressCommandletVersion, INDEX_NONE); + + const uint64 ACLStartTimeCycles = FPlatformTime::Cycles64(); + + Context.UE4Clip->BoneCompressionSettings = Context.ACLCompressor; + Context.UE4Clip->RequestSyncAnimRecompression(); + + const uint64 ACLEndTimeCycles = FPlatformTime::Cycles64(); + + const uint64 ACLElapsedCycles = ACLEndTimeCycles - ACLStartTimeCycles; + const double ACLElapsedTimeSec = FPlatformTime::ToSeconds64(ACLElapsedCycles); + + if (Context.UE4Clip->IsCompressedDataValid()) + { + const bool bHasClipData = Context.UE4Clip->CompressedData.CompressedDataStructure != nullptr; + + AnimationErrorStats UE4ErrorStats; + uint32 WorstBone = INDEX_NONE; + float MaxError = 0.0f; + float WorstSampleTime = 0.0f; + + if (bHasClipData) + { + UE4ErrorStats = Context.UE4Clip->CompressedData.CompressedDataStructure->BoneCompressionErrorStats; + + CalculateClipError(Context.ACLTracks, Context.UE4Clip, Context.UE4Skeleton, WorstBone, MaxError, WorstSampleTime); + } + + const int32 CompressedSize = Context.UE4Clip->GetApproxCompressedSize(); + const double UE4CompressionRatio = double(Context.UE4RawSize) / double(CompressedSize); + const double ACLCompressionRatio = double(Context.ACLRawSize) / double(CompressedSize); + + Writer["ue4_acl"] = [&](sjson::ObjectWriter& Writer) + { + Writer["algorithm_name"] = TCHAR_TO_ANSI(*Context.UE4Clip->BoneCompressionSettings->GetClass()->GetName()); + Writer["codec_name"] = TCHAR_TO_ANSI(*GetCodecName(Context.UE4Clip->CompressedData.BoneCompressionCodec)); + Writer["compressed_size"] = CompressedSize; + Writer["ue4_compression_ratio"] = UE4CompressionRatio; + Writer["acl_compression_ratio"] = ACLCompressionRatio; + Writer["compression_time"] = ACLElapsedTimeSec; + Writer["ue4_max_error"] = UE4ErrorStats.MaxError; + Writer["ue4_avg_error"] = UE4ErrorStats.AverageError; + Writer["ue4_worst_bone"] = UE4ErrorStats.MaxErrorBone; + Writer["ue4_worst_time"] = UE4ErrorStats.MaxErrorTime; + Writer["acl_max_error"] = MaxError; + Writer["acl_worst_bone"] = WorstBone; + Writer["acl_worst_time"] = WorstSampleTime; + + if (PerformExhaustiveDump && bHasClipData) + { + DumpClipDetailedError(Context.ACLTracks, Context.UE4Clip, Context.UE4Skeleton, Writer); + } + }; + } + else + { + Writer["error"] = "failed to compress UE4 clip"; + } +} + +static bool IsKeyDropped(int32 NumFrames, const uint8* FrameTable, int32 NumKeys, float FrameRate, float SampleTime) +{ + if (NumFrames > 0xFF) + { + const uint16* Frames = (const uint16*)FrameTable; + for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) + { + const float FrameTime = Frames[KeyIndex] / FrameRate; + if (FMath::IsNearlyEqual(FrameTime, SampleTime, 0.001f)) + { + return false; + } + } + return true; + } + else + { + const uint8* Frames = (const uint8*)FrameTable; + for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) + { + const float FrameTime = Frames[KeyIndex] / FrameRate; + if (FMath::IsNearlyEqual(FrameTime, SampleTime, 0.001f)) + { + return false; + } + } + return true; + } +} + +static int32 GetCompressedNumberOfKeys(const FUECompressedAnimData& AnimData) +{ +#if ENGINE_MAJOR_VERSION >= 5 + return AnimData.CompressedNumberOfKeys; +#else + return AnimData.CompressedNumberOfFrames; +#endif +} + +static void CompressWithUE4KeyReduction(FCompressionContext& Context, bool PerformExhaustiveDump, sjson::Writer& Writer) +{ +#if ENGINE_MAJOR_VERSION >= 5 +#if ENGINE_MINOR_VERSION >= 2 + using AnimDataModelType = IAnimationDataModel; +#else + using AnimDataModelType = UAnimDataModel; +#endif +#endif + + // Force recompression and avoid the DDC + TGuardValue CompressGuard(Context.UE4Clip->CompressCommandletVersion, INDEX_NONE); + + const uint64 UE4StartTimeCycles = FPlatformTime::Cycles64(); + + Context.UE4Clip->BoneCompressionSettings = Context.KeyReductionCompressor; + Context.UE4Clip->RequestSyncAnimRecompression(); + + const uint64 UE4EndTimeCycles = FPlatformTime::Cycles64(); + + const uint64 UE4ElapsedCycles = UE4EndTimeCycles - UE4StartTimeCycles; + const double UE4ElapsedTimeSec = FPlatformTime::ToSeconds64(UE4ElapsedCycles); + + if (Context.UE4Clip->IsCompressedDataValid()) + { + const bool bHasClipData = Context.UE4Clip->CompressedData.CompressedDataStructure != nullptr; + + AnimationErrorStats UE4ErrorStats; + uint32 WorstBone = INDEX_NONE; + float MaxError = 0.0f; + float WorstSampleTime = 0.0f; + + if (bHasClipData) + { + UE4ErrorStats = Context.UE4Clip->CompressedData.CompressedDataStructure->BoneCompressionErrorStats; + + CalculateClipError(Context.ACLTracks, Context.UE4Clip, Context.UE4Skeleton, WorstBone, MaxError, WorstSampleTime); + } + + const int32 CompressedSize = Context.UE4Clip->GetApproxCompressedSize(); + const double UE4CompressionRatio = double(Context.UE4RawSize) / double(CompressedSize); + const double ACLCompressionRatio = double(Context.ACLRawSize) / double(CompressedSize); + + Writer["ue4_keyreduction"] = [&](sjson::ObjectWriter& Writer) + { + Writer["algorithm_name"] = TCHAR_TO_ANSI(*Context.UE4Clip->BoneCompressionSettings->GetClass()->GetName()); + Writer["codec_name"] = TCHAR_TO_ANSI(*GetCodecName(Context.UE4Clip->CompressedData.BoneCompressionCodec)); + Writer["compressed_size"] = CompressedSize; + Writer["ue4_compression_ratio"] = UE4CompressionRatio; + Writer["acl_compression_ratio"] = ACLCompressionRatio; + Writer["compression_time"] = UE4ElapsedTimeSec; + Writer["ue4_max_error"] = UE4ErrorStats.MaxError; + Writer["ue4_avg_error"] = UE4ErrorStats.AverageError; + Writer["ue4_worst_bone"] = UE4ErrorStats.MaxErrorBone; + Writer["ue4_worst_time"] = UE4ErrorStats.MaxErrorTime; + Writer["acl_max_error"] = MaxError; + Writer["acl_worst_bone"] = WorstBone; + Writer["acl_worst_time"] = WorstSampleTime; + + if (PerformExhaustiveDump && bHasClipData) + { + DumpClipDetailedError(Context.ACLTracks, Context.UE4Clip, Context.UE4Skeleton, Writer); + } + + // Number of animated keys before any key reduction for animated tracks (without constant/default tracks) + int32 TotalNumAnimatedKeys = 0; + + // Number of animated keys dropped after key reduction for animated tracks (without constant/default tracks) + int32 TotalNumDroppedAnimatedKeys = 0; + + // Number of animated tracks (not constant/default) + int32 NumAnimatedTracks = 0; + + Writer["dropped_track_keys"] = [&](sjson::ArrayWriter& Writer) + { + if (!bHasClipData) + { + return; // No data, nothing to append + } + + const FUECompressedAnimData& AnimData = static_cast(*Context.UE4Clip->CompressedData.CompressedDataStructure); + +#if ENGINE_MAJOR_VERSION >= 5 + const AnimDataModelType* ClipData = Context.UE4Clip->GetDataModel(); + const TArray& RawTracks = ClipData->GetBoneAnimationTracks(); + const int32 NumSamples = ClipData->GetNumberOfFrames(); +#else + const TArray& RawTracks = Context.UE4Clip->GetRawAnimationData(); + const int32 NumSamples = Context.UE4Clip->GetRawNumberOfFrames(); +#endif + + const int32 NumTracks = RawTracks.Num(); + + const int32* TrackOffsets = AnimData.CompressedTrackOffsets.GetData(); + const auto& ScaleOffsets = AnimData.CompressedScaleOffsets; + + const AnimationCompressionFormat RotationFormat = AnimData.RotationCompressionFormat; + const AnimationCompressionFormat TranslationFormat = AnimData.TranslationCompressionFormat; + const AnimationCompressionFormat ScaleFormat = AnimData.ScaleCompressionFormat; + + // offset past Min and Range data + const int32 RotationStreamOffset = (RotationFormat == ACF_IntervalFixed32NoW) ? (sizeof(float) * 6) : 0; + const int32 TranslationStreamOffset = (TranslationFormat == ACF_IntervalFixed32NoW) ? (sizeof(float) * 6) : 0; + const int32 ScaleStreamOffset = (ScaleFormat == ACF_IntervalFixed32NoW) ? (sizeof(float) * 6) : 0; + + for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) + { + const int32* TrackData = TrackOffsets + (TrackIndex * 4); + const int32 NumTransKeys = TrackData[1]; + + // Skip constant/default tracks + if (NumTransKeys > 1) + { + const int32 DroppedTransCount = NumSamples - NumTransKeys; + const float DroppedRatio = float(DroppedTransCount) / float(NumSamples); + Writer.push(DroppedRatio); + + TotalNumAnimatedKeys += NumSamples; + TotalNumDroppedAnimatedKeys += DroppedTransCount; + NumAnimatedTracks++; + } + + const int32 NumRotKeys = TrackData[3]; + + // Skip constant/default tracks + if (NumRotKeys > 1) + { + const int32 DroppedRotCount = NumSamples - NumRotKeys; + const float DroppedRatio = float(DroppedRotCount) / float(NumSamples); + Writer.push(DroppedRatio); + + TotalNumAnimatedKeys += NumSamples; + TotalNumDroppedAnimatedKeys += DroppedRotCount; + NumAnimatedTracks++; + } + + if (ScaleOffsets.IsValid()) + { + const int32 NumScaleKeys = ScaleOffsets.GetOffsetData(TrackIndex, 1); + + // Skip constant/default tracks + if (NumScaleKeys > 1) + { + const int32 DroppedScaleCount = NumSamples - NumScaleKeys; + const float DroppedRatio = float(DroppedScaleCount) / float(NumSamples); + Writer.push(DroppedRatio); + + TotalNumAnimatedKeys += NumSamples; + TotalNumDroppedAnimatedKeys += DroppedScaleCount; + NumAnimatedTracks++; + } + } + } + }; + + Writer["total_num_animated_keys"] = TotalNumAnimatedKeys; + Writer["total_num_dropped_animated_keys"] = TotalNumDroppedAnimatedKeys; + + Writer["dropped_pose_keys"] = [&](sjson::ArrayWriter& Writer) + { + if (!bHasClipData) + { + return; // No data, nothing to append + } + + const FUECompressedAnimData& AnimData = static_cast(*Context.UE4Clip->CompressedData.CompressedDataStructure); + +#if ENGINE_MAJOR_VERSION >= 5 + const AnimDataModelType* ClipData = Context.UE4Clip->GetDataModel(); + const TArray& RawTracks = ClipData->GetBoneAnimationTracks(); + const int32 NumSamples = ClipData->GetNumberOfFrames(); +#else + const TArray& RawTracks = Context.UE4Clip->GetRawAnimationData(); + const int32 NumSamples = Context.UE4Clip->GetRawNumberOfFrames(); +#endif + + const float SequenceLength = GetSequenceLength(*Context.UE4Clip); + const int32 NumTracks = RawTracks.Num(); + const int32 NumCompressedKeys = GetCompressedNumberOfKeys(AnimData); + + const float FrameRate = (NumSamples - 1) / SequenceLength; + + const uint8* ByteStream = AnimData.CompressedByteStream.GetData(); + const int32* TrackOffsets = AnimData.CompressedTrackOffsets.GetData(); + const auto& ScaleOffsets = AnimData.CompressedScaleOffsets; + + const AnimationCompressionFormat RotationFormat = AnimData.RotationCompressionFormat; + const AnimationCompressionFormat TranslationFormat = AnimData.TranslationCompressionFormat; + const AnimationCompressionFormat ScaleFormat = AnimData.ScaleCompressionFormat; + + // offset past Min and Range data + const int32 RotationStreamOffset = (RotationFormat == ACF_IntervalFixed32NoW) ? (sizeof(float) * 6) : 0; + const int32 TranslationStreamOffset = (TranslationFormat == ACF_IntervalFixed32NoW) ? (sizeof(float) * 6) : 0; + const int32 ScaleStreamOffset = (ScaleFormat == ACF_IntervalFixed32NoW) ? (sizeof(float) * 6) : 0; + + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const float SampleTime = float(SampleIndex) / FrameRate; + + int32 DroppedRotCount = 0; + int32 DroppedTransCount = 0; + int32 DroppedScaleCount = 0; + for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) + { + const int32* TrackData = TrackOffsets + (TrackIndex * 4); + + const int32 TransKeysOffset = TrackData[0]; + const int32 NumTransKeys = TrackData[1]; + const uint8* TransStream = ByteStream + TransKeysOffset; + + const uint8* TransFrameTable = TransStream + TranslationStreamOffset + (NumTransKeys * CompressedTranslationStrides[TranslationFormat] * CompressedTranslationNum[TranslationFormat]); + TransFrameTable = Align(TransFrameTable, 4); + + // Skip constant/default tracks + if (NumTransKeys > 1 && IsKeyDropped(NumCompressedKeys, TransFrameTable, NumTransKeys, FrameRate, SampleTime)) + { + DroppedTransCount++; + } + + const int32 RotKeysOffset = TrackData[2]; + const int32 NumRotKeys = TrackData[3]; + const uint8* RotStream = ByteStream + RotKeysOffset; + + const uint8* RotFrameTable = RotStream + RotationStreamOffset + (NumRotKeys * CompressedRotationStrides[RotationFormat] * CompressedRotationNum[RotationFormat]); + RotFrameTable = Align(RotFrameTable, 4); + + // Skip constant/default tracks + if (NumRotKeys > 1 && IsKeyDropped(NumCompressedKeys, RotFrameTable, NumRotKeys, FrameRate, SampleTime)) + { + DroppedRotCount++; + } + + if (ScaleOffsets.IsValid()) + { + const int32 ScaleKeysOffset = ScaleOffsets.GetOffsetData(TrackIndex, 0); + const int32 NumScaleKeys = ScaleOffsets.GetOffsetData(TrackIndex, 1); + const uint8* ScaleStream = ByteStream + ScaleKeysOffset; + + const uint8* ScaleFrameTable = ScaleStream + ScaleStreamOffset + (NumScaleKeys * CompressedScaleStrides[ScaleFormat] * CompressedScaleNum[ScaleFormat]); + ScaleFrameTable = Align(ScaleFrameTable, 4); + + // Skip constant/default tracks + if (NumScaleKeys > 1 && IsKeyDropped(NumCompressedKeys, ScaleFrameTable, NumScaleKeys, FrameRate, SampleTime)) + { + DroppedScaleCount++; + } + } + } + + const int32 TotalDroppedCount = DroppedRotCount + DroppedTransCount + DroppedScaleCount; + const float DropRatio = NumAnimatedTracks != 0 ? (float(TotalDroppedCount) / float(NumAnimatedTracks)) : 1.0f; + Writer.push(DropRatio); + } + }; + +#if DO_CHECK && 0 + { + // Double check our count + const int32 NumSamples = Context.UE4Clip->GetRawNumberOfFrames(); + const TArray& RawTracks = Context.UE4Clip->GetRawAnimationData(); + const int32 NumTracks = RawTracks.Num(); + const int32* TrackOffsets = Context.UE4Clip->CompressedTrackOffsets.GetData(); + const FCompressedOffsetData& ScaleOffsets = Context.UE4Clip->CompressedScaleOffsets; + + int32 DroppedRotCount = 0; + int32 DroppedTransCount = 0; + int32 DroppedScaleCount = 0; + for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) + { + const int32* TrackData = TrackOffsets + (TrackIndex * 4); + + const int32 NumTransKeys = TrackData[1]; + + // Skip constant/default tracks + if (NumTransKeys > 1) + { + DroppedTransCount += NumSamples - NumTransKeys; + } + + const int32 NumRotKeys = TrackData[3]; + + // Skip constant/default tracks + if (NumRotKeys > 1) + { + DroppedRotCount += NumSamples - NumRotKeys; + } + + if (ScaleOffsets.IsValid()) + { + const int32 NumScaleKeys = ScaleOffsets.GetOffsetData(TrackIndex, 1); + + // Skip constant/default tracks + if (NumScaleKeys > 1) + { + DroppedScaleCount += NumSamples - NumScaleKeys; + } + } + } + check(TotalNumDroppedKeys == (DroppedRotCount + DroppedTransCount + DroppedScaleCount)); + } +#endif + }; + } + else + { + Writer["error"] = "failed to compress UE4 clip"; + } +} + +static void ClearClip(UAnimSequence* UE4Clip) +{ +#if ENGINE_MAJOR_VERSION >= 5 + UE4Clip->ResetAnimation(); +#else + UE4Clip->RecycleAnimSequence(); +#endif +} + +struct CompressAnimationsFunctor +{ + template + void DoIt(UCommandlet* Commandlet, UPackage* Package, const TArray& Tokens, const TArray& Switches) + { + TArray AnimSequences; + for (TObjectIterator It; It; ++It) + { + UAnimSequence* AnimSeq = *It; + if (AnimSeq->IsIn(Package)) + { + AnimSequences.Add(AnimSeq); + } + } + + // Skip packages that contain no Animations. + const int32 NumAnimSequences = AnimSequences.Num(); + if (NumAnimSequences == 0) + { + return; + } + + UACLStatsDumpCommandlet* StatsCommandlet = Cast(Commandlet); + FFileManagerGeneric FileManager; + + for (int32 SequenceIndex = 0; SequenceIndex < NumAnimSequences; ++SequenceIndex) + { + UAnimSequence* UE4Clip = AnimSequences[SequenceIndex]; + + // Make sure all our required dependencies are loaded + FAnimationUtils::EnsureAnimSequenceLoaded(*UE4Clip); + + USkeleton* UE4Skeleton = UE4Clip->GetSkeleton(); + if (UE4Skeleton == nullptr) + { + continue; + } + + FString Filename = UE4Clip->GetPathName(); + if (StatsCommandlet->PerformCompression) + { + Filename = FString::Printf(TEXT("%X_stats.sjson"), GetTypeHash(Filename)); + } + else if (StatsCommandlet->PerformClipExtraction) + { + Filename = FString::Printf(TEXT("%X.acl.sjson"), GetTypeHash(Filename)); + } + + FString UE4OutputPath = FPaths::Combine(*StatsCommandlet->OutputDir, *Filename).Replace(TEXT("/"), TEXT("\\")); + + if (StatsCommandlet->ResumeTask && FileManager.FileExists(*UE4OutputPath)) + { + continue; + } + + const bool bIsAdditive = UE4Clip->IsValidAdditive(); + if (bIsAdditive && StatsCommandlet->SkipAdditiveClips) + { + continue; + } + + FCompressionContext Context; + Context.AutoCompressor = StatsCommandlet->AutoCompressionSettings; + Context.ACLCompressor = StatsCommandlet->ACLCompressionSettings; + Context.UE4Clip = UE4Clip; + Context.UE4Skeleton = UE4Skeleton; + +#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1 + FCompressibleAnimData CompressibleData(UE4Clip, false, nullptr); +#else + FCompressibleAnimData CompressibleData(UE4Clip, false); +#endif + + acl::track_array_qvvf ACLTracks = BuildACLTransformTrackArray(ACLAllocatorImpl, CompressibleData, StatsCommandlet->ACLCodec->DefaultVirtualVertexDistance, StatsCommandlet->ACLCodec->SafeVirtualVertexDistance, false); + + // TODO: Add support for additive clips + //acl::track_array_qvvf ACLBaseTracks; + //if (CompressibleData.bIsValidAdditive) + //ACLBaseTracks = BuildACLTransformTrackArray(Allocator, CompressibleData, StatsCommandlet->ACLCodec->DefaultVirtualVertexDistance, StatsCommandlet->ACLCodec->SafeVirtualVertexDistance, true); + + Context.ACLTracks = MoveTemp(ACLTracks); + Context.ACLRawSize = Context.ACLTracks.get_raw_size(); + Context.UE4RawSize = UE4Clip->GetApproxRawSize(); + + if (StatsCommandlet->PerformCompression) + { + UE_LOG(LogAnimationCompression, Verbose, TEXT("Compressing: %s (%d / %d)"), *UE4Clip->GetPathName(), SequenceIndex, NumAnimSequences); + + FArchive* OutputWriter = FileManager.CreateFileWriter(*UE4OutputPath); + if (OutputWriter == nullptr) + { + // Opening the file handle can fail if the file path is too long on Windows. UE4 does not properly handle long paths + // and adding the \\?\ prefix manually doesn't work, UE4 mangles it when it normalizes the path. + ClearClip(UE4Clip); + continue; + } + + // Make sure any pending async compression that might have started during load or construction is done + UE4Clip->WaitOnExistingCompression(); + + UE4SJSONStreamWriter StreamWriter(OutputWriter); + sjson::Writer Writer(StreamWriter); + + Writer["duration"] = GetSequenceLength(*UE4Clip); + Writer["num_samples"] = GetNumSamples(CompressibleData); + Writer["ue4_raw_size"] = Context.UE4RawSize; + Writer["acl_raw_size"] = Context.ACLRawSize; + + if (StatsCommandlet->TryAutomaticCompression) + { + CompressWithUE4Auto(Context, StatsCommandlet->PerformExhaustiveDump, Writer); + } + + if (StatsCommandlet->TryACLCompression) + { + CompressWithACL(Context, StatsCommandlet->PerformExhaustiveDump, Writer); + } + + if (StatsCommandlet->TryKeyReduction) + { + CompressWithUE4KeyReduction(Context, StatsCommandlet->PerformExhaustiveDump, Writer); + } + + OutputWriter->Close(); + } + else if (StatsCommandlet->PerformClipExtraction) + { + UE_LOG(LogAnimationCompression, Verbose, TEXT("Extracting: %s (%d / %d)"), *UE4Clip->GetPathName(), SequenceIndex, NumAnimSequences); + + acl::compression_settings Settings; + StatsCommandlet->ACLCodec->GetCompressionSettings(Settings); + + const acl::error_result Error = acl::write_track_list(Context.ACLTracks, Settings, TCHAR_TO_ANSI(*UE4OutputPath)); + if (Error.any()) + { + UE_LOG(LogAnimationCompression, Warning, TEXT("Failed to write ACL clip file: %s"), ANSI_TO_TCHAR(Error.c_str())); + } + } + + ClearClip(UE4Clip); + } + } +}; + +UACLStatsDumpCommandlet::UACLStatsDumpCommandlet(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + IsClient = false; + IsServer = false; + IsEditor = true; + LogToConsole = true; + ShowErrorCount = true; +} + +static void ClearCompressedData(UAnimSequence* UE4Clip) +{ +#if ENGINE_MAJOR_VERSION >= 5 + UE4Clip->CompressedData.ClearCompressedBoneData(); + UE4Clip->CompressedData.ClearCompressedCurveData(); +#else + UE4Clip->ClearCompressedBoneData(); + UE4Clip->ClearCompressedCurveData(); +#endif +} + +int32 UACLStatsDumpCommandlet::Main(const FString& Params) +{ + TArray Tokens; + TArray Switches; + TMap ParamsMap; + UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamsMap); + + if (!ParamsMap.Contains(TEXT("output"))) + { + UE_LOG(LogAnimationCompression, Error, TEXT("Missing commandlet argument: -output=")); + return 0; + } + + OutputDir = ParamsMap[TEXT("output")]; + + PerformExhaustiveDump = Switches.Contains(TEXT("error")); + PerformCompression = Switches.Contains(TEXT("compress")); + PerformClipExtraction = Switches.Contains(TEXT("extract")); + TryAutomaticCompression = Switches.Contains(TEXT("auto")); + TryACLCompression = Switches.Contains(TEXT("acl")); + TryKeyReductionRetarget = Switches.Contains(TEXT("keyreductionrt")); + TryKeyReduction = TryKeyReductionRetarget || Switches.Contains(TEXT("keyreduction")); + ResumeTask = Switches.Contains(TEXT("resume")); + SkipAdditiveClips = Switches.Contains(TEXT("noadditive")) || true; // Disabled for now, TODO add support for it + const bool HasInput = ParamsMap.Contains(TEXT("input")); + + if (PerformClipExtraction) + { + // We don't support extracting additive clips + SkipAdditiveClips = true; + } + + if (PerformCompression && PerformClipExtraction) + { + UE_LOG(LogAnimationCompression, Error, TEXT("Cannot compress and extract clips at the same time")); + return 0; + } + + if (!PerformCompression && !PerformClipExtraction) + { + UE_LOG(LogAnimationCompression, Error, TEXT("Must compress or extract clips")); + return 0; + } + + if (PerformClipExtraction && ParamsMap.Contains(TEXT("input"))) + { + UE_LOG(LogAnimationCompression, Error, TEXT("Cannot use an input directory when extracting clips")); + return 0; + } + + // Make sure to log everything + LogAnimationCompression.SetVerbosity(ELogVerbosity::All); + + if (TryAutomaticCompression) + { + AutoCompressionSettings = FAnimationUtils::GetDefaultAnimationBoneCompressionSettings(); + AutoCompressionSettings->bForceBelowThreshold = true; + + if (ParamsMap.Contains(TEXT("MasterTolerance"))) + { + AutoCompressionSettings->ErrorThreshold = FCString::Atof(*ParamsMap[TEXT("MasterTolerance")]); + } + } + + if (TryACLCompression || !HasInput) + { + ACLCompressionSettings = NewObject(this, UAnimBoneCompressionSettings::StaticClass()); + ACLCodec = NewObject(this, UAnimBoneCompressionCodec_ACL::StaticClass()); + ACLCompressionSettings->Codecs.Add(ACLCodec); + ACLCompressionSettings->AddToRoot(); + } + + if (TryKeyReduction) + { + KeyReductionCompressionSettings = NewObject(this, UAnimBoneCompressionSettings::StaticClass()); + KeyReductionCodec = NewObject(this, UAnimCompress_RemoveLinearKeys::StaticClass()); + KeyReductionCodec->RotationCompressionFormat = ACF_Float96NoW; + KeyReductionCodec->TranslationCompressionFormat = ACF_None; + KeyReductionCodec->ScaleCompressionFormat = ACF_None; + KeyReductionCodec->bActuallyFilterLinearKeys = true; + KeyReductionCodec->bRetarget = TryKeyReductionRetarget; + + KeyReductionCompressionSettings->Codecs.Add(KeyReductionCodec); + KeyReductionCompressionSettings->AddToRoot(); + } + + FFileManagerGeneric FileManager; + FileManager.MakeDirectory(*OutputDir, true); + + if (!HasInput) + { + // No source directory, use the current project instead + ACLRawDir = TEXT(""); + + DoActionToAllPackages(this, Params.ToUpper()); + return 0; + } + else + { + check(PerformCompression); + + // Use source directory + ACLRawDir = ParamsMap[TEXT("input")]; + +#if (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 26) || ENGINE_MAJOR_VERSION >= 5 + UPackage* TempPackage = CreatePackage(TEXT("/Temp/ACL")); +#else + UPackage* TempPackage = CreatePackage(nullptr, TEXT("/Temp/ACL")); +#endif + + TArray FilesLegacy; + FileManager.FindFiles(FilesLegacy, *ACLRawDir, TEXT(".acl.sjson")); // Legacy ASCII file format + + TArray FilesBinary; + FileManager.FindFiles(FilesBinary, *ACLRawDir, TEXT(".acl")); // ACL 2.0+ binary format + + TArray Files; + Files.Append(FilesLegacy); + Files.Append(FilesBinary); + + for (const FString& Filename : Files) + { + const FString ACLClipPath = FPaths::Combine(*ACLRawDir, *Filename); + + FString UE4StatFilename = Filename.Replace(TEXT(".acl.sjson"), TEXT("_stats.sjson"), ESearchCase::CaseSensitive); + UE4StatFilename = UE4StatFilename.Replace(TEXT(".acl"), TEXT("_stats.sjson"), ESearchCase::CaseSensitive); + + const FString UE4StatPath = FPaths::Combine(*OutputDir, *UE4StatFilename); + + if (ResumeTask && FileManager.FileExists(*UE4StatPath)) + { + continue; + } + + UE_LOG(LogAnimationCompression, Verbose, TEXT("Compressing: %s"), *Filename); + + FArchive* StatWriter = FileManager.CreateFileWriter(*UE4StatPath); + if (StatWriter == nullptr) + { + // Opening the file handle can fail if the file path is too long on Windows. UE4 does not properly handle long paths + // and adding the \\?\ prefix manually doesn't work, UE4 mangles it when it normalizes the path. + continue; + } + + UE4SJSONStreamWriter StreamWriter(StatWriter); + sjson::Writer Writer(StreamWriter); + + acl::track_array_qvvf ACLTracks; + + const TCHAR* ErrorMsg = ReadACLClip(FileManager, ACLClipPath, ACLAllocatorImpl, ACLTracks); + if (ErrorMsg == nullptr) + { + USkeleton* UE4Skeleton = NewObject(TempPackage, USkeleton::StaticClass()); + ConvertSkeleton(ACLTracks, UE4Skeleton); + + UAnimSequence* UE4Clip = NewObject(TempPackage, UAnimSequence::StaticClass()); + ConvertClip(ACLTracks, UE4Clip, UE4Skeleton); + + // Make sure any pending async compression that might have started during load or construction is done + UE4Clip->WaitOnExistingCompression(); + + FCompressionContext Context; + Context.AutoCompressor = AutoCompressionSettings; + Context.ACLCompressor = ACLCompressionSettings; + Context.KeyReductionCompressor = KeyReductionCompressionSettings; + Context.UE4Clip = UE4Clip; + Context.UE4Skeleton = UE4Skeleton; + Context.ACLTracks = MoveTemp(ACLTracks); + + Context.ACLRawSize = Context.ACLTracks.get_raw_size(); + Context.UE4RawSize = UE4Clip->GetApproxRawSize(); + + Writer["duration"] = GetSequenceLength(*UE4Clip); + Writer["num_samples"] = Context.ACLTracks.get_num_samples_per_track(); + Writer["ue4_raw_size"] = Context.UE4RawSize; + Writer["acl_raw_size"] = Context.ACLRawSize; + + if (TryAutomaticCompression) + { + CompressWithUE4Auto(Context, PerformExhaustiveDump, Writer); + + ClearCompressedData(UE4Clip); + } + + if (TryACLCompression) + { + CompressWithACL(Context, PerformExhaustiveDump, Writer); + + ClearCompressedData(UE4Clip); + } + + if (TryKeyReduction) + { + CompressWithUE4KeyReduction(Context, PerformExhaustiveDump, Writer); + + ClearCompressedData(UE4Clip); + } + + ClearClip(UE4Clip); + } + else + { + Writer["error"] = TCHAR_TO_ANSI(ErrorMsg); + } + + StatWriter->Close(); + } + } + + return 0; +} diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AnimationCompressionLibraryDatabaseFactory.cpp b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AnimationCompressionLibraryDatabaseFactory.cpp new file mode 100644 index 00000000..4395eeb9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AnimationCompressionLibraryDatabaseFactory.cpp @@ -0,0 +1,16 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "AnimationCompressionLibraryDatabaseFactory.h" +#include "AnimationCompressionLibraryDatabase.h" + +UAnimationCompressionLibraryDatabaseFactory::UAnimationCompressionLibraryDatabaseFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bCreateNew = true; + SupportedClass = UAnimationCompressionLibraryDatabase::StaticClass(); +} + +UObject* UAnimationCompressionLibraryDatabaseFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AssetTypeActions_AnimationCompressionLibraryDatabase.cpp b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AssetTypeActions_AnimationCompressionLibraryDatabase.cpp new file mode 100644 index 00000000..ea1be3ff --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AssetTypeActions_AnimationCompressionLibraryDatabase.cpp @@ -0,0 +1,76 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "AssetTypeActions_AnimationCompressionLibraryDatabase.h" +#include "AnimBoneCompressionCodec_ACLDatabase.h" + +#include "EditorStyleSet.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" + +void FAssetTypeActions_AnimationCompressionLibraryDatabase::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + TSharedRef AssetEditor = FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects); + + auto DatabaseAssets = GetTypedWeakObjectPtrs(InObjects); + if (DatabaseAssets.Num() == 1) + { + TSharedPtr PluginCommands = MakeShareable(new FUICommandList); + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FAssetTypeActions_AnimationCompressionLibraryDatabase::AddToolbarExtension, DatabaseAssets[0])); + AssetEditor->AddToolbarExtender(ToolbarExtender); + + AssetEditor->RegenerateMenusAndToolbars(); + } +} + +void FAssetTypeActions_AnimationCompressionLibraryDatabase::AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr DatabasePtr) +{ + Builder.BeginSection("Build"); + Builder.AddToolBarButton( + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimationCompressionLibraryDatabase::ExecuteBuild, DatabasePtr) + ), + NAME_None, + FText::FromString(TEXT("Build")), + FText::FromString(TEXT("Builds the database from all the animation sequences that reference this database through their codec.")), +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.ApplyCompression") +#else + FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression") +#endif + ); + Builder.EndSection(); +} + +void FAssetTypeActions_AnimationCompressionLibraryDatabase::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) +{ + auto DatabaseAssets = GetTypedWeakObjectPtrs(InObjects); + + if (DatabaseAssets.Num() != 1) + { + return; + } + + MenuBuilder.AddMenuEntry( + FText::FromString(TEXT("Build")), + FText::FromString(TEXT("Builds the database from all the animation sequences that reference this database through their codec.")), +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.ApplyCompression.Small"), +#else + FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression.Small"), +#endif + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimationCompressionLibraryDatabase::ExecuteBuild, DatabaseAssets[0]) + ) + ); +} + +void FAssetTypeActions_AnimationCompressionLibraryDatabase::ExecuteBuild(TWeakObjectPtr DatabasePtr) +{ + if (!DatabasePtr.IsValid()) + { + return; + } + + UAnimationCompressionLibraryDatabase* Database = DatabasePtr.Get(); + Database->UpdateReferencingAnimSequenceList(); +} diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AssetTypeActions_AnimationCompressionLibraryDatabase.h b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AssetTypeActions_AnimationCompressionLibraryDatabase.h new file mode 100644 index 00000000..e0c7a41c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Private/AssetTypeActions_AnimationCompressionLibraryDatabase.h @@ -0,0 +1,27 @@ +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "AnimationCompressionLibraryDatabase.h" + +class FAssetTypeActions_AnimationCompressionLibraryDatabase : public FAssetTypeActions_Base +{ +public: + // IAssetTypeActions Implementation + virtual FText GetName() const override { return FText::FromString(TEXT("ACL Database")); } + virtual FColor GetTypeColor() const override { return FColor(255, 255, 0); } + virtual UClass* GetSupportedClass() const override { return UAnimationCompressionLibraryDatabase::StaticClass(); } + virtual bool CanFilter() override { return true; } + virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; } + + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + + virtual bool HasActions(const TArray& InObjects) const override { return true; } + virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; + +private: + void AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr DatabasePtr); + void ExecuteBuild(TWeakObjectPtr DatabasePtr); +}; diff --git a/Plugins/ACLPlugin/Source/ACLPluginEditor/Public/IACLPluginEditorModule.h b/Plugins/ACLPlugin/Source/ACLPluginEditor/Public/IACLPluginEditorModule.h new file mode 100644 index 00000000..e61c2594 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ACLPluginEditor/Public/IACLPluginEditorModule.h @@ -0,0 +1,22 @@ +#pragma once + +// Copyright 2020 Nicholas Frechette. All Rights Reserved. + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +/** The editor ACL plugin module interface. */ +class IACLPluginEditor : public IModuleInterface +{ +public: + static inline IACLPluginEditor& Get() + { + return FModuleManager::LoadModuleChecked("ACLPluginEditor"); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ACLPluginEditor"); + } +}; diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.all-contributorsrc b/Plugins/ACLPlugin/Source/ThirdParty/acl/.all-contributorsrc new file mode 100644 index 00000000..51eacf6b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.all-contributorsrc @@ -0,0 +1,88 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "CodyDWJones", + "name": "CodyDWJones", + "avatar_url": "https://avatars.githubusercontent.com/u/28773740?v=4", + "profile": "https://github.com/CodyDWJones", + "contributions": [ + "code", + "data", + "maintenance", + "tool", + "infra", + "ideas" + ] + }, + { + "login": "Meradrin", + "name": "Meradrin", + "avatar_url": "https://avatars.githubusercontent.com/u/7066278?v=4", + "profile": "https://github.com/Meradrin", + "contributions": [ + "code" + ] + }, + { + "login": "tirpidz", + "name": "Martin Turcotte", + "avatar_url": "https://avatars.githubusercontent.com/u/9991876?v=4", + "profile": "https://github.com/tirpidz", + "contributions": [ + "code", + "tool", + "ideas" + ] + }, + { + "login": "vjeffh", + "name": "vjeffh", + "avatar_url": "https://avatars.githubusercontent.com/u/22382688?v=4", + "profile": "https://github.com/vjeffh", + "contributions": [ + "code" + ] + }, + { + "login": "Romain-Piquois", + "name": "Romain-Piquois", + "avatar_url": "https://avatars.githubusercontent.com/u/3689912?v=4", + "profile": "https://github.com/Romain-Piquois", + "contributions": [ + "bug" + ] + }, + { + "login": "janisozaur", + "name": "Michał Janiszewski", + "avatar_url": "https://avatars.githubusercontent.com/u/550290?v=4", + "profile": "https://github.com/janisozaur", + "contributions": [ + "code", + "tool", + "maintenance", + "infra" + ] + }, + { + "login": "visualphoenix", + "name": "Raymond Barbiero", + "avatar_url": "https://avatars.githubusercontent.com/u/394175?v=4", + "profile": "http://keybase.io/visualphoenix", + "contributions": [ + "ideas" + ] + } + ], + "contributorsPerLine": 7, + "projectName": "acl", + "projectOwner": "nfrechette", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.editorconfig b/Plugins/ACLPlugin/Source/ThirdParty/acl/.editorconfig new file mode 100644 index 00000000..4e06eca7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +[*.{cpp,h,py,txt,sjson,cmake,java}] +charset = utf-8 +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{gradle,gradle.in}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/FUNDING.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/FUNDING.yml new file mode 100644 index 00000000..724e02b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +#patreon: # Replace with a single Patreon username +#open_collective: # Replace with a single Open Collective username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +#liberapay: # Replace with a single Liberapay username +#issuehunt: # Replace with a single IssueHunt username +#otechie: # Replace with a single Otechie username +#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] + +github: [nfrechette] diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/workflows/build.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/workflows/build.yml new file mode 100644 index 00000000..998b3f2f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/workflows/build.yml @@ -0,0 +1,151 @@ +name: build + +on: + push: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + pull_request: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + +jobs: + linux: + runs-on: ubuntu-18.04 + strategy: + matrix: + compiler: [gcc5, gcc6, gcc7, gcc8, gcc9, gcc10, clang4, clang5, clang6, clang7, clang8, clang9, clang10, clang11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_linux_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64 nosimd) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -nosimd -build + - name: Running unit tests (release-x64 nosimd) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -nosimd -unit_test + osx: + runs-on: macos-10.15 + strategy: + matrix: + compiler: [xcode10, xcode11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_osx_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Debug -cpu x86 -unit_test + - name: Clean + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -compiler osx -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -compiler osx -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Release -cpu x86 -unit_test + - name: Clean + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -compiler osx -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -compiler osx -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64 nosimd) + run: python3 make.py -compiler osx -config Release -cpu x64 -nosimd -build + - name: Running unit tests (release-x64 nosimd) + run: python3 make.py -compiler osx -config Release -cpu x64 -nosimd -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (debug-arm64) + run: python3 make.py -compiler ios -config Debug -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64) + run: python3 make.py -compiler ios -config Release -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64 nosimd) + run: python3 make.py -compiler ios -config Release -build -nosimd + emscripten: + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Cache emsdk + uses: actions/cache@v1 + id: 'emsdk' + with: + path: 'emsdk-cache' + key: ${{ runner.os }}-emsdk-1.39.11 + - name: Setup emsdk + uses: mymindstorm/setup-emsdk@v7 + with: + version: 1.39.11 + no-cache: true + actions-cache-folder: 'emsdk-cache' + - name: Cache emsdk build artifacts + uses: actions/cache@v1 + id: 'emsdk-build-artifacts' + with: + path: '~/.emscripten_cache' + key: ${{ runner.os }}-emsdk-build-artifacts-1.39.11 + - name: Build (debug) + run: python3 make.py -compiler emscripten -config debug -build +# Don't run debug unit tests, they are too slow and sometime time out +# - name: Unit tests (debug) +# run: python3 make.py -compiler emscripten -config debug -unit_test + - name: Clean + run: python3 make.py -clean + - name: Build (release) + run: python3 make.py -compiler emscripten -config release -build + - name: Unit tests (release) + run: python3 make.py -compiler emscripten -config release -unit_test diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/workflows/sonarcloud.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..31b1066b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.github/workflows/sonarcloud.yml @@ -0,0 +1,42 @@ +name: sonarcloud + +on: + push: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + pull_request: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + +jobs: + sonarcloud: + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup build wrapper + run: | + wget -q https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip + unzip build-wrapper-linux-x86.zip + - name: Setup Sonar Scanner + run: | + wget -q https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.2.0.1873-linux.zip + unzip sonar-scanner-cli-4.2.0.1873-linux.zip + - name: Setup compiler + run: ./tools/setup_linux_compiler.sh clang5 + - name: Build with wrapper + run: ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw_output python3 make.py -compiler clang5 -config Release -cpu x64 -build + - name: Run Sonar Scanner and upload to Sonarcloud + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io + run: ./sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.gitignore b/Plugins/ACLPlugin/Source/ThirdParty/acl/.gitignore new file mode 100644 index 00000000..aaf1552b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.gitignore @@ -0,0 +1,10 @@ +# Temporary data rules +build/ +*.suo +**/__pycache__/ +.vscode/ + +# Test data rules +test_data/*.zip +test_data/test_data* +test_data/decomp_data* diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/.gitmodules b/Plugins/ACLPlugin/Source/ThirdParty/acl/.gitmodules new file mode 100644 index 00000000..dde15019 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/.gitmodules @@ -0,0 +1,14 @@ +[submodule "external/sjson-cpp"] + path = external/sjson-cpp + url = https://github.com/nfrechette/sjson-cpp + branch = develop +[submodule "external/catch2"] + path = external/catch2 + url = https://github.com/catchorg/Catch2 +[submodule "external/rtm"] + path = external/rtm + url = https://github.com/nfrechette/rtm.git + branch = develop +[submodule "external/benchmark"] + path = external/benchmark + url = https://github.com/google/benchmark diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/CHANGELOG.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/CHANGELOG.md new file mode 100644 index 00000000..54ef8e4a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/CHANGELOG.md @@ -0,0 +1,174 @@ +# Significant changes per release + +## 2.0.4 + +* Disable versioned namespace by default to avoid breaking ABI in patch release + +## 2.0.3 + +* Update sjson-cpp to v0.8.2 +* Update rtm to v2.1.3 +* Add versioned namespace to allow multiple versions to coexist within a binary +* Fix database sampling interpolation when using a rounding mode other than `none` +* Other minor fixes + +## 2.0.2 + +* Fix potential heap corruption when stripping a database that isn't split +* Fix database tier bulk data size to include its padding + +## 2.0.1 + +* Fix incorrect seek offset when seeking past 0.0 in a single frame clip + +## 2.0.0 + +* Unified and cleaned up APIs +* Cleaned up naming convention to match C++ stdlib, boost +* Introduced streaming database support +* Decompression profiling now uses Google Benchmark +* Decompression has been heavily optimized +* Compression has been heavily optimized +* First release to support backwards compatibility going forward +* Migrated all math to Realtime Math +* Clips now support 4 billion samples/tracks +* WebAssembly support added through emscripten +* Many other improvements + +## 1.3.5 + +* Gracefully fail compression when we have more than 50000 samples +* Update Catch2 to 2.13.1 to work with latest CMake + +## 1.3.4 + +* Avoid assert when using an additive base with a static pose/single frame + +## 1.3.3 + +* Fix single track decompression when scale is present with more than one segment +* Gracefully fail compression when we have more than 65535 samples + +## 1.3.2 + +* Fix crash when compressing with an empty track array +* Strip unused code when stat logging isn't required +* Fix CompressedClip hash to be deterministic + +## 1.3.1 + +* Fix bug with scalar track decompression where garbage could be returned +* Fix scalar track quantization to properly check the resulting error +* Fix scalar track creation and properly copy the sample size +* Other minor fixes and improvements + +## 1.3.0 + +* Added support for VS2019, GCC 9, clang7, and Xcode 11 +* Updated sjson-cpp and added a dependency on Realtime Math (RTM) +* Optimized compression and decompression significantly +* Added support for multiple root bones +* Added support for scalar track compression +* Many bug fixes and improvements + +## 1.2.1 + +* Silence SSE floating point exceptions during compression +* Minor fixes + +## 1.2.0 + +* Added support for GCC 8, clang 6, Xcode 10, and Windows ARM64 +* Updated catch2 and sjson-cpp +* Integrated SonarCloud +* Added a compression level setting and changed default to *Medium* +* Various bug fixes, minor optimizations, and cleanup + +## 1.1.0 + +* Added proper ARM NEON support +* Properly detect SSE2 with MSVC if AVX isn't used +* Lots of decompression performance optimizations +* Minor fixes and cleanup + +## 1.0.0 + +* Minor additions to fully support UE4 +* Minor cleanup + +## 0.8.0 + +* Improved error handling +* Added additive clip support +* Added `acl_decompressor` tool to profile and test decompression +* Increased warning levels to highest possible +* Many more improvements and fixes + +## 0.7.0 + +* Added full support for Android and iOS +* Added support for GCC6 and GCC7 on Linux +* Downgraded C++ version to from 14 to 11 +* Added regression tests +* Added lots of unit tests for core and math headers +* Many more improvements and fixes + +## 0.6.0 + +* Hooked up continuous build integration +* Added support for VS2017 on Windows +* Added support for GCC5, Clang4, and Clang5 on Linux +* Added support for Xcode 8.3 and Xcode 9.2 on OS X +* Added support for x86 on Windows, Linux, and OS X +* Better handle scale with built in error metrics +* Many more improvements and fixes + +## 0.5.0 + +* Added support for 3D scale +* Added partial support for Android (works in Unreal 4.15) +* A fix to the variable bit rate optimization algorithm +* Added a CLA system +* Refactoring to support multiple algorithms better +* More changes and additions to stat logging +* Many more improvements and fixes + +## 0.4.0 + +* Lots of math performance, accuracy, and consistency improvements +* Implemented segmenting support in uniformly sampled algorithm +* Range reduction per segment support added +* Minor fixes to fbx2acl +* Optimized variable quantization considerably +* Major changes to which stats are dumped and how they are processed +* Many more improvements and fixes + +## 0.3.0 + +* Added CMake support +* Improved error measuring and accuracy +* Improved variable quantization +* Convert most math to float32 to improve accuracy and performance +* Many more improvements and fixes + +## 0.2.0 + +* Added clip_writer to create ACL files from a game integration +* Added some unit tests and moved them into their own project +* Added basic per track variable quantization +* Added CMake support +* Lots of cleanup, minor changes, and fixes + +## 0.1.0 + +Initial release! + +* Uniformly sampled algorithm +* Various rotation and vector formats +* Clip range reduction +* ACL SJSON file format +* Custom allocator interface +* Assert overrides +* Custom math types and functions +* Various tools to test the library +* Visual Studio 2015 supported, x86 and x64 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/CMakeLists.txt new file mode 100644 index 00000000..92256ce4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required (VERSION 3.2) +project(acl NONE) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") + +include(CMakeUtils) +include(CMakeCompiler) +include(CMakePlatforms) + +set(USE_AVX_INSTRUCTIONS false CACHE BOOL "Use AVX instructions") +set(USE_POPCNT_INSTRUCTIONS false CACHE BOOL "Use POPCOUNT instructions") +set(USE_SIMD_INSTRUCTIONS true CACHE BOOL "Use SIMD instructions") +set(USE_SJSON true CACHE BOOL "Use SJSON") +set(CPU_INSTRUCTION_SET false CACHE STRING "CPU instruction set") +set(BUILD_BENCHMARK_EXE false CACHE BOOL "Enable the benchmark projects") + +set(TEST_DATA_DIR "" CACHE STRING "The full path of the regression test data directory") +set(DECOMP_DATA_DIR "" CACHE STRING "The full path of the decompression data directory") + +if(CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES Debug Release) + set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING "Reset the configurations to what we need" FORCE) +endif() + +# Determine if we should include unit tests +set(INCLUDE_UNIT_TESTS true) + +if(PLATFORM_IOS) + # Catch2 2.13 doesn't compile on iOS with Xcode 8 and 9 + if(PLATFORM_XCODE_VERSION MATCHES "^8\\.") + set(INCLUDE_UNIT_TESTS false) + elseif(PLATFORM_XCODE_VERSION MATCHES "^9\\.") + set(INCLUDE_UNIT_TESTS false) + endif() +endif() + +# Grab all of our include files +file(GLOB_RECURSE ACL_INCLUDE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/includes/*.h + ${PROJECT_SOURCE_DIR}/docs/*.md + ${PROJECT_SOURCE_DIR}/cmake/*.cmake + ${PROJECT_SOURCE_DIR}/tools/release_scripts/*.py + ) + +create_source_groups("${ACL_INCLUDE_FILES}" ${PROJECT_SOURCE_DIR}) + +file(GLOB ACL_ROOT_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.md + ${PROJECT_SOURCE_DIR}/*.py) + +# Add every natvis file +file(GLOB_RECURSE NATVIS_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.natvis) + +if(MSVC) + # With MSVC, the custom target creates a Utility project. Even though it isn't a proper C++ project + # that compiles into something, MSVC will still parse the code and report intellisense information. + # A utility project doesn't have an editable field in its properties to add an include directory + # but adding it anyway like this is enough to make sure the headers are found by intellisense. + include_directories("${PROJECT_SOURCE_DIR}/includes") +endif() + +# Create a dummy target so they show up in the IDE +add_custom_target(${PROJECT_NAME} SOURCES ${ACL_INCLUDE_FILES} ${ACL_ROOT_FILES} ${NATVIS_FILES}) + +# Enable CTest +enable_testing() + +# Add other projects +add_subdirectory("${PROJECT_SOURCE_DIR}/tools/acl_compressor") + +if(BUILD_BENCHMARK_EXE) + add_subdirectory("${PROJECT_SOURCE_DIR}/tools/acl_decompressor") +endif() + +if(INCLUDE_UNIT_TESTS) + add_subdirectory("${PROJECT_SOURCE_DIR}/tests") +endif() + +# Custom regression testing (requires SJSON support) +if(USE_SJSON) + if(PLATFORM_ANDROID) + add_subdirectory("${PROJECT_SOURCE_DIR}/tools/regression_tester_android") + elseif(PLATFORM_IOS) + add_subdirectory("${PROJECT_SOURCE_DIR}/tools/regression_tester_ios") + endif() +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/CODE_OF_CONDUCT.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..48fcffad --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at zeno490@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/CONTRIBUTING.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/CONTRIBUTING.md new file mode 100644 index 00000000..4cd10b54 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# How to contribute + +Thank you for your interest in the Animation Compression Library! All contributions that follow our guidelines below and abide by our [code of conduct](CODE_OF_CONDUCT.md) are welcome. Together we can rid the video game world of slippery feet and jittery hands! + +In this document you will find relevant reading material, what contributions we are looking for, and what we are *not* looking for. + +Project contact email: zeno490@gmail.com + +# Getting set up + +See the [getting started](./docs/getting_started.md) section for details on how to generate project solutions, build, and run the unit tests. + +Every pull request should trigger continuous integration on every platform we support and most of them will also execute the unit tests automatically (except on Android and iOS). + +The project roadmap for the next few milestones is tracked with [GitHub issues](https://github.com/nfrechette/acl/issues). [Backlog issues](https://github.com/nfrechette/acl/milestone/4) are things I would like to get done eventually but that have not been prioritized yet. + +Whether you create an issue or a pull request, I will do my best to comment or reply within 48 hours. + +# Contributions we are looking for + +Several issues already have a [help wanted](https://github.com/nfrechette/acl/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label. Those tasks should be either reasonably simple or tasks that I do not think I will get the chance to get to very soon. + +Feature requests are welcome providing that they fit within the project scope. For smaller features, you are welcome to create an issue with as much relevant information as you can. If it makes sense, I will prioritize it or add it to the backlog, and if I feel it is beyond the scope of the project, I will tell you why and close the issue. For larger research topics to investigate, the best way to move forward is to reach out by email. I do want to pursue several larger topics for research but it does not make sense to maintain each one of them within the main branches. Some might ultimately be included but those type of contributions are best done in a parallel research branch. + +# Contributions we are *not* looking for + +A lot of older compilers do not properly support **C++11** and there is no plan to support them. This also applies to platforms that are either not mainstream or that we cannot easily test with continuous integration. If you would like to support such an exotic environment, reach out by email first so we can discuss this. + +This library's focus is solely on animation compression. There is no plan to include blending of animation poses or state machines to drive that process. Other middlewares and libraries already cover that field reasonably well. + +If you aren't sure, don't be afraid to reach out by email or on discord! diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/LICENSE b/Plugins/ACLPlugin/Source/ThirdParty/acl/LICENSE new file mode 100644 index 00000000..4767cb74 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/README.md new file mode 100644 index 00000000..08d69cc7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/README.md @@ -0,0 +1,114 @@ +[![CLA assistant](https://cla-assistant.io/readme/badge/nfrechette/acl)](https://cla-assistant.io/nfrechette/acl) +[![All Contributors](https://img.shields.io/github/all-contributors/nfrechette/acl)](#contributors-) +[![Build status](https://ci.appveyor.com/api/projects/status/8h1jwmhumqh9ie3h/branch/develop?svg=true)](https://ci.appveyor.com/project/nfrechette/acl) +[![Build status](https://github.com/nfrechette/acl/workflows/build/badge.svg)](https://github.com/nfrechette/acl/actions) +[![Sonar Status](https://sonarcloud.io/api/project_badges/measure?project=nfrechette_acl&metric=alert_status)](https://sonarcloud.io/dashboard?id=nfrechette_acl) +[![GitHub release](https://img.shields.io/github/release/nfrechette/acl.svg)](https://github.com/nfrechette/acl/releases) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nfrechette/acl/master/LICENSE) +[![Discord](https://img.shields.io/discord/691048241864769647?label=discord)](https://discord.gg/UERt4bS) + +# Animation Compression Library + +Animation compression is a fundamental aspect of modern video game engines. Not only is it important to keep the memory footprint down but it is also critical to keep the animation clip sampling performance fast. + +The more memory an animation clip consumes, the slower it will be to sample it and extract a character pose at runtime. For these reasons, any game that attempts to push the boundaries of what the hardware can achieve will at some point need to implement some form of animation compression. + +While some degree of compression can easily be achieved with simple tricks, achieving high compression ratios, fast decompression, while simultaneously not compromising the accuracy of the resulting compressed animation requires a great deal of care. + +## Goals + +This library has four primary goals: + +* Implement state of the art and production ready animation compression algorithms +* Be easy to integrate into modern video game engines +* Serve as a benchmark to compare various techniques against one another +* Document what works and doesn't work + +Algorithms are optimized with a focus on (in this particular order): + +* Minimizing the compression artifacts in order to reach high cinematographic quality +* Fast decompression on all our supported hardware +* A small memory footprint to lower memory pressure at runtime as well as reducing disk and network usage + +Decompression speed will not be sacrificed for a smaller memory footprint nor will accuracy be compromised under any circumstances. + +## Philosophy + +Much thought was put into designing the library for it to be as flexible and powerful as possible. To this end, the following decisions were made: + +* The library consists of **100% C++11** header files and is thus easy to integrate in any game engine +* [An intermediary clip format](./docs/the_acl_file_format.md) is supported in order to facilitate debugging and bug reporting +* All allocations use a [game provided allocator](./includes/acl/core/iallocator.h) +* All asserts use a [game provided macro](./includes/acl/core/error.h) + +## Supported platforms + +* Windows VS2015 x86 and x64 +* Windows (VS2017, VS2019) x86, x64, and ARM64 +* Windows VS2019 with clang9 x86 and x64 +* Linux (gcc 5 to 10) x86 and x64 +* Linux (clang 4 to 11) x86 and x64 +* OS X (Xcode 10.3) x86 and x64 +* OS X (Xcode 11.2) x64 +* Android (NDK 21) ARMv7-A and ARM64 +* iOS (Xcode 10.3, 11.2) ARM64 +* Emscripten (1.39.11) WASM + +The above supported platform list is only what is tested every release but if it compiles, it should run just fine. + +Note: *VS2017* and *VS2019* compile with *ARM64* on *AppVeyor* but I have no device to test them with. + +The [Unreal Engine](https://www.unrealengine.com/en-US/blog) is supported through a plugin found [here](https://github.com/nfrechette/acl-ue4-plugin). + +## Getting started + +This library is **100%** headers as such you just need to include them in your own project to start using it. However, if you wish to run the unit tests, regression tests, to contribute to ACL or use it for research, head on over to the [getting started](./docs/getting_started.md) section in order to setup your environment and make sure to check out the [contributing guidelines](CONTRIBUTING.md). + +If you would like to integrate ACL into your own game engine, follow the integration instructions [here](./docs#how-to-integrate-the-library). + +## Performance metrics + +* [Carnegie-Mellon University database performance](./docs/cmu_performance.md) +* [Paragon database performance](./docs/paragon_performance.md) +* [Matinee fight scene performance](./docs/fight_scene_performance.md) +* [Decompression performance](./docs/decompression_performance.md) + +## External dependencies + +You don't need anything else to get started: everything is self contained. +See [here](./external) for details. + +## License, copyright, and code of conduct + +This project uses the [MIT license](LICENSE). + +Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + +

CodyDWJones

💻 🔣 🚧 🔧 🚇 🤔

Meradrin

💻

Martin Turcotte

💻 🔧 🤔

vjeffh

💻

Romain-Piquois

🐛

Michał Janiszewski

💻 🔧 🚧 🚇

Raymond Barbiero

🤔
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/appveyor.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/appveyor.yml new file mode 100644 index 00000000..cfaa8e4a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/appveyor.yml @@ -0,0 +1,44 @@ +version: 2.0.4.{build} + +environment: + PYTHON: "C:\\Python36-x64\\python.exe" + matrix: + - toolchain: msvc + - toolchain: clang + +image: + - Visual Studio 2015 + - Visual Studio 2017 + - Visual Studio 2019 + +configuration: + - Debug + - Release + +platform: + - x86 + - x64 + - arm64 + +matrix: + exclude: + - image: Visual Studio 2015 + platform: arm64 + - image: Visual Studio 2015 + toolchain: clang + - image: Visual Studio 2017 + toolchain: clang + - image: Visual Studio 2019 + platform: arm64 + toolchain: clang + - image: Previous Visual Studio 2019 + platform: arm64 + toolchain: clang + +install: +- cmd: >- + git submodule update --init --recursive + +build_script: +- cmd: >- + .\tools\appveyor_ci.bat "%APPVEYOR_BUILD_WORKER_IMAGE%" %platform% %configuration% %toolchain% "%PYTHON%" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakeCompiler.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakeCompiler.cmake new file mode 100644 index 00000000..43d971be --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakeCompiler.cmake @@ -0,0 +1,76 @@ +cmake_minimum_required (VERSION 3.2) + +macro(setup_default_compiler_flags _project_name) + if(MSVC) + # Replace some default compiler switches and add new ones + STRING(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI + STRING(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Bump warnings to W4 + target_compile_options(${_project_name} PRIVATE /Zi) # Add debug info + target_compile_options(${_project_name} PRIVATE /Oi) # Generate intrinsic functions + target_compile_options(${_project_name} PRIVATE /WX) # Treat warnings as errors + target_compile_options(${_project_name} PRIVATE /MP) # Enable parallel compilation + + if(MSVC_VERSION GREATER 1900) + # VS2017 and above + target_compile_options(${_project_name} PRIVATE /permissive-) + endif() + + if(USE_SIMD_INSTRUCTIONS) + if(USE_AVX_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "/arch:AVX") + endif() + else() + add_definitions(-DRTM_NO_INTRINSICS) + endif() + + if(USE_POPCNT_INSTRUCTIONS) + add_definitions(-DACL_USE_POPCOUNT) + endif() + + # Add linker flags + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") + else() + # TODO: Handle OS X properly: https://stackoverflow.com/questions/5334095/cmake-multiarchitecture-compilation + if(CPU_INSTRUCTION_SET MATCHES "x86") + target_compile_options(${_project_name} PRIVATE "-m32") + target_link_libraries(${_project_name} PRIVATE "-m32") + elseif(CPU_INSTRUCTION_SET MATCHES "x64") + target_compile_options(${_project_name} PRIVATE "-m64") + target_link_libraries(${_project_name} PRIVATE "-m64") + endif() + + if(CPU_INSTRUCTION_SET MATCHES "x86" OR CPU_INSTRUCTION_SET MATCHES "x64") + if(USE_SIMD_INSTRUCTIONS) + if(USE_AVX_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "-mavx") + target_compile_options(${_project_name} PRIVATE "-mbmi") + else() + target_compile_options(${_project_name} PRIVATE "-msse4.1") + endif() + else() + add_definitions(-DRTM_NO_INTRINSICS) + endif() + + if(USE_POPCNT_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "-mpopcnt") + endif() + endif() + + target_compile_options(${_project_name} PRIVATE -Wall -Wextra) # Enable all warnings + target_compile_options(${_project_name} PRIVATE -Wshadow) # Enable shadowing warnings + target_compile_options(${_project_name} PRIVATE -Werror) # Treat warnings as errors + + if (PLATFORM_EMSCRIPTEN) + # Remove '-g' from compilation flags since it sometimes crashes the compiler + string(REPLACE "-g" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + string(REPLACE "-g" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + else() + target_compile_options(${_project_name} PRIVATE -g) # Enable debug symbols + endif() + + # Link against the atomic library with x86 clang 4 and 5 + if(CPU_INSTRUCTION_SET MATCHES "x86" AND CLANG_VERSION_MAJOR VERSION_LESS 5 AND NOT PLATFORM_OSX) + target_link_libraries(${_project_name} PRIVATE "-latomic") + endif() + endif() +endmacro() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakePlatforms.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakePlatforms.cmake new file mode 100644 index 00000000..a2b440db --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakePlatforms.cmake @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.2) + +if(PLATFORM_NAME) + return() # Already set +endif() + +# Detect which platform we have +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(PLATFORM_WINDOWS 1) + set(PLATFORM_NAME "Windows") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + if(PLATFORM_IOS) + set(PLATFORM_NAME "iOS") + else() + set(PLATFORM_OSX 1) + set(PLATFORM_NAME "OS X") + endif() + + # Get the Xcode version being used. + execute_process(COMMAND xcodebuild -version + OUTPUT_VARIABLE PLATFORM_XCODE_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX MATCH "Xcode [0-9\\.]+" PLATFORM_XCODE_VERSION "${PLATFORM_XCODE_VERSION}") + string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" PLATFORM_XCODE_VERSION "${PLATFORM_XCODE_VERSION}") + message(STATUS "Building with Xcode version: ${PLATFORM_XCODE_VERSION}") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(PLATFORM_LINUX 1) + set(PLATFORM_NAME "Linux") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") + set(PLATFORM_ANDROID 1) + set(PLATFORM_NAME "Android") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + set(PLATFORM_EMSCRIPTEN 1) + set(PLATFORM_NAME "Emscripten") +else() + message(FATAL_ERROR "Unknown platform ${CMAKE_SYSTEM_NAME}!") +endif() + +message(STATUS "Detected platform: ${PLATFORM_NAME}") diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakeUtils.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakeUtils.cmake new file mode 100644 index 00000000..a54c84fd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/CMakeUtils.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required (VERSION 3.2) + +# Create groups for the test source files, this creates the proper directory structure under the +# Visual Studio filters +macro(create_source_groups _source_files _relative_directory) + foreach(_file IN ITEMS ${_source_files}) + get_filename_component(_file_path "${_file}" PATH) + file(RELATIVE_PATH _file_path_rel "${_relative_directory}" "${_file_path}") + string(REPLACE "/" "\\" _group_path "${_file_path_rel}") + string(REPLACE "..\\" "" _group_path "${_group_path}") + source_group("${_group_path}" FILES "${_file}") + endforeach() +endmacro() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/Toolchain-Android.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/Toolchain-Android.cmake new file mode 100644 index 00000000..1644cf49 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/Toolchain-Android.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.2) + +# For Android, we just set the platform name as we won't be using CMake to build anything. +# Instead Gradle is used through CMake. + +set(PLATFORM_ANDROID 1) +set(PLATFORM_NAME "Android") + +# Remap our CPU instruction set +if(CPU_INSTRUCTION_SET MATCHES "armv7") + set(CPU_INSTRUCTION_SET "armeabi-v7a") +elseif(CPU_INSTRUCTION_SET MATCHES "arm64") + set(CPU_INSTRUCTION_SET "arm64-v8a") +endif() + +# Set our misc asset directory +set(ACL_ANDROID_MISC_DIR ${CMAKE_SOURCE_DIR}/tools/android_misc) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/Toolchain-iOS.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/Toolchain-iOS.cmake new file mode 100644 index 00000000..115bf2fb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/cmake/Toolchain-iOS.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required (VERSION 3.2) + +set(CMAKE_SYSTEM_NAME Darwin) + +# Set here instead of CMakePlatforms.cmake since we can't distinguis otherwise +set(PLATFORM_IOS 1) + +# Find and set the C/C++ compiler paths, cmake doesn't seem to do this properly on its own +execute_process(COMMAND xcrun --sdk iphoneos --find clang OUTPUT_VARIABLE CMAKE_C_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND xcrun --sdk iphoneos --find clang++ OUTPUT_VARIABLE CMAKE_CXX_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) + +set(CMAKE_MACOSX_BUNDLE YES) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") +set(CMAKE_OSX_SYSROOT iphoneos CACHE STRING "") +set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "") +set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 11.0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/README.md new file mode 100644 index 00000000..d3e02d2c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/README.md @@ -0,0 +1,37 @@ +# Supported algorithms + +ACL aims to support a few core algorithms that are well suited for production use and other algorithms that are interesting to compare against. + +* [Uniformly sampled](algorithm_uniformly_sampled.md) + +# How to integrate the library + +* [Handling asserts](handling_asserts.md) +* [Implementing an allocator](implementing_an_allocator.md) +* [Creating a raw track list](creating_a_raw_track_list.md) +* [Compressing tracks](compressing_raw_tracks.md) +* [Decompressing tracks](decompressing_a_track_list.md) +* [Database and streaming support](database_support.md) +* [Other considerations](misc_integration_details.md) +* [Migrating from an earlier ACL version](migrating.md) + +See how it's integrated into Unreal Engine 4 [here](https://github.com/nfrechette/acl-ue4-plugin)! + +# Performance metrics + +* [Carnegie-Mellon University database performance](cmu_performance.md) +* [Paragon database performance](paragon_performance.md) +* [Matinee fight scene performance](fight_scene_performance.md) +* [Decompression performance](decompression_performance.md) + +# Research + +* [Arithmetic accuracy](research_arithmetic_accuracy.md) + +# Miscellaneous + +* [The ACL file format](the_acl_file_format.md) +* [Rotation and vector formats](rotation_and_vector_formats.md) +* [Range reduction](range_reduction.md) +* [Error metrics](error_metrics.md) +* [Performance graph generation](graph_generation.md) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/additive_clips.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/additive_clips.md new file mode 100644 index 00000000..0a4fc618 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/additive_clips.md @@ -0,0 +1,33 @@ +# Additive animation clips + +Additive animation clips are a core tool within an animation runtime but sadly, how additive animations are blended and applied is not standard. As such, this library aims to support the most common additive modes. Contributions welcome! + +The core logic for additive clips lives [here](../includes/acl/additive_utils.h). + +## Relative additive clips + +Clips in this space are applied on top of their base clip with a simple multiplication: + +`result = additive * base` + +This is the standard way to represent things in local space of something else. For example, every bone is in relative and local space of its parent bone. To convert a bone from local space to its parent space, we multiply them. + +## Additive 0 + +*I couldn't come up with a better name for this, suggestions welcome!* + +Clips in this space have their rotations multiplied with the base, the translations are added, and the scale is combined as follow: + +`scale = additive_scale * base_scale` + +Among others, this mode is used by [ozz-animation](http://guillaumeblanc.github.io/ozz-animation/). + +## Additive 1 + +*I couldn't come up with a better name for this, suggestions welcome!* + +Clips in this space have their rotations multiplied with the base, the translations are added, and the scale is combined as follow: + +`scale = (1.0 + additive_scale) * base_scale` + +This is the additive mode used by Unreal 4. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/algorithm_uniformly_sampled.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/algorithm_uniformly_sampled.md new file mode 100644 index 00000000..b21f3a59 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/algorithm_uniformly_sampled.md @@ -0,0 +1,7 @@ +# Algorithm: uniformly sampled + +The uniformly sampled algorithm is by far the simplest. A clip is sampled at a fixed internal (e.g. 30 FPS) and each sample is retained. Constant tracks are compacted to a single sample and default tracks are dropped. + +Compression is achieved by storing the samples on a reduced number of bits using the various [rotation and vector formats](rotation_and_vector_formats.md). + +Decompression is very fast because the data is uniformly spaced. Each sample is sorted by time and by track to ensure that when we sample a specific point in time, all the relevant samples are contiguous in memory for optimal cache locality during decompression. This also means that the decompression performance is independent of the playback direction. It will be the same when playing forward, backward, and randomly. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/cmu_performance.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/cmu_performance.md new file mode 100644 index 00000000..90f3cf3e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/cmu_performance.md @@ -0,0 +1,44 @@ +# Carnegie-Mellon University database performance + +| | v2.0.0 | v1.3.0 | v1.2.0 | +| --------------------- | ---------- | ---------- | ---------- | +| **Compressed size** | 72.14 MB | 71.00 MB | 71.01 MB | +| **Compression ratio** | 19.81 : 1 | 20.13 : 1 | 20.13 : 1 | +| **Compression time** | 1m 55s | 2m 22.3s | 6m 9.71s | +| **Compression speed** | 12727.73 KB/sec | 10285.52 KB/sec | 3958.99 KB/sec | +| **Max error** | 0.0725 cm | 0.0725 cm | 0.0725 cm | +| **Error 99th percentile** | 0.0089 cm | 0.0089 cm | 0.0089 cm | +| **Samples below error threshold** | 99.86 % | 99.86 % | 99.86 % | + +*Note: Starting with v1.3.0 measurements have been made with a Ryzen 2950X CPU while prior versions used an Intel i7 6850K.* + +# Data and method used + +To compile the statistics, the [animation database from Carnegie-Mellon University](http://mocap.cs.cmu.edu/) is used. +The raw animation clips in FBX form can be found on the Unity asset store [here](https://www.assetstore.unity3d.com/en/#!/content/19991). +They were converted to the [ACL file format](the_acl_file_format.md) using the [fbx2acl](../tools/fbx2acl) script. Data available upon request, it is far too large for GitHub. + +* Number of clips: **2534** +* Sample rate: **24 FPS** +* Total duration: **9h 49m 37.58s** +* Raw size: **1429.38 MB** (10x float32 * num bones * num samples) + +ACL supports various compression methods but only the overall best variant will be tracked here (see `get_default_compression_settings()` for details). + +The error is measured **3cm** away from each bone to simulate the visual mesh skinning process as described [here](error_metrics.md) with the default error threshold of **0.01cm**. The **99th** percentile and the number of samples below the error threshold are calculated by measuring the error on every bone at every sample. + +The performance of ACL in Unreal Engine 4 is tracked by the plugin [here](https://github.com/nfrechette/acl-ue4-plugin/blob/develop/Docs/cmu_performance.md). + +# Results in images + +![Compression ratio VS max error per clip](images/acl_cmu_compression_ratio_vs_max_error.png) + +![Compression ratio distribution](images/acl_cmu_compression_ratio_distribution.png) + +![Max error distribution](images/acl_cmu_max_error_distribution.png) + +![Distribution of the error for every bone at every key frame](images/acl_cmu_exhaustive_error.png) + +![Distribution of the error for every bone at every key frame (top 10%)](images/acl_cmu_exhaustive_error_top_10.png) + +![Distribution of clip durations](images/acl_cmu_clip_durations.png) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/compressing_raw_tracks.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/compressing_raw_tracks.md new file mode 100644 index 00000000..cc85335c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/compressing_raw_tracks.md @@ -0,0 +1,53 @@ +# Compressing scalar tracks + +Once you have created a [raw track list](creating_a_raw_track_list.md) and an [allocator instance](implementing_an_allocator.md), you are ready to compress it. + +For now, we only implement a single algorithm: [uniformly sampled](algorithm_uniformly_sampled.md). This is a simple and excellent algorithm to use for everyday animation clips. + +## Compressing scalar tracks + +*Compression settings are currently required as an argument but not used by scalar tracks. It is a placeholder.* +*Segmenting is currently not supported by scalar tracks.* + +```c++ +#include + +using namespace acl; + +compression_settings settings; + +OutputStats stats; +compressed_tracks* out_compressed_tracks = nullptr; +ErrorResult result = compress_track_list(allocator, raw_track_list, settings, out_compressed_tracks, stats); +``` + +## Compressing transform tracks + +The compression level used will dictate how much time to spend optimizing the variable bit rates. Lower levels are faster but produce a larger compressed size. + +While we support various [rotation and vector quantization formats](rotation_and_vector_formats.md), the *variable* variants are generally the best. It is safe to use them for all your clips but if you do happen to run into issues with some exotic clips, you can easily fallback to less aggressive variants. + +Selecting the right [error metric](error_metrics.md) is important and you will want to carefully pick the one that best approximates how your game engine performs skinning. + +The last important setting to choose is the `error_threshold`. This is used in conjunction with the error metric and the virtual vertex distance (shell distance) in order to guarantee that a certain quality is maintained. A default value of **0.01cm** is safe to use and it most likely should never be changed unless the units you are using differ. If you do run into issues where compression artifacts are visible, in all likelihood the virtual vertex distance used on the problematic bones is not conservative enough. + +```c++ +#include + +using namespace acl; + +compression_settings settings; +settings.level = compression_level8::medium; +settings.rotation_format = rotation_format8::quatf_drop_w_variable; +settings.translation_format = vector_format8::vector3f_variable; +settings.scale_format = vector_format8::vector3f_variable; + +qvvf_transform_error_metric error_metric; +settings.error_metric = &error_metric; + +output_stats stats; +compressed_tracks* out_compressed_tracks = nullptr; +error_result result = compress_track_list(allocator, raw_track_list, settings, out_compressed_tracks, stats); +``` + +You can also query the current default and recommended settings with this function: `get_default_compression_settings()`. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/creating_a_raw_track_list.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/creating_a_raw_track_list.md new file mode 100644 index 00000000..073ba5e0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/creating_a_raw_track_list.md @@ -0,0 +1,84 @@ +# Creating a raw track list + +Once you have an [allocator instance](implementing_an_allocator.md), the next step is to create a raw track list and populate it with the data we will want to compress. Doing so is fairly straight forward and you will only need a few things: + +* [An allocator instance](implementing_an_allocator.md) +* The number of tracks (a track array cannot be resized) +* The number of samples per track (each track has the same number of samples) +* The rate at which samples are recorded (e.g. 30 == 30 FPS) +* Access to the [Realtime Math](../external/README.md) headers since its types are used by the API + +```c++ +#include + +using namespace acl; + +uint32_t num_tracks = 4; +uint32_t num_samples_per_track = 20; +float sample_rate = 30.0f; +track_array_float3f raw_track_list(allocator, num_tracks); +``` + +Once you have created an instance, simply populate the track data. Note that at the moment, tracks contained in the array are untyped but they must all have the same track type (e.g. `float2f`), the same number of samples per track, and the same sample rate. + +Each track requires a track description. It contains a number of important properties: + +* `output_index`: after compression, this is the index of the track. This allows re-ordering for LOD processing and other similar use cases. +* `parent_index`: for transform tracks, indicates the parent transform index it is relative to (in local space of). +* `precision`: the precision we aim to attain when optimizing the bit rate. The resulting compression error is nearly guaranteed to be below this threshold. +* `shell_distance`: for transform tracks, indicates the distance at which we measure the error, see [error metric function](error_metrics.md). + +```c++ +track_desc_scalarf desc0; +desc0.output_index = 0; +desc0.precision = 0.001F; + +track_desc_transformf desc1; +desc1.output_index = 0; +desc1.parent_index = 0; +desc1.precision = 0.01F; +desc1.shell_distance = 3.0F; +``` + +Tracks can be created in one of four ways: + +* `make_copy(..)`: creates a new track and copies the data internally. The new instance owns the memory and the original data is no longer required. +* `make_reserve(..)`: creates a new track and allocates the required memory without initializing it. It owns the new allocated memory. +* `make_owner(..)`: creates a new track and takes ownership of the provided source pointer. No copy takes place. +* `make_ref(..)`: creates a new track and references the provided source data. No copy takes place and the new instance does not own the referenced memory. + +`make_owner(..)` and `make_ref(..)` are handy when the track data is already allocated somewhere and doesn't need to be copied explicitly. Functions that take source data as input also support a custom `stride` and make no assumption about the data layout for utmost flexibility. + +```c++ +track_float3f raw_track0 = track_float3f::make_reserve(desc0, allocator, num_samples, sample_rate); +raw_track0[0] = rtm::float3f{ 1.0F, 3123.0F, 315.13F }; +raw_track0[1] = rtm::float3f{ 2.333F, 321.13F, 31.66F }; +raw_track0[2] = rtm::float3f{ 3.123F, 81.0F, 913.13F }; +raw_track0[3] = rtm::float3f{ 4.5F, 91.13F, 41.135F }; +// ... +raw_track_list[0] = std::move(raw_track0); +``` + +Once your raw track list has been populated with data, it is ready for [compression](compressing_raw_tracks.md). The data contained within the `track_array` will be read-only. + +## Additive animation clips + +If the clip you are compressing is an additive clip, you will also need to create an instance for the base clip. Once you have both clip instances, you can compress them together with `compress_track_list(..)`. This will allow you to specify the clip instance that represents the base clip as well as the [format](additive_clips.md) used by the additive clip. + +The library assumes that the raw clip data has already been transformed to be in additive or relative space. + +## Re-ordering or stripping bones + +Sometimes it is desirable to re-order the bones being outputted or strip them altogether. This could be to facilitate LOD support or various forms of skeleton changes without needing to re-import the clips. This is easily achieved by setting the desired `output_index` on each `track_desc_transformf` contained in a `track_qvvf`. The default value is the track index. You can use `k_invalid_track_index` to strip the track from the final compressed output. + +*Note that each `output_index` needs to be unique and there can be no gaps. If **20** bones are outputted, the indices must run from **[0 .. 20)**.* + +## Compressing morph target blend weights + +Curves that drive a morph target (aka blend shape) are not ordinary and require special consideration. They ultimately drive an object space mesh deformation and as such we can leverage that information to reduce the memory footprint and simplify the task of choosing a suitable `precision` value. + +If you have access to the morph target deformation information, you can calculate the largest vertex displacement for a given curve. Once you have that value, use a morph target deformation precision value such as **0.1 mm** and use the maximum vertex displacement to calculate the desired blend weight `precision` value like this: + +`blend weight precision = vertex precision / vertex displacement delta` + +See [here](https://nfrechette.github.io/2020/05/04/morph_target_compresion/) for more details. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/database_support.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/database_support.md new file mode 100644 index 00000000..25b3fb0e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/database_support.md @@ -0,0 +1,70 @@ +# Database and streaming support + +ACL 2.0 now supports compressing multiple animation clips into a single database to facilitate streaming as well as data stripping. See [here](https://nfrechette.github.io/2021/01/17/progressive_animation_streaming/) for details on what it can do and how it works. + +See [here](https://github.com/nfrechette/acl-ue4-plugin) to take a look at how the UE4 plugin supports it. + +## How to build a database + +In order to be able to build a database with the right key frames, metadata must be added during compression. To do so, make sure to enable `include_contributing_error` inside your `acl::compression_settings`. + +```c++ +acl::compression_settings settings = acl::get_default_compression_settings(); +settings.include_contributing_error = true; +``` + +This will add metadata to the compressed byte stream which will later be stripped once the database is created. Its footprint is only temporary as it isn't needed at runtime when decompressing. + +The database creation API is exposed in [acl/compression/compress.h](../includes/acl/compression/compress.h). + +First, use `build_database(..)` to create a database. It takes as input the compressed animation clips you wish to merge and it will output new compressed animation clips and the database they are bound to. All of these buffers are binary blobs and can be moved around with `std::memcpy` safely. The only requirement is that they be 16 bytes aligned. + +Once the database is created, its bulk data (the part that can be optionally streamed) will be part of the database byte buffer. To strip it into a separate buffer that can be omitted or streamed later, use `split_database_bulk_data(..)`. This will output a new database along with its two bulk data buffers. + +If some quality tiers aren't necessary on your platform of choice (e.g. mobile), you can strip them by calling `strip_database_quality_tier(..)`. The bulk data does not change and if it had been stripped, the stripped tier's buffer can simply be freed. + +## Decompressing with a database + +At runtime, animation clips that are bound to a database can be decompressed without the database. If you attempt to do so, only the data within the clip will be used (lowest visual quality). + +In order to use a database, a [acl::database_context](../includes/decompression/database/database.h) must be created for it. + +```c++ +const acl::compressed_database* database = ... +acl::iallocator& allocator = ... + +acl::database_context database_context; +database_context.initialize(allocator, *database); +``` + +The above will work if the database contains its bulk data inline (if it hasn't been stripped). To support streaming (whether from memory, memory mapped, network, or other IO) a [acl::database_streamer](../includes/acl/decompression/database/database_streamer.h) must be provided. See the linked header for details on how to implement it. + +```c++ +acl::null_database_streamer medium_streamer(medium_data, medium_data_size); +acl::null_database_streamer low_streamer(low_data, low_data_size); + +acl::database_context database_context; +database_context.initialize(allocator, *database, medium_streamer, low_streamer); +``` + +If a quality tier has been stripped, its streamer will never be used and any streamer can be provided. Streamers must live as long as the database does. The streamers are responsible for streaming data in and out. + +When the time comes to decompress, simply provide the database context alongside the compressed tracks data and make sure database support is enabled in your decompression settings (by default that code is stripped). + +```c++ +struct my_decompression_settings : acl::default_transform_decompression_settings +{ + using database_settings_type = default_database_settings; +}; + +const acl::compressed_tracks* compressed_clip_data = ... + +acl::decompression_context context; +context.initialize(compressed_clip_data, database_context); +``` + +The rest of the decompression code remains unchanged. + +It is safe to stream in data while decompression is in progress. Doing so it thread safe. However, only a single stream in/out request can be in flight at a time and streaming out cannot be done while decompression is in progress. + +Once a streamer finishes a read request (e.g. file IO), it can complete the stream request from any thread. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/decompressing_a_track_list.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/decompressing_a_track_list.md new file mode 100644 index 00000000..febfb3c8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/decompressing_a_track_list.md @@ -0,0 +1,38 @@ +# Decompressing a track list + +Once you have a [compressed track list](compressing_raw_tracks.md), the first order of business to decompress it is to select a set of `decompression_settings`. The decompression settings `struct` is populated with `constexpr` functions that can be overridden to turn on or off the code generation per feature. + +The next thing you need is a `decompression_context` instance. This will allow you to actually decompress tracks. You can also use it to seek arbitrarily in the list. For safety, the decompression context is templated with the decompression settings. + +```c++ +#include "acl/decompression/decompress.h" + +using namespace acl; + +decompression_context context; + +context.initialize(*tracks); +context.seek(sample_time, sample_rounding_policy::none); + +// create an instance of 'track_writer' so we can write the output somewhere +context.decompress_track(track_index, my_track_writer); // a single track +context.decompress_tracks(my_track_writer); // all tracks +``` + +As shown, a context must be initialized with a compressed track list instance. Some context objects such as the one used by uniform sampling can be re-used by any compressed track list and does not need to be re-created while others might require this. In order to detect when this might be required, the function `is_dirty(const compressed_tracks& tracks)` is provided. Some context objects cannot be created on the stack and must be dynamically allocated with an allocator instance. The functions `make_decompression_context(...)` are provided for this purpose. + +You can seek anywhere in a track list but you will need to handle looping manually in your game engine. When seeking, you must also provide a `sample_rounding_policy` to dictate how the interpolation is to be performed. See [here](../includes/acl/core/interpolation_utils.h) for details. + +Every decompression function supported by the context is prefixed with `decompress_*`. A `track_writer` is used for optimized output writing. You can implement your own and coerce to your own math types. The type is templated on the `decompress_*` functions in order to be easily inlined. + +The API is the same for scalar and joint transform tracks. For optimal code generation, ensure the decompression settings used are tuned to the expected data. See the header where it is defined for more information. + +## Floating point exceptions + +For performance reasons, the decompression code assumes that the caller has already disabled all floating point exceptions. This avoids the need to save/restore them with every call. ACL provides helpers in [acl/core/floating_point_exceptions.h](..\includes\acl\core\floating_point_exceptions.h) to assist and optionally this behavior can be controlled by overriding `decompression_settings::disable_fp_exeptions()`. + +## Backwards compatibility + +By default, decompression will support every ACL version prior to and including ACL 2.0. If you wish to only support a single version and to strip the unnecessary code, you can specify your own decompression settings struct along with the desired `static constexpr compressed_tracks_version16 version_supported() { return compressed_tracks_version16::any; }` implementation. + +*There is no backwards compatibility support for ACL 1.3 and earlier.* diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/decompression_performance.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/decompression_performance.md new file mode 100644 index 00000000..bcd1fcd2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/decompression_performance.md @@ -0,0 +1,61 @@ +# Decompression performance + +In order to measure the decompression performance, the [acl_decompressor](../tools/acl_decompressor) tool is used to extract the relevant metrics and a [python script](../tools/graph_generation) is used to parse them. + +Here are the clips we measure on: + +* The 42 [regression clips](../test_data) from [CMU](cmu_performance.md) database +* The 5 clips from the [Matinee fight](fight_scene_performance.md) scene + +Note that the data is not yet conveniently packaged. + +Here are the platforms we measure on: + +* Desktop: Intel i7-6850K @ 3.8 GHz (for v1.2 and earlier) +* Desktop: Ryzen 2950X @ 3.5 GHz (for v1.3 and later) +* Laptop: MacBook Pro mid 2014 @ 2.6 GHz +* Phone: Android Pixel 3 @ 2.5 GHz +* Tablet: iPad Pro 10.5 inch @ 2.39 GHz + +We only show a few compilers and architectures to keep the graphs readable. + +**Unless otherwise specified, the results are from release [2.0.0](https://github.com/nfrechette/acl/releases/tag/v2.0.0)** + +## Uniformly sampled algorithm + +The uniformly sampled algorithm offers consistent performance regardless of the playback direction. Shown here is the median performance of `decompress_pose` with a cold CPU cache for **3** clips with forward, backward, and random playback on the *iPad*. + +| Clip Name | Forward | Backward | Random | +| ------------ | --------- | --------- | --------- | +| 104_30 | 0.978 us | 1.010 us | 1.014 us | +| Trooper_1 | 2.432 us | 2.258 us | 2.402 us | +| Trooper_Main | 30.953 us | 30.658 us | 30.776 us | + +As can be seen, the performance is consistent regardless of the playback direction. It also remains consistent regardless of the clip sample rate and the clip playback rate. + +### decompress_pose + +This function decompresses a whole pose in one go. Shown here is forward playback with a cold CPU cache. + +![Uniform decompress_pose CMU Performance](images/acl_decomp_uniform_pose_cmu.png) +![Uniform decompress_pose Matinee Performance](images/acl_decomp_uniform_pose_matinee.png) + +Here is the delta with the previous version: + +![Uniform decompress_pose CMU Speed Delta](images/acl_decomp_delta_uniform_pose_cmu_speed.png) + +![Uniform decompress_pose Matinee Speed Delta](images/acl_decomp_delta_uniform_pose_matinee_speed.png) + +### decompress_bone + +This function decompresses a single bone. To generate the graphs, we measure the cost of decompressing a whole pose one bone at a time. Shown here is forward playback with a cold CPU cache. + +![Uniform decompress_bone CMU Performance](images/acl_decomp_uniform_bone_cmu.png) + +![Uniform decompress_bone Matinee Performance](images/acl_decomp_uniform_bone_matinee.png) + +Here is the delta with the previous version: + +![Uniform decompress_bone CMU Speed Delta](images/acl_decomp_delta_uniform_bone_cmu_speed.png) + +![Uniform decompress_bone Matinee Speed Delta](images/acl_decomp_delta_uniform_bone_matinee_speed.png) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/error_metrics.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/error_metrics.md new file mode 100644 index 00000000..9b5bb097 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/error_metrics.md @@ -0,0 +1,30 @@ +# Transform error metric + +A proper error metric is central to every animation compression algorithm and ACL is no different. The general technique implemented is the one described on this [blog post](https://nfrechette.github.io/2016/11/01/anim_compression_accuracy/) and the various implementations as well as the interfaces live [here](../includes/acl/compression/transform_error_metrics.h). + +Some care must be taken when selecting which error metric to use. If the error it calculates isn't representative of how it would be calculated in the host game engine, the resulting visual fidelity might suffer. ACL implements a number of popular implementations and you are free to implement and use your own. The `qvvf_transform_error_metric` error metric is a sensible default. + +## qvvf_transform_error_metric + +This implementation will use `rtm::qvvf` (which implements Vector-Quaternion-Vector (*VQV*) arithmetic) to calculate the error both when scale is present and when it isn't and for both local and object space. It is a solid default and it should handle very well most clips in the wild. However take note that because it uses a simple `vector3` to represent the 3D scale component, it cannot properly handle skew and shear that arises as a result of combining multiple transforms in a bone chain. In practice it rarely matters and if you truly need this, an error metric that uses matrices when scale is present might perform better or by using Vector-Quaternion-Matrix (*VQM*) arithmetic. + +## qvvf_matrix3x4f_transform_error_metric + +This implementation uses `rtm::qvvf` when there is no scale in both local and object space and as well as local space when there is scale. This is generally safe because there is no skew or shear present in the transform. However, when scale is present the object space error metric will convert the transforms into `rtm::matrix3x4f` in order to combine them into the final object space transform for the bone. This properly handles 3D scale but due to numerical accuracy constraints the error can accumulate to unacceptable levels when very large or very small scale is present and combined with very large translations. + +## additive_qvvf_transform_error_metric + +This implementation is based on the `qvvf_transform_error_metric` and handles additive and regular animation clips. See [here](additive_clips.md) for the various additive operations supported and which one to select for your game engine. + +For the purpose of measuring the error, we ignore object space additive blending. It typically does not play a significant role in the resulting error because the pose is fundamentally very close. As such, the additive clip is always applied in local space to the base clip. + +If your game engine performs additive blending in a way that ACL does not support, you can modify the sources (and hopefully submit a pull request) to include it or you can implement your own error metric. It is fairly simple, just follow the examples already present in the code. + +## Implementing your own error metric + +In order to implement your own error metric you need to figure out how your host game engine combines the local space bone transforms into object space in order to do the same. Once you have that information, implement a class that derives from the `itransform_error_metric` interface. You can use the other error metrics as examples. You will then be able to provide it to the compression algorithm by feeding it to the `CompressionSettings`. + +Fundamentally the interface requires you to implement two versions of the error metric: with or without scale. The variants with no scale are only present as an optimization to avoid unnecessary computation when a clip does not contain any scale. It is common enough to warrant an optimized code path. + +If your error metric uses a different type than `rtm::qvvf`, you can implement the other functions as needed. See the interface for details. + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/fight_scene_performance.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/fight_scene_performance.md new file mode 100644 index 00000000..09f6f2bd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/fight_scene_performance.md @@ -0,0 +1,29 @@ +# Matinee fight scene performance + +| | v2.0.0 | v1.3.0 | v1.2.0 | +| --------------------- | --------- | --------- | --------- | +| **Compressed size** | 8.18 MB | 8.18 MB | 8.77 MB | +| **Compression ratio** | 7.62 : 1 | 7.63 : 1 | 7.11 : 1 | +| **Compression time** | 3.86s | 4.89s | 20.27s | +| **Compression speed** | 16534.75 KB/sec | 13074.59 KB/sec | 3150.43 KB/sec | +| **Max error** | 0.0635 cm | 0.0634 cm | 0.0591 cm | +| **Error 99th percentile** | 0.0201 cm | 0.0201 cm | 0.0382 cm | +| **Samples below error threshold** | 97.91 % | 97.90 % | 94.61 % | + +*Note: Starting with v1.3.0 measurements have been made with a Ryzen 2950X CPU while prior versions used an Intel i7 6850K.* + +# Data and method used + +To compile these statistics, the [Matinee fight scene](https://nfrechette.github.io/2017/10/05/acl_in_ue4/) is used. + +* Number of clips: **5** +* Sample rate: **30 FPS** +* Cinematic duration: **66 seconds** +* *Troopers* 1-4 have **71** bones and the *Main Trooper* has **551** bones +* Raw size: **62.38 MB** (10x float32 * num bones * num samples) + +ACL supports various compression methods but only the overall best variant will be tracked here (see `get_default_compression_settings()` for details). + +The error is measured **3cm** away from each bone to simulate the visual mesh skinning process as described [here](error_metrics.md) with the default error threshold of **0.01cm**. The **99th** percentile and the number of samples below the error threshold are calculated by measuring the error on every bone at every sample. + +The performance of ACL in Unreal Engine 4 is tracked by the plugin [here](https://github.com/nfrechette/acl-ue4-plugin/blob/develop/Docs/fight_scene_performance.md). diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/getting_started.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/getting_started.md new file mode 100644 index 00000000..5b32bcc5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/getting_started.md @@ -0,0 +1,51 @@ +# Getting started + +In order to contribute to ACL and use the various tools provided for development and research, you will first need to setup your environment. + +## Setting up your environment + +### Windows, Linux, and OS X for x86 and x64 + +1. Install *CMake 3.2* or higher (*3.14* for Visual Studio 2019, or *3.10* on OS X with *Xcode 10*), *Python 2.7 or 3*, and the proper compiler for your platform. +2. Execute `git submodule update --init` to get the files of external submodules (e.g. Catch2). +3. Generate the IDE solution with: `python make.py` + The solution is generated under `./build` +4. Build the IDE solution with: `python make.py -build` +5. Run the unit tests with: `python make.py -unit_test` +6. Run the regression tests with: `python make.py -regression_test` + +On all three platforms, *AVX* support can be enabled by using the `-avx` switch. + +### Windows ARM64 + +For *Windows on ARM64*, the steps are identical to *x86 and x64* but you will need *CMake 3.13 or higher* and you must provide the architecture on the command line: `python make.py -compiler vs2017 -cpu arm64` + +### Android + +For *Android*, the steps are identical to *Windows, Linux, and OS X* but you also need to install *Android NDK 21* (or higher). The build uses `gradle` and `-unit_test` as well as `-regression_test` will deploy and run on the device when executed (make sure that the `adb` executable is in your `PATH` for this to work). + +*Android Studio v3.5* can be used to launch and debug. After running *CMake* to build and generate everything, the *Android Studio* projects can be found under the `./build` directory. + +### iOS + +For *iOS*, the steps are identical to the other platforms but due to code signing, you will need to perform the builds from *Xcode* manually. Note that this is only an issue if you attempt to use the tools or the unit tests locally. + +In order to run these manually: + +* Open the *Xcode* project for the appropriate tool +* In the project settings, enable automatic code signing and select your development team +* Build and run on your device + +Note that *iOS* builds have never been tested on an emulator. + +### Emscripten + +Emscripten support currently only has been tested on OS X and Linux. To use it, make sure to install a recent version of Emscripten SDK 1.39.11+. + +## Commit message format + +This library uses the [angular.js message format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) and it is enforced with commit linting through every pull request. + +## Generating the stats and graphs + +See [here](graph_generation.md) to find out how the various statistics and graphs we track are generated. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/graph_generation.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/graph_generation.md new file mode 100644 index 00000000..ce80287d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/graph_generation.md @@ -0,0 +1,31 @@ +# Graph generation + +In order to track our progress and where we stand against the competition, we generate lots of data and turn it into graphs as seen [here](cmu_performance.md) for CMU, [here](paragon_performance.md) for Paragon, and [here](fight_scene_performance.md) for the Matinee fight scene. + +The steps to generate these graphs are detailed here for transparency. + +## Generating the compression performance data + +The first step is to run the compression algorithms in order to extract the raw performance data. This is done by running the [acl_compressor](../tools/acl_compressor) executable. + +We run it on the input data with the associated python script found [here](../tools/acl_compressor/acl_compressor.py). Typical usage looks like this: + +`python acl_compressor.py -acl= -stats= -parallel=4 -csv_summary -csv_bit_rate -csv_error` + +This script generates various CSV files. + +Yet another script automates dumping various permutations and can be found [here](../tools/release_scripts/extract_stats.py). + +## Generating the decompression performance data + +Much like the compression data, we use a custom tool to generate the decompression CSV files. It is fairly simple with the python script found [here](../tools/acl_decompressor/acl_decompressor.py). Typical usage looks like this: + +`python acl_decompressor -stats= -csv [-ios] [-android]` + +The script will generate various CSV files. Note that for *iOS* you must copy the generated SJSON files manually from the device after running the *acl_decompressor* tool manually as well as add the `-ios` switch to the python script, and for *Android* you must add the `-android` switch after running the tool manually. + +## Generating the graphs + +The generated CSV files in the previous steps can be very large and contain lots of things. In order to make sense of it and to make graph generation easier, we use an array of scripts to parse, extract, and format the data into simpler and cleaner CSV files. + +These scripts can be found [here](../tools/graph_generation) along with some instructions on how to run them. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/handling_asserts.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/handling_asserts.md new file mode 100644 index 00000000..d5674fe2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/handling_asserts.md @@ -0,0 +1,47 @@ +# Handling asserts + +This library uses a simple system to handle asserts. Asserts are fatal and must terminate otherwise the behavior is undefined if execution continues. + +A total of 4 behaviors are supported: + +* We can print to `stderr` and `abort` +* We can `throw` and exception +* We can call a custom function +* Do nothing and strip the check at compile time (**default behavior**) + +Everything necessary is implemented in [**acl/core/error.h**](../includes/acl/core/error.h). + +*Note: It is **NOT** possible to have different assert behaviors in two or more C++ files. All the C++ files within your static or dynamic library that reference ACL must all use the same assert handling strategy. In other words, you cannot enable the asserts for compression and disable them for decompression if the code lives within the same static or dynamic library. + +## Aborting + +In order to enable the aborting behavior, simply define the macro `ACL_ON_ASSERT_ABORT`: + +`#define ACL_ON_ASSERT_ABORT` + +## Throwing + +In order to enable the throwing behavior, simply define the macro `ACL_ON_ASSERT_THROW`: + +`#define ACL_ON_ASSERT_THROW` + +Note that the type of the exception thrown is `std::runtime_error`. + +## Custom function + +In order to enable the custom function calling behavior, define the macro `ACL_ON_ASSERT_CUSTOM` with the name of the function to call: + +`#define ACL_ON_ASSERT_CUSTOM on_custom_assert_impl` + +Note that the function signature is as follow: `void on_custom_assert_impl(const char* expression, int line, const char* file, const char* format, ...) {}` + +You can also define your own assert implementation by defining the `ACL_ASSERT` macro as well: + +```c++ +#define ACL_ON_ASSERT_CUSTOM +#define ACL_ASSERT(expression, format, ...) checkf(expression, ANSI_TO_TCHAR(format), #__VA_ARGS__) +``` + +## No checks + +By default if no macro mentioned above is defined, all asserts will be stripped at compile time. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_clip_durations.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_clip_durations.png new file mode 100644 index 00000000..95f8b7a4 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_clip_durations.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_compression_ratio_distribution.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_compression_ratio_distribution.png new file mode 100644 index 00000000..b3dd1efc Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_compression_ratio_distribution.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_compression_ratio_vs_max_error.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_compression_ratio_vs_max_error.png new file mode 100644 index 00000000..05457f8a Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_compression_ratio_vs_max_error.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_exhaustive_error.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_exhaustive_error.png new file mode 100644 index 00000000..f3e7dd8f Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_exhaustive_error.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_exhaustive_error_top_10.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_exhaustive_error_top_10.png new file mode 100644 index 00000000..8912b331 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_exhaustive_error_top_10.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_max_error_distribution.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_max_error_distribution.png new file mode 100644 index 00000000..c550f63b Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_cmu_max_error_distribution.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_bone_cmu_speed.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_bone_cmu_speed.png new file mode 100644 index 00000000..58b7634b Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_bone_cmu_speed.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_bone_matinee_speed.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_bone_matinee_speed.png new file mode 100644 index 00000000..c50bfd47 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_bone_matinee_speed.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_pose_cmu_speed.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_pose_cmu_speed.png new file mode 100644 index 00000000..3b50faa7 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_pose_cmu_speed.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_pose_matinee_speed.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_pose_matinee_speed.png new file mode 100644 index 00000000..13dedfcc Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_delta_uniform_pose_matinee_speed.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_bone_cmu.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_bone_cmu.png new file mode 100644 index 00000000..d34441ad Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_bone_cmu.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_bone_matinee.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_bone_matinee.png new file mode 100644 index 00000000..c4103b9f Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_bone_matinee.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_pose_cmu.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_pose_cmu.png new file mode 100644 index 00000000..020b7a27 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_pose_cmu.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_pose_matinee.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_pose_matinee.png new file mode 100644 index 00000000..a5431c9d Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_decomp_uniform_pose_matinee.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_clip_durations.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_clip_durations.png new file mode 100644 index 00000000..9c194c3f Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_clip_durations.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_compression_ratio_distribution.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_compression_ratio_distribution.png new file mode 100644 index 00000000..acdafc68 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_compression_ratio_distribution.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_compression_ratio_vs_max_error.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_compression_ratio_vs_max_error.png new file mode 100644 index 00000000..5f77b14a Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_compression_ratio_vs_max_error.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_exhaustive_error.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_exhaustive_error.png new file mode 100644 index 00000000..4a60e002 Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_exhaustive_error.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_exhaustive_error_top_10.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_exhaustive_error_top_10.png new file mode 100644 index 00000000..db3b533b Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_exhaustive_error_top_10.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_max_error_distribution.png b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_max_error_distribution.png new file mode 100644 index 00000000..9acbd27a Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/images/acl_paragon_max_error_distribution.png differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/implementing_an_allocator.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/implementing_an_allocator.md new file mode 100644 index 00000000..bed55051 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/implementing_an_allocator.md @@ -0,0 +1,12 @@ +# Implementing an allocator + +The interface is very simple and provided in [**acl/core/iallocator.h**](../includes/acl/core/iallocator.h). ACL also provides an implementation that uses the system `malloc` and `free` through [**acl/core/ansi_allocator.h**](../includes/acl/core/ansi_allocator.h). You can use it as is or for inspiration to implement your own. + +Only two functions are exposed and required by the interface: + +* `allocate(size_t size, size_t alignment)` +* `deallocate(void* ptr, size_t size)` + +The `deallocate` function will be provided with the same size used to allocate the memory. + +There is no global allocator instance to set or use. Instead, every function that might allocate memory takes an explicit allocator argument. This avoids the need for global state which might impede thread safety and helps keep the library 100% headers. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/migrating.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/migrating.md new file mode 100644 index 00000000..ffef65cb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/migrating.md @@ -0,0 +1,47 @@ +# Migrating from older releases + +## Migrating to ACL 2.0 from 1.3 + +ACL 2.0 brought along with it a lot of cleanup of high level APIs, file organization, and migrated all math usage to [Realtime Math](https://github.com/nfrechette/rtm). + +### Coding standard + +Most code in ACL 1.3 used `CamelCase` but everything has now been migrated to `snake_case`. This is to ensure consistency with the rest of the C++ standard library and other prominant libraries. Enums, classes, and functions have been renamed to the new standard but have otherwise mostly kept the same names. + +### Code organization + +All implementation details have now been moved into the `acl::acl_impl` namespace to avoid polluting the main namespace. Most of that code now also lives under `impl` directories (e.g. `acl/compression/impl/clip_context.h`). + +Furthermore, the public headers try and stay as lean as possible with only function and type signatures where possible. Implementation details have been moved to headers with a `.impl.h` suffix (e.g. `acl/compression/impl/compress.impl.h`). + +Going forward, only code in public headers will be subject to deprecation rules and backward compatibility support. Implementation details are free to move and change in any release. + +### Realtime Math + +RTM started off as a fork of ACL that contained only its math and it is maintained separately. Types were renamed but most functions have kept the same name. + +* acl::Vector4_32 -> rtm::vector4f +* acl::Quat_32 -> rtm::quatf +* acl::Transform_32 -> rtm::qvvf + +See the ACL source code for example usage but migrating should be mostly painless. + +ACL now relies on RTM to detect the compiler used and the architecture being compiled to. It also relies on RTM for a few other math and code generation related macros. All macros start with a `RTM_` prefix much like previously `ACL_`. Macros mostly kept the same name with the new prefix. + +### New compression and decompression APIs + +ACL 1.3 introduced a new compression/decompression APIs for scalar tracks that used `acl::track_array` and `acl::track`. Both of these have typed aliases as well for the supported math types (e.g. `acl::track_qvvf`). ACL 2.0 now exclusively uses this new API even for hierarchical joint based animations. See [here](compressing_raw_tracks.md) for example compression usage. + +The same feature set is still supported but some parameters moved out of the `acl::CompressionSettings` (now called `acl::compression_settings`) and have been made tunable per individual track. + +A single header now contains all compression related entry points under [acl/compression/compress.h](../includes/acl/compression/compress.h). + +The decompression API is very similar to the old one and migrating should be fairly straight forward. It relies on as much statically known information as possible via `acl::decompression_settings` to strip out as much unused code as possible. Backwards compatibility with older file formats is also controlled that way. A few other features are controlled that way as well. See [here](decompressing_a_track_list.md) for example decompression usage. + +A single header now contains all decompression related entry points under [acl/decompression/decompress.h](../includes/acl/decompression/decompress.h). + +### Error metrics + +While the logic of the error metric hasn't itself changed, the API has been rewritten in order to allow better code generation and faster execution. The old `ISkeletalErrorMetric` interface has been renamed to `itransform_error_metric` and it can be found under [acl/compression/transform_error_metrics.h](../includes/acl/compression/transform_error_metrics.h). + +Error metrics now allow operating and caching other transform types (e.g. `rtm::matrix3x4f`). See the above header for example usage. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/misc_integration_details.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/misc_integration_details.md new file mode 100644 index 00000000..25e95fb3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/misc_integration_details.md @@ -0,0 +1,14 @@ +# Other integration considerations + +## Useful defines + +A small number of defines are exposed and can be used to finetune the behavior. + +### RTM_NO_INTRINSICS + +This define prevents the usage of intrinsics unless explicitly requested by the integration manually (e.g. you can still defined `RTM_SSE2_INTRINSICS` yourself). Everything will default to pure scalar implementations. + +### ACL_USE_POPCOUNT + +This enables the usage of the `POPCNT` intrinsics [when available](https://en.wikipedia.org/wiki/Bit_Manipulation_Instruction_Sets) on x86/x64 CPUs. It is currently not possible to determine at compile time when it is supported. For example *Haswell* CPUs have support for AVX2 but not `POPCNT`. The macro is automatically enabled on *Xbox One* but not yet on *PlayStation 4* (even though it is supported, contributions welcome). + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/paragon_performance.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/paragon_performance.md new file mode 100644 index 00000000..439e1ad2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/paragon_performance.md @@ -0,0 +1,58 @@ +# Paragon database performance + +| | v2.0.0 | v1.3.0 | v1.2.0 | +| --------------------- | ------------- | ------------- | ------------- | +| **Compressed size** | 208.93 MB | 208.32 MB | 218.58 MB | +| **Compression ratio** | 20.47 : 1 | 20.53 : 1 | 19.56 : 1 | +| **Compression time** | 8m 10.69s | 10m 23.05s | 28m 56.48s | +| **Compression speed** | 8923.56 KB/sec | 7027.87 KB/sec | 2521.62 KB/sec | +| **Max error** | 4.1029 cm | 2.8669 cm | 4.0184 cm | +| **Error 99th percentile** | 0.0099 cm | 0.0099 cm | 0.0116 cm | +| **Samples below error threshold** | 99.04 % | 99.04 % | 98.85 % | + +Notes: + +* The error is unusually high and above **1 cm** for **4** exotic clips +* Starting with v1.3.0 measurements have been made with a Ryzen 2950X CPU while prior versions used an Intel i7 6850K. +* Even though the 'max error' appears to have increased significantly in 2.0.0, the error is not visible to the naked eye due to the exotic nature of the clip it happens in + +# Data and method used + +To compile these statistics, a large number of animations from [Paragon](https://www.epicgames.com/paragon) are used. + +In *October 2017* the animations were manually extracted and converted to the [ACL file format](the_acl_file_format.md) losslessly. The data is sadly **NOT** available upon request. +Epic has permitted [Nicholas Frechette](https://github.com/nfrechette) to use them for research purposes only under a non-disclosure agreement. + +* Number of clips: **6558** +* Total duration: **7h 0m 45.27s** +* Raw size: **4276.11 MB** (10x float32 * num bones * num samples) + +The data set contains among other things: + +* Lots of characters with varying number of bones +* Animated objects of various shape and form +* Very short and very long clips +* Clips with unusual sample rate (as low as **2** FPS!) +* World space clips +* Lots of 3D scale +* Lots of other exotic clips + +ACL supports various compression methods but only the overall best variant will be tracked here (see `get_default_compression_settings()` for details). + +The error is measured **3cm** away from each bone to simulate the visual mesh skinning process as described [here](error_metrics.md) with the default error threshold of **0.01cm**. The **99th** percentile and the number of samples below the error threshold are calculated by measuring the error on every bone at every sample. + +The performance of ACL in Unreal Engine 4 is tracked by the plugin [here](https://github.com/nfrechette/acl-ue4-plugin/blob/develop/Docs/paragon_performance.md). + +# Results in images + +![Compression ratio VS max error per clip](images/acl_paragon_compression_ratio_vs_max_error.png) + +![Compression ratio distribution](images/acl_paragon_compression_ratio_distribution.png) + +![Max error distribution](images/acl_paragon_max_error_distribution.png) + +![Distribution of the error for every bone at every key frame](images/acl_paragon_exhaustive_error.png) + +![Distribution of the error for every bone at every key frame (top 10%)](images/acl_paragon_exhaustive_error_top_10.png) + +![Distribution of clip durations](images/acl_paragon_clip_durations.png) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/range_reduction.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/range_reduction.md new file mode 100644 index 00000000..72818164 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/range_reduction.md @@ -0,0 +1,14 @@ +# Range reduction + +Range reduction is performed by calculating the range of possible values in a list and normalizing the values within that range. Reconstructing the original value is trivial: + +`origial_value = (normalized_value * range_extent) + range_min` + +This is an important optimization to keep the memory footprint as low as possible because it typically allows us to increase the precision retained on the normalized values. + +This feature can be enabled at the clip level where entire tracks are quantized over their full range as well as at the segment level where they are normalized over the segment only. Enabling the feature at the segment level requires it to also be enabled at the clip level because we store the range information in quantized form. This is entirely controlled by the underlying compression algorithm. + +Additional reading: + +* [How it works](https://nfrechette.github.io/2016/11/09/anim_compression_range_reduction/) +* [How much it helps](https://nfrechette.github.io/2017/09/10/acl_v0.4.0/) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/research_arithmetic_accuracy.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/research_arithmetic_accuracy.md new file mode 100644 index 00000000..12a65b0b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/research_arithmetic_accuracy.md @@ -0,0 +1,7 @@ +# Research: Arithmetic Accuracy + +In order to investigate the accuracy and performance implications of using **float32**, **float64**, or **fixed point** arithmetic, a research branch was created. + +Supporting **float32** and **float64** in parallel and comparing them extensively proved somewhat simple with the help of templates. However, **fixed point** arithmetic differs considerably and instead a playground was created to investigate. + +The conclusions can be found in detail [here](https://nfrechette.github.io/2017/12/29/acl_research_arithmetic/) and the code lives [here](https://github.com/nfrechette/acl/tree/research/float-vs-double-vs-fixed-point). diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/rotation_and_vector_formats.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/rotation_and_vector_formats.md new file mode 100644 index 00000000..2cd167ac --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/rotation_and_vector_formats.md @@ -0,0 +1,31 @@ +In order to save memory, quantization is used on sampled values to reduce their footprint. To do so, a number of formats are supported. + +# Vector formats + +### Vector3 Full + +Simple full precision format. Each component `[X, Y, Z]` is stored with full floating point precision using `float32`. + +### Vector3 Variable + +The compression algorithm will search for the optimal bit rate among **19** possible values. An algorithm will select which bit rate to use for each track while keeping the memory footprint and error as low as possible. This format requires range reduction to be enabled. + +# Rotation formats + +Internally, rotation formats reuse the vector formats with some tweaks. + +### Quat Full + +A full precision quaternion format. Each component `[X, Y, Z, W]` is stored with full precision using `float32`. + +## Dropping the quaternion W component + +Every rotation can be represented by two distinct and opposite quaternions: a quaternion and its negated opposite. This is possible because [quaternions represent a hypersphere](https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#The_hypersphere_of_rotations). As such, a component can be dropped and trivially reconstructed with a square root simply by ensuring that the component is positive and the quaternion normalized during compression. + +### Quat Full + +Same as Vector3 96 above to store `[X, Y, Z]`. + +### Quat Variable + +See [Vector3 Variable](rotation_and_vector_formats.md#vector3-variable) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/the_acl_file_format.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/the_acl_file_format.md new file mode 100644 index 00000000..a8e9cbf4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/docs/the_acl_file_format.md @@ -0,0 +1,11 @@ +# The ACL file format + +To keep the library as simple and minimal as possible, it does not support common animation file formats out of the box. There are too many out there and many of them would require linking 3rd party libraries, adding dependencies in the process. It is assumed that most games already have a pipeline to load animation files and perhaps even pre-process them in some way before handing the data over to the compression algorithm. As such, for our internal testing purposes and for debugging, a [Simplified JSON](http://help.autodesk.com/view/Stingray/ENU/?guid=__stingray_help_managing_content_sjson_html) file is used to represent an animation clip. These files can easily be compressed with *gzip* and be read by a human. + +Small auxiliary tools exist to convert to this format such as the [fbx2acl](../tools/fbx2acl/fbx2acl.py) python script. The library also supports writing ACL files from a populated [skeleton and clip](../includes/acl/io/clip_writer.h). + +It is important to note that this file format is not meant to be used in production (although it could). Its primary purpose is to help us debug and reproduce issues by attaching an ACL file to bug reports. For this purpose they can be generated with a binary exact representation where each floating point value is stored as a hexadecimal string of the underlying 64 bit value. + +A reference ACL file can be found [here](../tools/format_reference.acl.sjson). + +Note that in order to use the ACL clip reader and writer, you will need to include the `sjson-cpp` headers prior to the ACL headers. This is done to decouple the dependency should you be using your own version. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/README.md new file mode 100644 index 00000000..f4a90883 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/README.md @@ -0,0 +1,23 @@ +# External dependencies + +## Runtime dependencies + +### Realtime Math + +[Reamtime Math v2.1.3](https://github.com/nfrechette/rtm/releases/tag/v2.1.3) (MIT License) is used for its optimized math. + +## Development dependencies + +### Catch2 + +[Catch2 v2.13.1](https://github.com/catchorg/Catch2/releases/tag/v2.13.1) (Boost Software License v1.0) is used by our [unit tests](../tests). You will only need it if you run the unit tests and it is included as-is without modifications. + +### Google Benchmark + +[Google Benchmark v1.5.1](https://github.com/google/benchmark/releases/tag/v1.5.1) (Apache License 2.0) is used to benchmark various functions. You will only need it if you run the benchmarks and it is included as-is without modifications. + +### sjson-cpp + +[sjson-cpp v0.8.2](https://github.com/nfrechette/sjson-cpp/releases/tag/v0.8.2) (MIT License) is used by our [ACL file format](../docs/the_acl_file_format.md) [clip reader](../includes/acl/io/clip_reader.h) and [clip writer](../includes/acl/io/clip_writer.h) as well as by the [acl_compressor](../tools/acl_compressor) tool used for regression testing and profiling. Unless you use our ACL file format at runtime (which you shouldn't), you will not have this dependency included at all. + +In fact, to use any of these things you must include `sjson-cpp` relevant headers manually before you include the ACL headers that need them. For convenience, you can use the included version here or your own version as long as the API remains compatible. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.all-contributorsrc b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.all-contributorsrc new file mode 100644 index 00000000..d700b309 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.all-contributorsrc @@ -0,0 +1,36 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "CodyDWJones", + "name": "CodyDWJones", + "avatar_url": "https://avatars.githubusercontent.com/u/28773740?v=4", + "profile": "https://github.com/CodyDWJones", + "contributions": [ + "code", + "platform", + "infra" + ] + }, + { + "login": "mwerschy", + "name": "Malte Werschy", + "avatar_url": "https://avatars.githubusercontent.com/u/6616804?v=4", + "profile": "https://github.com/mwerschy", + "contributions": [ + "code", + "platform" + ] + } + ], + "contributorsPerLine": 7, + "projectName": "rtm", + "projectOwner": "nfrechette", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.editorconfig b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.editorconfig new file mode 100644 index 00000000..22452119 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +[*.{cpp,h,py,cmake,txt,java}] +charset = utf-8 +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{gradle,gradle.in}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/FUNDING.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/FUNDING.yml new file mode 100644 index 00000000..724e02b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +#patreon: # Replace with a single Patreon username +#open_collective: # Replace with a single Open Collective username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +#liberapay: # Replace with a single Liberapay username +#issuehunt: # Replace with a single IssueHunt username +#otechie: # Replace with a single Otechie username +#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] + +github: [nfrechette] diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/workflows/build.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/workflows/build.yml new file mode 100644 index 00000000..998b3f2f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/workflows/build.yml @@ -0,0 +1,151 @@ +name: build + +on: + push: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + pull_request: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + +jobs: + linux: + runs-on: ubuntu-18.04 + strategy: + matrix: + compiler: [gcc5, gcc6, gcc7, gcc8, gcc9, gcc10, clang4, clang5, clang6, clang7, clang8, clang9, clang10, clang11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_linux_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64 nosimd) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -nosimd -build + - name: Running unit tests (release-x64 nosimd) + run: python3 make.py -compiler ${{ matrix.compiler }} -config Release -cpu x64 -nosimd -unit_test + osx: + runs-on: macos-10.15 + strategy: + matrix: + compiler: [xcode10, xcode11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_osx_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Debug -cpu x86 -unit_test + - name: Clean + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -compiler osx -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -compiler osx -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -compiler osx -config Release -cpu x86 -unit_test + - name: Clean + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -compiler osx -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -compiler osx -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64 nosimd) + run: python3 make.py -compiler osx -config Release -cpu x64 -nosimd -build + - name: Running unit tests (release-x64 nosimd) + run: python3 make.py -compiler osx -config Release -cpu x64 -nosimd -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (debug-arm64) + run: python3 make.py -compiler ios -config Debug -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64) + run: python3 make.py -compiler ios -config Release -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64 nosimd) + run: python3 make.py -compiler ios -config Release -build -nosimd + emscripten: + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Cache emsdk + uses: actions/cache@v1 + id: 'emsdk' + with: + path: 'emsdk-cache' + key: ${{ runner.os }}-emsdk-1.39.11 + - name: Setup emsdk + uses: mymindstorm/setup-emsdk@v7 + with: + version: 1.39.11 + no-cache: true + actions-cache-folder: 'emsdk-cache' + - name: Cache emsdk build artifacts + uses: actions/cache@v1 + id: 'emsdk-build-artifacts' + with: + path: '~/.emscripten_cache' + key: ${{ runner.os }}-emsdk-build-artifacts-1.39.11 + - name: Build (debug) + run: python3 make.py -compiler emscripten -config debug -build +# Don't run debug unit tests, they are too slow and sometime time out +# - name: Unit tests (debug) +# run: python3 make.py -compiler emscripten -config debug -unit_test + - name: Clean + run: python3 make.py -clean + - name: Build (release) + run: python3 make.py -compiler emscripten -config release -build + - name: Unit tests (release) + run: python3 make.py -compiler emscripten -config release -unit_test diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/workflows/sonarcloud.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..31b1066b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.github/workflows/sonarcloud.yml @@ -0,0 +1,42 @@ +name: sonarcloud + +on: + push: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + pull_request: + branches: + - '**' + paths-ignore: + - docs/** + - '**/*.md' + +jobs: + sonarcloud: + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup build wrapper + run: | + wget -q https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip + unzip build-wrapper-linux-x86.zip + - name: Setup Sonar Scanner + run: | + wget -q https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.2.0.1873-linux.zip + unzip sonar-scanner-cli-4.2.0.1873-linux.zip + - name: Setup compiler + run: ./tools/setup_linux_compiler.sh clang5 + - name: Build with wrapper + run: ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw_output python3 make.py -compiler clang5 -config Release -cpu x64 -build + - name: Run Sonar Scanner and upload to Sonarcloud + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io + run: ./sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.gitignore b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.gitignore new file mode 100644 index 00000000..053145c1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.gitignore @@ -0,0 +1,5 @@ +# Temporary data rules +build/ +*.suo +**/__pycache__/ +.vscode/ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.gitmodules b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.gitmodules new file mode 100644 index 00000000..1064c225 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/.gitmodules @@ -0,0 +1,6 @@ +[submodule "external/catch2"] + path = external/catch2 + url = https://github.com/catchorg/Catch2 +[submodule "external/benchmark"] + path = external/benchmark + url = https://github.com/google/benchmark diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CHANGELOG.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CHANGELOG.md new file mode 100644 index 00000000..d299fffc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CHANGELOG.md @@ -0,0 +1,42 @@ +# Significant changes per release + +## 2.1.3 + +* Add versioned namespace to allow multiple versions to coexist within a binary + +## 2.1.2 + +* Fix MSVC/clang static analysis warnings +* Improve MSVC ARM64 intrinsic support detection + +## 2.1.1 + +* Avoid enabling NEON when just `__arm__` is present + +## 2.1.0 + +* Force inlined most functions to improve MSVC code generation +* Migrated from Travis to GitHub Actions +* Added support for clang 11 +* Lots of minor additions, fixes, and improvements + +## 2.0.0 + +* Added support for GCC10, clang8, clang9, clang10, VS 2019 clang, and emscripten +* Added a lot of matrix math +* Added trigonometric functions (scalar and vector) +* Angle types have been removed +* Lots of optimizations and improvements +* Tons of cleanup to ensure a consistent API + +## 1.1.0 + +* Added support for Windows ARM64 +* Added support for VS2019, GCC9, clang7, and Xcode 11 +* Added support for Intel FMA and ARM64 FMA +* Many optimizations, minor fixes, and cleanup + +## 1.0.0 + +Initial release migrating and improving the code from the Animation Compression Library. + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CMakeLists.txt new file mode 100644 index 00000000..8d75d85d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required (VERSION 3.2) +project(rtm NONE) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") + +include(CMakeUtils) +include(CMakeCompiler) +include(CMakePlatforms) + +set(USE_AVX_INSTRUCTIONS false CACHE BOOL "Use AVX instructions") +set(USE_AVX2_INSTRUCTIONS false CACHE BOOL "Use AVX2 instructions") +set(USE_SIMD_INSTRUCTIONS true CACHE BOOL "Use SIMD instructions") +set(WITH_VECTOR_MIX_TESTS false CACHE BOOL "Enable vector_mix unit tests") +set(CPU_INSTRUCTION_SET false CACHE STRING "CPU instruction set") +set(BUILD_BENCHMARK_EXE false CACHE BOOL "Enable the benchmark projects") + +if(CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES Debug Release) + set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING "Reset the configurations to what we need" FORCE) +endif() + +# Grab all of our include files +file(GLOB_RECURSE RTM_INCLUDE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/includes/*.h + ${PROJECT_SOURCE_DIR}/docs/*.md + ${PROJECT_SOURCE_DIR}/cmake/*.cmake + ${PROJECT_SOURCE_DIR}/tools/release_scripts/*.py + ${PROJECT_SOURCE_DIR}/tools/vs_visualizers/*.natvis + ) + +create_source_groups("${RTM_INCLUDE_FILES}" ${PROJECT_SOURCE_DIR}) + +file(GLOB RTM_ROOT_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.md + ${PROJECT_SOURCE_DIR}/*.py) + +# Create a dummy target so they show up in the IDE +add_custom_target(${PROJECT_NAME} SOURCES ${RTM_INCLUDE_FILES} ${RTM_ROOT_FILES}) + +# Enable CTest +enable_testing() + +# Add other projects +add_subdirectory("${PROJECT_SOURCE_DIR}/tests") + +if(BUILD_BENCHMARK_EXE) + # Our benchmark executable + add_subdirectory("${PROJECT_SOURCE_DIR}/tools/bench") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CODE_OF_CONDUCT.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..48fcffad --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at zeno490@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CONTRIBUTING.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CONTRIBUTING.md new file mode 100644 index 00000000..f0a21e28 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# How to contribute + +Thank you for your interest in the Realtime Math library! All contributions that follow our guidelines below and abide by our [code of conduct](CODE_OF_CONDUCT.md) are welcome. + +In this document you will find relevant reading material, what contributions we are looking for, and what we are *not* looking for. + +Project contact email: zeno490@gmail.com + +# Getting set up + +See the [getting started](./docs/getting_started.md) section for details on how to generate project solutions, build, and run the unit tests. + +Every pull request should trigger continuous integration on every platform we support and most of them will also execute the unit tests automatically (except on Android and iOS). + +The project roadmap for the next few milestones is tracked with [GitHub issues](https://github.com/nfrechette/rtm/issues). [Backlog issues](https://github.com/nfrechette/rtm/milestone/1) are things I would like to get done eventually but that have not been prioritized yet. + +Whether you create an issue or a pull request, I will do my best to comment or reply within 48 hours. + +# Contributions we are looking for + +Several issues already have a [help wanted](https://github.com/nfrechette/rtm/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label. Those tasks should be either reasonably simple or tasks that I do not think I will get the chance to get to very soon. + +Feature requests are welcome providing that they fit within the project scope. For smaller features, you are welcome to create an issue with as much relevant information as you can. If it makes sense, I will prioritize it or add it to the backlog, and if I feel it is beyond the scope of the project, I will tell you why and close the issue. + +# Contributions we are *not* looking for + +A lot of older compilers do not properly support **C++11** and there is no plan to support them. + +If you aren't sure, don't be afraid to reach out by email or on discord! diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/LICENSE b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/LICENSE new file mode 100644 index 00000000..56c93514 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Nicholas Frechette + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/README.md new file mode 100644 index 00000000..5453a289 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/README.md @@ -0,0 +1,81 @@ +[![CLA assistant](https://cla-assistant.io/readme/badge/nfrechette/rtm)](https://cla-assistant.io/nfrechette/rtm) +[![All Contributors](https://img.shields.io/github/all-contributors/nfrechette/rtm)](#contributors-) +[![Build status](https://ci.appveyor.com/api/projects/status/7eh9maq9a721e5on/branch/develop?svg=true)](https://ci.appveyor.com/project/nfrechette/rtm) +[![Build status](https://github.com/nfrechette/rtm/workflows/build/badge.svg)](https://github.com/nfrechette/rtm/actions) +[![Sonar Status](https://sonarcloud.io/api/project_badges/measure?project=nfrechette_rtm&metric=alert_status)](https://sonarcloud.io/dashboard?id=nfrechette_rtm) +[![GitHub release](https://img.shields.io/github/release/nfrechette/rtm.svg)](https://github.com/nfrechette/rtm/releases) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nfrechette/rtm/master/LICENSE) +[![Discord](https://img.shields.io/discord/691048241864769647?label=discord)](https://discord.gg/UERt4bS) + +# Realtime Math + +This library is geared towards realtime applications that require their math to be as fast as possible. Much care was taken to maximize inlining opportunities and for code generation to be optimal when a function isn't inlined by passing values in registers whenever possible. It contains 3D and 4D arithmetic commonly used in video games and realtime applications. + +It offers an alternative to [GLM](https://github.com/g-truc/glm) and [DirectX Math](https://github.com/Microsoft/DirectXMath). See [here](https://nfrechette.github.io/2019/01/19/introducing_realtime_math/) for a comparison with similar libraries. + +## Philosophy + +Much thought was put into designing the library for it to be as flexible and powerful as possible. To this end, the following decisions were made: + +* The library consists of **100% C++11** header files and is thus easy to integrate in any project +* The interface follows C-style conventions to ensure optimal code generation +* Both *float32* and *float64* arithmetic are supported +* Row vectors are used +* See [here](./docs/api_conventions.md) for more details + +## Supported platforms + +* Windows VS2015 x86 and x64 +* Windows (VS2017, VS2019) x86, x64, and ARM64 +* Windows VS2019 with clang9 x86 and x64 +* Linux (gcc 5 to 10) x86 and x64 +* Linux (clang 4 to 11) x86 and x64 +* OS X (Xcode 10.3) x86 and x64 +* OS X (Xcode 11.2) x64 +* Android (NDK 21) ARMv7-A and ARM64 +* iOS (Xcode 10.3, 11.2) ARM64 +* Emscripten (1.39.11) WASM + +The above supported platform list is only what is tested every release but if it compiles, it should work just fine. + +Note: *VS2017* and *VS2019* compile with *ARM64* on *AppVeyor* but I have no device to test them with. + +## Getting started + +This library is **100%** headers as such you just need to include them in your own project to start using it. However, if you wish to run the unit tests or to contribute to RTM head on over to the [getting started](./docs/getting_started.md) section in order to setup your environment and make sure to check out the [contributing guidelines](CONTRIBUTING.md). + +## External dependencies + +You don't need anything else to get started: everything is self contained. +See [here](./external) for details. + +## License, copyright, and code of conduct + +This project uses the [MIT license](LICENSE). + +Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors + +This project was started from the math code found in the [Animation Compression Library](https://github.com/nfrechette/acl) v1.1.0 and it retains the copyright of the original contributors. + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + +

CodyDWJones

💻 📦 🚇

Malte Werschy

💻 📦
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/appveyor.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/appveyor.yml new file mode 100644 index 00000000..3dcf5217 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/appveyor.yml @@ -0,0 +1,44 @@ +version: 2.1.3.{build} + +environment: + PYTHON: "C:\\Python36-x64\\python.exe" + matrix: + - toolchain: msvc + - toolchain: clang + +image: + - Visual Studio 2015 + - Visual Studio 2017 + - Visual Studio 2019 + +configuration: + - Debug + - Release + +platform: + - x86 + - x64 + - arm64 + +matrix: + exclude: + - image: Visual Studio 2015 + platform: arm64 + - image: Visual Studio 2015 + toolchain: clang + - image: Visual Studio 2017 + toolchain: clang + - image: Visual Studio 2019 + platform: arm64 + toolchain: clang + - image: Previous Visual Studio 2019 + platform: arm64 + toolchain: clang + +install: +- cmd: >- + git submodule update --init --recursive + +build_script: +- cmd: >- + .\tools\appveyor_ci.bat "%APPVEYOR_BUILD_WORKER_IMAGE%" %platform% %configuration% %toolchain% "%PYTHON%" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakeCompiler.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakeCompiler.cmake new file mode 100644 index 00000000..82eed29a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakeCompiler.cmake @@ -0,0 +1,64 @@ +cmake_minimum_required (VERSION 3.2) + +macro(setup_default_compiler_flags _project_name) + if(MSVC) + # Replace some default compiler switches and add new ones + STRING(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI + STRING(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Bump warnings to W4 + target_compile_options(${_project_name} PRIVATE /Zi) # Add debug info + target_compile_options(${_project_name} PRIVATE /Oi) # Generate intrinsic functions + target_compile_options(${_project_name} PRIVATE /WX) # Treat warnings as errors + target_compile_options(${_project_name} PRIVATE /MP) # Enable parallel compilation + + if(MSVC_VERSION GREATER 1900) + # VS2017 and above + target_compile_options(${_project_name} PRIVATE /permissive-) + endif() + + if(USE_SIMD_INSTRUCTIONS) + if(USE_AVX2_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "/arch:AVX2") + elseif(USE_AVX_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "/arch:AVX") + endif() + else() + add_definitions(-DRTM_NO_INTRINSICS) + endif() + + # Add linker flags + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") + else() + # TODO: Handle OS X properly: https://stackoverflow.com/questions/5334095/cmake-multiarchitecture-compilation + if(CPU_INSTRUCTION_SET MATCHES "x86") + target_compile_options(${_project_name} PRIVATE "-m32") + target_link_libraries(${_project_name} PRIVATE "-m32") + elseif(CPU_INSTRUCTION_SET MATCHES "x64") + target_compile_options(${_project_name} PRIVATE "-m64") + target_link_libraries(${_project_name} PRIVATE "-m64") + endif() + + if(CPU_INSTRUCTION_SET MATCHES "x86" OR CPU_INSTRUCTION_SET MATCHES "x64") + if(USE_SIMD_INSTRUCTIONS) + if(USE_AVX2_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "-mavx2") + target_compile_options(${_project_name} PRIVATE "-mavx") + target_compile_options(${_project_name} PRIVATE "-mbmi") + target_compile_options(${_project_name} PRIVATE "-mfma") + elseif(USE_AVX_INSTRUCTIONS) + target_compile_options(${_project_name} PRIVATE "-mavx") + target_compile_options(${_project_name} PRIVATE "-mbmi") + else() + target_compile_options(${_project_name} PRIVATE "-msse4.1") + endif() + else() + add_definitions(-DRTM_NO_INTRINSICS) + endif() + endif() + + target_compile_options(${_project_name} PRIVATE -Wall -Wextra) # Enable all warnings + target_compile_options(${_project_name} PRIVATE -Wshadow) # Enable shadowing warnings + target_compile_options(${_project_name} PRIVATE -Werror) # Treat warnings as errors + + target_compile_options(${_project_name} PRIVATE -g) # Enable debug symbols + endif() +endmacro() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakePlatforms.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakePlatforms.cmake new file mode 100644 index 00000000..8f88f160 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakePlatforms.cmake @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.2) + +if(PLATFORM_NAME) + return() # Already set +endif() + +# Detect which platform we have +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(PLATFORM_WINDOWS 1) + set(PLATFORM_NAME "Windows") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + if(PLATFORM_IOS) + set(PLATFORM_NAME "iOS") + else() + set(PLATFORM_OSX 1) + set(PLATFORM_NAME "OS X") + endif() +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(PLATFORM_LINUX 1) + set(PLATFORM_NAME "Linux") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") + set(PLATFORM_ANDROID 1) + set(PLATFORM_NAME "Android") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + set(PLATFORM_EMSCRIPTEN 1) + set(PLATFORM_NAME "Emscripten") +else() + message(FATAL_ERROR "Unknown platform ${CMAKE_SYSTEM_NAME}!") +endif() + +message(STATUS "Detected platform: ${PLATFORM_NAME}") diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakeUtils.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakeUtils.cmake new file mode 100644 index 00000000..a54c84fd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/CMakeUtils.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required (VERSION 3.2) + +# Create groups for the test source files, this creates the proper directory structure under the +# Visual Studio filters +macro(create_source_groups _source_files _relative_directory) + foreach(_file IN ITEMS ${_source_files}) + get_filename_component(_file_path "${_file}" PATH) + file(RELATIVE_PATH _file_path_rel "${_relative_directory}" "${_file_path}") + string(REPLACE "/" "\\" _group_path "${_file_path_rel}") + string(REPLACE "..\\" "" _group_path "${_group_path}") + source_group("${_group_path}" FILES "${_file}") + endforeach() +endmacro() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/Toolchain-Android.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/Toolchain-Android.cmake new file mode 100644 index 00000000..707802b9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/Toolchain-Android.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.2) + +# For Android, we just set the platform name as we won't be using CMake to build anything. +# Instead Gradle is used through CMake. + +set(PLATFORM_ANDROID 1) +set(PLATFORM_NAME "Android") + +# Remap our CPU instruction set +if(CPU_INSTRUCTION_SET MATCHES "armv7") + set(CPU_INSTRUCTION_SET "armeabi-v7a") +elseif(CPU_INSTRUCTION_SET MATCHES "arm64") + set(CPU_INSTRUCTION_SET "arm64-v8a") +endif() + +# Set our misc asset directory +set(RTM_ANDROID_MISC_DIR ${CMAKE_SOURCE_DIR}/tools/android_misc) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/Toolchain-iOS.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/Toolchain-iOS.cmake new file mode 100644 index 00000000..115bf2fb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/cmake/Toolchain-iOS.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required (VERSION 3.2) + +set(CMAKE_SYSTEM_NAME Darwin) + +# Set here instead of CMakePlatforms.cmake since we can't distinguis otherwise +set(PLATFORM_IOS 1) + +# Find and set the C/C++ compiler paths, cmake doesn't seem to do this properly on its own +execute_process(COMMAND xcrun --sdk iphoneos --find clang OUTPUT_VARIABLE CMAKE_C_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND xcrun --sdk iphoneos --find clang++ OUTPUT_VARIABLE CMAKE_CXX_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) + +set(CMAKE_MACOSX_BUNDLE YES) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") +set(CMAKE_OSX_SYSROOT iphoneos CACHE STRING "") +set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "") +set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 11.0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/README.md new file mode 100644 index 00000000..60fc6bf2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/README.md @@ -0,0 +1,7 @@ +# Documentation + +* [API and other conventions](api_conventions.md) +* [Types supported](types_supported.md) +* [SIMD support](simd_support.md) +* [Handling asserts](handling_asserts.md) +* [Getting started](getting_started.md) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/api_conventions.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/api_conventions.md new file mode 100644 index 00000000..6bf287be --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/api_conventions.md @@ -0,0 +1,36 @@ +# API and other conventions + +## C-style + +C-style interfaces are generally preferred with a few supporting types. The simpler C-style interface leads to less intermediate code that the compiler needs to work with and it increases the likelihood that functions will be inlined. Inlining is a critical and integral part of performance for any math library. In general, code commonly used and that would benefit the most from being inlined uses a C-style API (vectors, quaternions, etc) while less critical code relies on various helper types (angles, helpers, etc). + +## C++11 + +We restrict the code to use C++11 as it is the most widely used flavor on the platforms we currently support. *constexpr* in particular is used as much as possible. Despite the C-style API, everything lives under the *rtm* namespace and implementation details not meant to be consumed by clients is hidden inside the *rtm_impl* namespace as well as the *impl* header directory. + +The library is 100% comprised of C++ headers and no linking is required. This makes for the easiest integration possible and it also gives us more freedom with what and when we can change things. + +## Argument passing + +This library supports many architectures, platforms, and compilers and sadly there is no consensus on how SIMD types should be passed by argument or returned by value. It is generally best to pass as many things by register, when possible, but usually only a certain number of registers can be used for it. Some platforms support aggregate types being passed by register (either by argument and/or by return value), others do not. To keep things as simple as possible, aliases are used for every type such as: *vector4f_arg0, vector4f_arg1, ..., vector4f_argn*. + +RTM tries its best to do things optimally. Generally speaking, for code that isn't performance critical the difference will be very small and you are free to pass things by value or *const&* in your own code but using the argument aliases is encouraged. + +## Matrix multiplication ordering + +Whether you call it pre or post-multiplication, or left or right multiplication, it boils down to whether vectors are represented as rows or as columns. + +In the former case, multiplication with a matrix takes the form `v' = vM` while in the later case we have `v' = Mv`. Linear algebra typically treats vectors as *columns* and OpenGL opted to use that convention for [that reason](http://steve.hollasch.net/cgindex/math/matrix/column-vec.html). If you think of matrices as functions that modify an input and return an output it ends up reading like this: `result = object_to_world(local_to_object(input))`. This reads right-to-left as is common with nested function evaluation. Sadly, this somewhat breaks down when using multiplication operators: `result = object_to_world * local_to_object * input`. Regardless of how operator precedence works, the result will be identical but most modern programming languages (and western languages) read left-to-right. + +On the other hand, DirectX uses *row* vectors and ends up with the much more natural: `result = input * local_to_object * object_to_world`. Your input is in local space, it gets transformed into object space before finally ending up in world space. Clean, clear, and readable. If you instead multiply the two matrices together on their own, you get the clear `local_to_world = local_to_object * object_to_world` instead of the awkward `local_to_world = object_to_world * local_to_object` you would get with OpenGL and *column* vectors. + +An executive decision was made for all vectors to be row vectors for their improved readability. + +## Coordinate frame + +When relevant, a left-handed coordinate system is used: + +* X axis = forward +* Y axis = right +* Z axis = up + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/getting_started.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/getting_started.md new file mode 100644 index 00000000..fcbc3c04 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/getting_started.md @@ -0,0 +1,49 @@ +# Getting started + +In order to contribute to RTM you will first need to setup your environment. + +## Setting up your environment + +### Windows, Linux, and OS X for x86 and x64 + +1. Install *CMake 3.2* or higher (*3.14* for Visual Studio 2019, or *3.10* on OS X with *Xcode 10*), *Python 2.7 or 3*, and the proper compiler for your platform. +2. Execute `git submodule update --init` to get the files of external submodules (e.g. Catch2). +3. Generate the IDE solution with: `python make.py` + The solution is generated under `./build` +4. Build the IDE solution with: `python make.py -build` +5. Run the unit tests with: `python make.py -unit_test` +6. Build and run benchmarks with the `-bench` switch + +On all three platforms, *AVX* support can be enabled by using the `-avx` switch and *AVX2* with `-avx2`. Intrinsic usage can be turned off with `-nosimd`. + +### Windows ARM64 + +For *Windows on ARM64*, the steps are identical to *x86 and x64* but you will need *CMake 3.13 or higher* and you must provide the architecture on the command line: `python make.py -compiler vs2017 -cpu arm64` + +### Android + +For *Android*, the steps are identical to *Windows, Linux, and OS X* but you also need to install *Android NDK 21* (or higher). The build uses `gradle` and `-unit_test` as well as `-bench` will deploy and run on the device when executed (make sure that the `adb` executable is in your `PATH` for this to work). + +*Android Studio v3.5* can be used to launch and debug. After running *CMake* to build and generate everything, the *Android Studio* projects can be found under the `./build` directory. + +Note that running the benchmark on Android can be done but it currently involves a number of manual steps. + +### iOS + +For *iOS*, the steps are identical to the other platforms but due to code signing, you will need to perform the builds from *Xcode* manually. Note that this is only an issue if you attempt to use the unit tests locally. + +In order to run these manually: + +* Open the *Xcode* project for the appropriate tool +* In the project settings, enable automatic code signing and select your development team +* Build and run on your device + +Note that *iOS* builds have never been tested on an emulator. + +### Emscripten + +Emscripten support currently only has been tested on OS X and Linux. To use it, make sure to install a recent version of Emscripten SDK 1.39.11+. + +## Commit message format + +This library uses the [angular.js message format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) and it is enforced with commit linting through every pull request. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/handling_asserts.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/handling_asserts.md new file mode 100644 index 00000000..57626831 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/handling_asserts.md @@ -0,0 +1,47 @@ +# Handling asserts + +This library uses a simple system to handle asserts. Asserts are fatal and must terminate otherwise the behavior is undefined if execution continues. + +A total of 4 behaviors are supported: + +* We can print to `stderr` and `abort` +* We can `throw` and exception +* We can call a custom function +* Do nothing and strip the check at compile time (**default behavior**) + +Everything necessary is implemented in [**rtm/impl/error.h**](../includes/rtm/impl/error.h). + +*Note: It is **NOT** possible to have different assert behaviors in two or more C++ object files. All the C++ files within your static or dynamic library that reference RTM must all use the same assert handling strategy.* + +## Aborting + +In order to enable the aborting behavior, simply define the macro `RTM_ON_ASSERT_ABORT`: + +`#define RTM_ON_ASSERT_ABORT` + +## Throwing + +In order to enable the throwing behavior, simply define the macro `RTM_ON_ASSERT_THROW`: + +`#define RTM_ON_ASSERT_THROW` + +Note that the type of the exception thrown is `std::runtime_error`. + +## Custom function + +In order to enable the custom function calling behavior, define the macro `RTM_ON_ASSERT_CUSTOM` with the name of the function to call: + +`#define RTM_ON_ASSERT_CUSTOM on_custom_assert_impl` + +Note that the function signature is as follow: `void on_custom_assert_impl(const char* expression, int line, const char* file, const char* format, ...) {}` + +You can also define your own assert implementation by defining the `RTM_ASSERT` macro as well: + +```c++ +#define RTM_ON_ASSERT_CUSTOM +#define RTM_ASSERT(expression, format, ...) checkf(expression, ANSI_TO_TCHAR(format), #__VA_ARGS__) +``` + +## No checks + +By default if no macro mentioned above is defined, all asserts will be stripped at compile time. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/simd_support.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/simd_support.md new file mode 100644 index 00000000..a739e071 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/simd_support.md @@ -0,0 +1,12 @@ +# SIMD support + +## x86 and x64 + +Various versions of SSE are supported: SSE2, SSE3, SSE4, AVX, AVX2, and FMA. + +*Note that even when FMA is enabled, its intrinsics are not used because they appear slower on at least Haswell and Ryzen.* + +## ARM + +Both ARM NEON and ARM64 NEON are supported. + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/types_supported.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/types_supported.md new file mode 100644 index 00000000..7c8bdacf --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/docs/types_supported.md @@ -0,0 +1,49 @@ +# Types supported + +Realtime Math supports the most commonly used types in video games and realtime applications in a SIMD friendly way with a strong focus on performance. + +All types come in various flavors with a corresponding suffix for their width (e.g. vector4f): + +* **f** for *float32* +* **d** for *float64* +* **s** for *int16* +* **i** for *int32* +* **q** for *int64* + +## Scalar + +Both regular *float & double* types are supported for most operations and a special SIMD friendly scalar type as well: *scalarf & scalard*. Different architectures have an easier or harder time working with scalar floating point numbers. For example, older PowerPC processors had to write to memory and reload from it to transfer from one register file into another (e.g convert from a float to a SIMD vector). Modern processors handle this much better but inefficiencies remain, especially with SSE. While it is free to convert a SIMD scalar into a float with *_mm_cvtss_f32(..)* the reverse generally requires the compiler to fill the unused SIMD lanes with known values (either zero or the same). This introduces an extra instruction that isn't always required when only the first lane is used such as with *scalar_sqrt_reciprocal(..)*. By introducing a type for SIMD scalar values, each platform is free to make an optimal choice. + +## Vector 4D + +Lower dimensions vectors will not be supported with proper types but functions can support them with an appropriate suffix (e.g. *vector_dot3(..)*). If no suffix is specified, functions operate on the full width of the vector. When a function operates on a reduced number of SIMD lanes, the extra unused lanes will have undefined values. + +*Vectors are row vectors in RTM and thus multiply on the left of matrices.* + +## Mask 4D + +A comparison mask used by vector selection/blending. Each SIMD lane consists of all ones (true) or zeroes (false) depending on the condition. + +## Quaternion + +Quaternions are 4D complex numbers commonly used to represent 3D rotations (when normalized). The **[xyz]** components are the real part while the **[w]** component is the imaginary part. + +## QVV (quaternion-vector-vector) + +A QVV represents an affine transform in three distinct parts: a rotation quaternion, a vector3 scale, and a vector3 translation. This type is commonly used in video games as it is very fast to work with and more compact than a full affine matrix. It properly handles positive non-uniform scaling but negative scaling is a bit more problematic. A best effort is made by converting the quaternion to a matrix when necessary. If scale fidelity is important, consider using an affine matrix 3x4 instead. + +## Matrix 3x3 + +A generic 3x3 matrix. Suitable to represent rotations mixed with 3D scale or anything else that might fit. + +## Matrix 3x4 + +An 3x4 affine matrix represents a 3D rotation, 3D translation, and 3D scale. It properly deals with skew/shear when present but once scale with mirroring is combined, it cannot be safely extracted back. Affine matrices are 4x4 but have their last row always equal to **[0, 0, 0, 1]** which is why it is named 3x4. + +## Matrix 4x4 + +A generic 4x4 matrix. Suitable to represent 3D projection matrices and the likes. + +## Unaligned and storage friendly types + +When manipulating vectors of various width, it is often desirable to store them as an unaligned sequence of floats with no padding. For example, while a 3D mesh has a number of `float3` vertices, storing and manipulating them as `vector4f` would use 33% more memory. To that end, a number of types are provided to help with this: `float2f, float2d, float3f, float3d, float4f, float4d`. These types have no alignment requirement beyond the natural float/double alignment. Functions such as `vector_load3(const float3f* input)` can load them from memory and return a vector4 of the correct type. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/external/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/external/README.md new file mode 100644 index 00000000..d45c4153 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/external/README.md @@ -0,0 +1,11 @@ +# External dependencies + +Good news! There are no external dependencies needed by this library at runtime! + +## Benchmark + +[Google Benchmark v1.5.1](https://github.com/google/benchmark/releases/tag/v1.5.1) (Apache License 2.0) is used to benchmark various functions. You will only need it if you run the benchmark tool and it is included as-is without modifications. + +## Catch2 + +[Catch2 v2.11.0](https://github.com/catchorg/Catch2/releases/tag/v2.11.0) (Boost Software License v1.0) is used by our [unit tests](../tests). You will only need it if you run the unit tests and it is included as-is without modifications. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/constants.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/constants.h new file mode 100644 index 00000000..86a0108c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/constants.h @@ -0,0 +1,138 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +////////////////////////////////////////////////////////////////////////// +// This macro allows us to easily define constants that coerce to the proper desired type. +////////////////////////////////////////////////////////////////////////// +#define RTM_DEFINE_FLOAT_CONSTANT(name, value) \ + namespace rtm_impl \ + { \ + /* Structure that holds our value at compile time */ \ + template \ + struct RTM_JOIN_TOKENS(float_constant_, name) \ + { \ + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr operator double() const noexcept { return sign_bias ? value : -value; } \ + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr operator float() const noexcept { return sign_bias ? static_cast(value) : static_cast(-value); } \ + /* Unary helpers */ \ + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_JOIN_TOKENS(float_constant_, name) operator-() const noexcept { return RTM_JOIN_TOKENS(float_constant_, name) {}; } \ + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_JOIN_TOKENS(float_constant_, name) operator+() const noexcept { return RTM_JOIN_TOKENS(float_constant_, name) {}; } \ + }; \ + /* Multiplication helpers */ \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator*(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs * double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator*(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) * rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator*(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs * float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator*(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) * rhs; } \ + /* Division helpers */ \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator/(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs / double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator/(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) / rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator/(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs / float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator/(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) / rhs; } \ + /* Addition helpers */ \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator+(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs + double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator+(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) + rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator+(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs + float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator+(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) + rhs; } \ + /* Subtraction helpers */ \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator-(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs - double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double operator-(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) - rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator-(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs - float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float operator-(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) - rhs; } \ + /* Relational helpers */ \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs < double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) < rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs < float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) < rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<=(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs <= double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<=(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) <= rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<=(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs <= float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator<=(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) <= rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs > double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) > rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs > float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) > rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>=(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs >= double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>=(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) >= rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>=(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs >= float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator>=(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) >= rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator==(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs == double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator==(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) == rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator==(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs == float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator==(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) == rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator!=(double lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs != double(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator!=(RTM_JOIN_TOKENS(float_constant_, name) lhs, double rhs) noexcept { return double(lhs) != rhs; } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator!=(float lhs, RTM_JOIN_TOKENS(float_constant_, name) rhs) noexcept { return lhs != float(rhs); } \ + template RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool operator!=(RTM_JOIN_TOKENS(float_constant_, name) lhs, float rhs) noexcept { return float(lhs) != rhs; } \ + } \ + /* Function user code calls to return the constant value */ \ + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_JOIN_TOKENS(rtm_impl::float_constant_, name) name() noexcept { return RTM_JOIN_TOKENS(rtm_impl::float_constant_, name) {}; } + +namespace rtm +{ + + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace constants + { + ////////////////////////////////////////////////////////////////////////// + // Constants are defined here. Usage is meant to be as natural as possible. + // e.g. float foo = rtm::constants::pi(); + // double bar = rtm::constants::pi() * 0.5; + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // PI + RTM_DEFINE_FLOAT_CONSTANT(pi, 3.141592653589793238462643383279502884) + + ////////////////////////////////////////////////////////////////////////// + // PI / 2 + RTM_DEFINE_FLOAT_CONSTANT(half_pi, 1.570796326794896619231321691639751442) + + ////////////////////////////////////////////////////////////////////////// + // PI * 2 + RTM_DEFINE_FLOAT_CONSTANT(two_pi, 6.283185307179586476925286766559005768) + + ////////////////////////////////////////////////////////////////////////// + // 1.0 / (PI * 2) + RTM_DEFINE_FLOAT_CONSTANT(one_div_two_pi, 1.591549430918953357688837633725143620e-01) + + ////////////////////////////////////////////////////////////////////////// + // PI / 180 + RTM_DEFINE_FLOAT_CONSTANT(pi_div_one_eighty, 0.01745329251994329576923690768489) + + ////////////////////////////////////////////////////////////////////////// + // 180 / PI + RTM_DEFINE_FLOAT_CONSTANT(one_eighty_div_pi, 57.295779513082320876798154814105) + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/fwd.h new file mode 100644 index 00000000..c9fe6057 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/fwd.h @@ -0,0 +1,37 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/version.h" + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public Realtime Math types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +// For now, just include the types header since it is already very compact and lightweight +#include "rtm/types.h" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/compiler_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/compiler_utils.h new file mode 100644 index 00000000..43ef59bd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/compiler_utils.h @@ -0,0 +1,82 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/impl/detect_compiler.h" + +////////////////////////////////////////////////////////////////////////// +// Because this library is made entirely of headers, we have no control over the +// compilation flags used. However, in some cases, certain options must be forced. +// To do this, every header is wrapped in two macros to push and pop the necessary +// pragmas. +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_COMPILER_MSVC) + #define RTM_IMPL_FILE_PRAGMA_PUSH \ + /* Disable fast math, it can hurt precision for little to no performance gain due to the heavy usage of intrinsics. */ \ + __pragma(float_control(precise, on, push)) + + #define RTM_IMPL_FILE_PRAGMA_POP \ + __pragma(float_control(pop)) +#else + #define RTM_IMPL_FILE_PRAGMA_PUSH + #define RTM_IMPL_FILE_PRAGMA_POP +#endif + +////////////////////////////////////////////////////////////////////////// +// In some cases, for performance reasons, we wish to disable stack security +// check cookies. This macro serves this purpose. +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_COMPILER_MSVC) + #define RTM_DISABLE_SECURITY_COOKIE_CHECK __declspec(safebuffers) +#else + #define RTM_DISABLE_SECURITY_COOKIE_CHECK +#endif + +////////////////////////////////////////////////////////////////////////// +// Force inline macros for when it is necessary. +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_COMPILER_MSVC) + #define RTM_FORCE_INLINE __forceinline +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + #define RTM_FORCE_INLINE __attribute__((always_inline)) inline +#else + #define RTM_FORCE_INLINE inline +#endif + +////////////////////////////////////////////////////////////////////////// +// Force no-inline macros for when it is necessary. +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_COMPILER_MSVC) + #define RTM_FORCE_NOINLINE __declspec(noinline) +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + #define RTM_FORCE_NOINLINE __attribute__((noinline)) +#else + #define RTM_FORCE_NOINLINE +#endif + +////////////////////////////////////////////////////////////////////////// +// Joins two pre-processor tokens: RTM_JOIN_TOKENS(foo, bar) yields 'foobar' +////////////////////////////////////////////////////////////////////////// +#define RTM_JOIN_TOKENS(a, b) a ## b diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_arch.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_arch.h new file mode 100644 index 00000000..f7f705a9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_arch.h @@ -0,0 +1,38 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// Macro to identify our platform architecture +////////////////////////////////////////////////////////////////////////// +#if defined(_M_X64) || defined(__x86_64__) + #define RTM_ARCH_X64 +#elif defined(_M_IX86) || defined(__i386__) + #define RTM_ARCH_X86 +#elif defined(_M_ARM64) || defined(__aarch64__) + #define RTM_ARCH_ARM64 +#elif defined(_M_ARM) || defined(__ARM_NEON) + #define RTM_ARCH_ARM +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_compiler.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_compiler.h new file mode 100644 index 00000000..5afddc5a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_compiler.h @@ -0,0 +1,44 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// Macro to identify individual compilers +////////////////////////////////////////////////////////////////////////// +#if defined(__GNUG__) && !defined(__clang__) + #define RTM_COMPILER_GCC +#elif defined(__clang__) + #define RTM_COMPILER_CLANG +#elif defined(_MSC_VER) && !defined(__clang__) + #define RTM_COMPILER_MSVC _MSC_VER + #define RTM_COMPILER_MSVC_2013 1800 + #define RTM_COMPILER_MSVC_2015 1900 + #define RTM_COMPILER_MSVC_2017 1910 + #define RTM_COMPILER_MSVC_2019 1920 + + #if RTM_COMPILER_MSVC < RTM_COMPILER_MSVC_2015 + #pragma message("Warning: This version of visual studio isn't officially supported") + #endif +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_features.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_features.h new file mode 100644 index 00000000..da8bcac5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/detect_features.h @@ -0,0 +1,107 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/impl/detect_arch.h" +#include "rtm/impl/detect_compiler.h" + +////////////////////////////////////////////////////////////////////////// +// Helper macro to determine if vrndns_f32 is supported (ARM64 only) +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_ARCH_ARM64) && !defined(RTM_IMPL_VRNDNS_SUPPORTED) + // ARM documentation states __ARM_FEATURE_DIRECTED_ROUNDING must be defined + #if defined(__ARM_FEATURE_DIRECTED_ROUNDING) + // Only support it with clang for now + #if defined(RTM_COMPILER_CLANG) + // Apple redefines __clang_major__ to match the XCode version + #if defined(__APPLE__) + #if __clang_major__ >= 10 + // Apple clang supports it starting with XCode 10 + #define RTM_IMPL_VRNDNS_SUPPORTED + #endif + #else + #if __clang_major__ >= 6 + // Ordinary clang supports it starting with clang 6 + #define RTM_IMPL_VRNDNS_SUPPORTED + #endif + #endif + #endif + #endif + + // MSVC doesn't appear to define __ARM_FEATURE_DIRECTED_ROUNDING but it supports the + // intrinsic as of VS2019 + // MSVC uses defines for the ARM intrinsics, use them to perform feature detection + #if !defined(RTM_IMPL_VRNDNS_SUPPORTED) && defined(RTM_COMPILER_MSVC) && defined(vrndns_f32) + #define RTM_IMPL_VRNDNS_SUPPORTED + #endif +#endif + +////////////////////////////////////////////////////////////////////////// +// Helper macro to determine if the vca* (e.g vcagtq_f32) family of intrinsics are supported (ARM64 only) +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_ARCH_ARM64) && !defined(RTM_IMPL_VCA_SUPPORTED) + #if defined(RTM_COMPILER_MSVC) + #if defined(vcaleq_f32) + // Support was introduced in VS2019 + // MSVC uses defines for the ARM intrinsics, use them to perform feature detection + #define RTM_IMPL_VCA_SUPPORTED + #endif + #else + // Always enable with GCC and clang for now + #define RTM_IMPL_VCA_SUPPORTED + #endif +#endif + +////////////////////////////////////////////////////////////////////////// +// Helper macro to determine if the vc*z* (e.g vceqq_f32) family of intrinsics are supported (ARM64 only) +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_ARCH_ARM64) && !defined(RTM_IMPL_VCZ_SUPPORTED) + #if defined(RTM_COMPILER_MSVC) + #if defined(vceqzq_f32) + // Support was introduced in VS2019 + // MSVC uses defines for the ARM intrinsics, use them to perform feature detection + #define RTM_IMPL_VCZ_SUPPORTED + #endif + #else + // Always enable with GCC and clang for now + #define RTM_IMPL_VCZ_SUPPORTED + #endif +#endif + +////////////////////////////////////////////////////////////////////////// +// Helper macro to determine if the vsqrtq_f32 intrinsic is supported (ARM64 only) +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_ARCH_ARM64) && !defined(RTM_IMPL_VSQRT_SUPPORTED) + #if defined(RTM_COMPILER_MSVC) + #if defined(vsqrtq_f32) + // Support was introduced in VS2019 + // MSVC uses defines for the ARM intrinsics, use them to perform feature detection + #define RTM_IMPL_VSQRT_SUPPORTED + #endif + #else + // Always enable with GCC and clang for now + #define RTM_IMPL_VSQRT_SUPPORTED + #endif +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/error.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/error.h new file mode 100644 index 00000000..058366a0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/error.h @@ -0,0 +1,189 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +////////////////////////////////////////////////////////////////////////// +// This library uses a simple system to handle asserts. Asserts are fatal and must terminate +// otherwise the behavior is undefined if execution continues. +// +// A total of 4 behaviors are supported: +// - We can print to stderr and abort +// - We can throw and exception +// - We can call a custom function +// - Do nothing and strip the check at compile time (default behavior) +// +// Aborting: +// In order to enable the aborting behavior, simply define the macro RTM_ON_ASSERT_ABORT: +// #define RTM_ON_ASSERT_ABORT +// +// Throwing: +// In order to enable the throwing behavior, simply define the macro RTM_ON_ASSERT_THROW: +// #define RTM_ON_ASSERT_THROW +// Note that the type of the exception thrown is rtm::runtime_assert. +// +// Custom function: +// In order to enable the custom function calling behavior, define the macro RTM_ON_ASSERT_CUSTOM +// with the name of the function to call: +// #define RTM_ON_ASSERT_CUSTOM on_custom_assert_impl +// Note that the function signature is as follow: +// void on_custom_assert_impl(const char* expression, int line, const char* file, const char* format, ...) {} +// +// You can also define your own assert implementation by defining the RTM_ASSERT macro as well: +// #define RTM_ON_ASSERT_CUSTOM +// #define RTM_ASSERT(expression, format, ...) checkf(expression, ANSI_TO_TCHAR(format), #__VA_ARGS__) +// +// No checks: +// By default if no macro mentioned above is defined, all asserts will be stripped +// at compile time. +////////////////////////////////////////////////////////////////////////// + +#if defined(RTM_ON_ASSERT_ABORT) + + #include + #include + #include + + namespace rtm + { + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void on_assert_abort(const char* expression, int line, const char* file, const char* format, ...) + { + (void)expression; + (void)line; + (void)file; + + va_list args; + va_start(args, format); + + std::vfprintf(stderr, format, args); + std::fprintf(stderr, "\n"); + + va_end(args); + + std::abort(); + } + } + + RTM_IMPL_VERSION_NAMESPACE_END + } + + #define RTM_ASSERT(expression, format, ...) do { if (!(expression)) RTM_IMPL_NAMESPACE::rtm_impl::on_assert_abort(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(false) + #define RTM_HAS_ASSERT_CHECKS + #define RTM_NO_EXCEPT noexcept + +#elif defined(RTM_ON_ASSERT_THROW) + + #include + #include + #include + #include + + namespace rtm + { + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + class runtime_assert final : public std::runtime_error + { + using std::runtime_error::runtime_error; // Inherit constructors + }; + + namespace rtm_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void on_assert_throw(const char* expression, int line, const char* file, const char* format, ...) + { + (void)expression; + (void)line; + (void)file; + + constexpr int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + va_list args; + va_start(args, format); + + const int count = vsnprintf(buffer, buffer_size, format, args); + + va_end(args); + + if (count >= 0 && count < buffer_size) + throw runtime_assert(std::string(&buffer[0], count)); + else + throw runtime_assert("Failed to format assert message!\n"); + } + } + + RTM_IMPL_VERSION_NAMESPACE_END + } + + #define RTM_ASSERT(expression, format, ...) do { if (!(expression)) RTM_IMPL_NAMESPACE::rtm_impl::on_assert_throw(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(false) + #define RTM_HAS_ASSERT_CHECKS + #define RTM_NO_EXCEPT + +#elif defined(RTM_ON_ASSERT_CUSTOM) + + #if !defined(RTM_ASSERT) + #define RTM_ASSERT(expression, format, ...) do { if (!(expression)) RTM_ON_ASSERT_CUSTOM(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(false) + #endif + + #define RTM_HAS_ASSERT_CHECKS + #define RTM_NO_EXCEPT + +#else + + #define RTM_ASSERT(expression, format, ...) ((void)0) + #define RTM_NO_EXCEPT noexcept + +#endif + +////////////////////////////////////////////////////////////////////////// +// Deprecation support +////////////////////////////////////////////////////////////////////////// + +#if defined(__has_cpp_attribute) && __cplusplus >= 201402L + #if __has_cpp_attribute(deprecated) + #define RTM_DEPRECATED(msg) [[deprecated(msg)]] + #endif +#endif + +#if !defined(RTM_DEPRECATED) + #if defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + #define RTM_DEPRECATED(msg) __attribute__((deprecated)) + #elif defined(RTM_COMPILER_MSVC) + #define RTM_DEPRECATED(msg) __declspec(deprecated) + #else + #define RTM_DEPRECATED(msg) + #endif +#endif + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/mask_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/mask_common.h new file mode 100644 index 00000000..c9e87e51 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/mask_common.h @@ -0,0 +1,265 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct mask4_bool_set + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4d() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const uint64_t x_mask = x ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t y_mask = y ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t z_mask = z ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t w_mask = w ? 0xFFFFFFFFFFFFFFFFULL : 0; + + return mask4d{ _mm_castsi128_pd(_mm_set_epi64x(y_mask, x_mask)), _mm_castsi128_pd(_mm_set_epi64x(w_mask, z_mask)) }; +#else + const uint64_t x_mask = x ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t y_mask = y ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t z_mask = z ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t w_mask = w ? 0xFFFFFFFFFFFFFFFFULL : 0; + + return mask4d{ x_mask, y_mask, z_mask, w_mask }; +#endif + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4q() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const uint64_t x_mask = x ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t y_mask = y ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t z_mask = z ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t w_mask = w ? 0xFFFFFFFFFFFFFFFFULL : 0; + + return mask4q{ _mm_set_epi64x(y_mask, x_mask), _mm_set_epi64x(w_mask, z_mask) }; +#else + const uint64_t x_mask = x ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t y_mask = y ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t z_mask = z ? 0xFFFFFFFFFFFFFFFFULL : 0; + const uint64_t w_mask = w ? 0xFFFFFFFFFFFFFFFFULL : 0; + + return mask4q{ x_mask, y_mask, z_mask, w_mask }; +#endif + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4f() const RTM_NO_EXCEPT + { + const uint32_t x_mask = x ? 0xFFFFFFFFU : 0; + const uint32_t y_mask = y ? 0xFFFFFFFFU : 0; + const uint32_t z_mask = z ? 0xFFFFFFFFU : 0; + const uint32_t w_mask = w ? 0xFFFFFFFFU : 0; + +#if defined(RTM_SSE2_INTRINSICS) + return _mm_castsi128_ps(_mm_set_epi32(w_mask, z_mask, y_mask, x_mask)); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t V0 = vcreate_f32(((uint64_t)x_mask) | ((uint64_t)(y_mask) << 32)); + float32x2_t V1 = vcreate_f32(((uint64_t)z_mask) | ((uint64_t)(w_mask) << 32)); + return vcombine_f32(V0, V1); +#else + return mask4f{ x_mask, y_mask, z_mask, w_mask }; +#endif + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4i() const RTM_NO_EXCEPT + { + const uint32_t x_mask = x ? 0xFFFFFFFFU : 0; + const uint32_t y_mask = y ? 0xFFFFFFFFU : 0; + const uint32_t z_mask = z ? 0xFFFFFFFFU : 0; + const uint32_t w_mask = w ? 0xFFFFFFFFU : 0; + +#if defined(RTM_SSE2_INTRINSICS) + return _mm_set_epi32(w_mask, z_mask, y_mask, x_mask); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t V0 = vcreate_f32(((uint64_t)x_mask) | ((uint64_t)(y_mask) << 32)); + float32x2_t V1 = vcreate_f32(((uint64_t)z_mask) | ((uint64_t)(w_mask) << 32)); + return RTM_IMPL_MASK4i_SET(vcombine_f32(V0, V1)); +#else + return mask4i{ x_mask, y_mask, z_mask, w_mask }; +#endif + } + + bool x; + bool y; + bool z; + bool w; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a mask4 from all 4 bool components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::mask4_bool_set RTM_SIMD_CALL mask_set(bool x, bool y, bool z, bool w) RTM_NO_EXCEPT + { + return rtm_impl::mask4_bool_set{ x, y, z, w }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct mask4_uint32_set + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4f() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_castsi128_ps(_mm_set_epi32(w, z, y, x)); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t V0 = vcreate_f32(((uint64_t)x) | ((uint64_t)(y) << 32)); + float32x2_t V1 = vcreate_f32(((uint64_t)z) | ((uint64_t)(w) << 32)); + return vcombine_f32(V0, V1); +#else + return mask4f{ x, y, z, w }; +#endif + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4i() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_set_epi32(w, z, y, x); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t V0 = vcreate_f32(((uint64_t)x) | ((uint64_t)(y) << 32)); + float32x2_t V1 = vcreate_f32(((uint64_t)z) | ((uint64_t)(w) << 32)); + return RTM_IMPL_MASK4i_SET(vcombine_f32(V0, V1)); +#else + return mask4i{ x, y, z, w }; +#endif + } + + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a mask4 from 4 uint32 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::mask4_uint32_set RTM_SIMD_CALL mask_set(uint32_t x, uint32_t y, uint32_t z, uint32_t w) RTM_NO_EXCEPT + { + return rtm_impl::mask4_uint32_set{ x, y, z, w }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct mask4_uint64_set + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4d() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // HACK ALERT! + // VS2015, VS2017, and VS2019 crash when compiling with _mm_set_epi64x() here. + // To work around this, we use alternative code. We assume that the high and low words + // are identical in the mask, which should be true. + // See: https://github.com/nfrechette/rtm/issues/84 + ////////////////////////////////////////////////////////////////////////// + #if defined(RTM_COMPILER_MSVC) && defined(RTM_ARCH_X86) && !defined(NDEBUG) + const uint32_t x_mask = x ? 0xFFFFFFFFU : 0; + const uint32_t y_mask = y ? 0xFFFFFFFFU : 0; + const uint32_t z_mask = z ? 0xFFFFFFFFU : 0; + const uint32_t w_mask = w ? 0xFFFFFFFFU : 0; + + return mask4d{ _mm_castsi128_pd(_mm_set_epi32(y_mask, y_mask, x_mask, x_mask)), _mm_castsi128_pd(_mm_set_epi32(w_mask, w_mask, z_mask, z_mask)) }; + #else + return mask4d{ _mm_castsi128_pd(_mm_set_epi64x(y, x)), _mm_castsi128_pd(_mm_set_epi64x(w, z)) }; + #endif +#else + return mask4d{ x, y, z, w }; +#endif + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator mask4q() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // HACK ALERT! + // VS2015, VS2017, and VS2019 crash when compiling with _mm_set_epi64x() here. + // To work around this, we use alternative code. We assume that the high and low words + // are identical in the mask, which should be true. + // See: https://github.com/nfrechette/rtm/issues/84 + ////////////////////////////////////////////////////////////////////////// + #if defined(RTM_COMPILER_MSVC) && defined(RTM_ARCH_X86) && !defined(NDEBUG) + const uint32_t x_mask = x ? 0xFFFFFFFFU : 0; + const uint32_t y_mask = y ? 0xFFFFFFFFU : 0; + const uint32_t z_mask = z ? 0xFFFFFFFFU : 0; + const uint32_t w_mask = w ? 0xFFFFFFFFU : 0; + + return mask4q{ _mm_set_epi32(y_mask, y_mask, x_mask, x_mask), _mm_set_epi32(w_mask, w_mask, z_mask, z_mask) }; + #else + return mask4q{ _mm_set_epi64x(y, x), _mm_set_epi64x(w, z) }; + #endif +#else + return mask4q{ x, y, z, w }; +#endif + } + + uint64_t x; + uint64_t y; + uint64_t z; + uint64_t w; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a mask4 from 4 uint64 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::mask4_uint64_set RTM_SIMD_CALL mask_set(uint64_t x, uint64_t y, uint64_t z, uint64_t w) RTM_NO_EXCEPT + { + return rtm_impl::mask4_uint64_set{ x, y, z, w }; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_affine_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_affine_common.h new file mode 100644 index 00000000..220c447b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_affine_common.h @@ -0,0 +1,350 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/vector4f.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/quatf.h" +#include "rtm/quatd.h" +#include "rtm/type_traits.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // A helper struct to convert a quaternion to matrices of similar width. + // Note: We use a float type as a template argument because GCC loses alignment + // attributes on template argument types. + ////////////////////////////////////////////////////////////////////////// + template + struct matrix_from_quat_helper + { + using quat = typename float_traits::quat; + using vector4 = typename float_traits::vector4; + using matrix3x3 = typename float_traits::matrix3x3; + using matrix3x4 = typename float_traits::matrix3x4; + + RTM_DISABLE_SECURITY_COOKIE_CHECK inline RTM_SIMD_CALL operator matrix3x3() const RTM_NO_EXCEPT + { + RTM_ASSERT(quat_is_normalized(quat_input), "Quaternion is not normalized"); + + const float_type x2 = quat_get_x(quat_input) + quat_get_x(quat_input); + const float_type y2 = quat_get_y(quat_input) + quat_get_y(quat_input); + const float_type z2 = quat_get_z(quat_input) + quat_get_z(quat_input); + const float_type xx = quat_get_x(quat_input) * x2; + const float_type xy = quat_get_x(quat_input) * y2; + const float_type xz = quat_get_x(quat_input) * z2; + const float_type yy = quat_get_y(quat_input) * y2; + const float_type yz = quat_get_y(quat_input) * z2; + const float_type zz = quat_get_z(quat_input) * z2; + const float_type wx = quat_get_w(quat_input) * x2; + const float_type wy = quat_get_w(quat_input) * y2; + const float_type wz = quat_get_w(quat_input) * z2; + + const vector4 x_axis = vector_set(float_type(1.0) - (yy + zz), xy + wz, xz - wy, float_type(0.0)); + const vector4 y_axis = vector_set(xy - wz, float_type(1.0) - (xx + zz), yz + wx, float_type(0.0)); + const vector4 z_axis = vector_set(xz + wy, yz - wx, float_type(1.0) - (xx + yy), float_type(0.0)); + return matrix3x3{ x_axis, y_axis, z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK inline RTM_SIMD_CALL operator matrix3x4() const RTM_NO_EXCEPT + { + RTM_ASSERT(quat_is_normalized(quat_input), "Quaternion is not normalized"); + + const float_type x2 = quat_get_x(quat_input) + quat_get_x(quat_input); + const float_type y2 = quat_get_y(quat_input) + quat_get_y(quat_input); + const float_type z2 = quat_get_z(quat_input) + quat_get_z(quat_input); + const float_type xx = quat_get_x(quat_input) * x2; + const float_type xy = quat_get_x(quat_input) * y2; + const float_type xz = quat_get_x(quat_input) * z2; + const float_type yy = quat_get_y(quat_input) * y2; + const float_type yz = quat_get_y(quat_input) * z2; + const float_type zz = quat_get_z(quat_input) * z2; + const float_type wx = quat_get_w(quat_input) * x2; + const float_type wy = quat_get_w(quat_input) * y2; + const float_type wz = quat_get_w(quat_input) * z2; + + const vector4 x_axis = vector_set(float_type(1.0) - (yy + zz), xy + wz, xz - wy, float_type(0.0)); + const vector4 y_axis = vector_set(xy - wz, float_type(1.0) - (xx + zz), yz + wx, float_type(0.0)); + const vector4 z_axis = vector_set(xz + wy, yz - wx, float_type(1.0) - (xx + yy), float_type(0.0)); + const vector4 w_axis = vector_zero(); + return matrix3x4{ x_axis, y_axis, z_axis, w_axis }; + } + + quat quat_input; + }; + + ////////////////////////////////////////////////////////////////////////// + // A helper struct to convert a 3D scale vector to matrices of similar width. + // Note: We use a float type as a template argument because GCC loses alignment + // attributes on template argument types. + ////////////////////////////////////////////////////////////////////////// + template + struct matrix_from_scale_helper + { + using vector4 = typename float_traits::vector4; + using matrix3x3 = typename float_traits::matrix3x3; + using matrix3x4 = typename float_traits::matrix3x4; + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3() const RTM_NO_EXCEPT + { + const vector4 zero = vector_zero(); + const vector4 x_axis = vector_mix(scale, zero); + const vector4 y_axis = vector_mix(scale, zero); + const vector4 z_axis = vector_mix(scale, zero); + return matrix3x3{ x_axis, y_axis, z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4() const RTM_NO_EXCEPT + { + const vector4 zero = vector_zero(); + const vector4 x_axis = vector_mix(scale, zero); + const vector4 y_axis = vector_mix(scale, zero); + const vector4 z_axis = vector_mix(scale, zero); + return matrix3x4{ x_axis, y_axis, z_axis, zero }; + } + + vector4 scale; + }; + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr vector4f RTM_SIMD_CALL matrix_get_axis(vector4f_arg0 x_axis, vector4f_arg1 y_axis, vector4f_arg2 z_axis, axis3 axis) + { + return axis == axis3::x ? x_axis : (axis == axis3::y ? y_axis : z_axis); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr const vector4d& RTM_SIMD_CALL matrix_get_axis(const vector4d& x_axis, const vector4d& y_axis, const vector4d& z_axis, axis3 axis) + { + return axis == axis3::x ? x_axis : (axis == axis3::y ? y_axis : z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3x3 matrix into a rotation quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_from_matrix(vector4f_arg0 x_axis, vector4f_arg1 y_axis, vector4f_arg2 z_axis) RTM_NO_EXCEPT + { + // TODO: Rework this function, we should be able to handle one axis with zero scale by using the largest diagonal element + // See here for details: https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ + // We should be able to remove the case with trace > 0 as well by always using the diagonal derivation + // This should give some result as well if at least one axis has non-zero scale. + // Unclear what the result will be, if any, with a zero matrix + + const vector4f zero = vector_zero(); + if (vector_all_near_equal3(x_axis, zero) || vector_all_near_equal3(y_axis, zero) || vector_all_near_equal3(z_axis, zero)) + return quat_identity(); // Zero scale not supported, return the identity + + const float x_axis_x = vector_get_x(x_axis); + const float y_axis_y = vector_get_y(y_axis); + const float z_axis_z = vector_get_z(z_axis); + + const float mtx_trace = x_axis_x + y_axis_y + z_axis_z; + if (mtx_trace > 0.0F) + { + const float x_axis_y = vector_get_y(x_axis); + const float x_axis_z = vector_get_z(x_axis); + + const float y_axis_x = vector_get_x(y_axis); + const float y_axis_z = vector_get_z(y_axis); + + const float z_axis_x = vector_get_x(z_axis); + const float z_axis_y = vector_get_y(z_axis); + + const float inv_trace = scalar_sqrt_reciprocal(mtx_trace + 1.0F); + const float half_inv_trace = inv_trace * 0.5F; + + const float x = (y_axis_z - z_axis_y) * half_inv_trace; + const float y = (z_axis_x - x_axis_z) * half_inv_trace; + const float z = (x_axis_y - y_axis_x) * half_inv_trace; + const float w = scalar_reciprocal(inv_trace) * 0.5F; + + return quat_normalize(quat_set(x, y, z, w)); + } + else + { + // Note that axis3::xyz have the same values as mix4::xyz + int32_t best_axis = (int32_t)axis4::x; + if (y_axis_y > x_axis_x) + best_axis = (int32_t)axis4::y; + if (z_axis_z > vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(best_axis))) + best_axis = (int32_t)axis4::z; + + const int32_t next_best_axis = (best_axis + 1) % 3; + const int32_t next_next_best_axis = (next_best_axis + 1) % 3; + + const float mtx_pseudo_trace = 1.0F + + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(best_axis)) - + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_best_axis)), mix4(next_best_axis)) - + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_next_best_axis)), mix4(next_next_best_axis)); + + const float inv_pseudo_trace = scalar_sqrt_reciprocal(mtx_pseudo_trace); + const float half_inv_pseudo_trace = inv_pseudo_trace * 0.5F; + + float quat_values[4]; + quat_values[best_axis] = scalar_reciprocal(inv_pseudo_trace) * 0.5F; + quat_values[next_best_axis] = half_inv_pseudo_trace * + (vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(next_best_axis)) + + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_best_axis)), mix4(best_axis))); + quat_values[next_next_best_axis] = half_inv_pseudo_trace * + (vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(next_next_best_axis)) + + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_next_best_axis)), mix4(best_axis))); + quat_values[3] = half_inv_pseudo_trace * + (vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_best_axis)), mix4(next_next_best_axis)) - + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_next_best_axis)), mix4(next_best_axis))); + + return quat_normalize(quat_load(&quat_values[0])); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3x3 matrix into a rotation quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd RTM_SIMD_CALL quat_from_matrix(const vector4d& x_axis, const vector4d& y_axis, const vector4d& z_axis) RTM_NO_EXCEPT + { + const vector4d zero = vector_zero(); + if (vector_all_near_equal3(x_axis, zero) || vector_all_near_equal3(y_axis, zero) || vector_all_near_equal3(z_axis, zero)) + return quat_identity(); // Zero scale not supported, return the identity + + const double x_axis_x = vector_get_x(x_axis); + const double y_axis_y = vector_get_y(y_axis); + const double z_axis_z = vector_get_z(z_axis); + + const double mtx_trace = x_axis_x + y_axis_y + z_axis_z; + if (mtx_trace > 0.0) + { + const double x_axis_y = vector_get_y(x_axis); + const double x_axis_z = vector_get_z(x_axis); + + const double y_axis_x = vector_get_x(y_axis); + const double y_axis_z = vector_get_z(y_axis); + + const double z_axis_x = vector_get_x(z_axis); + const double z_axis_y = vector_get_y(z_axis); + + const double inv_trace = scalar_sqrt_reciprocal(mtx_trace + 1.0); + const double half_inv_trace = inv_trace * 0.5; + + const double x = (y_axis_z - z_axis_y) * half_inv_trace; + const double y = (z_axis_x - x_axis_z) * half_inv_trace; + const double z = (x_axis_y - y_axis_x) * half_inv_trace; + const double w = scalar_reciprocal(inv_trace) * 0.5; + + return quat_normalize(quat_set(x, y, z, w)); + } + else + { + // Note that axis3::xyz have the same values as mix4::xyz + int32_t best_axis = (int32_t)axis3::x; + if (y_axis_y > x_axis_x) + best_axis = (int32_t)axis3::y; + if (z_axis_z > vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(best_axis))) + best_axis = (int32_t)axis3::z; + + const int32_t next_best_axis = (best_axis + 1) % 3; + const int32_t next_next_best_axis = (next_best_axis + 1) % 3; + + const double mtx_pseudo_trace = 1.0 + + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(best_axis)) - + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_best_axis)), mix4(next_best_axis)) - + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_next_best_axis)), mix4(next_next_best_axis)); + + const double inv_pseudo_trace = scalar_sqrt_reciprocal(mtx_pseudo_trace); + const double half_inv_pseudo_trace = inv_pseudo_trace * 0.5; + + double quat_values[4]; + quat_values[best_axis] = scalar_reciprocal(inv_pseudo_trace) * 0.5; + quat_values[next_best_axis] = half_inv_pseudo_trace * + (vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(next_best_axis)) + + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_best_axis)), mix4(best_axis))); + quat_values[next_next_best_axis] = half_inv_pseudo_trace * + (vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(best_axis)), mix4(next_next_best_axis)) + + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_next_best_axis)), mix4(best_axis))); + quat_values[3] = half_inv_pseudo_trace * + (vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_best_axis)), mix4(next_next_best_axis)) - + vector_get_component(matrix_get_axis(x_axis, y_axis, z_axis, axis3(next_next_best_axis)), mix4(next_best_axis))); + + return quat_normalize(quat_load(&quat_values[0])); + } + } + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a rotation quaternion into a 3x3 or 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK constexpr rtm_impl::matrix_from_quat_helper RTM_SIMD_CALL matrix_from_quat(quatf_arg0 quat) RTM_NO_EXCEPT + { + return rtm_impl::matrix_from_quat_helper{ quat }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a rotation quaternion into a 3x3 or 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK constexpr rtm_impl::matrix_from_quat_helper RTM_SIMD_CALL matrix_from_quat(const quatd& quat) RTM_NO_EXCEPT + { + return rtm_impl::matrix_from_quat_helper{ quat }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a rotation quaternion into a 3x3 or 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK constexpr rtm_impl::matrix_from_quat_helper RTM_SIMD_CALL matrix_from_rotation(quatf_arg0 quat) RTM_NO_EXCEPT + { + return rtm_impl::matrix_from_quat_helper{ quat }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a rotation quaternion into a 3x3 or 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK constexpr rtm_impl::matrix_from_quat_helper RTM_SIMD_CALL matrix_from_rotation(const quatd& quat) RTM_NO_EXCEPT + { + return rtm_impl::matrix_from_quat_helper{ quat }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3D scale vector into a 3x3 or 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::matrix_from_scale_helper RTM_SIMD_CALL matrix_from_scale(vector4f_arg0 scale) RTM_NO_EXCEPT + { + return rtm_impl::matrix_from_scale_helper{ scale }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3D scale vector into a 3x3 or 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::matrix_from_scale_helper RTM_SIMD_CALL matrix_from_scale(const vector4d& scale) RTM_NO_EXCEPT + { + return rtm_impl::matrix_from_scale_helper{ scale }; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_cast.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_cast.h new file mode 100644 index 00000000..f31ec8d9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_cast.h @@ -0,0 +1,302 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/vector4f.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // A helper struct to cast matrices with similar width. + ////////////////////////////////////////////////////////////////////////// + template + struct matrix_caster {}; + + template<> + struct matrix_caster + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr explicit matrix_caster(const matrix3x3f& mtx_) RTM_NO_EXCEPT : mtx(mtx_) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return mtx; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return matrix3x3d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return matrix3x4f{ mtx.x_axis, mtx.y_axis, mtx.z_axis, vector_zero() }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + const vector4d z_axis = vector_cast(mtx.z_axis); + return matrix3x4d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), z_axis, z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + return matrix4x4f{ mtx.x_axis, mtx.y_axis, mtx.z_axis, mtx.z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + const vector4d z_axis = vector_cast(mtx.z_axis); + return matrix4x4d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), z_axis, z_axis }; + } + + const matrix3x3f& mtx; + }; + + template<> + struct matrix_caster + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr explicit matrix_caster(const matrix3x3d& mtx_) RTM_NO_EXCEPT : mtx(mtx_) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return matrix3x3f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return mtx; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + const vector4f z_axis = vector_cast(mtx.z_axis); + return matrix3x4f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), z_axis, z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return matrix3x4d{ mtx.x_axis, mtx.y_axis, mtx.z_axis, vector_zero() }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + const vector4f z_axis = vector_cast(mtx.z_axis); + return matrix4x4f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), z_axis, z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + return matrix4x4d{ mtx.x_axis, mtx.y_axis, mtx.z_axis, mtx.z_axis }; + } + + const matrix3x3d& mtx; + }; + + template<> + struct matrix_caster + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr explicit matrix_caster(const matrix3x4f& mtx_) RTM_NO_EXCEPT : mtx(mtx_) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return matrix3x3f{ mtx.x_axis, mtx.y_axis, mtx.z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return matrix3x3d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return mtx; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return matrix3x4d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis), vector_cast(mtx.w_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + const scalarf zero = scalar_set(0.0F); + const scalarf one = scalar_set(1.0F); + return matrix4x4f{ vector_set_w(mtx.x_axis, zero), vector_set_w(mtx.y_axis, zero), vector_set_w(mtx.z_axis, zero), vector_set_w(mtx.w_axis, one) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + const scalarf zero = scalar_set(0.0F); + const scalarf one = scalar_set(1.0F); + return matrix4x4d{ vector_cast(vector_set_w(mtx.x_axis, zero)), vector_cast(vector_set_w(mtx.y_axis, zero)), vector_cast(vector_set_w(mtx.z_axis, zero)), vector_cast(vector_set_w(mtx.w_axis, one)) }; + } + + const matrix3x4f& mtx; + }; + + template<> + struct matrix_caster + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr explicit matrix_caster(const matrix3x4d& mtx_) RTM_NO_EXCEPT : mtx(mtx_) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return matrix3x3f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return matrix3x3d{ mtx.x_axis, mtx.y_axis, mtx.z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return matrix3x4f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis), vector_cast(mtx.w_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return mtx; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + const scalard zero = scalar_set(0.0); + const scalard one = scalar_set(1.0); + return matrix4x4f{ vector_cast(vector_set_w(mtx.x_axis, zero)), vector_cast(vector_set_w(mtx.y_axis, zero)), vector_cast(vector_set_w(mtx.z_axis, zero)), vector_cast(vector_set_w(mtx.w_axis, one)) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + const scalard zero = scalar_set(0.0); + const scalard one = scalar_set(1.0); + return matrix4x4d{ vector_set_w(mtx.x_axis, zero), vector_set_w(mtx.y_axis, zero), vector_set_w(mtx.z_axis, zero), vector_set_w(mtx.w_axis, one) }; + } + + const matrix3x4d& mtx; + }; + + template<> + struct matrix_caster + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr explicit matrix_caster(const matrix4x4f& mtx_) RTM_NO_EXCEPT : mtx(mtx_) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return matrix3x3f{ mtx.x_axis, mtx.y_axis, mtx.z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return matrix3x3d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return matrix3x4f{ mtx.x_axis, mtx.y_axis, mtx.z_axis, mtx.w_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return matrix3x4d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis), vector_cast(mtx.w_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + return mtx; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + return matrix4x4d{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis), vector_cast(mtx.w_axis) }; + } + + const matrix4x4f& mtx; + }; + + template<> + struct matrix_caster + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr explicit matrix_caster(const matrix4x4d& mtx_) RTM_NO_EXCEPT : mtx(mtx_) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return matrix3x3f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return matrix3x3d{ mtx.x_axis, mtx.y_axis, mtx.z_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return matrix3x4f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis), vector_cast(mtx.w_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return matrix3x4d{ mtx.x_axis, mtx.y_axis, mtx.z_axis, mtx.w_axis }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + return matrix4x4f{ vector_cast(mtx.x_axis), vector_cast(mtx.y_axis), vector_cast(mtx.z_axis), vector_cast(mtx.w_axis) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + return mtx; + } + + const matrix4x4d& mtx; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a matrix from one variant to another. + // Note: When casting a smaller matrix into a larger one, new elements are + // undefined. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::matrix_caster RTM_SIMD_CALL matrix_cast(const matrix_type& input) RTM_NO_EXCEPT + { + return rtm_impl::matrix_caster(input); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_common.h new file mode 100644 index 00000000..cbee7b95 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/matrix_common.h @@ -0,0 +1,174 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/vector4f.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/type_traits.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_cast.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various matrix types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct matrix_identity_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3d() const RTM_NO_EXCEPT + { + return matrix3x3d{ vector_set(1.0, 0.0, 0.0, 0.0), vector_set(0.0, 1.0, 0.0, 0.0), vector_set(0.0, 0.0, 1.0, 0.0) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x3f() const RTM_NO_EXCEPT + { + return matrix3x3f{ vector_set(1.0F, 0.0F, 0.0F, 0.0F), vector_set(0.0F, 1.0F, 0.0F, 0.0F), vector_set(0.0F, 0.0F, 1.0F, 0.0F) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return matrix3x4d{ vector_set(1.0, 0.0, 0.0, 0.0), vector_set(0.0, 1.0, 0.0, 0.0), vector_set(0.0, 0.0, 1.0, 0.0), vector_set(0.0, 0.0, 0.0, 1.0) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return matrix3x4f{ vector_set(1.0F, 0.0F, 0.0F, 0.0F), vector_set(0.0F, 1.0F, 0.0F, 0.0F), vector_set(0.0F, 0.0F, 1.0F, 0.0F), vector_set(0.0F, 0.0F, 0.0F, 1.0F) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + return matrix4x4d{ vector_set(1.0, 0.0, 0.0, 0.0), vector_set(0.0, 1.0, 0.0, 0.0), vector_set(0.0, 0.0, 1.0, 0.0), vector_set(0.0, 0.0, 0.0, 1.0) }; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + return matrix4x4f{ vector_set(1.0F, 0.0F, 0.0F, 0.0F), vector_set(0.0F, 1.0F, 0.0F, 0.0F), vector_set(0.0F, 0.0F, 1.0F, 0.0F), vector_set(0.0F, 0.0F, 0.0F, 1.0F) }; + } + }; + + ////////////////////////////////////////////////////////////////////////// + // A helper struct to set matrices with similar width. + // Note: We use a float type as a template argument because GCC loses alignment + // attributes on template argument types. + ////////////////////////////////////////////////////////////////////////// + template + struct matrix_setter4x4 + { + using vector4 = typename float_traits::vector4; + + ////////////////////////////////////////////////////////////////////////// + // Sets all 4 axes and creates a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x4d() const RTM_NO_EXCEPT + { + return matrix3x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 4 axes and creates a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix3x4f() const RTM_NO_EXCEPT + { + return matrix3x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 4 axes and creates a 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix4x4d() const RTM_NO_EXCEPT + { + return matrix4x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 4 axes and creates a 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator matrix4x4f() const RTM_NO_EXCEPT + { + return matrix4x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + vector4 x_axis; + vector4 y_axis; + vector4 z_axis; + vector4 w_axis; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 3 axes and creates a matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr matrix3x3f RTM_SIMD_CALL matrix_set(vector4f_arg0 x_axis, vector4f_arg1 y_axis, vector4f_arg2 z_axis) RTM_NO_EXCEPT + { + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 3 axes and creates a matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr matrix3x3d RTM_SIMD_CALL matrix_set(const vector4d& x_axis, const vector4d& y_axis, const vector4d& z_axis) RTM_NO_EXCEPT + { + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 4 axes and creates a matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::matrix_setter4x4 RTM_SIMD_CALL matrix_set(vector4f_arg0 x_axis, vector4f_arg1 y_axis, vector4f_arg2 z_axis, vector4f_arg3 w_axis) RTM_NO_EXCEPT + { + return rtm_impl::matrix_setter4x4{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets all 4 axes and creates a matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::matrix_setter4x4 RTM_SIMD_CALL matrix_set(const vector4d& x_axis, const vector4d& y_axis, const vector4d& z_axis, const vector4d& w_axis) RTM_NO_EXCEPT + { + return rtm_impl::matrix_setter4x4{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the identity matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::matrix_identity_impl RTM_SIMD_CALL matrix_identity() RTM_NO_EXCEPT + { + return rtm_impl::matrix_identity_impl(); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/memory_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/memory_utils.h new file mode 100644 index 00000000..e99b6800 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/memory_utils.h @@ -0,0 +1,275 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/error.h" + +#include +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// +// This file contains various memory related utility functions and other misc helpers. +// Everything is hidden in an impl namespace even though they could be useful and used +// by anyone only to avoid polluting the rtm namespace. If these become useful enough, +// they might be moved into their own external dependency. +////////////////////////////////////////////////////////////////////////// + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // Allows static branching without any warnings + ////////////////////////////////////////////////////////////////////////// + template + struct static_condition { static constexpr bool test() RTM_NO_EXCEPT { return true; } }; + + template<> + struct static_condition { static constexpr bool test() RTM_NO_EXCEPT { return false; } }; + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input is a power of two, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool is_power_of_two(size_t input) RTM_NO_EXCEPT + { + return input != 0 && (input & (input - 1)) == 0; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the alignment provided satisfies the requirement for the provided Type, false otherwise. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool is_alignment_valid(size_t alignment) RTM_NO_EXCEPT + { + return is_power_of_two(alignment) && alignment >= alignof(Type); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the pointer provided satisfies the specified alignment, false otherwise. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool is_aligned_to(PtrType* value, size_t alignment) RTM_NO_EXCEPT + { + RTM_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return (reinterpret_cast(value) & (alignment - 1)) == 0; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the integral value provided satisfies the specified alignment, false otherwise. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool is_aligned_to(IntegralType value, size_t alignment) RTM_NO_EXCEPT + { + RTM_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return (static_cast(value) & (alignment - 1)) == 0; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the provided pointer satisfies the alignment of PtrType, false otherwise. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool is_aligned(PtrType* value) RTM_NO_EXCEPT + { + return is_aligned_to(value, alignof(PtrType)); + } + + ////////////////////////////////////////////////////////////////////////// + // The input pointer is rounded up to the desired alignment. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE PtrType* align_to(PtrType* value, size_t alignment) RTM_NO_EXCEPT + { + RTM_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return reinterpret_cast((reinterpret_cast(value) + (alignment - 1)) & ~(alignment - 1)); + } + + ////////////////////////////////////////////////////////////////////////// + // The input integral value is rounded up to the desired alignment. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE IntegralType align_to(IntegralType value, size_t alignment) RTM_NO_EXCEPT + { + RTM_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return static_cast((static_cast(value) + (alignment - 1)) & ~(alignment - 1)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the array size for the provided array. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr size_t get_array_size(ElementType const (&)[num_elements]) RTM_NO_EXCEPT { return num_elements; } + + ////////////////////////////////////////////////////////////////////////// + // Type safe casting + ////////////////////////////////////////////////////////////////////////// + template + struct safe_ptr_to_ptr_cast_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE static DestPtrType* cast(SrcType* input) RTM_NO_EXCEPT + { + RTM_ASSERT(is_aligned_to(input, alignof(DestPtrType)), "reinterpret_cast would result in an unaligned pointer"); + return reinterpret_cast(input); + } + }; + + template + struct safe_ptr_to_ptr_cast_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK static RTM_FORCE_INLINE constexpr void* cast(SrcType* input) RTM_NO_EXCEPT { return input; } + }; + + template + struct safe_int_to_ptr_cast_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE static DestPtrType* cast(SrcType input) RTM_NO_EXCEPT + { + RTM_ASSERT(is_aligned_to(input, alignof(DestPtrType)), "reinterpret_cast would result in an unaligned pointer"); + return reinterpret_cast(input); + } + }; + + template + struct safe_int_to_ptr_cast_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK static RTM_FORCE_INLINE constexpr void* cast(SrcType input) RTM_NO_EXCEPT { return reinterpret_cast(input); } + }; + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE DestPtrType* safe_ptr_cast(SrcType* input) RTM_NO_EXCEPT + { + return safe_ptr_to_ptr_cast_impl::cast(input); + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE DestPtrType* safe_ptr_cast(SrcType input) RTM_NO_EXCEPT + { + return safe_int_to_ptr_cast_impl::cast(input); + } + +#if defined(RTM_COMPILER_GCC) + // GCC sometimes complains about comparisons being always true due to partial template + // evaluation. Disable that warning since we know it is safe. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wtype-limits" +#endif + + template + struct safe_underlying_type { using type = typename std::underlying_type::type; }; + + template + struct safe_underlying_type { using type = Type; }; + + template + struct is_static_cast_safe_s + { + RTM_DISABLE_SECURITY_COOKIE_CHECK static RTM_FORCE_INLINE bool test(SrcType input) RTM_NO_EXCEPT + { + using SrcRealType = typename safe_underlying_type::value>::type; + + if (static_condition<(std::is_signed::value == std::is_signed::value)>::test()) + return SrcType(DstType(input)) == input; + else if (static_condition<(std::is_signed::value)>::test()) + return int64_t(input) >= 0 && SrcType(DstType(input)) == input; + else + return uint64_t(input) <= uint64_t(std::numeric_limits::max()); + }; + }; + + template + struct is_static_cast_safe_s + { + RTM_DISABLE_SECURITY_COOKIE_CHECK static RTM_FORCE_INLINE bool test(SrcType input) RTM_NO_EXCEPT + { + return SrcType(DstType(input)) == input; + } + }; + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool is_static_cast_safe(SrcType input) RTM_NO_EXCEPT + { + // TODO: In C++17 this should be folded to constexpr if + return is_static_cast_safe_s::value || std::is_floating_point::value)>::test()>::test(input); + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE DstType safe_static_cast(SrcType input) RTM_NO_EXCEPT + { +#if defined(RTM_HAS_ASSERT_CHECKS) + const bool is_safe = is_static_cast_safe(input); + RTM_ASSERT(is_safe, "Unsafe static cast resulted in data loss"); +#endif + + return static_cast(input); + } + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif + + ////////////////////////////////////////////////////////////////////////// + // Reads a DataType from the input pointer regardless of its alignment. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE DataType unaligned_read(const void* input) RTM_NO_EXCEPT + { + DataType result; + std::memcpy(&result, input, sizeof(DataType)); + return result; + } + + ////////////////////////////////////////////////////////////////////////// + // Reads a DataType from the input pointer with an alignment check. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE DataType aligned_read(const void* input) RTM_NO_EXCEPT + { + return *safe_ptr_cast(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a DataType into the output pointer regardless of its alignment. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void unaligned_write(DataType input, void* output) RTM_NO_EXCEPT + { + std::memcpy(output, &input, sizeof(DataType)); + } + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/quat_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/quat_common.h new file mode 100644 index 00000000..4363812c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/quat_common.h @@ -0,0 +1,103 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Creates a quaternion from all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set(float x, float y, float z, float w) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_set_ps(w, z, y, x); +#elif defined(RTM_NEON_INTRINSICS) +#if 1 + float32x2_t V0 = vcreate_f32(((uint64_t)*(const uint32_t*)&x) | ((uint64_t)(*(const uint32_t*)&y) << 32)); + float32x2_t V1 = vcreate_f32(((uint64_t)*(const uint32_t*)&z) | ((uint64_t)(*(const uint32_t*)&w) << 32)); + return vcombine_f32(V0, V1); +#else + float __attribute__((aligned(16))) data[4] = { x, y, z, w }; + return vld1q_f32(data); +#endif +#else + return quatf{ x, y, z, w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a quaternion from all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd RTM_SIMD_CALL quat_set(double x, double y, double z, double w) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ _mm_set_pd(y, x), _mm_set_pd(w, z) }; +#else + return quatd{ x, y, z, w }; +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various quaternion types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quat_identity_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator quatd() const RTM_NO_EXCEPT + { + return quat_set(0.0, 0.0, 0.0, 1.0); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator quatf() const RTM_NO_EXCEPT + { + return quat_set(0.0F, 0.0F, 0.0F, 1.0F); + } + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the identity quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quat_identity_impl RTM_SIMD_CALL quat_identity() RTM_NO_EXCEPT + { + return rtm_impl::quat_identity_impl(); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/qvv_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/qvv_common.h new file mode 100644 index 00000000..d175602d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/qvv_common.h @@ -0,0 +1,86 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Creates a QVV transform from a rotation quaternion, a translation, and a 3D scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr qvvf RTM_SIMD_CALL qvv_set(quatf_arg0 rotation, vector4f_arg1 translation, vector4f_arg2 scale) RTM_NO_EXCEPT + { + return qvvf{ rotation, translation, scale }; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a QVV transform from a rotation quaternion, a translation, and a 3D scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr qvvd RTM_SIMD_CALL qvv_set(const quatd& rotation, const vector4d& translation, const vector4d& scale) RTM_NO_EXCEPT + { + return qvvd{ rotation, translation, scale }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various QVV transform types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct qvv_identity_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator qvvd() const RTM_NO_EXCEPT + { + return qvv_set(quat_identity(), vector_zero(), vector_set(1.0)); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator qvvf() const RTM_NO_EXCEPT + { + return qvv_set(quat_identity(), vector_zero(), vector_set(1.0F)); + } + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the identity QVV transform. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::qvv_identity_impl RTM_SIMD_CALL qvv_identity() RTM_NO_EXCEPT + { + return rtm_impl::qvv_identity_impl(); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/scalar_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/scalar_common.h new file mode 100644 index 00000000..0ae8bb26 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/scalar_common.h @@ -0,0 +1,105 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various scalar types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct scalar_loaderf + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + return *ptr; + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_load_ss(ptr) }; + } +#endif + + const float* ptr; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various scalar types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct scalar_loaderd + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + return *ptr; + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ _mm_load_sd(ptr) }; + } +#endif + + const double* ptr; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Loads a scalar from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::scalar_loaderf RTM_SIMD_CALL scalar_load(const float* input) RTM_NO_EXCEPT + { + return rtm_impl::scalar_loaderf{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Loads a scalar from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::scalar_loaderd RTM_SIMD_CALL scalar_load(const double* input) RTM_NO_EXCEPT + { + return rtm_impl::scalar_loaderd{ input }; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/type_args.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/type_args.h new file mode 100644 index 00000000..ee5603dd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/type_args.h @@ -0,0 +1,469 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/types.h" +#include "rtm/version.h" + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Register passing typedefs + ////////////////////////////////////////////////////////////////////////// + +#if defined(RTM_USE_VECTORCALL) + // On x64 with __vectorcall, the first 6x vector4f/quatf arguments can be passed by value in a register, + // everything else afterwards is passed by const&. They can also be returned by register. + using vector4f_arg0 = const vector4f; + using vector4f_arg1 = const vector4f; + using vector4f_arg2 = const vector4f; + using vector4f_arg3 = const vector4f; + using vector4f_arg4 = const vector4f; + using vector4f_arg5 = const vector4f; + using vector4f_arg6 = const vector4f&; + using vector4f_arg7 = const vector4f&; + using vector4f_argn = const vector4f&; + + using quatf_arg0 = const quatf; + using quatf_arg1 = const quatf; + using quatf_arg2 = const quatf; + using quatf_arg3 = const quatf; + using quatf_arg4 = const quatf; + using quatf_arg5 = const quatf; + using quatf_arg6 = const quatf&; + using quatf_arg7 = const quatf&; + using quatf_argn = const quatf&; + + using scalarf_arg0 = const scalarf; + using scalarf_arg1 = const scalarf; + using scalarf_arg2 = const scalarf; + using scalarf_arg3 = const scalarf; + using scalarf_arg4 = const scalarf; + using scalarf_arg5 = const scalarf; + using scalarf_arg6 = const scalarf&; + using scalarf_arg7 = const scalarf&; + using scalarf_argn = const scalarf&; + + using mask4f_arg0 = const mask4f; + using mask4f_arg1 = const mask4f; + using mask4f_arg2 = const mask4f; + using mask4f_arg3 = const mask4f; + using mask4f_arg4 = const mask4f; + using mask4f_arg5 = const mask4f; + using mask4f_arg6 = const mask4f&; + using mask4f_arg7 = const mask4f&; + using mask4f_argn = const mask4f&; + + using mask4i_arg0 = const mask4i; + using mask4i_arg1 = const mask4i; + using mask4i_arg2 = const mask4i; + using mask4i_arg3 = const mask4i; + using mask4i_arg4 = const mask4i; + using mask4i_arg5 = const mask4i; + using mask4i_arg6 = const mask4i&; + using mask4i_arg7 = const mask4i&; + using mask4i_argn = const mask4i&; + + // With __vectorcall, vector aggregates are also passed by register and they can use up to 4 registers. + + using qvvf_arg0 = const qvvf; + using qvvf_arg1 = const qvvf; + using qvvf_argn = const qvvf&; + + using matrix3x3f_arg0 = const matrix3x3f; + using matrix3x3f_arg1 = const matrix3x3f&; + using matrix3x3f_argn = const matrix3x3f&; + + using matrix3x4f_arg0 = const matrix3x4f; + using matrix3x4f_arg1 = const matrix3x4f&; + using matrix3x4f_argn = const matrix3x4f&; + + using matrix4x4f_arg0 = const matrix4x4f; + using matrix4x4f_arg1 = const matrix4x4f&; + using matrix4x4f_argn = const matrix4x4f&; +#elif defined(RTM_NEON64_INTRINSICS) + // On ARM64 NEON, the first 8x vector4f/quatf arguments can be passed by value in a register, + // everything else afterwards is passed by const&. They can also be returned by register. + + using vector4f_arg0 = const vector4f; + using vector4f_arg1 = const vector4f; + using vector4f_arg2 = const vector4f; + using vector4f_arg3 = const vector4f; + using vector4f_arg4 = const vector4f; + using vector4f_arg5 = const vector4f; + using vector4f_arg6 = const vector4f; + using vector4f_arg7 = const vector4f; + using vector4f_argn = const vector4f&; + + using quatf_arg0 = const quatf; + using quatf_arg1 = const quatf; + using quatf_arg2 = const quatf; + using quatf_arg3 = const quatf; + using quatf_arg4 = const quatf; + using quatf_arg5 = const quatf; + using quatf_arg6 = const quatf; + using quatf_arg7 = const quatf; + using quatf_argn = const quatf&; + + using scalarf_arg0 = const scalarf; + using scalarf_arg1 = const scalarf; + using scalarf_arg2 = const scalarf; + using scalarf_arg3 = const scalarf; + using scalarf_arg4 = const scalarf; + using scalarf_arg5 = const scalarf; + using scalarf_arg6 = const scalarf; + using scalarf_arg7 = const scalarf; + using scalarf_argn = const scalarf; + + using mask4f_arg0 = const mask4f; + using mask4f_arg1 = const mask4f; + using mask4f_arg2 = const mask4f; + using mask4f_arg3 = const mask4f; + using mask4f_arg4 = const mask4f; + using mask4f_arg5 = const mask4f; + using mask4f_arg6 = const mask4f; + using mask4f_arg7 = const mask4f; + using mask4f_argn = const mask4f&; + + using mask4i_arg0 = const mask4i; + using mask4i_arg1 = const mask4i; + using mask4i_arg2 = const mask4i; + using mask4i_arg3 = const mask4i; + using mask4i_arg4 = const mask4i; + using mask4i_arg5 = const mask4i; + using mask4i_arg6 = const mask4i; + using mask4i_arg7 = const mask4i; + using mask4i_argn = const mask4i&; + + // With ARM64 NEON, vector aggregates are also passed by register but the whole aggregate + // must fit in the number of registers available (e.g. we can pass 2x qvvf but not 3x). + // A qvvf can also be returned by register. + + using qvvf_arg0 = const qvvf; + using qvvf_arg1 = const qvvf; + using qvvf_argn = const qvvf&; + + using matrix3x3f_arg0 = const matrix3x3f; + using matrix3x3f_arg1 = const matrix3x3f; + using matrix3x3f_argn = const matrix3x3f&; + + using matrix3x4f_arg0 = const matrix3x4f; + using matrix3x4f_arg1 = const matrix3x4f; + using matrix3x4f_argn = const matrix3x4f&; + + using matrix4x4f_arg0 = const matrix4x4f; + using matrix4x4f_arg1 = const matrix4x4f; + using matrix4x4f_argn = const matrix4x4f&; +#elif defined(RTM_NEON_INTRINSICS) + // On ARM NEON, the first 4x vector4f/quatf arguments can be passed by value in a register, + // everything else afterwards is passed by const&. They can also be returned by register. + + using vector4f_arg0 = const vector4f; + using vector4f_arg1 = const vector4f; + using vector4f_arg2 = const vector4f; + using vector4f_arg3 = const vector4f; + using vector4f_arg4 = const vector4f&; + using vector4f_arg5 = const vector4f&; + using vector4f_arg6 = const vector4f&; + using vector4f_arg7 = const vector4f&; + using vector4f_argn = const vector4f&; + + using quatf_arg0 = const quatf; + using quatf_arg1 = const quatf; + using quatf_arg2 = const quatf; + using quatf_arg3 = const quatf; + using quatf_arg4 = const quatf&; + using quatf_arg5 = const quatf&; + using quatf_arg6 = const quatf&; + using quatf_arg7 = const quatf&; + using quatf_argn = const quatf&; + + using scalarf_arg0 = const scalarf; + using scalarf_arg1 = const scalarf; + using scalarf_arg2 = const scalarf; + using scalarf_arg3 = const scalarf; + using scalarf_arg4 = const scalarf; + using scalarf_arg5 = const scalarf; + using scalarf_arg6 = const scalarf; + using scalarf_arg7 = const scalarf; + using scalarf_argn = const scalarf; + + using mask4f_arg0 = const mask4f; + using mask4f_arg1 = const mask4f; + using mask4f_arg2 = const mask4f; + using mask4f_arg3 = const mask4f; + using mask4f_arg4 = const mask4f&; + using mask4f_arg5 = const mask4f&; + using mask4f_arg6 = const mask4f&; + using mask4f_arg7 = const mask4f&; + using mask4f_argn = const mask4f&; + + using mask4i_arg0 = const mask4i; + using mask4i_arg1 = const mask4i; + using mask4i_arg2 = const mask4i; + using mask4i_arg3 = const mask4i; + using mask4i_arg4 = const mask4i&; + using mask4i_arg5 = const mask4i&; + using mask4i_arg6 = const mask4i&; + using mask4i_arg7 = const mask4i&; + using mask4i_argn = const mask4i&; + + // ARM NEON does not support passing aggregates by register. + + using qvvf_arg0 = const qvvf&; + using qvvf_arg1 = const qvvf&; + using qvvf_argn = const qvvf&; + + using matrix3x3f_arg0 = const matrix3x3f&; + using matrix3x3f_arg1 = const matrix3x3f&; + using matrix3x3f_argn = const matrix3x3f&; + + using matrix3x4f_arg0 = const matrix3x4f&; + using matrix3x4f_arg1 = const matrix3x4f&; + using matrix3x4f_argn = const matrix3x4f&; + + using matrix4x4f_arg0 = const matrix4x4f&; + using matrix4x4f_arg1 = const matrix4x4f&; + using matrix4x4f_argn = const matrix4x4f&; +#elif defined(RTM_ARCH_X64) && defined(RTM_COMPILER_GCC) + // On x64 with gcc, the first 8x vector4f/quatf arguments can be passed by value in a register, + // everything else afterwards is passed by const&. They can also be returned by register. + + using vector4f_arg0 = const vector4f; + using vector4f_arg1 = const vector4f; + using vector4f_arg2 = const vector4f; + using vector4f_arg3 = const vector4f; + using vector4f_arg4 = const vector4f; + using vector4f_arg5 = const vector4f; + using vector4f_arg6 = const vector4f; + using vector4f_arg7 = const vector4f; + using vector4f_argn = const vector4f&; + + using quatf_arg0 = const quatf; + using quatf_arg1 = const quatf; + using quatf_arg2 = const quatf; + using quatf_arg3 = const quatf; + using quatf_arg4 = const quatf; + using quatf_arg5 = const quatf; + using quatf_arg6 = const quatf; + using quatf_arg7 = const quatf; + using quatf_argn = const quatf&; + + using scalarf_arg0 = const scalarf; + using scalarf_arg1 = const scalarf; + using scalarf_arg2 = const scalarf; + using scalarf_arg3 = const scalarf; + using scalarf_arg4 = const scalarf; + using scalarf_arg5 = const scalarf; + using scalarf_arg6 = const scalarf; + using scalarf_arg7 = const scalarf; + using scalarf_argn = const scalarf&; + + using mask4f_arg0 = const mask4f; + using mask4f_arg1 = const mask4f; + using mask4f_arg2 = const mask4f; + using mask4f_arg3 = const mask4f; + using mask4f_arg4 = const mask4f; + using mask4f_arg5 = const mask4f; + using mask4f_arg6 = const mask4f; + using mask4f_arg7 = const mask4f; + using mask4f_argn = const mask4f&; + + using mask4i_arg0 = const mask4i; + using mask4i_arg1 = const mask4i; + using mask4i_arg2 = const mask4i; + using mask4i_arg3 = const mask4i; + using mask4i_arg4 = const mask4i; + using mask4i_arg5 = const mask4i; + using mask4i_arg6 = const mask4i; + using mask4i_arg7 = const mask4i; + using mask4i_argn = const mask4i&; + + // gcc does not appear to support passing and returning aggregates by register + + using qvvf_arg0 = const qvvf&; + using qvvf_arg1 = const qvvf&; + using qvvf_argn = const qvvf&; + + using matrix3x3f_arg0 = const matrix3x3f&; + using matrix3x3f_arg1 = const matrix3x3f&; + using matrix3x3f_argn = const matrix3x3f&; + + using matrix3x4f_arg0 = const matrix3x4f&; + using matrix3x4f_arg1 = const matrix3x4f&; + using matrix3x4f_argn = const matrix3x4f&; + + using matrix4x4f_arg0 = const matrix4x4f&; + using matrix4x4f_arg1 = const matrix4x4f&; + using matrix4x4f_argn = const matrix4x4f&; +#elif defined(RTM_ARCH_X64) && defined(RTM_COMPILER_CLANG) + // On x64 with clang, the first 8x vector4f/quatf arguments can be passed by value in a register, + // everything else afterwards is passed by const&. They can also be returned by register. + + using vector4f_arg0 = const vector4f; + using vector4f_arg1 = const vector4f; + using vector4f_arg2 = const vector4f; + using vector4f_arg3 = const vector4f; + using vector4f_arg4 = const vector4f; + using vector4f_arg5 = const vector4f; + using vector4f_arg6 = const vector4f; + using vector4f_arg7 = const vector4f; + using vector4f_argn = const vector4f&; + + using quatf_arg0 = const quatf; + using quatf_arg1 = const quatf; + using quatf_arg2 = const quatf; + using quatf_arg3 = const quatf; + using quatf_arg4 = const quatf; + using quatf_arg5 = const quatf; + using quatf_arg6 = const quatf; + using quatf_arg7 = const quatf; + using quatf_argn = const quatf&; + + using scalarf_arg0 = const scalarf; + using scalarf_arg1 = const scalarf; + using scalarf_arg2 = const scalarf; + using scalarf_arg3 = const scalarf; + using scalarf_arg4 = const scalarf; + using scalarf_arg5 = const scalarf; + using scalarf_arg6 = const scalarf; + using scalarf_arg7 = const scalarf; + using scalarf_argn = const scalarf&; + + using mask4f_arg0 = const mask4f; + using mask4f_arg1 = const mask4f; + using mask4f_arg2 = const mask4f; + using mask4f_arg3 = const mask4f; + using mask4f_arg4 = const mask4f; + using mask4f_arg5 = const mask4f; + using mask4f_arg6 = const mask4f; + using mask4f_arg7 = const mask4f; + using mask4f_argn = const mask4f&; + + using mask4i_arg0 = const mask4i; + using mask4i_arg1 = const mask4i; + using mask4i_arg2 = const mask4i; + using mask4i_arg3 = const mask4i; + using mask4i_arg4 = const mask4i; + using mask4i_arg5 = const mask4i; + using mask4i_arg6 = const mask4i; + using mask4i_arg7 = const mask4i; + using mask4i_argn = const mask4i&; + + // We could pass up to 2 full qvvf types by register and the rotation/translation of + // the third but aggregates are not returned by register. + // TODO: Measure the impact of this because it could potentially degrade performance + // if multiple qvv_mul(..) are called, forcing the return value to be written and read back + // before the next function call can be made. It might be faster regardless as the compiler + // might be able to insert other instructions in between. + + using qvvf_arg0 = const qvvf&; + using qvvf_arg1 = const qvvf&; + using qvvf_argn = const qvvf&; + + using matrix3x3f_arg0 = const matrix3x3f&; + using matrix3x3f_arg1 = const matrix3x3f&; + using matrix3x3f_argn = const matrix3x3f&; + + using matrix3x4f_arg0 = const matrix3x4f&; + using matrix3x4f_arg1 = const matrix3x4f&; + using matrix3x4f_argn = const matrix3x4f&; + + using matrix4x4f_arg0 = const matrix4x4f&; + using matrix4x4f_arg1 = const matrix4x4f&; + using matrix4x4f_argn = const matrix4x4f&; +#else + // On every other platform, everything is passed by const& + using vector4f_arg0 = const vector4f&; + using vector4f_arg1 = const vector4f&; + using vector4f_arg2 = const vector4f&; + using vector4f_arg3 = const vector4f&; + using vector4f_arg4 = const vector4f&; + using vector4f_arg5 = const vector4f&; + using vector4f_arg6 = const vector4f&; + using vector4f_arg7 = const vector4f&; + using vector4f_argn = const vector4f&; + + using quatf_arg0 = const quatf&; + using quatf_arg1 = const quatf&; + using quatf_arg2 = const quatf&; + using quatf_arg3 = const quatf&; + using quatf_arg4 = const quatf&; + using quatf_arg5 = const quatf&; + using quatf_arg6 = const quatf&; + using quatf_arg7 = const quatf&; + using quatf_argn = const quatf&; + + using scalarf_arg0 = const scalarf; + using scalarf_arg1 = const scalarf; + using scalarf_arg2 = const scalarf; + using scalarf_arg3 = const scalarf; + using scalarf_arg4 = const scalarf; + using scalarf_arg5 = const scalarf; + using scalarf_arg6 = const scalarf; + using scalarf_arg7 = const scalarf; + using scalarf_argn = const scalarf; + + using mask4f_arg0 = const mask4f&; + using mask4f_arg1 = const mask4f&; + using mask4f_arg2 = const mask4f&; + using mask4f_arg3 = const mask4f&; + using mask4f_arg4 = const mask4f&; + using mask4f_arg5 = const mask4f&; + using mask4f_arg6 = const mask4f&; + using mask4f_arg7 = const mask4f&; + using mask4f_argn = const mask4f&; + + using mask4i_arg0 = const mask4i&; + using mask4i_arg1 = const mask4i&; + using mask4i_arg2 = const mask4i&; + using mask4i_arg3 = const mask4i&; + using mask4i_arg4 = const mask4i&; + using mask4i_arg5 = const mask4i&; + using mask4i_arg6 = const mask4i&; + using mask4i_arg7 = const mask4i&; + using mask4i_argn = const mask4i&; + + using qvvf_arg0 = const qvvf&; + using qvvf_arg1 = const qvvf&; + using qvvf_argn = const qvvf&; + + using matrix3x3f_arg0 = const matrix3x3f&; + using matrix3x3f_arg1 = const matrix3x3f&; + using matrix3x3f_argn = const matrix3x3f&; + + using matrix3x4f_arg0 = const matrix3x4f&; + using matrix3x4f_arg1 = const matrix3x4f&; + using matrix3x4f_argn = const matrix3x4f&; + + using matrix4x4f_arg0 = const matrix4x4f&; + using matrix4x4f_arg1 = const matrix4x4f&; + using matrix4x4f_argn = const matrix4x4f&; +#endif + + RTM_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/vector_common.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/vector_common.h new file mode 100644 index 00000000..9e545cbe --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/impl/vector_common.h @@ -0,0 +1,617 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/types.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/scalarf.h" +#include "rtm/scalard.h" +#include "rtm/version.h" + +#include +#include + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set(float x, float y, float z, float w) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_set_ps(w, z, y, x); +#elif defined(RTM_NEON_INTRINSICS) +#if 1 + float32x2_t V0 = vcreate_f32(((uint64_t)*(const uint32_t*)&x) | ((uint64_t)(*(const uint32_t*)&y) << 32)); + float32x2_t V1 = vcreate_f32(((uint64_t)*(const uint32_t*)&z) | ((uint64_t)(*(const uint32_t*)&w) << 32)); + return vcombine_f32(V0, V1); +#else + float __attribute__((aligned(16))) data[4] = { x, y, z, w }; + return vld1q_f32(data); +#endif +#else + return vector4f{ x, y, z, w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from the [xyz] components and sets [w] to 0.0. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set(float x, float y, float z) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_set_ps(0.0F, z, y, x); +#elif defined(RTM_NEON_INTRINSICS) +#if 1 + float32x2_t V0 = vcreate_f32(((uint64_t)*(const uint32_t*)&x) | ((uint64_t)(*(const uint32_t*)&y) << 32)); + float32x2_t V1 = vcreate_f32((uint64_t)*(const uint32_t*)&z); + return vcombine_f32(V0, V1); +#else + float __attribute__((aligned(16))) data[4] = { x, y, z }; + return vld1q_f32(data); +#endif +#else + return vector4f{ x, y, z, 0.0f }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from a single value for all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set(float xyzw) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_set_ps1(xyzw); +#elif defined(RTM_NEON_INTRINSICS) + return vdupq_n_f32(xyzw); +#else + return vector4f{ xyzw, xyzw, xyzw, xyzw }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set(scalarf_arg0 x, scalarf_arg1 y, scalarf_arg2 z, scalarf_arg3 w) RTM_NO_EXCEPT + { + const __m128 xy = _mm_unpacklo_ps(x.value, y.value); + const __m128 zw = _mm_unpacklo_ps(z.value, w.value); + return _mm_shuffle_ps(xy, zw, _MM_SHUFFLE(1, 0, 1, 0)); + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from the [xyz] components and sets [w] to 0.0. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set(scalarf_arg0 x, scalarf_arg1 y, scalarf_arg2 z) RTM_NO_EXCEPT + { + const __m128 xy = _mm_unpacklo_ps(x.value, y.value); + const __m128 zw = _mm_unpacklo_ps(z.value, _mm_setzero_ps()); + return _mm_shuffle_ps(xy, zw, _MM_SHUFFLE(1, 0, 1, 0)); + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from a single value for all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set(scalarf_arg0 xyzw) RTM_NO_EXCEPT + { + return _mm_shuffle_ps(xyzw.value, xyzw.value, _MM_SHUFFLE(0, 0, 0, 0)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set(double x, double y, double z, double w) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_set_pd(y, x), _mm_set_pd(w, z) }; +#else + return vector4d{ x, y, z, w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from the [xyz] components and sets [w] to 0.0. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set(double x, double y, double z) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_set_pd(y, x), _mm_set_pd(0.0, z) }; +#else + return vector4d{ x, y, z, 0.0 }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from a single value for all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set(double xyzw) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128d xyzw_pd = _mm_set1_pd(xyzw); + return vector4d{ xyzw_pd, xyzw_pd }; +#else + return vector4d{ xyzw, xyzw, xyzw, xyzw }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d RTM_SIMD_CALL vector_set(scalard x, scalard y, scalard z, scalard w) RTM_NO_EXCEPT + { + const __m128d xy = _mm_unpacklo_pd(x.value, y.value); + const __m128d zw = _mm_unpacklo_pd(z.value, w.value); + return vector4d{ xy, zw }; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from the [xyz] components and sets [w] to 0.0. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d RTM_SIMD_CALL vector_set(scalard x, scalard y, scalard z) RTM_NO_EXCEPT + { + const __m128d xy = _mm_unpacklo_pd(x.value, y.value); + const __m128d zw = _mm_unpacklo_pd(z.value, _mm_setzero_pd()); + return vector4d{ xy, zw }; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a vector4 from a single value for all 4 components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d RTM_SIMD_CALL vector_set(scalard xyzw) RTM_NO_EXCEPT + { + const __m128d xyzw_pd = _mm_shuffle_pd(xyzw.value, xyzw.value, 0); + return vector4d{ xyzw_pd, xyzw_pd }; + } +#endif + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // Returns true if mix4 component is one of [xyzw] + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool is_mix_xyzw(mix4 arg) RTM_NO_EXCEPT { return uint32_t(arg) <= uint32_t(mix4::w); } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if mix4 component is one of [abcd] + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool is_mix_abcd(mix4 arg) RTM_NO_EXCEPT { return uint32_t(arg) >= uint32_t(mix4::a); } + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to help manipulate SIMD masks. + ////////////////////////////////////////////////////////////////////////// + union mask_converter + { + uint64_t u64; + uint32_t u32[2]; + + RTM_DISABLE_SECURITY_COOKIE_CHECK explicit RTM_FORCE_INLINE constexpr mask_converter(uint64_t value) RTM_NO_EXCEPT : u64(value) {} + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr operator uint32_t() const RTM_NO_EXCEPT { return u32[0]; } + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr operator uint64_t() const RTM_NO_EXCEPT { return u64; } + }; + + ////////////////////////////////////////////////////////////////////////// + // Returns a SIMD mask value from a boolean. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr mask_converter get_mask_value(bool is_true) RTM_NO_EXCEPT + { + return mask_converter(is_true ? uint64_t(0xFFFFFFFFFFFFFFFFULL) : uint64_t(0)); + } + + ////////////////////////////////////////////////////////////////////////// + // Selects if_false if the SIMD mask value is 0, otherwise if_true. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double select(uint64_t mask, double if_true, double if_false) RTM_NO_EXCEPT + { + return mask == 0 ? if_false : if_true; + } + + ////////////////////////////////////////////////////////////////////////// + // Selects if_false if the SIMD mask value is 0, otherwise if_true. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float select(uint32_t mask, float if_true, float if_false) RTM_NO_EXCEPT + { + return mask == 0 ? if_false : if_true; + } + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector_zero_impl + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4d() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128d zero_pd = _mm_setzero_pd(); + return vector4d{ zero_pd, zero_pd }; +#else + return vector_set(0.0); +#endif + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4f() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_setzero_ps(); +#else + return vector_set(0.0F); +#endif + } + }; + + ////////////////////////////////////////////////////////////////////////// + // Various vector widths we can load + ////////////////////////////////////////////////////////////////////////// + enum class vector_unaligned_loader_width + { + vec1, + vec2, + vec3, + vec4, + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + template + struct vector_unaligned_loader + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4d() const RTM_NO_EXCEPT + { + switch (width) + { + case vector_unaligned_loader_width::vec1: + { + double data[1]; + std::memcpy(&data[0], ptr, sizeof(double) * 1); + return vector_set(data[0], 0.0, 0.0, 0.0); + } + case vector_unaligned_loader_width::vec2: + { + double data[2]; + std::memcpy(&data[0], ptr, sizeof(double) * 2); + return vector_set(data[0], data[1], 0.0, 0.0); + } + case vector_unaligned_loader_width::vec3: + { + double data[3]; + std::memcpy(&data[0], ptr, sizeof(double) * 3); + return vector_set(data[0], data[1], data[2], 0.0); + } + case vector_unaligned_loader_width::vec4: + default: + { + vector4d result; + std::memcpy(&result, ptr, sizeof(vector4d)); + return result; + } + } + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4f() const RTM_NO_EXCEPT + { + switch (width) + { + case vector_unaligned_loader_width::vec1: + { + float data[1]; + std::memcpy(&data[0], ptr, sizeof(float) * 1); + return vector_set(data[0], 0.0F, 0.0F, 0.0F); + } + case vector_unaligned_loader_width::vec2: + { + float data[2]; + std::memcpy(&data[0], ptr, sizeof(float) * 2); + return vector_set(data[0], data[1], 0.0F, 0.0F); + } + case vector_unaligned_loader_width::vec3: + { + float data[3]; + std::memcpy(&data[0], ptr, sizeof(float) * 3); + return vector_set(data[0], data[1], data[2], 0.0F); + } + case vector_unaligned_loader_width::vec4: + default: + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_loadu_ps((const float*)ptr); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u8(vld1q_u8(ptr)); +#else + vector4f result; + std::memcpy(&result, ptr, sizeof(vector4f)); + return result; +#endif + } + } + } + + const uint8_t* ptr; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_to_scalarf + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(value); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(value, 0); +#else + return value.x; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ value }; + } +#endif + + vector4f value; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_to_scalard + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(value.xy); +#else + return value.x; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ value.xy }; + } +#endif + + vector4d value; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_get_min_component + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128 zwzw = _mm_movehl_ps(value, value); + __m128 xz_yw_zz_ww = _mm_min_ps(value, zwzw); + __m128 yw_yw_yw_yw = _mm_shuffle_ps(xz_yw_zz_ww, xz_yw_zz_ww, _MM_SHUFFLE(1, 1, 1, 1)); + return _mm_cvtss_f32(_mm_min_ps(xz_yw_zz_ww, yw_yw_yw_yw)); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t xy_zw = vpmin_f32(vget_low_f32(value), vget_high_f32(value)); + return vget_lane_f32(vpmin_f32(xy_zw, xy_zw), 0); +#else + return scalar_min(scalar_min(value.x, value.y), scalar_min(value.z, value.w)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + __m128 zwzw = _mm_movehl_ps(value, value); + __m128 xz_yw_zz_ww = _mm_min_ps(value, zwzw); + __m128 yw_yw_yw_yw = _mm_shuffle_ps(xz_yw_zz_ww, xz_yw_zz_ww, _MM_SHUFFLE(1, 1, 1, 1)); + return scalarf{ _mm_min_ps(xz_yw_zz_ww, yw_yw_yw_yw) }; + } +#endif + + vector4f value; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_get_max_component + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128 zwzw = _mm_movehl_ps(value, value); + __m128 xz_yw_zz_ww = _mm_max_ps(value, zwzw); + __m128 yw_yw_yw_yw = _mm_shuffle_ps(xz_yw_zz_ww, xz_yw_zz_ww, _MM_SHUFFLE(1, 1, 1, 1)); + return _mm_cvtss_f32(_mm_max_ps(xz_yw_zz_ww, yw_yw_yw_yw)); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t xy_zw = vpmax_f32(vget_low_f32(value), vget_high_f32(value)); + return vget_lane_f32(vpmax_f32(xy_zw, xy_zw), 0); +#else + return scalar_max(scalar_max(value.x, value.y), scalar_max(value.z, value.w)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + __m128 zwzw = _mm_movehl_ps(value, value); + __m128 xz_yw_zz_ww = _mm_max_ps(value, zwzw); + __m128 yw_yw_yw_yw = _mm_shuffle_ps(xz_yw_zz_ww, xz_yw_zz_ww, _MM_SHUFFLE(1, 1, 1, 1)); + return scalarf{ _mm_max_ps(xz_yw_zz_ww, yw_yw_yw_yw) }; + } +#endif + + vector4f value; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_get_min_component + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xz_yw = _mm_min_pd(value.xy, value.zw); + __m128d yw_yw = _mm_shuffle_pd(xz_yw, xz_yw, 1); + return _mm_cvtsd_f64(_mm_min_pd(xz_yw, yw_yw)); +#else + return scalar_min(scalar_min(value.x, value.y), scalar_min(value.z, value.w)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + __m128d xz_yw = _mm_min_pd(value.xy, value.zw); + __m128d yw_yw = _mm_shuffle_pd(xz_yw, xz_yw, 1); + return scalard{ _mm_min_pd(xz_yw, yw_yw) }; + } +#endif + + vector4d value; + }; + + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_get_max_component + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xz_yw = _mm_max_pd(value.xy, value.zw); + __m128d yw_yw = _mm_shuffle_pd(xz_yw, xz_yw, 1); + return _mm_cvtsd_f64(_mm_max_pd(xz_yw, yw_yw)); +#else + return scalar_max(scalar_max(value.x, value.y), scalar_max(value.z, value.w)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + __m128d xz_yw = _mm_max_pd(value.xy, value.zw); + __m128d yw_yw = _mm_shuffle_pd(xz_yw, xz_yw, 1); + return scalard{ _mm_max_pd(xz_yw, yw_yw) }; + } +#endif + + vector4d value; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a vector consisting of all zeros. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector_zero_impl RTM_SIMD_CALL vector_zero() RTM_NO_EXCEPT + { + return rtm_impl::vector_zero_impl(); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector4 from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector_unaligned_loader RTM_SIMD_CALL vector_load(const uint8_t* input) RTM_NO_EXCEPT + { + return rtm_impl::vector_unaligned_loader{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector1 from memory and sets the [yzw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector_unaligned_loader RTM_SIMD_CALL vector_load1(const uint8_t* input) RTM_NO_EXCEPT + { + return rtm_impl::vector_unaligned_loader{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector2 from memory and sets the [zw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector_unaligned_loader RTM_SIMD_CALL vector_load2(const uint8_t* input) RTM_NO_EXCEPT + { + return rtm_impl::vector_unaligned_loader{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector3 from memory and sets the [w] component to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector_unaligned_loader RTM_SIMD_CALL vector_load3(const uint8_t* input) RTM_NO_EXCEPT + { + return rtm_impl::vector_unaligned_loader{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Coerces an vector4 input into a scalar by grabbing the first SIMD lane. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_to_scalarf RTM_SIMD_CALL vector_as_scalar(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_to_scalarf{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Coerces an vector4 input into a scalar by grabbing the first SIMD lane. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_to_scalard RTM_SIMD_CALL vector_as_scalar(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_to_scalard{ input }; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/macros.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/macros.h new file mode 100644 index 00000000..ac0eba42 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/macros.h @@ -0,0 +1,404 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +////////////////////////////////////////////////////////////////////////// +// This file contains helper macros to help improve code generation where required. +////////////////////////////////////////////////////////////////////////// + +#if defined(RTM_NEON64_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULV_ADD(v0, v1, v2) vfmaq_f32((v2), (v0), (v1)) +#elif defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULV_ADD(v0, v1, v2) vmlaq_f32((v2), (v0), (v1)) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULV_ADD(v0, v1, v2) _mm_add_ps(_mm_mul_ps((v0), (v1)), (v2)) +#else + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULV_ADD(v0, v1, v2) RTM_IMPL_NAMESPACE::vector4f { (v2).x + ((v0).x * (v1).x), (v2).y + ((v0).y * (v1).y), (v2).z + ((v0).z * (v1).z), (v2).w + ((v0).w * (v1).w) } +#endif + +#if defined(RTM_NEON64_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULS_ADD(v0, s1, v2) vfmaq_n_f32((v2), (v0), (s1)) +#elif defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULS_ADD(v0, s1, v2) vmlaq_n_f32((v2), (v0), (s1)) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULS_ADD(v0, s1, v2) _mm_add_ps(_mm_mul_ps((v0), _mm_set_ps1((s1))), (v2)) +#else + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_MULS_ADD(v0, s1, v2) RTM_IMPL_NAMESPACE::vector4f { (v2).x + ((v0).x * (s1)), (v2).y + ((v0).y * (s1)), (v2).z + ((v0).z * (s1)), (v2).w + ((v0).w * (s1)) } +#endif + +#if defined(RTM_NEON64_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * v1) - v2) + // This is mathematically equivalent to: v2 - (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULV_SUB(v0, v1, v2) vfmsq_f32((v2), (v0), (v1)) +#elif defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * v1) - v2) + // This is mathematically equivalent to: v2 - (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULV_SUB(v0, v1, v2) vmlsq_f32((v2), (v0), (v1)) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * v1) - v2) + // This is mathematically equivalent to: v2 - (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULV_SUB(v0, v1, v2) _mm_sub_ps((v2), _mm_mul_ps((v0), (v1))) +#else + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * v1) - v2) + // This is mathematically equivalent to: v2 - (v0 * v1) + // All three inputs must be an rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULV_SUB(v0, v1, v2) RTM_IMPL_NAMESPACE::vector4f { (v2).x - ((v0).x * (v1).x), (v2).y - ((v0).y * (v1).y), (v2).z - ((v0).z * (v1).z), (v2).w - ((v0).w * (v1).w) } +#endif + +#if defined(RTM_NEON64_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULS_SUB(v0, s1, v2) vfmsq_n_f32((v2), (v0), (s1)) +#elif defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULS_SUB(v0, s1, v2) vmlsq_n_f32((v2), (v0), (s1)) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULS_SUB(v0, s1, v2) _mm_sub_ps((v2), _mm_mul_ps((v0), _mm_set_ps1((s1)))) +#else + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + // The v0 and v2 inputs must be a rtm::vector4f and s1 must be a float. + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_NEG_MULS_SUB(v0, s1, v2) RTM_IMPL_NAMESPACE::vector4f { (v2).x - ((v0).x * (s1)), (v2).y - ((v0).y * (s1)), (v2).z - ((v0).z * (s1)), (v2).w - ((v0).w * (s1)) } +#endif + +#if defined(RTM_AVX_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_SELECT(mask, if_true, if_false) _mm_blendv_ps(if_false, if_true, mask) + + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR2D_SELECT(mask, if_true, if_false) _mm_blendv_pd(if_false, if_true, mask) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_SELECT(mask, if_true, if_false) _mm_or_ps(_mm_andnot_ps(mask, if_false), _mm_and_ps(if_true, mask)) + + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR2D_SELECT(mask, if_true, if_false) _mm_or_pd(_mm_andnot_pd(mask, if_false), _mm_and_pd(if_true, mask)) +#elif defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + #define RTM_VECTOR4F_SELECT(mask, if_true, if_false) vbslq_f32(mask, if_true, if_false) + + // RTM_VECTOR2D_SELECT not defined for NEON yet, TODO +#else + // Macros not defined for scalar code path +#endif + +#if defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x4 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_4X4(input_xyzw0, input_xyzw1, input_xyzw2, input_xyzw3, output_xxxx, output_yyyy, output_zzzz, output_wwww) \ + do { \ + const float32x4x2_t x0x2y0y2_z0z2w0w2 = vzipq_f32((input_xyzw0), (input_xyzw2)); \ + const float32x4x2_t x1x3y1y3_z1z3w1w3 = vzipq_f32((input_xyzw1), (input_xyzw3)); \ + const float32x4x2_t x0x1x2x3_y0y1y2y3 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[0], x1x3y1y3_z1z3w1w3.val[0]); \ + const float32x4x2_t z0z1z2z3_w0w1w2w3 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[1], x1x3y1y3_z1z3w1w3.val[1]); \ + (output_xxxx) = x0x1x2x3_y0y1y2y3.val[0]; \ + (output_yyyy) = x0x1x2x3_y0y1y2y3.val[1]; \ + (output_zzzz) = z0z1z2z3_w0w1w2w3.val[0]; \ + (output_wwww) = z0z1z2z3_w0w1w2w3.val[1]; \ + } while(0) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x4 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_4X4(input_xyzw0, input_xyzw1, input_xyzw2, input_xyzw3, output_xxxx, output_yyyy, output_zzzz, output_wwww) \ + do { \ + const __m128 x0y0x1y1 = _mm_shuffle_ps((input_xyzw0), (input_xyzw1), _MM_SHUFFLE(1, 0, 1, 0)); \ + const __m128 z0w0z1w1 = _mm_shuffle_ps((input_xyzw0), (input_xyzw1), _MM_SHUFFLE(3, 2, 3, 2)); \ + const __m128 x2y2x3y3 = _mm_shuffle_ps((input_xyzw2), (input_xyzw3), _MM_SHUFFLE(1, 0, 1, 0)); \ + const __m128 z2w2z3w3 = _mm_shuffle_ps((input_xyzw2), (input_xyzw3), _MM_SHUFFLE(3, 2, 3, 2)); \ + (output_xxxx) = _mm_shuffle_ps(x0y0x1y1, x2y2x3y3, _MM_SHUFFLE(2, 0, 2, 0)); \ + (output_yyyy) = _mm_shuffle_ps(x0y0x1y1, x2y2x3y3, _MM_SHUFFLE(3, 1, 3, 1)); \ + (output_zzzz) = _mm_shuffle_ps(z0w0z1w1, z2w2z3w3, _MM_SHUFFLE(2, 0, 2, 0)); \ + (output_wwww) = _mm_shuffle_ps(z0w0z1w1, z2w2z3w3, _MM_SHUFFLE(3, 1, 3, 1)); \ + } while(0) +#else + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x4 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_4X4(input_xyzw0, input_xyzw1, input_xyzw2, input_xyzw3, output_xxxx, output_yyyy, output_zzzz, output_wwww) \ + do { \ + const float input_x0 = (input_xyzw0).x; \ + const float input_y0 = (input_xyzw0).y; \ + const float input_z0 = (input_xyzw0).z; \ + const float input_w0 = (input_xyzw0).w; \ + const float input_x1 = (input_xyzw1).x; \ + const float input_y1 = (input_xyzw1).y; \ + const float input_z1 = (input_xyzw1).z; \ + const float input_w1 = (input_xyzw1).w; \ + const float input_x2 = (input_xyzw2).x; \ + const float input_y2 = (input_xyzw2).y; \ + const float input_z2 = (input_xyzw2).z; \ + const float input_w2 = (input_xyzw2).w; \ + const float input_x3 = (input_xyzw3).x; \ + const float input_y3 = (input_xyzw3).y; \ + const float input_z3 = (input_xyzw3).z; \ + const float input_w3 = (input_xyzw3).w; \ + (output_xxxx) = RTM_IMPL_NAMESPACE::vector4f { input_x0, input_x1, input_x2, input_x3 }; \ + (output_yyyy) = RTM_IMPL_NAMESPACE::vector4f { input_y0, input_y1, input_y2, input_y3 }; \ + (output_zzzz) = RTM_IMPL_NAMESPACE::vector4f { input_z0, input_z1, input_z2, input_z3 }; \ + (output_wwww) = RTM_IMPL_NAMESPACE::vector4f { input_w0, input_w1, input_w2, input_w3 }; \ + } while(0) +#endif + +#if defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x3 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_3X3(input_xyz0, input_xyz1, input_xyz2, output_xxx, output_yyy, output_zzz) \ + do { \ + const float32x4x2_t x0x2y0y2_z0z2w0w2 = vzipq_f32((input_xyz0), (input_xyz2)); \ + const float32x4x2_t x1x1y1y1_z1z1w1w1 = vzipq_f32((input_xyz1), (input_xyz1)); \ + const float32x4x2_t x0x1x2x1_y0y1y2y1 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[0], x1x1y1y1_z1z1w1w1.val[0]); \ + const float32x4x2_t z0z1z2z1_w0w1w2w1 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[1], x1x1y1y1_z1z1w1w1.val[1]); \ + (output_xxx) = x0x1x2x1_y0y1y2y1.val[0]; \ + (output_yyy) = x0x1x2x1_y0y1y2y1.val[1]; \ + (output_zzz) = z0z1z2z1_w0w1w2w1.val[0]; \ + } while(0) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x3 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_3X3(input_xyz0, input_xyz1, input_xyz2, output_xxx, output_yyy, output_zzz) \ + do { \ + const __m128 input_xyz2_ = (input_xyz2); \ + const __m128 x0y0x1y1 = _mm_shuffle_ps((input_xyz0), (input_xyz1), _MM_SHUFFLE(1, 0, 1, 0)); \ + const __m128 z0w0z1w1 = _mm_shuffle_ps((input_xyz0), (input_xyz1), _MM_SHUFFLE(3, 2, 3, 2)); \ + (output_xxx) = _mm_shuffle_ps(x0y0x1y1, input_xyz2_, _MM_SHUFFLE(2, 0, 2, 0)); \ + (output_yyy) = _mm_shuffle_ps(x0y0x1y1, input_xyz2_, _MM_SHUFFLE(3, 1, 3, 1)); \ + (output_zzz) = _mm_shuffle_ps(z0w0z1w1, input_xyz2_, _MM_SHUFFLE(2, 2, 2, 0)); \ + } while(0) +#else + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x3 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_3X3(input_xyz0, input_xyz1, input_xyz2, output_xxx, output_yyy, output_zzz) \ + do { \ + const float input_x0 = (input_xyz0).x; \ + const float input_y0 = (input_xyz0).y; \ + const float input_z0 = (input_xyz0).z; \ + const float input_x1 = (input_xyz1).x; \ + const float input_y1 = (input_xyz1).y; \ + const float input_z1 = (input_xyz1).z; \ + const float input_x2 = (input_xyz2).x; \ + const float input_y2 = (input_xyz2).y; \ + const float input_z2 = (input_xyz2).z; \ + (output_xxx) = RTM_IMPL_NAMESPACE::vector4f { input_x0, input_x1, input_x2, input_x2 }; \ + (output_yyy) = RTM_IMPL_NAMESPACE::vector4f { input_y0, input_y1, input_y2, input_y2 }; \ + (output_zzz) = RTM_IMPL_NAMESPACE::vector4f { input_z0, input_z1, input_z2, input_z2 }; \ + } while(0) +#endif + +#if defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x3 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_4X3(input_xyz0, input_xyz1, input_xyz2, input_xyz3, output_xxxx, output_yyyy, output_zzzz) \ + do { \ + const float32x4x2_t x0x2y0y2_z0z2w0w2 = vzipq_f32((input_xyz0), (input_xyz2)); \ + const float32x4x2_t x1x3y1y3_z1z3w1w3 = vzipq_f32((input_xyz1), (input_xyz3)); \ + const float32x4x2_t x0x1x2x3_y0y1y2y3 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[0], x1x3y1y3_z1z3w1w3.val[0]); \ + const float32x4x2_t z0z1z2z3_w0w1w2w3 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[1], x1x3y1y3_z1z3w1w3.val[1]); \ + (output_xxxx) = x0x1x2x3_y0y1y2y3.val[0]; \ + (output_yyyy) = x0x1x2x3_y0y1y2y3.val[1]; \ + (output_zzzz) = z0z1z2z3_w0w1w2w3.val[0]; \ + } while(0) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x3 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_4X3(input_xyz0, input_xyz1, input_xyz2, input_xyz3, output_xxxx, output_yyyy, output_zzzz) \ + do { \ + const __m128 x0y0x1y1 = _mm_shuffle_ps((input_xyz0), (input_xyz1), _MM_SHUFFLE(1, 0, 1, 0)); \ + const __m128 z0w0z1w1 = _mm_shuffle_ps((input_xyz0), (input_xyz1), _MM_SHUFFLE(3, 2, 3, 2)); \ + const __m128 x2y2x3y3 = _mm_shuffle_ps((input_xyz2), (input_xyz3), _MM_SHUFFLE(1, 0, 1, 0)); \ + const __m128 z2w2z3w3 = _mm_shuffle_ps((input_xyz2), (input_xyz3), _MM_SHUFFLE(3, 2, 3, 2)); \ + (output_xxxx) = _mm_shuffle_ps(x0y0x1y1, x2y2x3y3, _MM_SHUFFLE(2, 0, 2, 0)); \ + (output_yyyy) = _mm_shuffle_ps(x0y0x1y1, x2y2x3y3, _MM_SHUFFLE(3, 1, 3, 1)); \ + (output_zzzz) = _mm_shuffle_ps(z0w0z1w1, z2w2z3w3, _MM_SHUFFLE(2, 0, 2, 0)); \ + } while(0) +#else + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x3 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_4X3(input_xyz0, input_xyz1, input_xyz2, input_xyz3, output_xxxx, output_yyyy, output_zzzz) \ + do { \ + const float input_x0 = (input_xyz0).x; \ + const float input_y0 = (input_xyz0).y; \ + const float input_z0 = (input_xyz0).z; \ + const float input_x1 = (input_xyz1).x; \ + const float input_y1 = (input_xyz1).y; \ + const float input_z1 = (input_xyz1).z; \ + const float input_x2 = (input_xyz2).x; \ + const float input_y2 = (input_xyz2).y; \ + const float input_z2 = (input_xyz2).z; \ + const float input_x3 = (input_xyz3).x; \ + const float input_y3 = (input_xyz3).y; \ + const float input_z3 = (input_xyz3).z; \ + (output_xxxx) = RTM_IMPL_NAMESPACE::vector4f { input_x0, input_x1, input_x2, input_x3 }; \ + (output_yyyy) = RTM_IMPL_NAMESPACE::vector4f { input_y0, input_y1, input_y2, input_y3 }; \ + (output_zzzz) = RTM_IMPL_NAMESPACE::vector4f { input_z0, input_z1, input_z2, input_z3 }; \ + } while(0) +#endif + +#if defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x4 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_3X4(input_xyzw0, input_xyzw1, input_xyzw2, output_xxx, output_yyy, output_zzz, output_www) \ + do { \ + const float32x4x2_t x0x2y0y2_z0z2w0w2 = vzipq_f32((input_xyzw0), (input_xyzw2)); \ + const float32x4x2_t x1x1y1y1_z1z1w1w1 = vzipq_f32((input_xyzw1), (input_xyzw1)); \ + const float32x4x2_t x0x1x2x1_y0y1y2y1 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[0], x1x1y1y1_z1z1w1w1.val[0]); \ + const float32x4x2_t z0z1z2z1_w0w1w2w1 = vzipq_f32(x0x2y0y2_z0z2w0w2.val[1], x1x1y1y1_z1z1w1w1.val[1]); \ + (output_xxx) = x0x1x2x1_y0y1y2y1.val[0]; \ + (output_yyy) = x0x1x2x1_y0y1y2y1.val[1]; \ + (output_zzz) = z0z1z2z1_w0w1w2w1.val[0]; \ + (output_www) = z0z1z2z1_w0w1w2w1.val[1]; \ + } while(0) +#elif defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x4 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_3X4(input_xyzw0, input_xyzw1, input_xyzw2, output_xxx, output_yyy, output_zzz, output_www) \ + do { \ + const __m128 input_xyzw2_ = (input_xyzw2); \ + const __m128 x0y0x1y1 = _mm_shuffle_ps((input_xyzw0), (input_xyzw1), _MM_SHUFFLE(1, 0, 1, 0)); \ + const __m128 z0w0z1w1 = _mm_shuffle_ps((input_xyzw0), (input_xyzw1), _MM_SHUFFLE(3, 2, 3, 2)); \ + (output_xxx) = _mm_shuffle_ps(x0y0x1y1, input_xyzw2_, _MM_SHUFFLE(0, 0, 2, 0)); \ + (output_yyy) = _mm_shuffle_ps(x0y0x1y1, input_xyzw2_, _MM_SHUFFLE(1, 1, 3, 1)); \ + (output_zzz) = _mm_shuffle_ps(z0w0z1w1, input_xyzw2_, _MM_SHUFFLE(2, 2, 2, 0)); \ + (output_www) = _mm_shuffle_ps(z0w0z1w1, input_xyzw2_, _MM_SHUFFLE(3, 3, 3, 1)); \ + } while(0) +#else + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x4 matrix. + // All inputs and outputs must be rtm::vector4f. + ////////////////////////////////////////////////////////////////////////// + #define RTM_MATRIXF_TRANSPOSE_3X4(input_xyzw0, input_xyzw1, input_xyzw2, output_xxx, output_yyy, output_zzz, output_www) \ + do { \ + const float input_x0 = (input_xyzw0).x; \ + const float input_y0 = (input_xyzw0).y; \ + const float input_z0 = (input_xyzw0).z; \ + const float input_w0 = (input_xyzw0).w; \ + const float input_x1 = (input_xyzw1).x; \ + const float input_y1 = (input_xyzw1).y; \ + const float input_z1 = (input_xyzw1).z; \ + const float input_w1 = (input_xyzw1).w; \ + const float input_x2 = (input_xyzw2).x; \ + const float input_y2 = (input_xyzw2).y; \ + const float input_z2 = (input_xyzw2).z; \ + const float input_w2 = (input_xyzw2).w; \ + (output_xxx) = RTM_IMPL_NAMESPACE::vector4f { input_x0, input_x1, input_x2, input_x2 }; \ + (output_yyy) = RTM_IMPL_NAMESPACE::vector4f { input_y0, input_y1, input_y2, input_y2 }; \ + (output_zzz) = RTM_IMPL_NAMESPACE::vector4f { input_z0, input_z1, input_z2, input_z2 }; \ + (output_www) = RTM_IMPL_NAMESPACE::vector4f { input_w0, input_w1, input_w2, input_w2 }; \ + } while(0) +#endif + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4d.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4d.h new file mode 100644 index 00000000..673fc7f1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4d.h @@ -0,0 +1,181 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/mask_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4d [x] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_x(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(_mm_castpd_si128(input.xy)); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(_mm_castpd_si128(input.xy)); +#endif +#else + return input.x; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4d [y] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_y(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(_mm_castpd_si128(_mm_shuffle_pd(input.xy, input.xy, 1))); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(_mm_castpd_si128(_mm_shuffle_pd(input.xy, input.xy, 1))); +#endif +#else + return input.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4d [z] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_z(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(_mm_castpd_si128(input.zw)); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(_mm_castpd_si128(input.zw)); +#endif +#else + return input.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4d [w] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_w(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(_mm_castpd_si128(_mm_shuffle_pd(input.zw, input.zw, 1))); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(_mm_castpd_si128(_mm_shuffle_pd(input.zw, input.zw, 1))); +#endif +#else + return input.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_pd(input.xy) & _mm_movemask_pd(input.zw)) == 3; +#else + return input.x != 0 && input.y != 0 && input.z != 0 && input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true2(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_pd(input.xy) == 3; +#else + return input.x != 0 && input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true3(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_pd(input.xy) == 3 && (_mm_movemask_pd(input.zw) & 1) != 0; +#else + return input.x != 0 && input.y != 0 && input.z != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_pd(input.xy) | _mm_movemask_pd(input.zw)) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0 || input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true2(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_pd(input.xy) != 0; +#else + return input.x != 0 || input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true3(const mask4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_pd(input.xy) != 0 || (_mm_movemask_pd(input.zw) & 1) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0; +#endif + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4f.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4f.h new file mode 100644 index 00000000..74bec87a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4f.h @@ -0,0 +1,193 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/mask_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4f [x] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_x(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_castps_si128(input)); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(vreinterpretq_u32_f32(input), 0); +#else + return input.x; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4f [y] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_y(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_castps_si128(_mm_shuffle_ps(input, input, _MM_SHUFFLE(1, 1, 1, 1)))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(vreinterpretq_u32_f32(input), 1); +#else + return input.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4f [z] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_z(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_castps_si128(_mm_shuffle_ps(input, input, _MM_SHUFFLE(2, 2, 2, 2)))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(vreinterpretq_u32_f32(input), 2); +#else + return input.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4f [w] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_w(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_castps_si128(_mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 3, 3, 3)))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(vreinterpretq_u32_f32(input), 3); +#else + return input.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(input) == 0xF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vreinterpretq_u32_f32(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) == 0xFFFFFFFFU; +#else + return input.x != 0 && input.y != 0 && input.z != 0 && input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true2(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(input) & 0x3) == 0x3; +#elif defined(RTM_NEON_INTRINSICS) + return vget_lane_u64(vget_low_u32(vreinterpretq_u32_f32(input)), 0) == 0xFFFFFFFFFFFFFFFFULL; +#else + return input.x != 0 && input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true3(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(input) & 0x7) == 0x7; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vreinterpretq_u32_f32(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) == 0x00FFFFFFU; +#else + return input.x != 0 && input.y != 0 && input.z != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(input) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vreinterpretq_u32_f32(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0 || input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true2(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(input) & 0x3) != 0; +#elif defined(RTM_NEON_INTRINSICS) + return vget_lane_u64(vget_low_u32(vreinterpretq_u32_f32(input)), 0) != 0; +#else + return input.x != 0 || input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true3(mask4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(input) & 0x7) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vreinterpretq_u32_f32(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0; +#endif + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4i.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4i.h new file mode 100644 index 00000000..25cf4386 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4i.h @@ -0,0 +1,193 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/mask_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4i [x] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_x(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(input); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(RTM_IMPL_MASK4i_GET(input), 0); +#else + return input.x; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4i [y] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_y(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_shuffle_epi32(input, _MM_SHUFFLE(1, 1, 1, 1))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(RTM_IMPL_MASK4i_GET(input), 1); +#else + return input.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4i [z] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_z(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_shuffle_epi32(input, _MM_SHUFFLE(2, 2, 2, 2))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(RTM_IMPL_MASK4i_GET(input), 2); +#else + return input.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4i [w] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint32_t RTM_SIMD_CALL mask_get_w(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsi128_si32(_mm_shuffle_epi32(input, _MM_SHUFFLE(3, 3, 3, 3))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_u32(RTM_IMPL_MASK4i_GET(input), 3); +#else + return input.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_epi8(input) == 0xFFFF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = RTM_IMPL_MASK4i_GET(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) == 0xFFFFFFFFU; +#else + return input.x != 0 && input.y != 0 && input.z != 0 && input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true2(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_epi8(input) & 0x00FF) == 0x00FF; +#elif defined(RTM_NEON_INTRINSICS) + return vget_lane_u64(vget_low_u32(RTM_IMPL_MASK4i_GET(input)), 0) == 0xFFFFFFFFFFFFFFFFULL; +#else + return input.x != 0 && input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true3(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_epi8(input) & 0x0FFF) == 0x0FFF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = RTM_IMPL_MASK4i_GET(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) == 0x00FFFFFFU; +#else + return input.x != 0 && input.y != 0 && input.z != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_epi8(input) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = RTM_IMPL_MASK4i_GET(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0 || input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true2(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_epi8(input) & 0x00FF) != 0; +#elif defined(RTM_NEON_INTRINSICS) + return vget_lane_u64(vget_low_u32(RTM_IMPL_MASK4i_GET(input)), 0) != 0; +#else + return input.x != 0 || input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true3(mask4i_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_epi8(input) & 0x0FFF) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = RTM_IMPL_MASK4i_GET(input); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0; +#endif + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4q.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4q.h new file mode 100644 index 00000000..82129dca --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/mask4q.h @@ -0,0 +1,181 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/mask_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4q [x] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_x(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(input.xy); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(input.xy); +#endif +#else + return input.x; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4q [y] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_y(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(_mm_shuffle_epi32(input.xy, _MM_SHUFFLE(3, 2, 3, 2))); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(_mm_shuffle_epi32(input.xy, _MM_SHUFFLE(3, 2, 3, 2))); +#endif +#else + return input.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4q [z] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_z(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(input.zw); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(input.zw); +#endif +#else + return input.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the mask4q [w] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE uint64_t RTM_SIMD_CALL mask_get_w(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) +#if defined(RTM_ARCH_X64) + return _mm_cvtsi128_si64(_mm_shuffle_epi32(input.zw, _MM_SHUFFLE(3, 2, 3, 2))); +#else + // Just sign extend on 32bit systems + return (uint64_t)_mm_cvtsi128_si32(_mm_shuffle_epi32(input.zw, _MM_SHUFFLE(3, 2, 3, 2))); +#endif +#else + return input.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_epi8(input.xy) & _mm_movemask_epi8(input.zw)) == 0xFFFF; +#else + return input.x != 0 && input.y != 0 && input.z != 0 && input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true2(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_epi8(input.xy) == 0xFFFF; +#else + return input.x != 0 && input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are true, otherwise false: all(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_all_true3(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_epi8(input.xy) == 0xFFFF && (_mm_movemask_epi8(input.zw) & 0x00FF) == 0x00FF; +#else + return input.x != 0 && input.y != 0 && input.z != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_epi8(input.xy) | _mm_movemask_epi8(input.zw)) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0 || input.w != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true2(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_epi8(input.xy) != 0; +#else + return input.x != 0 || input.y != 0; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are true, otherwise false: any(input != 0) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL mask_any_true3(const mask4q& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_epi8(input.xy) != 0 || (_mm_movemask_epi8(input.zw) & 0x00FF) != 0; +#else + return input.x != 0 || input.y != 0 || input.z != 0; +#endif + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/math.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/math.h new file mode 100644 index 00000000..ffe6c228 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/math.h @@ -0,0 +1,116 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/impl/detect_arch.h" +#include "rtm/impl/detect_compiler.h" + +////////////////////////////////////////////////////////////////////////// +// Detect which intrinsics the current compilation environment supports. +////////////////////////////////////////////////////////////////////////// + +#if !defined(RTM_NO_INTRINSICS) + #if defined(__AVX2__) + #define RTM_AVX2_INTRINSICS + #define RTM_FMA_INTRINSICS + #define RTM_AVX_INTRINSICS + #define RTM_SSE4_INTRINSICS + #define RTM_SSE3_INTRINSICS + #define RTM_SSE2_INTRINSICS + #elif defined(__AVX__) + #define RTM_AVX_INTRINSICS + #define RTM_SSE4_INTRINSICS + #define RTM_SSE3_INTRINSICS + #define RTM_SSE2_INTRINSICS + #elif defined(__SSE4_1__) + #define RTM_SSE4_INTRINSICS + #define RTM_SSE3_INTRINSICS + #define RTM_SSE2_INTRINSICS + #elif defined(__SSSE3__) + #define RTM_SSE3_INTRINSICS + #define RTM_SSE2_INTRINSICS + #elif defined(__SSE2__) || defined(RTM_ARCH_X86) || defined(RTM_ARCH_X64) + #define RTM_SSE2_INTRINSICS + #endif + + #if defined(RTM_ARCH_ARM64) + #define RTM_NEON_INTRINSICS + #define RTM_NEON64_INTRINSICS + #elif defined(RTM_ARCH_ARM) + #define RTM_NEON_INTRINSICS + #endif + + // If SSE2 and NEON aren't used, we default to the scalar implementation + #if !defined(RTM_SSE2_INTRINSICS) && !defined(RTM_NEON_INTRINSICS) + #define RTM_NO_INTRINSICS + #endif +#endif + +#if defined(RTM_SSE2_INTRINSICS) + #include + #include + + // With MSVC and SSE2, we can use the __vectorcall calling convention to pass vector types and aggregates by value through registers + // for improved code generation + #if defined(RTM_COMPILER_MSVC) && !defined(_MANAGED) && !defined(_M_CEE) && (!defined(_M_IX86_FP) || (_M_IX86_FP > 1)) && !defined(RTM_SIMD_CALL) + #if RTM_COMPILER_MSVC >= RTM_COMPILER_MSVC_2015 + #define RTM_USE_VECTORCALL + #endif + #endif +#endif + +#if defined(RTM_SSE3_INTRINSICS) + #include +#endif + +#if defined(RTM_SSE4_INTRINSICS) + #include +#endif + +#if defined(RTM_AVX_INTRINSICS) + #include +#endif + +#if defined(RTM_NEON64_INTRINSICS) && defined(RTM_COMPILER_MSVC) + // MSVC specific header + #include +#elif defined(RTM_NEON_INTRINSICS) + #include +#endif + +// Specify the SIMD calling convention is we can +#if !defined(RTM_SIMD_CALL) + #if defined(RTM_USE_VECTORCALL) + #define RTM_SIMD_CALL __vectorcall + #else + #define RTM_SIMD_CALL + #endif +#endif + +// By default, we include the type definitions, feature detection, and error handling +#include "rtm/impl/detect_features.h" +#include "rtm/impl/error.h" +#include "rtm/types.h" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x3d.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x3d.h new file mode 100644 index 00000000..2f7126c8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x3d.h @@ -0,0 +1,373 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_common.h" +#include "rtm/impl/matrix_affine_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the desired 3x3 matrix axis. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr const vector4d& matrix_get_axis(const matrix3x3d& input, axis3 axis) RTM_NO_EXCEPT + { + return axis == axis3::x ? input.x_axis : (axis == axis3::y ? input.y_axis : input.z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3x3 matrix into a rotation quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_from_matrix(const matrix3x3d& input) RTM_NO_EXCEPT + { + return rtm_impl::quat_from_matrix(input.x_axis, input.y_axis, input.z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two 3x3 matrices. + // Multiplication order is as follow: local_to_world = matrix_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d RTM_SIMD_CALL matrix_mul(const matrix3x3d& lhs, const matrix3x3d& rhs) RTM_NO_EXCEPT + { + vector4d tmp = vector_mul(vector_dup_x(lhs.x_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.x_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.x_axis), rhs.z_axis, tmp); + vector4d x_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.y_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.y_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.y_axis), rhs.z_axis, tmp); + vector4d y_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.z_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.z_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.z_axis), rhs.z_axis, tmp); + vector4d z_axis = tmp; + + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 3x3 matrix and a 3D vector. + // Multiplication order is as follow: world_position = matrix_mul(local_vector, local_to_world) + // Note: The proper way to transform a normal by a matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d RTM_SIMD_CALL matrix_mul_vector3(const vector4d& vec3, const matrix3x3d& mtx) RTM_NO_EXCEPT + { + vector4d tmp; + + tmp = vector_mul(vector_dup_x(vec3), mtx.x_axis); + tmp = vector_mul_add(vector_dup_y(vec3), mtx.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(vec3), mtx.z_axis, tmp); + + return tmp; + } + + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x3 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x3d RTM_SIMD_CALL matrix_transpose(const matrix3x3d& input) RTM_NO_EXCEPT + { + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d x_axis = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d y_axis = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d z_axis = vector_mix(v02_v03_v12_v13, input.z_axis); + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x3 matrix. + // If the input matrix is not invertible, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d RTM_SIMD_CALL matrix_inverse(const matrix3x3d& input) RTM_NO_EXCEPT + { + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4d v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4d v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4d v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4d v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4d v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4d x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4d v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4d v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4d v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4d v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4d v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4d y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4d v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4d v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4d v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4d v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4d v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4d z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4d o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4d o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const double det = vector_dot3(o00_o10_o20, input.x_axis); + const vector4d inv_det = vector_set(scalar_reciprocal(det)); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x3 matrix. + // If the input matrix has a determinant whose absolute value is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d RTM_SIMD_CALL matrix_inverse(const matrix3x3d& input, const matrix3x3d& fallback, double threshold = 1.0E-8) RTM_NO_EXCEPT + { + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4d v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4d v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4d v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4d v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4d v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4d x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4d v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4d v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4d v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4d v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4d v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4d y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4d v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4d v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4d v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4d v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4d v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4d z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4d o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4d o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const double det = vector_dot3(o00_o10_o20, input.x_axis); + if (scalar_abs(det) < threshold) + return fallback; + + const vector4d inv_det = vector_set(scalar_reciprocal(det)); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the determinant of the input 3x3 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard matrix_determinant(const matrix3x3d& input) RTM_NO_EXCEPT + { + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4d v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4d v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4d v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4d v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4d v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4d x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4d v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4d v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4d v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4d v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4d v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4d y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4d v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4d v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4d v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4d v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4d v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4d z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4d o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4d o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + return vector_dot3(o00_o10_o20, input.x_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the minor of the input 3x3 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra) + // The minor is the determinant of the sub-matrix input when the specified + // row and column are removed. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard matrix_minor(const matrix3x3d& input, axis3 row, axis3 column) RTM_NO_EXCEPT + { + // The minor boils down to calculating the determinant of a 2x2 matrix. + // det([a, b], [c, d]) = (a * d) - (b * c) + + // Find which two rows we need. + vector4d row0; + vector4d row1; + if (row == axis3::x) + { + row0 = input.y_axis; + row1 = input.z_axis; + } + else if (row == axis3::y) + { + row0 = input.x_axis; + row1 = input.z_axis; + } + else + { + row0 = input.x_axis; + row1 = input.y_axis; + } + + // Because our input is a 3x3 matrix, there are only a few possibilities for the 2x2 part: + // row0 = [x0, y0, z0] + // row1 = [x1, y1, z1] + // det([x0, y0], [x1, y1]) = (x0 * y1) - (y0 * x1) (z removed) + // det([x0, z0], [x1, z1]) = (x0 * z1) - (z0 * x1) (y removed) + // det([y0, z0], [y1, z1]) = (y0 * z1) - (z0 * y1) (x removed) + // det([column0, column1], [column2, column3]) = (column0 * column3) - (column1 * column2) + + // For performance reasons, we can compute all three determinants at the same time. + const vector4d column0 = vector_mix(row0, row0); + const vector4d column1 = vector_mix(row0, row0); + const vector4d column2 = vector_mix(row1, row1); + const vector4d column3 = vector_mix(row1, row1); + + const vector4d determinants = vector_neg_mul_sub(column1, column2, vector_mul(column0, column3)); + + // Extract the one we need + if (column == axis3::x) + return vector_get_z(determinants); + else if (column == axis3::y) + return vector_get_y(determinants); + else + return vector_get_x(determinants); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the cofactor matrix of the input 3x3 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Cofactor_expansion_of_the_determinant + // Note: The proper way to transform a normal by a matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d matrix_cofactor(const matrix3x3d& input) RTM_NO_EXCEPT + { + const vector4d x_axis = vector_cross3(input.y_axis, input.z_axis); + const vector4d y_axis = vector_cross3(input.z_axis, input.x_axis); + const vector4d z_axis = vector_cross3(input.x_axis, input.y_axis); + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the adjugate of the input matrix. + // See: https://en.wikipedia.org/wiki/Adjugate_matrix + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d matrix_adjugate(const matrix3x3d& input) RTM_NO_EXCEPT + { + return matrix_transpose(matrix_cofactor(input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Removes the 3D scale from a 3x3 matrix. + // Note that if the scaling is 0.0 for a particular axis, the original rotation axis cannot + // be recovered trivially and no attempt is done to do so. In theory, we could use the other axes + // to try and recover it. + // TODO: Implement rotation recovering, perhaps in a separate function and rename this + // one to matrix_remove_non_zero_scale(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d matrix_remove_scale(const matrix3x3d& input) RTM_NO_EXCEPT + { + matrix3x3d result; + result.x_axis = vector_normalize3(input.x_axis, input.x_axis); + result.y_axis = vector_normalize3(input.y_axis, input.y_axis); + result.z_axis = vector_normalize3(input.z_axis, input.z_axis); + return result; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x3f.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x3f.h new file mode 100644 index 00000000..19887ed4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x3f.h @@ -0,0 +1,372 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/macros.h" +#include "rtm/math.h" +#include "rtm/vector4f.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_common.h" +#include "rtm/impl/matrix_affine_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the desired 3x3 matrix axis. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr vector4f RTM_SIMD_CALL matrix_get_axis(matrix3x3f_arg0 input, axis3 axis) RTM_NO_EXCEPT + { + return axis == axis3::x ? input.x_axis : (axis == axis3::y ? input.y_axis : input.z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3x3 matrix into a rotation quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_from_matrix(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quat_from_matrix(input.x_axis, input.y_axis, input.z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two 3x3 matrices. + // Multiplication order is as follow: local_to_world = matrix_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_mul(matrix3x3f_arg0 lhs, matrix3x3f_arg1 rhs) RTM_NO_EXCEPT + { + vector4f tmp = vector_mul(vector_dup_x(lhs.x_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.x_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.x_axis), rhs.z_axis, tmp); + vector4f x_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.y_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.y_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.y_axis), rhs.z_axis, tmp); + vector4f y_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.z_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.z_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.z_axis), rhs.z_axis, tmp); + vector4f z_axis = tmp; + + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 3x3 matrix and a 3D vector. + // Multiplication order is as follow: world_position = matrix_mul(local_vector, local_to_world) + // Note: The proper way to transform a normal by a matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL matrix_mul_vector3(vector4f_arg0 vec3, matrix3x3f_arg0 mtx) RTM_NO_EXCEPT + { + vector4f tmp; + + tmp = vector_mul(vector_dup_x(vec3), mtx.x_axis); + tmp = vector_mul_add(vector_dup_y(vec3), mtx.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(vec3), mtx.z_axis, tmp); + + return tmp; + } + + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x3 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x3f RTM_SIMD_CALL matrix_transpose(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + vector4f x_axis; + vector4f y_axis; + vector4f z_axis; + RTM_MATRIXF_TRANSPOSE_3X3(input.x_axis, input.y_axis, input.z_axis, x_axis, y_axis, z_axis); + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x3 matrix. + // If the input matrix is not invertible, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_inverse(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + const vector4f v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4f v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4f v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4f v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4f v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4f v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4f v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4f x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4f v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4f v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4f v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4f v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4f v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4f y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4f v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4f v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4f v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4f v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4f v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4f z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4f o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4f o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const scalarf det = vector_dot3(o00_o10_o20, input.x_axis); + const vector4f inv_det = vector_set(scalar_reciprocal(det)); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x3 matrix. + // If the input matrix has a determinant whose absolute value is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_inverse(matrix3x3f_arg0 input, matrix3x3f_arg1 fallback, float threshold = 1.0E-8F) RTM_NO_EXCEPT + { + const vector4f v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4f v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4f v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4f v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4f v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4f v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4f v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4f x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4f v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4f v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4f v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4f v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4f v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4f y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4f v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4f v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4f v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4f v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4f v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4f z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4f o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4f o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const scalarf det = vector_dot3(o00_o10_o20, input.x_axis); + if (scalar_cast(scalar_abs(det)) < threshold) + return fallback; + + const vector4f inv_det = vector_set(scalar_reciprocal(det)); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the determinant of the input 3x3 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL matrix_determinant(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + const vector4f v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4f v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4f v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4f v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4f v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4f v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4f v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4f x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4f v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4f v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4f v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4f v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4f v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4f y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4f v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4f v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4f v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4f v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4f v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4f z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4f o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4f o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + return vector_dot3(o00_o10_o20, input.x_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the minor of the input 3x3 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra) + // The minor is the determinant of the sub-matrix input when the specified + // row and column are removed. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL matrix_minor(matrix3x3f_arg0 input, axis3 row, axis3 column) RTM_NO_EXCEPT + { + // The minor boils down to calculating the determinant of a 2x2 matrix. + // det([a, b], [c, d]) = (a * d) - (b * c) + + // Find which two rows we need. + vector4f row0; + vector4f row1; + if (row == axis3::x) + { + row0 = input.y_axis; + row1 = input.z_axis; + } + else if (row == axis3::y) + { + row0 = input.x_axis; + row1 = input.z_axis; + } + else + { + row0 = input.x_axis; + row1 = input.y_axis; + } + + // Because our input is a 3x3 matrix, there are only a few possibilities for the 2x2 part: + // row0 = [x0, y0, z0] + // row1 = [x1, y1, z1] + // det([x0, y0], [x1, y1]) = (x0 * y1) - (y0 * x1) (z removed) + // det([x0, z0], [x1, z1]) = (x0 * z1) - (z0 * x1) (y removed) + // det([y0, z0], [y1, z1]) = (y0 * z1) - (z0 * y1) (x removed) + // det([column0, column1], [column2, column3]) = (column0 * column3) - (column1 * column2) + + // For performance reasons, we can compute all three determinants at the same time. + const vector4f column0 = vector_mix(row0, row0); + const vector4f column1 = vector_mix(row0, row0); + const vector4f column2 = vector_mix(row1, row1); + const vector4f column3 = vector_mix(row1, row1); + + const vector4f determinants = vector_neg_mul_sub(column1, column2, vector_mul(column0, column3)); + + // Extract the one we need + if (column == axis3::x) + return vector_get_z(determinants); + else if (column == axis3::y) + return vector_get_y(determinants); + else + return vector_get_x(determinants); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the cofactor matrix of the input 3x3 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Cofactor_expansion_of_the_determinant + // Note: The proper way to transform a normal by a matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_cofactor(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + const vector4f x_axis = vector_cross3(input.y_axis, input.z_axis); + const vector4f y_axis = vector_cross3(input.z_axis, input.x_axis); + const vector4f z_axis = vector_cross3(input.x_axis, input.y_axis); + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the adjugate of the input matrix. + // See: https://en.wikipedia.org/wiki/Adjugate_matrix + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_adjugate(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + return matrix_transpose(matrix_cofactor(input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Removes the 3D scale from a 3x3 matrix. + // Note that if the scaling is 0.0 for a particular axis, the original rotation axis cannot + // be recovered trivially and no attempt is done to do so. In theory, we could use the other axes + // to try and recover it. + // TODO: Implement rotation recovering, perhaps in a separate function and rename this + // one to matrix_remove_non_zero_scale(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_remove_scale(matrix3x3f_arg0 input) RTM_NO_EXCEPT + { + matrix3x3f result; + result.x_axis = vector_normalize3(input.x_axis, input.x_axis); + result.y_axis = vector_normalize3(input.y_axis, input.y_axis); + result.z_axis = vector_normalize3(input.z_axis, input.z_axis); + return result; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x4d.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x4d.h new file mode 100644 index 00000000..c8f52327 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x4d.h @@ -0,0 +1,470 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/matrix3x3d.h" +#include "rtm/quatd.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_common.h" +#include "rtm/impl/matrix_affine_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Converts a rotation 3x3 matrix into a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x4d RTM_SIMD_CALL matrix_from_rotation(const matrix3x3d& rotation) RTM_NO_EXCEPT + { + return matrix3x4d{ rotation.x_axis, rotation.y_axis, rotation.z_axis, vector_zero() }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a translation vector into a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x4d matrix_from_translation(const vector4d& translation) RTM_NO_EXCEPT + { + return matrix3x4d{ vector_set(1.0, 0.0, 0.0, 0.0), vector_set(0.0, 1.0, 0.0, 0.0), vector_set(0.0, 0.0, 1.0, 0.0), translation }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets a 3x4 affine matrix from a rotation quaternion, translation, and 3D scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4d matrix_from_qvv(const quatd& quat, const vector4d& translation, const vector4d& scale) RTM_NO_EXCEPT + { + RTM_ASSERT(quat_is_normalized(quat), "Quaternion is not normalized"); + + const double x2 = quat_get_x(quat) + quat_get_x(quat); + const double y2 = quat_get_y(quat) + quat_get_y(quat); + const double z2 = quat_get_z(quat) + quat_get_z(quat); + const double xx = quat_get_x(quat) * x2; + const double xy = quat_get_x(quat) * y2; + const double xz = quat_get_x(quat) * z2; + const double yy = quat_get_y(quat) * y2; + const double yz = quat_get_y(quat) * z2; + const double zz = quat_get_z(quat) * z2; + const double wx = quat_get_w(quat) * x2; + const double wy = quat_get_w(quat) * y2; + const double wz = quat_get_w(quat) * z2; + + const scalard scale_x = vector_get_x(scale); + const scalard scale_y = vector_get_y(scale); + const scalard scale_z = vector_get_z(scale); + + const vector4d x_axis = vector_mul(vector_set(1.0 - (yy + zz), xy + wz, xz - wy, 0.0), scale_x); + const vector4d y_axis = vector_mul(vector_set(xy - wz, 1.0 - (xx + zz), yz + wx, 0.0), scale_y); + const vector4d z_axis = vector_mul(vector_set(xz + wy, yz - wx, 1.0 - (xx + yy), 0.0), scale_z); + return matrix3x4d{ x_axis, y_axis, z_axis, translation }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a QVV transform into a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4d matrix_from_qvv(const qvvd& transform) RTM_NO_EXCEPT + { + return matrix_from_qvv(transform.rotation, transform.translation, transform.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the desired 3x4 affine matrix axis. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr const vector4d& matrix_get_axis(const matrix3x4d& input, axis4 axis) RTM_NO_EXCEPT + { + return axis == axis4::x ? input.x_axis : (axis == axis4::y ? input.y_axis : (axis == axis4::z ? input.z_axis : input.w_axis)); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3x4 affine matrix into a rotation quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_from_matrix(const matrix3x4d& input) RTM_NO_EXCEPT + { + return rtm_impl::quat_from_matrix(input.x_axis, input.y_axis, input.z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two 3x4 affine matrices. + // Multiplication order is as follow: local_to_world = matrix_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4d matrix_mul(const matrix3x4d& lhs, const matrix3x4d& rhs) RTM_NO_EXCEPT + { + vector4d tmp = vector_mul(vector_dup_x(lhs.x_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.x_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.x_axis), rhs.z_axis, tmp); + vector4d x_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.y_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.y_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.y_axis), rhs.z_axis, tmp); + vector4d y_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.z_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.z_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.z_axis), rhs.z_axis, tmp); + vector4d z_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.w_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.w_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.w_axis), rhs.z_axis, tmp); + vector4d w_axis = vector_add(rhs.w_axis, tmp); + return matrix3x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 3x4 affine matrix and a 3D point. + // Multiplication order is as follow: world_position = matrix_mul(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d matrix_mul_point3(const vector4d& point, const matrix3x4d& mtx) RTM_NO_EXCEPT + { + vector4d tmp0; + vector4d tmp1; + + tmp0 = vector_mul(vector_dup_x(point), mtx.x_axis); + tmp0 = vector_mul_add(vector_dup_y(point), mtx.y_axis, tmp0); + tmp1 = vector_mul_add(vector_dup_z(point), mtx.z_axis, mtx.w_axis); + + return vector_add(tmp0, tmp1); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 3x4 affine matrix and a 3D vector. + // Multiplication order is as follow: world_position = matrix_mul(local_vector, local_to_world) + // Note: The proper way to transform a normal by an affine matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix of the 3x3 rotation/scale part. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d matrix_mul_vector3(const vector4d& vec3, const matrix3x4d& mtx) RTM_NO_EXCEPT + { + vector4d tmp; + + tmp = vector_mul(vector_dup_x(vec3), mtx.x_axis); + tmp = vector_mul_add(vector_dup_y(vec3), mtx.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(vec3), mtx.z_axis, tmp); + + return tmp; + } + + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x4 affine matrix. + // Note: This transposes the upper 3x3 rotation/scale part of the matrix + // and it discards the translation. This is because a transposed 4x4 affine + // matrix is no longer affine due to its last row no longer being [0, 0, 0, 1]. + // The most common usage of an affine transpose operation is to construct the + // inverse transpose used to transform normal bi-vectors. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x3d matrix_transpose(const matrix3x4d& input) RTM_NO_EXCEPT + { + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d x_axis = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d y_axis = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d z_axis = vector_mix(v02_v03_v12_v13, input.z_axis); + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x4 affine matrix. + // If the input matrix is not invertible, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4d matrix_inverse(const matrix3x4d& input) RTM_NO_EXCEPT + { + // Invert the 3x3 portion of the matrix that contains the rotation and 3D scale + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4d v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4d v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4d v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4d v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4d v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4d x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4d v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4d v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4d v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4d v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4d v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4d y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4d v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4d v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4d v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4d v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4d v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4d z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4d o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4d o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const scalard det = vector_dot3(o00_o10_o20, input.x_axis); + const scalard inv_det_s = scalar_reciprocal(det); + const vector4d inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + // Invert the translation + const vector4d tmp0 = vector_mul(vector_dup_z(input.w_axis), z_axis); + const vector4d tmp1 = vector_mul_add(vector_dup_y(input.w_axis), y_axis, tmp0); + vector4d w_axis = vector_neg(vector_mul_add(vector_dup_x(input.w_axis), x_axis, tmp1)); + + return matrix3x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x4 affine matrix. + // If the input matrix has a determinant whose absolute value is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4d matrix_inverse(const matrix3x4d& input, const matrix3x4d& fallback, double threshold = 1.0E-8) RTM_NO_EXCEPT + { + // Invert the 3x3 portion of the matrix that contains the rotation and 3D scale + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4d v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4d v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4d v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4d v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4d v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4d x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4d v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4d v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4d v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4d v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4d v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4d y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4d v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4d v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4d v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4d v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4d v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4d z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4d o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4d o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const scalard det = vector_dot3(o00_o10_o20, input.x_axis); + if (scalar_cast(scalar_abs(det)) < threshold) + return fallback; + + const scalard inv_det_s = scalar_reciprocal(det); + const vector4d inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + // Invert the translation + const vector4d tmp0 = vector_mul(vector_dup_z(input.w_axis), z_axis); + const vector4d tmp1 = vector_mul_add(vector_dup_y(input.w_axis), y_axis, tmp0); + vector4d w_axis = vector_neg(vector_mul_add(vector_dup_x(input.w_axis), x_axis, tmp1)); + + return matrix3x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the determinant of the 3x3 rotation/scale part of the input 3x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard matrix_determinant(const matrix3x4d& input) RTM_NO_EXCEPT + { + const vector4d v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4d v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4d v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4d v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4d v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4d v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4d v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4d v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4d v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4d x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4d v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4d v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4d v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4d v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4d v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4d y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4d v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4d v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4d v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4d v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4d v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4d z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4d o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4d o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + return vector_dot3(o00_o10_o20, input.x_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the minor of the 3x3 rotation/scale part of the input 3x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra) + // The minor is the determinant of the sub-matrix input when the specified + // row and column are removed. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard matrix_minor(const matrix3x4d& input, axis3 row, axis3 column) RTM_NO_EXCEPT + { + // The minor boils down to calculating the determinant of a 2x2 matrix. + // det([a, b], [c, d]) = (a * d) - (b * c) + + // Find which two rows we need. + vector4d row0; + vector4d row1; + if (row == axis3::x) + { + row0 = input.y_axis; + row1 = input.z_axis; + } + else if (row == axis3::y) + { + row0 = input.x_axis; + row1 = input.z_axis; + } + else + { + row0 = input.x_axis; + row1 = input.y_axis; + } + + // Because our input is a 3x3 matrix, there are only a few possibilities for the 2x2 part: + // row0 = [x0, y0, z0] + // row1 = [x1, y1, z1] + // det([x0, y0], [x1, y1]) = (x0 * y1) - (y0 * x1) (z removed) + // det([x0, z0], [x1, z1]) = (x0 * z1) - (z0 * x1) (y removed) + // det([y0, z0], [y1, z1]) = (y0 * z1) - (z0 * y1) (x removed) + // det([column0, column1], [column2, column3]) = (column0 * column3) - (column1 * column2) + + // For performance reasons, we can compute all three determinants at the same time. + const vector4d column0 = vector_mix(row0, row0); + const vector4d column1 = vector_mix(row0, row0); + const vector4d column2 = vector_mix(row1, row1); + const vector4d column3 = vector_mix(row1, row1); + + const vector4d determinants = vector_neg_mul_sub(column1, column2, vector_mul(column0, column3)); + + // Extract the one we need + if (column == axis3::x) + return vector_get_z(determinants); + else if (column == axis3::y) + return vector_get_y(determinants); + else + return vector_get_x(determinants); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the cofactor matrix of the 3x3 rotation/scale part of the input 3x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Cofactor_expansion_of_the_determinant + // Note: The proper way to transform a normal by an affine matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix of the 3x3 rotation/scale part. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d matrix_cofactor(const matrix3x4d& input) RTM_NO_EXCEPT + { + const vector4d x_axis = vector_cross3(input.y_axis, input.z_axis); + const vector4d y_axis = vector_cross3(input.z_axis, input.x_axis); + const vector4d z_axis = vector_cross3(input.x_axis, input.y_axis); + return matrix3x3d{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the adjugate of the input matrix. + // See: https://en.wikipedia.org/wiki/Adjugate_matrix + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3d matrix_adjugate(const matrix3x4d& input) RTM_NO_EXCEPT + { + return matrix_transpose(matrix_cofactor(input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Removes the 3D scale from a 3x4 affine matrix. + // Note that if the scaling is 0.0 for a particular axis, the original rotation axis cannot + // be recovered trivially and no attempt is done to do so. In theory, we could use the other axes + // to try and recover it. + // TODO: Implement rotation recovering, perhaps in a separate function and rename this + // one to matrix_remove_non_zero_scale(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4d matrix_remove_scale(const matrix3x4d& input) RTM_NO_EXCEPT + { + matrix3x4d result; + result.x_axis = vector_normalize3(input.x_axis, input.x_axis); + result.y_axis = vector_normalize3(input.y_axis, input.y_axis); + result.z_axis = vector_normalize3(input.z_axis, input.z_axis); + result.w_axis = input.w_axis; + return result; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x4f.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x4f.h new file mode 100644 index 00000000..a2d48e75 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix3x4f.h @@ -0,0 +1,469 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/macros.h" +#include "rtm/math.h" +#include "rtm/matrix3x3f.h" +#include "rtm/quatf.h" +#include "rtm/vector4f.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_common.h" +#include "rtm/impl/matrix_affine_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Converts a rotation 3x3 matrix into a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x4f RTM_SIMD_CALL matrix_from_rotation(matrix3x3f_arg0 rotation) RTM_NO_EXCEPT + { + return matrix3x4f{ rotation.x_axis, rotation.y_axis, rotation.z_axis, vector_zero() }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a translation vector into a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x4f RTM_SIMD_CALL matrix_from_translation(vector4f_arg0 translation) RTM_NO_EXCEPT + { + return matrix3x4f{ vector_set(1.0F, 0.0F, 0.0F, 0.0F), vector_set(0.0F, 1.0F, 0.0F, 0.0F), vector_set(0.0F, 0.0F, 1.0F, 0.0F), translation }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets a 3x4 affine matrix from a rotation quaternion, translation, and 3D scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4f RTM_SIMD_CALL matrix_from_qvv(quatf_arg0 quat, vector4f_arg1 translation, vector4f_arg2 scale) RTM_NO_EXCEPT + { + RTM_ASSERT(quat_is_normalized(quat), "Quaternion is not normalized"); + + const float x2 = quat_get_x(quat) + quat_get_x(quat); + const float y2 = quat_get_y(quat) + quat_get_y(quat); + const float z2 = quat_get_z(quat) + quat_get_z(quat); + const float xx = quat_get_x(quat) * x2; + const float xy = quat_get_x(quat) * y2; + const float xz = quat_get_x(quat) * z2; + const float yy = quat_get_y(quat) * y2; + const float yz = quat_get_y(quat) * z2; + const float zz = quat_get_z(quat) * z2; + const float wx = quat_get_w(quat) * x2; + const float wy = quat_get_w(quat) * y2; + const float wz = quat_get_w(quat) * z2; + + const scalarf scale_x = vector_get_x(scale); + const scalarf scale_y = vector_get_y(scale); + const scalarf scale_z = vector_get_z(scale); + + const vector4f x_axis = vector_mul(vector_set(1.0F - (yy + zz), xy + wz, xz - wy, 0.0F), scale_x); + const vector4f y_axis = vector_mul(vector_set(xy - wz, 1.0F - (xx + zz), yz + wx, 0.0F), scale_y); + const vector4f z_axis = vector_mul(vector_set(xz + wy, yz - wx, 1.0F - (xx + yy), 0.0F), scale_z); + return matrix3x4f{ x_axis, y_axis, z_axis, translation }; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a QVV transform into a 3x4 affine matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4f RTM_SIMD_CALL matrix_from_qvv(qvvf_arg0 transform) RTM_NO_EXCEPT + { + return matrix_from_qvv(transform.rotation, transform.translation, transform.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the desired 3x4 affine matrix axis. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr const vector4f& matrix_get_axis(const matrix3x4f& input, axis4 axis) RTM_NO_EXCEPT + { + return axis == axis4::x ? input.x_axis : (axis == axis4::y ? input.y_axis : (axis == axis4::z ? input.z_axis : input.w_axis)); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts a 3x4 affine matrix into a rotation quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_from_matrix(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quat_from_matrix(input.x_axis, input.y_axis, input.z_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two 3x4 affine matrices. + // Multiplication order is as follow: local_to_world = matrix_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4f RTM_SIMD_CALL matrix_mul(matrix3x4f_arg0 lhs, matrix3x4f_arg1 rhs) RTM_NO_EXCEPT + { + vector4f tmp = vector_mul(vector_dup_x(lhs.x_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.x_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.x_axis), rhs.z_axis, tmp); + vector4f x_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.y_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.y_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.y_axis), rhs.z_axis, tmp); + vector4f y_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.z_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.z_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.z_axis), rhs.z_axis, tmp); + vector4f z_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.w_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.w_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.w_axis), rhs.z_axis, tmp); + vector4f w_axis = vector_add(rhs.w_axis, tmp); + return matrix3x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 3x4 affine matrix and a 3D point. + // Multiplication order is as follow: world_position = matrix_mul(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL matrix_mul_point3(vector4f_arg0 point, matrix3x4f_arg0 mtx) RTM_NO_EXCEPT + { + vector4f tmp0; + vector4f tmp1; + + tmp0 = vector_mul(vector_dup_x(point), mtx.x_axis); + tmp0 = vector_mul_add(vector_dup_y(point), mtx.y_axis, tmp0); + tmp1 = vector_mul_add(vector_dup_z(point), mtx.z_axis, mtx.w_axis); + + return vector_add(tmp0, tmp1); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 3x4 affine matrix and a 3D vector. + // Multiplication order is as follow: world_position = matrix_mul(local_vector, local_to_world) + // Note: The proper way to transform a normal by an affine matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix of the 3x3 rotation/scale part. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL matrix_mul_vector3(vector4f_arg0 vec3, matrix3x4f_arg0 mtx) RTM_NO_EXCEPT + { + vector4f tmp; + + tmp = vector_mul(vector_dup_x(vec3), mtx.x_axis); + tmp = vector_mul_add(vector_dup_y(vec3), mtx.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(vec3), mtx.z_axis, tmp); + + return tmp; + } + + ////////////////////////////////////////////////////////////////////////// + // Transposes a 3x4 affine matrix. + // Note: This transposes the upper 3x3 rotation/scale part of the matrix + // and it discards the translation. This is because a transposed 4x4 affine + // matrix is no longer affine due to its last row no longer being [0, 0, 0, 1]. + // The most common usage of an affine transpose operation is to construct the + // inverse transpose used to transform normal bi-vectors. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix3x3f RTM_SIMD_CALL matrix_transpose(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + vector4f x_axis; + vector4f y_axis; + vector4f z_axis; + RTM_MATRIXF_TRANSPOSE_3X3(input.x_axis, input.y_axis, input.z_axis, x_axis, y_axis, z_axis); + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x4 affine matrix. + // If the input matrix is not invertible, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4f RTM_SIMD_CALL matrix_inverse(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + // Invert the 3x3 portion of the matrix that contains the rotation and 3D scale + const vector4f v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4f v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4f v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4f v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4f v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4f v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4f v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4f x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4f v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4f v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4f v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4f v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4f v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4f y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4f v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4f v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4f v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4f v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4f v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4f z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4f o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4f o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const scalarf det = vector_dot3(o00_o10_o20, input.x_axis); + const scalarf inv_det_s = scalar_reciprocal(det); + const vector4f inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + // Invert the translation + const vector4f tmp0 = vector_mul(vector_dup_z(input.w_axis), z_axis); + const vector4f tmp1 = vector_mul_add(vector_dup_y(input.w_axis), y_axis, tmp0); + vector4f w_axis = vector_neg(vector_mul_add(vector_dup_x(input.w_axis), x_axis, tmp1)); + + return matrix3x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 3x4 affine matrix. + // If the input matrix has a determinant whose absolute value is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4f RTM_SIMD_CALL matrix_inverse(matrix3x4f_arg0 input, matrix3x4f_arg1 fallback, float threshold = 1.0E-8F) RTM_NO_EXCEPT + { + // Invert the 3x3 portion of the matrix that contains the rotation and 3D scale + const vector4f v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4f v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4f v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4f v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4f v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4f v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4f v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4f x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4f v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4f v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4f v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4f v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4f v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4f y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4f v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4f v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4f v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4f v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4f v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4f z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4f o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4f o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + const scalarf det = vector_dot3(o00_o10_o20, input.x_axis); + if (scalar_cast(scalar_abs(det)) < threshold) + return fallback; + + const scalarf inv_det_s = scalar_reciprocal(det); + const vector4f inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + + // Invert the translation + const vector4f tmp0 = vector_mul(vector_dup_z(input.w_axis), z_axis); + const vector4f tmp1 = vector_mul_add(vector_dup_y(input.w_axis), y_axis, tmp0); + vector4f w_axis = vector_neg(vector_mul_add(vector_dup_x(input.w_axis), x_axis, tmp1)); + + return matrix3x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the determinant of the 3x3 rotation/scale part of the input 3x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL matrix_determinant(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + const vector4f v00_v01_v10_v11 = vector_mix(input.x_axis, input.y_axis); + const vector4f v02_v03_v12_v13 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v00_v10_v20_v22 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v01_v11_v21_v23 = vector_mix(v00_v01_v10_v11, input.z_axis); + const vector4f v02_v12_v22_v22 = vector_mix(v02_v03_v12_v13, input.z_axis); + + const vector4f v11_v21_v01 = vector_mix(v01_v11_v21_v23, input.x_axis); + const vector4f v22_v02_v12_v10 = vector_mix(v02_v12_v22_v22, input.y_axis); + const vector4f v11v22_v21v02_v01v12 = vector_mul(v11_v21_v01, v22_v02_v12_v10); + + const vector4f v01_v02_v11_v12 = vector_mix(input.x_axis, input.y_axis); + + const vector4f v12_v01_v11 = vector_mix(v01_v02_v11_v12, v01_v11_v21_v23); + const vector4f v21_v22_v02 = vector_mix(input.z_axis, v22_v02_v12_v10); + + vector4f x_axis = vector_neg_mul_sub(v12_v01_v11, v21_v22_v02, v11v22_v21v02_v01v12); + + const vector4f v20_v00_v10_v22 = vector_mix(v00_v10_v20_v22, v22_v02_v12_v10); + const vector4f v12_v22_v02 = vector_mix(v02_v12_v22_v22, v21_v22_v02); + const vector4f v20v12_v00v22_v10v02 = vector_mul(v20_v00_v10_v22, v12_v22_v02); + + const vector4f v10_v02_v00 = vector_mix(v22_v02_v12_v10, v20_v00_v10_v22); + const vector4f v22_v20_v12 = vector_mix(v20_v00_v10_v22, v12_v22_v02); + + vector4f y_axis = vector_neg_mul_sub(v10_v02_v00, v22_v20_v12, v20v12_v00v22_v10v02); + + const vector4f v10_v20_v00 = vector_mix(v20_v00_v10_v22, v10_v02_v00); + const vector4f v21_v01_v11 = vector_mix(v11_v21_v01, v12_v01_v11); + const vector4f v10v21_v20v01_v00v11 = vector_mul(v10_v20_v00, v21_v01_v11); + + const vector4f v20_v00_v01 = vector_mix(v10_v20_v00, v11_v21_v01); + const vector4f v11_v21_v10 = vector_mix(v11_v21_v01, v10_v20_v00); + + vector4f z_axis = vector_neg_mul_sub(v20_v00_v01, v11_v21_v10, v10v21_v20v01_v00v11); + + const vector4f o00_o00_o10_o10 = vector_mix(x_axis, y_axis); + const vector4f o00_o10_o20 = vector_mix(o00_o00_o10_o10, z_axis); + + return vector_dot3(o00_o10_o20, input.x_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the minor of the 3x3 rotation/scale part of the input 3x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra) + // The minor is the determinant of the sub-matrix input when the specified + // row and column are removed. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL matrix_minor(matrix3x4f_arg0 input, axis3 row, axis3 column) RTM_NO_EXCEPT + { + // The minor boils down to calculating the determinant of a 2x2 matrix. + // det([a, b], [c, d]) = (a * d) - (b * c) + + // Find which two rows we need. + vector4f row0; + vector4f row1; + if (row == axis3::x) + { + row0 = input.y_axis; + row1 = input.z_axis; + } + else if (row == axis3::y) + { + row0 = input.x_axis; + row1 = input.z_axis; + } + else + { + row0 = input.x_axis; + row1 = input.y_axis; + } + + // Because our input is a 3x3 matrix, there are only a few possibilities for the 2x2 part: + // row0 = [x0, y0, z0] + // row1 = [x1, y1, z1] + // det([x0, y0], [x1, y1]) = (x0 * y1) - (y0 * x1) (z removed) + // det([x0, z0], [x1, z1]) = (x0 * z1) - (z0 * x1) (y removed) + // det([y0, z0], [y1, z1]) = (y0 * z1) - (z0 * y1) (x removed) + // det([column0, column1], [column2, column3]) = (column0 * column3) - (column1 * column2) + + // For performance reasons, we can compute all three determinants at the same time. + const vector4f column0 = vector_mix(row0, row0); + const vector4f column1 = vector_mix(row0, row0); + const vector4f column2 = vector_mix(row1, row1); + const vector4f column3 = vector_mix(row1, row1); + + const vector4f determinants = vector_neg_mul_sub(column1, column2, vector_mul(column0, column3)); + + // Extract the one we need + if (column == axis3::x) + return vector_get_z(determinants); + else if (column == axis3::y) + return vector_get_y(determinants); + else + return vector_get_x(determinants); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the cofactor matrix of the 3x3 rotation/scale part of the input 3x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Cofactor_expansion_of_the_determinant + // Note: The proper way to transform a normal by an affine matrix with non-uniform scale + // is to multiply the normal with the cofactor matrix of the 3x3 rotation/scale part. + // See: https://github.com/graphitemaster/normals_revisited + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_cofactor(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + const vector4f x_axis = vector_cross3(input.y_axis, input.z_axis); + const vector4f y_axis = vector_cross3(input.z_axis, input.x_axis); + const vector4f z_axis = vector_cross3(input.x_axis, input.y_axis); + return matrix3x3f{ x_axis, y_axis, z_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the adjugate of the input matrix. + // See: https://en.wikipedia.org/wiki/Adjugate_matrix + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x3f RTM_SIMD_CALL matrix_adjugate(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + return matrix_transpose(matrix_cofactor(input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Removes the 3D scale from a 3x4 affine matrix. + // Note that if the scaling is 0.0 for a particular axis, the original rotation axis cannot + // be recovered trivially and no attempt is done to do so. In theory, we could use the other axes + // to try and recover it. + // TODO: Implement rotation recovering, perhaps in a separate function and rename this + // one to matrix_remove_non_zero_scale(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix3x4f RTM_SIMD_CALL matrix_remove_scale(matrix3x4f_arg0 input) RTM_NO_EXCEPT + { + matrix3x4f result; + result.x_axis = vector_normalize3(input.x_axis, input.x_axis); + result.y_axis = vector_normalize3(input.y_axis, input.y_axis); + result.z_axis = vector_normalize3(input.z_axis, input.z_axis); + result.w_axis = input.w_axis; + return result; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix4x4d.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix4x4d.h new file mode 100644 index 00000000..c3fef317 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix4x4d.h @@ -0,0 +1,491 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/matrix3x3d.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the desired 4x4 matrix axis. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr const vector4d& matrix_get_axis(const matrix4x4d& input, axis4 axis) RTM_NO_EXCEPT + { + return axis == axis4::x ? input.x_axis : (axis == axis4::y ? input.y_axis : (axis == axis4::z ? input.z_axis : input.w_axis)); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two 4x4 matrices. + // Multiplication order is as follow: local_to_world = matrix_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4d RTM_SIMD_CALL matrix_mul(const matrix4x4d& lhs, const matrix4x4d& rhs) RTM_NO_EXCEPT + { + vector4d tmp = vector_mul(vector_dup_x(lhs.x_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.x_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.x_axis), rhs.z_axis, tmp); + vector4d x_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.y_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.y_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.y_axis), rhs.z_axis, tmp); + vector4d y_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.z_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.z_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.z_axis), rhs.z_axis, tmp); + vector4d z_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.w_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.w_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.w_axis), rhs.z_axis, tmp); + vector4d w_axis = vector_add(rhs.w_axis, tmp); + return matrix4x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 4x4 matrix and a 4D vector. + // Multiplication order is as follow: world_position = matrix_mul(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d RTM_SIMD_CALL matrix_mul_vector(const vector4d& vec4, const matrix4x4d& mtx) RTM_NO_EXCEPT + { + vector4d tmp; + + tmp = vector_mul(vector_dup_x(vec4), mtx.x_axis); + tmp = vector_mul_add(vector_dup_y(vec4), mtx.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(vec4), mtx.z_axis, tmp); + tmp = vector_mul_add(vector_dup_w(vec4), mtx.w_axis, tmp); + + return tmp; + } + + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix4x4d RTM_SIMD_CALL matrix_transpose(const matrix4x4d& input) RTM_NO_EXCEPT + { + vector4d tmp0 = vector_mix(input.x_axis, input.y_axis); + vector4d tmp1 = vector_mix(input.x_axis, input.y_axis); + vector4d tmp2 = vector_mix(input.z_axis, input.w_axis); + vector4d tmp3 = vector_mix(input.z_axis, input.w_axis); + + vector4d x_axis = vector_mix(tmp0, tmp2); + vector4d y_axis = vector_mix(tmp0, tmp2); + vector4d z_axis = vector_mix(tmp1, tmp3); + vector4d w_axis = vector_mix(tmp1, tmp3); + return matrix4x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 4x4 matrix. + // If the input matrix is not invertible, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4d RTM_SIMD_CALL matrix_inverse(const matrix4x4d& input) RTM_NO_EXCEPT + { + matrix4x4d input_transposed = matrix_transpose(input); + + vector4d v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + vector4d v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + vector4d v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + vector4d v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4d v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + vector4d v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + vector4d d0 = vector_mul(v00, v10); + vector4d d1 = vector_mul(v01, v11); + vector4d d2 = vector_mul(v02, v12); + + v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + d0 = vector_neg_mul_sub(v00, v10, d0); + d1 = vector_neg_mul_sub(v01, v11, d1); + d2 = vector_neg_mul_sub(v02, v12, d2); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4d v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + vector4d v13 = vector_mix(d1, d2); + + vector4d c0 = vector_mul(v00, v10); + vector4d c2 = vector_mul(v01, v11); + vector4d c4 = vector_mul(v02, v12); + vector4d c6 = vector_mul(v03, v13); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + c0 = vector_neg_mul_sub(v00, v10, c0); + c2 = vector_neg_mul_sub(v01, v11, c2); + c4 = vector_neg_mul_sub(v02, v12, c4); + c6 = vector_neg_mul_sub(v03, v13, c6); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + vector4d c1 = vector_neg_mul_sub(v00, v10, c0); + c0 = vector_mul_add(v00, v10, c0); + vector4d c3 = vector_mul_add(v01, v11, c2); + c2 = vector_neg_mul_sub(v01, v11, c2); + vector4d c5 = vector_neg_mul_sub(v02, v12, c4); + c4 = vector_mul_add(v02, v12, c4); + vector4d c7 = vector_mul_add(v03, v13, c6); + c6 = vector_neg_mul_sub(v03, v13, c6); + + vector4d x_axis = vector_mix(c0, c1); + vector4d y_axis = vector_mix(c2, c3); + vector4d z_axis = vector_mix(c4, c5); + vector4d w_axis = vector_mix(c6, c7); + + const scalard det = vector_dot(x_axis, input_transposed.x_axis); + const scalard inv_det_s = scalar_reciprocal(det); + vector4d inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + w_axis = vector_mul(w_axis, inv_det); + + return matrix4x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 4x4 matrix. + // If the input matrix has a determinant whose absolute value is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4d RTM_SIMD_CALL matrix_inverse(const matrix4x4d& input, const matrix4x4d& fallback, double threshold = 1.0E-8) RTM_NO_EXCEPT + { + matrix4x4d input_transposed = matrix_transpose(input); + + vector4d v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + vector4d v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + vector4d v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + vector4d v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4d v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + vector4d v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + vector4d d0 = vector_mul(v00, v10); + vector4d d1 = vector_mul(v01, v11); + vector4d d2 = vector_mul(v02, v12); + + v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + d0 = vector_neg_mul_sub(v00, v10, d0); + d1 = vector_neg_mul_sub(v01, v11, d1); + d2 = vector_neg_mul_sub(v02, v12, d2); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4d v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + vector4d v13 = vector_mix(d1, d2); + + vector4d c0 = vector_mul(v00, v10); + vector4d c2 = vector_mul(v01, v11); + vector4d c4 = vector_mul(v02, v12); + vector4d c6 = vector_mul(v03, v13); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + c0 = vector_neg_mul_sub(v00, v10, c0); + c2 = vector_neg_mul_sub(v01, v11, c2); + c4 = vector_neg_mul_sub(v02, v12, c4); + c6 = vector_neg_mul_sub(v03, v13, c6); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + vector4d c1 = vector_neg_mul_sub(v00, v10, c0); + c0 = vector_mul_add(v00, v10, c0); + vector4d c3 = vector_mul_add(v01, v11, c2); + c2 = vector_neg_mul_sub(v01, v11, c2); + vector4d c5 = vector_neg_mul_sub(v02, v12, c4); + c4 = vector_mul_add(v02, v12, c4); + vector4d c7 = vector_mul_add(v03, v13, c6); + c6 = vector_neg_mul_sub(v03, v13, c6); + + vector4d x_axis = vector_mix(c0, c1); + vector4d y_axis = vector_mix(c2, c3); + vector4d z_axis = vector_mix(c4, c5); + vector4d w_axis = vector_mix(c6, c7); + + scalard det = vector_dot(x_axis, input_transposed.x_axis); + if (scalar_cast(scalar_abs(det)) < threshold) + return fallback; + + scalard inv_det_s = scalar_reciprocal(det); + vector4d inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + w_axis = vector_mul(w_axis, inv_det); + + return matrix4x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the determinant of the input 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL matrix_determinant(const matrix4x4d& input) RTM_NO_EXCEPT + { + matrix4x4d input_transposed = matrix_transpose(input); + + vector4d v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + vector4d v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + vector4d v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + vector4d v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4d v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + vector4d v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + vector4d d0 = vector_mul(v00, v10); + vector4d d1 = vector_mul(v01, v11); + vector4d d2 = vector_mul(v02, v12); + + v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + d0 = vector_neg_mul_sub(v00, v10, d0); + d1 = vector_neg_mul_sub(v01, v11, d1); + d2 = vector_neg_mul_sub(v02, v12, d2); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4d v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + vector4d v13 = vector_mix(d1, d2); + + vector4d c0 = vector_mul(v00, v10); + vector4d c2 = vector_mul(v01, v11); + vector4d c4 = vector_mul(v02, v12); + vector4d c6 = vector_mul(v03, v13); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + c0 = vector_neg_mul_sub(v00, v10, c0); + c2 = vector_neg_mul_sub(v01, v11, c2); + c4 = vector_neg_mul_sub(v02, v12, c4); + c6 = vector_neg_mul_sub(v03, v13, c6); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + vector4d c1 = vector_neg_mul_sub(v00, v10, c0); + c0 = vector_mul_add(v00, v10, c0); + c2 = vector_neg_mul_sub(v01, v11, c2); + c4 = vector_mul_add(v02, v12, c4); + c6 = vector_neg_mul_sub(v03, v13, c6); + + vector4d x_axis = vector_mix(c0, c1); + + return vector_dot(x_axis, input_transposed.x_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the minor of the input 4x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra) + // The minor is the determinant of the sub-matrix input when the specified + // row and column are removed. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL matrix_minor(const matrix4x4d& input, axis4 row, axis4 column) RTM_NO_EXCEPT + { + vector4d row0; + vector4d row1; + vector4d row2; + + // Find which rows we need. + if (row == axis4::x) + { + row0 = input.y_axis; + row1 = input.z_axis; + row2 = input.w_axis; + } + else if (row == axis4::y) + { + row0 = input.x_axis; + row1 = input.z_axis; + row2 = input.w_axis; + } + else if (row == axis4::z) + { + row0 = input.x_axis; + row1 = input.y_axis; + row2 = input.w_axis; + } + else + { + row0 = input.x_axis; + row1 = input.y_axis; + row2 = input.z_axis; + } + + // Shift our row values into a proper 3x3 matrix + if (column == axis4::x) + { + row0 = vector_mix(row0, row0); + row1 = vector_mix(row1, row1); + row2 = vector_mix(row2, row2); + } + else if (column == axis4::y) + { + row0 = vector_mix(row0, row0); + row1 = vector_mix(row1, row1); + row2 = vector_mix(row2, row2); + } + else if (column == axis4::z) + { + row0 = vector_mix(row0, row0); + row1 = vector_mix(row1, row1); + row2 = vector_mix(row2, row2); + } + else + { + // Already lined up + } + + const matrix3x3d mtx = matrix_set(row0, row1, row2); + return matrix_determinant(mtx); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the cofactor matrix of the input 4x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Cofactor_expansion_of_the_determinant + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4d RTM_SIMD_CALL matrix_cofactor(const matrix4x4d& input) RTM_NO_EXCEPT + { + const scalard minor_xx = matrix_minor(input, axis4::x, axis4::x); + const scalard minor_xy = matrix_minor(input, axis4::x, axis4::y); + const scalard minor_xz = matrix_minor(input, axis4::x, axis4::z); + const scalard minor_xw = matrix_minor(input, axis4::x, axis4::w); + + const scalard minor_yx = matrix_minor(input, axis4::y, axis4::x); + const scalard minor_yy = matrix_minor(input, axis4::y, axis4::y); + const scalard minor_yz = matrix_minor(input, axis4::y, axis4::z); + const scalard minor_yw = matrix_minor(input, axis4::y, axis4::w); + + const scalard minor_zx = matrix_minor(input, axis4::z, axis4::x); + const scalard minor_zy = matrix_minor(input, axis4::z, axis4::y); + const scalard minor_zz = matrix_minor(input, axis4::z, axis4::z); + const scalard minor_zw = matrix_minor(input, axis4::z, axis4::w); + + const scalard minor_wx = matrix_minor(input, axis4::w, axis4::x); + const scalard minor_wy = matrix_minor(input, axis4::w, axis4::y); + const scalard minor_wz = matrix_minor(input, axis4::w, axis4::z); + const scalard minor_ww = matrix_minor(input, axis4::w, axis4::w); + + const vector4d xz_axis_signs = vector_set(1.0, -1.0, 1.0, -1.0); + const vector4d yw_axis_signs = vector_set(-1.0, 1.0, -1.0, 1.0); + + const vector4d x_axis = vector_mul(vector_set(minor_xx, minor_xy, minor_xz, minor_xw), xz_axis_signs); + const vector4d y_axis = vector_mul(vector_set(minor_yx, minor_yy, minor_yz, minor_yw), yw_axis_signs); + const vector4d z_axis = vector_mul(vector_set(minor_zx, minor_zy, minor_zz, minor_zw), xz_axis_signs); + const vector4d w_axis = vector_mul(vector_set(minor_wx, minor_wy, minor_wz, minor_ww), yw_axis_signs); + return matrix4x4d{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the adjugate of the input matrix. + // See: https://en.wikipedia.org/wiki/Adjugate_matrix + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4d RTM_SIMD_CALL matrix_adjugate(const matrix4x4d& input) RTM_NO_EXCEPT + { + return matrix_transpose(matrix_cofactor(input)); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix4x4f.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix4x4f.h new file mode 100644 index 00000000..24c9bd07 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/matrix4x4f.h @@ -0,0 +1,455 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/macros.h" +#include "rtm/math.h" +#include "rtm/matrix3x3f.h" +#include "rtm/vector4f.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/matrix_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the desired 4x4 matrix axis. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr vector4f RTM_SIMD_CALL matrix_get_axis(matrix4x4f_arg0 input, axis4 axis) RTM_NO_EXCEPT + { + return axis == axis4::x ? input.x_axis : (axis == axis4::y ? input.y_axis : (axis == axis4::z ? input.z_axis : input.w_axis)); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two 4x4 matrices. + // Multiplication order is as follow: local_to_world = matrix_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4f RTM_SIMD_CALL matrix_mul(matrix4x4f_arg0 lhs, matrix4x4f_arg1 rhs) RTM_NO_EXCEPT + { + vector4f tmp = vector_mul(vector_dup_x(lhs.x_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.x_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.x_axis), rhs.z_axis, tmp); + vector4f x_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.y_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.y_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.y_axis), rhs.z_axis, tmp); + vector4f y_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.z_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.z_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.z_axis), rhs.z_axis, tmp); + vector4f z_axis = tmp; + + tmp = vector_mul(vector_dup_x(lhs.w_axis), rhs.x_axis); + tmp = vector_mul_add(vector_dup_y(lhs.w_axis), rhs.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(lhs.w_axis), rhs.z_axis, tmp); + vector4f w_axis = vector_add(rhs.w_axis, tmp); + return matrix4x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a 4x4 matrix and a 4D vector. + // Multiplication order is as follow: world_position = matrix_mul(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL matrix_mul_vector(vector4f_arg0 vec4, matrix4x4f_arg0 mtx) RTM_NO_EXCEPT + { + vector4f tmp; + + tmp = vector_mul(vector_dup_x(vec4), mtx.x_axis); + tmp = vector_mul_add(vector_dup_y(vec4), mtx.y_axis, tmp); + tmp = vector_mul_add(vector_dup_z(vec4), mtx.z_axis, tmp); + tmp = vector_mul_add(vector_dup_w(vec4), mtx.w_axis, tmp); + + return tmp; + } + + ////////////////////////////////////////////////////////////////////////// + // Transposes a 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE matrix4x4f RTM_SIMD_CALL matrix_transpose(matrix4x4f_arg0 input) RTM_NO_EXCEPT + { + vector4f x_axis; + vector4f y_axis; + vector4f z_axis; + vector4f w_axis; + RTM_MATRIXF_TRANSPOSE_4X4(input.x_axis, input.y_axis, input.z_axis, input.w_axis, x_axis, y_axis, z_axis, w_axis); + return matrix4x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 4x4 matrix. + // If the input matrix is not invertible, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4f RTM_SIMD_CALL matrix_inverse(matrix4x4f_arg0 input) RTM_NO_EXCEPT + { + matrix4x4f input_transposed = matrix_transpose(input); + + vector4f v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + vector4f v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + vector4f v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + vector4f v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4f v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + vector4f v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + vector4f d0 = vector_mul(v00, v10); + vector4f d1 = vector_mul(v01, v11); + vector4f d2 = vector_mul(v02, v12); + + v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + d0 = vector_neg_mul_sub(v00, v10, d0); + d1 = vector_neg_mul_sub(v01, v11, d1); + d2 = vector_neg_mul_sub(v02, v12, d2); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4f v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + vector4f v13 = vector_mix(d1, d2); + + vector4f c0 = vector_mul(v00, v10); + vector4f c2 = vector_mul(v01, v11); + vector4f c4 = vector_mul(v02, v12); + vector4f c6 = vector_mul(v03, v13); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + c0 = vector_neg_mul_sub(v00, v10, c0); + c2 = vector_neg_mul_sub(v01, v11, c2); + c4 = vector_neg_mul_sub(v02, v12, c4); + c6 = vector_neg_mul_sub(v03, v13, c6); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + vector4f c1 = vector_neg_mul_sub(v00, v10, c0); + c0 = vector_mul_add(v00, v10, c0); + vector4f c3 = vector_mul_add(v01, v11, c2); + c2 = vector_neg_mul_sub(v01, v11, c2); + vector4f c5 = vector_neg_mul_sub(v02, v12, c4); + c4 = vector_mul_add(v02, v12, c4); + vector4f c7 = vector_mul_add(v03, v13, c6); + c6 = vector_neg_mul_sub(v03, v13, c6); + + vector4f x_axis = vector_mix(c0, c1); + vector4f y_axis = vector_mix(c2, c3); + vector4f z_axis = vector_mix(c4, c5); + vector4f w_axis = vector_mix(c6, c7); + + const scalarf det = vector_dot(x_axis, input_transposed.x_axis); + const scalarf inv_det_s = scalar_reciprocal(det); + const vector4f inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + w_axis = vector_mul(w_axis, inv_det); + + return matrix4x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Inverses a 4x4 matrix. + // If the input matrix has a determinant whose absolute value is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4f RTM_SIMD_CALL matrix_inverse(matrix4x4f_arg0 input, matrix4x4f_arg1 fallback, float threshold = 1.0E-8F) RTM_NO_EXCEPT + { + matrix4x4f input_transposed = matrix_transpose(input); + + vector4f v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + vector4f v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + vector4f v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + vector4f v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4f v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + vector4f v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + vector4f d0 = vector_mul(v00, v10); + vector4f d1 = vector_mul(v01, v11); + vector4f d2 = vector_mul(v02, v12); + + v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v11 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + d0 = vector_neg_mul_sub(v00, v10, d0); + d1 = vector_neg_mul_sub(v01, v11, d1); + d2 = vector_neg_mul_sub(v02, v12, d2); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4f v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + vector4f v13 = vector_mix(d1, d2); + + vector4f c0 = vector_mul(v00, v10); + vector4f c2 = vector_mul(v01, v11); + vector4f c4 = vector_mul(v02, v12); + vector4f c6 = vector_mul(v03, v13); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + c0 = vector_neg_mul_sub(v00, v10, c0); + c2 = vector_neg_mul_sub(v01, v11, c2); + c4 = vector_neg_mul_sub(v02, v12, c4); + c6 = vector_neg_mul_sub(v03, v13, c6); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v01 = vector_mix(input_transposed.x_axis, input_transposed.x_axis); + v02 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v03 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v10 = vector_mix(d0, d2); + v11 = vector_mix(d0, d2); + v12 = vector_mix(d1, d2); + v13 = vector_mix(d1, d2); + + vector4f c1 = vector_neg_mul_sub(v00, v10, c0); + c0 = vector_mul_add(v00, v10, c0); + vector4f c3 = vector_mul_add(v01, v11, c2); + c2 = vector_neg_mul_sub(v01, v11, c2); + vector4f c5 = vector_neg_mul_sub(v02, v12, c4); + c4 = vector_mul_add(v02, v12, c4); + vector4f c7 = vector_mul_add(v03, v13, c6); + c6 = vector_neg_mul_sub(v03, v13, c6); + + vector4f x_axis = vector_mix(c0, c1); + vector4f y_axis = vector_mix(c2, c3); + vector4f z_axis = vector_mix(c4, c5); + vector4f w_axis = vector_mix(c6, c7); + + const scalarf det = vector_dot(x_axis, input_transposed.x_axis); + if (scalar_cast(scalar_abs(det)) < threshold) + return fallback; + + const scalarf inv_det_s = scalar_reciprocal(det); + const vector4f inv_det = vector_set(inv_det_s); + + x_axis = vector_mul(x_axis, inv_det); + y_axis = vector_mul(y_axis, inv_det); + z_axis = vector_mul(z_axis, inv_det); + w_axis = vector_mul(w_axis, inv_det); + + return matrix4x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the determinant of the input 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL matrix_determinant(matrix4x4f_arg0 input) RTM_NO_EXCEPT + { + matrix4x4f input_transposed = matrix_transpose(input); + + vector4f v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + vector4f v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + vector4f v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + vector4f v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + vector4f d0 = vector_mul(v00, v10); + vector4f d2 = vector_mul(v02, v12); + + v00 = vector_mix(input_transposed.z_axis, input_transposed.z_axis); + v02 = vector_mix(input_transposed.z_axis, input_transposed.x_axis); + v10 = vector_mix(input_transposed.w_axis, input_transposed.w_axis); + v12 = vector_mix(input_transposed.w_axis, input_transposed.y_axis); + + d0 = vector_neg_mul_sub(v00, v10, d0); + d2 = vector_neg_mul_sub(v02, v12, d2); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v10 = vector_mix(d0, d2); + + vector4f c0 = vector_mul(v00, v10); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v10 = vector_mix(d0, d2); + + c0 = vector_neg_mul_sub(v00, v10, c0); + + v00 = vector_mix(input_transposed.y_axis, input_transposed.y_axis); + v10 = vector_mix(d0, d2); + + vector4f c1 = vector_neg_mul_sub(v00, v10, c0); + c0 = vector_mul_add(v00, v10, c0); + + vector4f x_axis = vector_mix(c0, c1); + + return vector_dot(x_axis, input_transposed.x_axis); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the minor of the input 4x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra) + // The minor is the determinant of the sub-matrix input when the specified + // row and column are removed. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL matrix_minor(matrix4x4f_arg0 input, axis4 row, axis4 column) RTM_NO_EXCEPT + { + vector4f row0; + vector4f row1; + vector4f row2; + + // Find which rows we need. + if (row == axis4::x) + { + row0 = input.y_axis; + row1 = input.z_axis; + row2 = input.w_axis; + } + else if (row == axis4::y) + { + row0 = input.x_axis; + row1 = input.z_axis; + row2 = input.w_axis; + } + else if (row == axis4::z) + { + row0 = input.x_axis; + row1 = input.y_axis; + row2 = input.w_axis; + } + else + { + row0 = input.x_axis; + row1 = input.y_axis; + row2 = input.z_axis; + } + + // Shift our row values into a proper 3x3 matrix + if (column == axis4::x) + { + row0 = vector_mix(row0, row0); + row1 = vector_mix(row1, row1); + row2 = vector_mix(row2, row2); + } + else if (column == axis4::y) + { + row0 = vector_mix(row0, row0); + row1 = vector_mix(row1, row1); + row2 = vector_mix(row2, row2); + } + else if (column == axis4::z) + { + row0 = vector_mix(row0, row0); + row1 = vector_mix(row1, row1); + row2 = vector_mix(row2, row2); + } + else + { + // Already lined up + } + + const matrix3x3f mtx = matrix_set(row0, row1, row2); + return matrix_determinant(mtx); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the cofactor matrix of the input 4x4 matrix. + // See: https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Cofactor_expansion_of_the_determinant + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4f RTM_SIMD_CALL matrix_cofactor(matrix4x4f_arg0 input) RTM_NO_EXCEPT + { + const scalarf minor_xx = matrix_minor(input, axis4::x, axis4::x); + const scalarf minor_xy = matrix_minor(input, axis4::x, axis4::y); + const scalarf minor_xz = matrix_minor(input, axis4::x, axis4::z); + const scalarf minor_xw = matrix_minor(input, axis4::x, axis4::w); + + const scalarf minor_yx = matrix_minor(input, axis4::y, axis4::x); + const scalarf minor_yy = matrix_minor(input, axis4::y, axis4::y); + const scalarf minor_yz = matrix_minor(input, axis4::y, axis4::z); + const scalarf minor_yw = matrix_minor(input, axis4::y, axis4::w); + + const scalarf minor_zx = matrix_minor(input, axis4::z, axis4::x); + const scalarf minor_zy = matrix_minor(input, axis4::z, axis4::y); + const scalarf minor_zz = matrix_minor(input, axis4::z, axis4::z); + const scalarf minor_zw = matrix_minor(input, axis4::z, axis4::w); + + const scalarf minor_wx = matrix_minor(input, axis4::w, axis4::x); + const scalarf minor_wy = matrix_minor(input, axis4::w, axis4::y); + const scalarf minor_wz = matrix_minor(input, axis4::w, axis4::z); + const scalarf minor_ww = matrix_minor(input, axis4::w, axis4::w); + + const vector4f xz_axis_signs = vector_set(1.0F, -1.0F, 1.0F, -1.0F); + const vector4f yw_axis_signs = vector_set(-1.0F, 1.0F, -1.0F, 1.0F); + + const vector4f x_axis = vector_mul(vector_set(minor_xx, minor_xy, minor_xz, minor_xw), xz_axis_signs); + const vector4f y_axis = vector_mul(vector_set(minor_yx, minor_yy, minor_yz, minor_yw), yw_axis_signs); + const vector4f z_axis = vector_mul(vector_set(minor_zx, minor_zy, minor_zz, minor_zw), xz_axis_signs); + const vector4f w_axis = vector_mul(vector_set(minor_wx, minor_wy, minor_wz, minor_ww), yw_axis_signs); + return matrix4x4f{ x_axis, y_axis, z_axis, w_axis }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the adjugate of the input matrix. + // See: https://en.wikipedia.org/wiki/Adjugate_matrix + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline matrix4x4f RTM_SIMD_CALL matrix_adjugate(matrix4x4f_arg0 input) RTM_NO_EXCEPT + { + return matrix_transpose(matrix_cofactor(input)); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/packing/quatd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/packing/quatd.h new file mode 100644 index 00000000..9442fe85 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/packing/quatd.h @@ -0,0 +1,70 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/quatd.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion on the hypersphere with a positive [w] component + // that represents the same 3D rotation as the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_ensure_positive_w(const quatd& input) RTM_NO_EXCEPT + { + return quat_get_w(input) >= 0.0 ? input : quat_neg(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a quaternion constructed from a vector3 representing the [xyz] + // components while reconstructing the [w] component by assuming it is positive. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_from_positive_w(const vector4d& input) RTM_NO_EXCEPT + { + const double input_x = vector_get_x(input); + const double input_y = vector_get_y(input); + const double input_z = vector_get_z(input); + + // Operation order is important here, due to rounding, ((1.0 - (X*X)) - Y*Y) - Z*Z is more accurate than 1.0 - dot3(xyz, xyz) + const double w_squared = ((1.0 - (input_x * input_x)) - (input_y * input_y)) - (input_z * input_z); + + // w_squared can be negative either due to rounding or due to quantization imprecision, we take the absolute value + // to ensure the resulting quaternion is always normalized with a positive W component + const double w = scalar_sqrt(scalar_abs(w_squared)); + return quat_set_w(vector_to_quat(input), w); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/packing/quatf.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/packing/quatf.h new file mode 100644 index 00000000..fa069903 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/packing/quatf.h @@ -0,0 +1,104 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/quatf.h" +#include "rtm/vector4f.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion on the hypersphere with a positive [w] component + // that represents the same 3D rotation as the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_ensure_positive_w(quatf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 sign_bit = { -0.0F, -0.0F, -0.0F, -0.0F }; + const __m128 input_sign = _mm_and_ps(input, sign_bit); + const __m128 bias = _mm_shuffle_ps(input_sign, input_sign, _MM_SHUFFLE(3, 3, 3, 3)); + return _mm_xor_ps(input, bias); +#elif defined(RTM_NEON_INTRINSICS) + alignas(16) constexpr uint32_t sign_bit_i[4] = { 0x80000000U, 0x80000000U, 0x80000000U, 0x80000000U }; + const uint32x4_t sign_bit = *reinterpret_cast(&sign_bit_i[0]); + const uint32x4_t input_u32 = vreinterpretq_u32_f32(input); + const uint32x4_t input_sign = vandq_u32(input_u32, sign_bit); + const uint32x4_t bias = vmovq_n_f32(vgetq_lane_f32(input_sign, 3)); + return vreinterpretq_f32_u32(veorq_u32(input_u32, bias)); +#else + return quat_get_w(input) >= 0.f ? input : quat_neg(input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a quaternion constructed from a vector3 representing the [xyz] + // components while reconstructing the [w] component by assuming it is positive. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_from_positive_w(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + + __m128 x2y2z2 = _mm_mul_ps(input, input); + __m128 one = _mm_set_ss(1.0F); + __m128 w_squared = _mm_sub_ss(_mm_sub_ss(_mm_sub_ss(one, x2y2z2), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(1, 1, 1, 1))), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(2, 2, 2, 2))); + w_squared = _mm_and_ps(w_squared, _mm_castsi128_ps(abs_mask)); + __m128 w = _mm_sqrt_ss(w_squared); + +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, w, 0x30); +#else + __m128 input_wyzx = _mm_shuffle_ps(input, input, _MM_SHUFFLE(0, 2, 1, 3)); + __m128 result_wyzx = _mm_move_ss(input_wyzx, w); + return _mm_shuffle_ps(result_wyzx, result_wyzx, _MM_SHUFFLE(0, 2, 1, 3)); +#endif +#elif defined(RTM_NEON_INTRINSICS) && 0 + // TODO: This is slower on ARMv7-A, measure again on ARM64, fewer instructions but the first + // sub is dependent on the result of the mul where the C impl below pipelines a bit better it seems + float32x4_t x2y2z2 = vmulq_f32(input, input); + float w_squared = ((1.0F - vgetq_lane_f32(x2y2z2, 0)) - vgetq_lane_f32(x2y2z2, 1)) - vgetq_lane_f32(x2y2z2, 2); + float w = scalar_sqrt(scalar_abs(w_squared)); + return vsetq_lane_f32(w, input, 3); +#else + // Operation order is important here, due to rounding, ((1.0 - (X*X)) - Y*Y) - Z*Z is more accurate than 1.0 - dot3(xyz, xyz) + float w_squared = ((1.0F - vector_get_x(input) * vector_get_x(input)) - vector_get_y(input) * vector_get_y(input)) - vector_get_z(input) * vector_get_z(input); + // w_squared can be negative either due to rounding or due to quantization imprecision, we take the absolute value + // to ensure the resulting quaternion is always normalized with a positive W component + float w = scalar_sqrt(scalar_abs(w_squared)); + return quat_set_w(vector_to_quat(input), w); +#endif + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/quatd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/quatd.h new file mode 100644 index 00000000..19718fdf --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/quatd.h @@ -0,0 +1,912 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/scalard.h" +#include "rtm/vector4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/memory_utils.h" +#include "rtm/impl/quat_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Setters, getters, and casts + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned quaternion from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_load(const double* input) RTM_NO_EXCEPT + { + return quat_set(input[0], input[1], input[2], input[3]); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned quat from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_load(const float4d* input) RTM_NO_EXCEPT + { + return quat_set(input->x, input->y, input->z, input->w); + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a vector4 to a quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd vector_to_quat(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ input.xy, input.zw }; +#else + return quatd{ input.x, input.y, input.z, input.w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a quaternion float32 variant to a float64 variant. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_cast(const quatf& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ _mm_cvtps_pd(input), _mm_cvtps_pd(_mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 2, 3, 2))) }; +#elif defined(RTM_NEON_INTRINSICS) + return quatd{ double(vgetq_lane_f32(input, 0)), double(vgetq_lane_f32(input, 1)), double(vgetq_lane_f32(input, 2)), double(vgetq_lane_f32(input, 3)) }; +#else + return quatd{ double(input.x), double(input.y), double(input.z), double(input.w) }; +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_get_x + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(input.xy); +#else + return input.x; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ input.xy }; + } +#endif + + quatd input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [x] component (real part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatd_quat_get_x quat_get_x(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_get_x{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_get_y + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_shuffle_pd(input.xy, input.xy, 1)); +#else + return input.y; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ _mm_shuffle_pd(input.xy, input.xy, 1) }; + } +#endif + + quatd input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [y] component (real part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatd_quat_get_y quat_get_y(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_get_y{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_get_z + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(input.zw); +#else + return input.z; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ input.zw }; + } +#endif + + quatd input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [z] component (real part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatd_quat_get_z quat_get_z(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_get_z{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_get_w + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_shuffle_pd(input.zw, input.zw, 1)); +#else + return input.w; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ _mm_shuffle_pd(input.zw, input.zw, 1) }; + } +#endif + + quatd input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [w] component (imaginary part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatd_quat_get_w quat_get_w(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_get_w{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [x] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_x(const quatd& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ _mm_move_sd(input.xy, _mm_set_sd(lane_value)), input.zw }; +#else + return quatd{ lane_value, input.y, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [x] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_x(const quatd& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return quatd{ _mm_move_sd(input.xy, lane_value.value), input.zw }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [y] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_y(const quatd& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ _mm_shuffle_pd(input.xy, _mm_set_sd(lane_value), 0), input.zw }; +#else + return quatd{ input.x, lane_value, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [y] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_y(const quatd& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return quatd{ _mm_shuffle_pd(input.xy, lane_value.value, 0), input.zw }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [z] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_z(const quatd& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ input.xy, _mm_move_sd(input.zw, _mm_set_sd(lane_value)) }; +#else + return quatd{ input.x, input.y, lane_value, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [z] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_z(const quatd& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return quatd{ input.xy, _mm_move_sd(input.zw, lane_value.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [w] component (imaginary part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_w(const quatd& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return quatd{ input.xy, _mm_shuffle_pd(input.zw, _mm_set_sd(lane_value), 0) }; +#else + return quatd{ input.x, input.y, input.z, lane_value }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [w] component (imaginary part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_set_w(const quatd& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return quatd{ input.xy, _mm_shuffle_pd(input.zw, lane_value.value, 0) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Writes a quaternion to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void quat_store(const quatd& input, double* output) RTM_NO_EXCEPT + { + output[0] = quat_get_x(input); + output[1] = quat_get_y(input); + output[2] = quat_get_z(input); + output[3] = quat_get_w(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a quaternion to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void quat_store(const quatd& input, float4d* output) RTM_NO_EXCEPT + { + output->x = quat_get_x(input); + output->y = quat_get_y(input); + output->z = quat_get_z(input); + output->w = quat_get_w(input); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Arithmetic + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion conjugate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_conjugate(const quatd& input) RTM_NO_EXCEPT + { + return quat_set(-quat_get_x(input), -quat_get_y(input), -quat_get_z(input), quat_get_w(input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two quaternions. + // Note that due to floating point rounding, the result might not be perfectly normalized. + // Multiplication order is as follow: local_to_world = quat_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_mul(const quatd& lhs, const quatd& rhs) RTM_NO_EXCEPT + { + double lhs_x = quat_get_x(lhs); + double lhs_y = quat_get_y(lhs); + double lhs_z = quat_get_z(lhs); + double lhs_w = quat_get_w(lhs); + + double rhs_x = quat_get_x(rhs); + double rhs_y = quat_get_y(rhs); + double rhs_z = quat_get_z(rhs); + double rhs_w = quat_get_w(rhs); + + double x = (rhs_w * lhs_x) + (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + double y = (rhs_w * lhs_y) - (rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + double z = (rhs_w * lhs_z) + (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + double w = (rhs_w * lhs_w) - (rhs_x * lhs_x) - (rhs_y * lhs_y) - (rhs_z * lhs_z); + + return quat_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a quaternion and a 3D vector, rotating it. + // Multiplication order is as follow: world_position = quat_mul_vector3(local_vector, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d quat_mul_vector3(const vector4d& vector, const quatd& rotation) RTM_NO_EXCEPT + { + quatd vector_quat = quat_set_w(vector_to_quat(vector), 0.0); + quatd inv_rotation = quat_conjugate(rotation); + return quat_to_vector(quat_mul(quat_mul(inv_rotation, vector_quat), rotation)); + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_dot + { + RTM_DISABLE_SECURITY_COOKIE_CHECK inline RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard lhs_x = quat_get_x(lhs); + const scalard lhs_y = quat_get_y(lhs); + const scalard lhs_z = quat_get_z(lhs); + const scalard lhs_w = quat_get_w(lhs); + const scalard rhs_x = quat_get_x(rhs); + const scalard rhs_y = quat_get_y(rhs); + const scalard rhs_z = quat_get_z(rhs); + const scalard rhs_w = quat_get_w(rhs); + const scalard xx = scalar_mul(lhs_x, rhs_x); + const scalard yy = scalar_mul(lhs_y, rhs_y); + const scalard zz = scalar_mul(lhs_z, rhs_z); + const scalard ww = scalar_mul(lhs_w, rhs_w); + return scalar_cast(scalar_add(scalar_add(xx, yy), scalar_add(zz, ww))); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK inline RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard lhs_x = quat_get_x(lhs); + const scalard lhs_y = quat_get_y(lhs); + const scalard lhs_z = quat_get_z(lhs); + const scalard lhs_w = quat_get_w(lhs); + const scalard rhs_x = quat_get_x(rhs); + const scalard rhs_y = quat_get_y(rhs); + const scalard rhs_z = quat_get_z(rhs); + const scalard rhs_w = quat_get_w(rhs); + const scalard xx = scalar_mul(lhs_x, rhs_x); + const scalard yy = scalar_mul(lhs_y, rhs_y); + const scalard zz = scalar_mul(lhs_z, rhs_z); + const scalard ww = scalar_mul(lhs_w, rhs_w); + return scalar_add(scalar_add(xx, yy), scalar_add(zz, ww)); + } +#endif + + quatd lhs; + quatd rhs; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Quaternion dot product: lhs . rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK constexpr rtm_impl::quatd_quat_dot quat_dot(const quatd& lhs, const quatd& rhs) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_dot{ lhs, rhs }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the squared length/norm of the quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK constexpr rtm_impl::quatd_quat_dot quat_length_squared(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_dot{ input, input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_length + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard len_sq = quat_length_squared(input); + return scalar_cast(scalar_sqrt(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard len_sq = quat_length_squared(input); + return scalar_sqrt(len_sq); + } +#endif + + quatd input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the length/norm of the quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatd_quat_length quat_length(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_length{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatd_quat_length_reciprocal + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard len_sq = quat_length_squared(input); + return scalar_cast(scalar_sqrt_reciprocal(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard len_sq = quat_length_squared(input); + return scalar_sqrt_reciprocal(len_sq); + } +#endif + + quatd input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal length/norm of the quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatd_quat_length_reciprocal quat_length_reciprocal(const quatd& input) RTM_NO_EXCEPT + { + return rtm_impl::quatd_quat_length_reciprocal{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a normalized quaternion. + // Note that if the input quaternion is invalid (pure zero or with NaN/Inf), + // the result is undefined. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_normalize(const quatd& input) RTM_NO_EXCEPT + { + // TODO: Use high precision recip sqrt function and vector_mul + double length = quat_length(input); + //float length_recip = quat_length_reciprocal(input); + vector4d input_vector = quat_to_vector(input); + //return vector_to_quat(vector_mul(input_vector, length_recip)); + return vector_to_quat(vector_div(input_vector, vector_set(length))); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation between start and end for a given alpha value. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when 'alpha' is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // Note that if 'start' and 'end' are on the opposite ends of the hypersphere, 'end' is negated + // before we interpolate. As such, when 'alpha' is 1.0, either 'end' or its negated equivalent + // is returned. Furthermore, if 'start' and 'end' aren't exactly normalized, the result might + // not match exactly when 'alpha' is 0.0 or 1.0 because we normalize the resulting quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_lerp(const quatd& start, const quatd& end, double alpha) RTM_NO_EXCEPT + { + // To ensure we take the shortest path, we apply a bias if the dot product is negative + vector4d start_vector = quat_to_vector(start); + vector4d end_vector = quat_to_vector(end); + double dot = vector_dot(start_vector, end_vector); + double bias = dot >= 0.0 ? 1.0 : -1.0; + // ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias)) + vector4d value = vector_mul_add(vector_mul(end_vector, bias), alpha, vector_neg_mul_sub(start_vector, alpha, start_vector)); + return quat_normalize(vector_to_quat(value)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation between start and end for a given alpha value. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when 'alpha' is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // Note that if 'start' and 'end' are on the opposite ends of the hypersphere, 'end' is negated + // before we interpolate. As such, when 'alpha' is 1.0, either 'end' or its negated equivalent + // is returned. Furthermore, if 'start' and 'end' aren't exactly normalized, the result might + // not match exactly when 'alpha' is 0.0 or 1.0 because we normalize the resulting quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_lerp(const quatd& start, const quatd& end, const scalard& alpha) RTM_NO_EXCEPT + { + // To ensure we take the shortest path, we apply a bias if the dot product is negative + vector4d start_vector = quat_to_vector(start); + vector4d end_vector = quat_to_vector(end); + double dot = vector_dot(start_vector, end_vector); + double bias = dot >= 0.0 ? 1.0 : -1.0; + // ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias)) + vector4d alpha_v = vector_set(alpha); + vector4d value = vector_mul_add(vector_mul(end_vector, bias), alpha_v, vector_neg_mul_sub(start_vector, alpha_v, start_vector)); + return quat_normalize(vector_to_quat(value)); + } +#endif + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the spherical interpolation between start and end for a given alpha value. + // See: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm + // Perhaps try this someday: http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/ + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd RTM_SIMD_CALL quat_slerp(const quatd& start, const quatd& end, const scalard& alpha) RTM_NO_EXCEPT + { + vector4d start_v = quat_to_vector(start); + vector4d end_v = quat_to_vector(end); + + vector4d cos_half_angle_v = vector_dot(start_v, end_v); + mask4d is_angle_negative = vector_less_than(cos_half_angle_v, vector_zero()); + + // If the two input quaternions aren't on the same half of the hypersphere, flip one and the angle sign + end_v = vector_select(is_angle_negative, vector_neg(end_v), end_v); + cos_half_angle_v = vector_select(is_angle_negative, vector_neg(cos_half_angle_v), cos_half_angle_v); + + // Clamp our half angle cosine + cos_half_angle_v = vector_clamp(cos_half_angle_v, vector_set(-1.0), vector_set(1.0)); + + scalard cos_half_angle = vector_get_x(cos_half_angle_v); + scalard half_angle = scalar_acos(cos_half_angle); + scalard sin_half_angle = scalar_sqrt(scalar_sub(scalar_set(1.0), scalar_mul(cos_half_angle, cos_half_angle))); + scalard inv_sin_half_angle = scalar_reciprocal(sin_half_angle); + + scalard start_contribution_angle = scalar_mul(scalar_sub(scalar_set(1.0), alpha), half_angle); + scalard end_contribution_angle = scalar_mul(alpha, half_angle); + vector4d contribution_angles = vector_set(start_contribution_angle, end_contribution_angle, start_contribution_angle, end_contribution_angle); + vector4d contributions = vector_mul(vector_sin(contribution_angles), inv_sin_half_angle); + vector4d start_contribution = vector_dup_x(contributions); + vector4d end_contribution = vector_dup_y(contributions); + + vector4d result = vector_add(vector_mul(start_v, start_contribution), vector_mul(end_v, end_contribution)); + return vector_to_quat(result); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the spherical interpolation between start and end for a given alpha value. + // See: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm + // Perhaps try this someday: http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/ + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd RTM_SIMD_CALL quat_slerp(const quatd& start, const quatd& end, double alpha) RTM_NO_EXCEPT + { + vector4d start_v = quat_to_vector(start); + vector4d end_v = quat_to_vector(end); + scalard alpha_s = scalar_set(alpha); + + vector4d cos_half_angle_v = vector_dot(start_v, end_v); + mask4d is_angle_negative = vector_less_than(cos_half_angle_v, vector_zero()); + + // If the two input quaternions aren't on the same half of the hypersphere, flip one and the angle sign + end_v = vector_select(is_angle_negative, vector_neg(end_v), end_v); + cos_half_angle_v = vector_select(is_angle_negative, vector_neg(cos_half_angle_v), cos_half_angle_v); + + // Clamp our half angle cosine + cos_half_angle_v = vector_clamp(cos_half_angle_v, vector_set(-1.0), vector_set(1.0)); + + scalard cos_half_angle = vector_get_x(cos_half_angle_v); + scalard half_angle = scalar_acos(cos_half_angle); + scalard sin_half_angle = scalar_sqrt(scalar_sub(scalar_set(1.0), scalar_mul(cos_half_angle, cos_half_angle))); + scalard inv_sin_half_angle = scalar_reciprocal(sin_half_angle); + + scalard start_contribution_angle = scalar_mul(scalar_sub(scalar_set(1.0), alpha_s), half_angle); + scalard end_contribution_angle = scalar_mul(alpha_s, half_angle); + vector4d contribution_angles = vector_set(start_contribution_angle, end_contribution_angle, start_contribution_angle, end_contribution_angle); + vector4d contributions = vector_mul(vector_sin(contribution_angles), inv_sin_half_angle); + vector4d start_contribution = vector_dup_x(contributions); + vector4d end_contribution = vector_dup_y(contributions); + + vector4d result = vector_add(vector_mul(start_v, start_contribution), vector_mul(end_v, end_contribution)); + return vector_to_quat(result); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a component wise negated quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_neg(const quatd& input) RTM_NO_EXCEPT + { + return vector_to_quat(vector_mul(quat_to_vector(input), -1.0)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion logarithm for a 3D rotation. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_rotation_log(const quatd& input) RTM_NO_EXCEPT + { + // The logarithm equation is as follow: + // log(q).xyz = (q.xyz / ||q.xyz||) * acos(q.w / ||q||) + // log(q).w = ln(||q||) + // + // If our quaternion is normalized (a rotation), our output W is always 0.0 since ln(1) = 0.0 + // Our XYZ simplifies as well down to: normalize(q.xyz) * acos(q.w) + // The length of our input XYZ can be calculated either as sqrt(dot(q.xyz, q.xyz)) or + // by taking the sine of the quaternion half angle with sin(acos(q.w)) + // + // If our input rotation is near the identity, we cannot calculate the output XYZ since we would + // divide by zero. If this happens we return a quaternion near zero which should reconstruct as the + // identity with quat_rotation_exp(..). + // + // If our quaternion isn't normalized, more math is required + + const vector4d input_v = quat_to_vector(input); + const scalard input_w = scalar_clamp((scalard)quat_get_w(input), scalar_set(-1.0), scalar_set(1.0)); + const scalard half_angle = scalar_acos(input_w); + const scalard xyz_inv_len = vector_length_reciprocal3(input_v); + vector4d result_xyz = vector_mul(input_v, scalar_mul(xyz_inv_len, half_angle)); + + // If we are near the identity, xyz will be set to our input xyz which should be near zero + // For a true identity input, we'll output zero + const mask4d is_input_near_identity = vector_greater_than(vector_set(input_w), vector_set(1.0 - 1.0E-8)); + result_xyz = vector_select(is_input_near_identity, input_v, result_xyz); + + return vector_to_quat(vector_set_w(result_xyz, 0.0)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion exponential for a 3D rotation. + // The result might not be fully normalized if the input is near zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatd quat_rotation_exp(const quatd& input) RTM_NO_EXCEPT + { + // The exponential equation is as follow: + // exp(q).xyz = exp(q.w) * (q.xyz / ||q.xyz||) * sin(||q.xyz||) + // exp(q).w = exp(q.w) * cos(||q.xyz||) + // + // If our output (normalized) quaternion is to represent a rotation, its logarithm passed as input has a W component equal to 0.0 + // Furthermore our input XYZ length is equal to our rotation half angle + // Since exp(0.0) = 1.0, our equation simplifies as follow: + // exp(q).xyz = (q.xyz / ||q.xyz||) * sin(||q.xyz||) = normalize(q.xyz) * sin(||q.xyz||) + // exp(q).w = cos(||q.xyz||) + // + // If our half angle is 0.0, the whole input is 0,0,0,0 and the result is undefined since we can use any rotation axis. + // When this happens, we return the approximated identity. + // This is easily achieved because cos(||q.xyz||) = cos(0.0) = 1.0 + // + // If our output quaternion does not represent a rotation, more math is required + + const vector4d input_v = quat_to_vector(input); + const scalard input_len = vector_length3(input_v); + const vector4d input_len_v = vector_set(input_len); + const vector4d input_normalized = vector_div(input_v, input_len_v); + const vector4d sincos = scalar_sincos(input_len); + + vector4d result_xyz = vector_mul(input_normalized, vector_dup_x(sincos)); + + // If we are near zero, xyz will be set to our input xyz which should be near zero + // For a true zero input, we'll output the identity + const mask4d is_input_near_zero = vector_less_than(input_len_v, vector_set(1.0E-8)); + result_xyz = vector_select(is_input_near_zero, input_v, result_xyz); + + const vector4d result_w = vector_dup_y(sincos); + const vector4d result = vector_mix(result_xyz, result_w); + return vector_to_quat(result); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Conversion to/from axis/angle/euler + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Returns the rotation axis and rotation angle that make up the input quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void quat_to_axis_angle(const quatd& input, vector4d& out_axis, double& out_angle) RTM_NO_EXCEPT + { + constexpr double epsilon = 1.0E-8; + constexpr double epsilon_squared = epsilon * epsilon; + + const scalard input_w = scalar_clamp((scalard)quat_get_w(input), scalar_set(-1.0), scalar_set(1.0)); + out_angle = scalar_cast(scalar_acos(input_w)) * 2.0; + + const double scale_sq = scalar_max(1.0 - quat_get_w(input) * quat_get_w(input), 0.0); + out_axis = scale_sq >= epsilon_squared ? vector_mul(quat_to_vector(input), vector_set(scalar_sqrt_reciprocal(scale_sq))) : vector_set(1.0, 0.0, 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the rotation axis part of the input quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d quat_get_axis(const quatd& input) RTM_NO_EXCEPT + { + constexpr double epsilon = 1.0E-8; + constexpr double epsilon_squared = epsilon * epsilon; + + const double scale_sq = scalar_max(1.0 - quat_get_w(input) * quat_get_w(input), 0.0); + return scale_sq >= epsilon_squared ? vector_mul(quat_to_vector(input), vector_set(scalar_sqrt_reciprocal(scale_sq))) : vector_set(1.0, 0.0, 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the rotation angle part of the input quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double quat_get_angle(const quatd& input) RTM_NO_EXCEPT + { + const scalard input_w = scalar_clamp((scalard)quat_get_w(input), scalar_set(-1.0), scalar_set(1.0)); + return scalar_cast(scalar_acos(input_w)) * 2.0; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a quaternion from a rotation axis and a rotation angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_from_axis_angle(const vector4d& axis, double angle) RTM_NO_EXCEPT + { + vector4d sincos_ = scalar_sincos(0.5 * angle); + vector4d sin_ = vector_dup_x(sincos_); + scalard cos_ = vector_get_y(sincos_); + + return vector_to_quat(vector_set_w(vector_mul(sin_, axis), cos_)); + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a quaternion from Euler Pitch/Yaw/Roll angles. + // Pitch is around the Y axis (right) + // Yaw is around the Z axis (up) + // Roll is around the X axis (forward) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatd quat_from_euler(double pitch, double yaw, double roll) RTM_NO_EXCEPT + { + double sp; + double sy; + double sr; + double cp; + double cy; + double cr; + + scalar_sincos(pitch * 0.5, sp, cp); + scalar_sincos(yaw * 0.5, sy, cy); + scalar_sincos(roll * 0.5, sr, cr); + + return quat_set(cr * sp * sy - sr * cp * cy, + -cr * sp * cy - sr * cp * sy, + cr * cp * sy - sr * sp * cy, + cr * cp * cy + sr * sp * sy); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Comparisons and masking + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input quaternion does not contain any NaN or Inf, otherwise false. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline bool quat_is_finite(const quatd& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + + const __m128d infinity = _mm_set1_pd(std::numeric_limits::infinity()); + __m128d is_infinity_xy = _mm_cmpeq_pd(abs_input_xy, infinity); + __m128d is_infinity_zw = _mm_cmpeq_pd(abs_input_zw, infinity); + + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + __m128d is_nan_zw = _mm_cmpneq_pd(input.zw, input.zw); + + __m128d is_not_finite_xy = _mm_or_pd(is_infinity_xy, is_nan_xy); + __m128d is_not_finite_zw = _mm_or_pd(is_infinity_zw, is_nan_zw); + __m128d is_not_finite = _mm_or_pd(is_not_finite_xy, is_not_finite_zw); + return _mm_movemask_pd(is_not_finite) == 0; +#else + return scalar_is_finite(quat_get_x(input)) && scalar_is_finite(quat_get_y(input)) && scalar_is_finite(quat_get_z(input)) && scalar_is_finite(quat_get_w(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input quaternion is normalized, otherwise false. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool quat_is_normalized(const quatd& input, double threshold = 0.00001) RTM_NO_EXCEPT + { + double length_squared = quat_length_squared(input); + return scalar_abs(length_squared - 1.0) <= threshold; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the two quaternions are nearly equal component wise, otherwise false. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool quat_near_equal(const quatd& lhs, const quatd& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_all_near_equal(quat_to_vector(lhs), quat_to_vector(rhs), threshold); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input quaternion is nearly equal to the identity quaternion + // by comparing its rotation angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline bool quat_near_identity(const quatd& input, double threshold_angle = 0.00284714461) RTM_NO_EXCEPT + { + // See the quatf version of quat_near_identity for details. + const scalard input_w = quat_get_w(input); + const scalard input_abs_w = scalar_min(scalar_abs(input_w), scalar_set(1.0)); + const double positive_w_angle = scalar_acos(scalar_cast(input_abs_w)) * 2.0; + return positive_w_angle <= threshold_angle; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/quatf.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/quatf.h new file mode 100644 index 00000000..902fe852 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/quatf.h @@ -0,0 +1,1396 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/scalarf.h" +#include "rtm/vector4f.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/memory_utils.h" +#include "rtm/impl/quat_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Setters, getters, and casts + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned quaternion from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_load(const float* input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_loadu_ps(input); +#elif defined(RTM_NEON_INTRINSICS) + return vld1q_f32(input); +#else + return quat_set(input[0], input[1], input[2], input[3]); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned quat from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_load(const float4f* input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_loadu_ps(&input->x); +#elif defined(RTM_NEON_INTRINSICS) + return vld1q_f32(&input->x); +#else + return quat_set(input->x, input->y, input->z, input->w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a vector4 to a quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL vector_to_quat(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) || defined(RTM_NEON_INTRINSICS) + return input; +#else + return quatf{ input.x, input.y, input.z, input.w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a quaternion float64 variant to a float32 variant. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_cast(const quatd& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_shuffle_ps(_mm_cvtpd_ps(input.xy), _mm_cvtpd_ps(input.zw), _MM_SHUFFLE(1, 0, 1, 0)); +#else + return quat_set(float(input.x), float(input.y), float(input.z), float(input.w)); +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_get_x + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(input); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 0); +#else + return input.x; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ input }; + } +#endif + + quatf input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [x] component (real part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_get_x RTM_SIMD_CALL quat_get_x(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_get_x{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_get_y + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_shuffle_ps(input, input, _MM_SHUFFLE(1, 1, 1, 1))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 1); +#else + return input.y; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_shuffle_ps(input, input, _MM_SHUFFLE(1, 1, 1, 1)) }; + } +#endif + + quatf input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [y] component (real part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_get_y RTM_SIMD_CALL quat_get_y(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_get_y{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_get_z + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_shuffle_ps(input, input, _MM_SHUFFLE(2, 2, 2, 2))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 2); +#else + return input.z; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_shuffle_ps(input, input, _MM_SHUFFLE(2, 2, 2, 2)) }; + } +#endif + + quatf input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [z] component (real part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_get_z RTM_SIMD_CALL quat_get_z(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_get_z{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_get_w + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 3, 3, 3))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 3); +#else + return input.w; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 3, 3, 3)) }; + } +#endif + + quatf input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion [w] component (imaginary part). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_get_w RTM_SIMD_CALL quat_get_w(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_get_w{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [x] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_x(quatf_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_move_ss(input, _mm_set_ss(lane_value)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 0); +#else + return quatf{ lane_value, input.y, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [x] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_x(quatf_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { + return _mm_move_ss(input, lane_value.value); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [y] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_y(quatf_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, _mm_set_ss(lane_value), 0x10); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 yxzw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 2, 0, 1)); + const __m128 vxzw = _mm_move_ss(yxzw, _mm_set_ss(lane_value)); + return _mm_shuffle_ps(vxzw, vxzw, _MM_SHUFFLE(3, 2, 0, 1)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 1); +#else + return quatf{ input.x, lane_value, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [y] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_y(quatf_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, lane_value.value, 0x10); +#else + const __m128 yxzw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 2, 0, 1)); + const __m128 vxzw = _mm_move_ss(yxzw, lane_value.value); + return _mm_shuffle_ps(vxzw, vxzw, _MM_SHUFFLE(3, 2, 0, 1)); +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [z] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_z(quatf_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, _mm_set_ss(lane_value), 0x20); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 zyxw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 0, 1, 2)); + const __m128 vyxw = _mm_move_ss(zyxw, _mm_set_ss(lane_value)); + return _mm_shuffle_ps(vyxw, vyxw, _MM_SHUFFLE(3, 0, 1, 2)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 2); +#else + return quatf{ input.x, input.y, lane_value, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [z] component (real part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_z(quatf_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, lane_value.value, 0x20); +#else + const __m128 zyxw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 0, 1, 2)); + const __m128 vyxw = _mm_move_ss(zyxw, lane_value.value); + return _mm_shuffle_ps(vyxw, vyxw, _MM_SHUFFLE(3, 0, 1, 2)); +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [w] component (imaginary part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_w(quatf_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, _mm_set_ss(lane_value), 0x30); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 wyzx = _mm_shuffle_ps(input, input, _MM_SHUFFLE(0, 2, 1, 3)); + const __m128 vyzx = _mm_move_ss(wyzx, _mm_set_ss(lane_value)); + return _mm_shuffle_ps(vyzx, vyzx, _MM_SHUFFLE(0, 2, 1, 3)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 3); +#else + return quatf{ input.x, input.y, input.z, lane_value }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the quaternion [w] component (imaginary part) and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_set_w(quatf_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, lane_value.value, 0x30); +#else + const __m128 wyzx = _mm_shuffle_ps(input, input, _MM_SHUFFLE(0, 2, 1, 3)); + const __m128 vyzx = _mm_move_ss(wyzx, lane_value.value); + return _mm_shuffle_ps(vyzx, vyzx, _MM_SHUFFLE(0, 2, 1, 3)); +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Writes a quaternion to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL quat_store(quatf_arg0 input, float* output) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + _mm_storeu_ps(output, input); +#else + output[0] = quat_get_x(input); + output[1] = quat_get_y(input); + output[2] = quat_get_z(input); + output[3] = quat_get_w(input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a quaternion to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL quat_store(quatf_arg0 input, float4f* output) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + _mm_storeu_ps(&output->x, input); +#else + output->x = quat_get_x(input); + output->y = quat_get_y(input); + output->z = quat_get_z(input); + output->w = quat_get_w(input); +#endif + } + + + + ////////////////////////////////////////////////////////////////////////// + // Arithmetic + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion conjugate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_conjugate(quatf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -0.0F, -0.0F, -0.0F, 0.0F }; + return _mm_xor_ps(input, signs); +#else + // On ARMv7 the scalar version performs best or among the best while on ARM64 it beats the others. + return quat_set(-quat_get_x(input), -quat_get_y(input), -quat_get_z(input), quat_get_w(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two quaternions. + // Note that due to floating point rounding, the result might not be perfectly normalized. + // Multiplication order is as follow: local_to_world = quat_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_mul(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // TODO: Profile this, the accuracy is the same as with SSE2, should be binary exact + constexpr __m128 signs_x = { 1.0F, 1.0F, 1.0F, -1.0F }; + constexpr __m128 signs_y = { 1.0F, -1.0F, 1.0F, 1.0F }; + constexpr __m128 signs_z = { 1.0F, 1.0F, -1.0F, 1.0F }; + constexpr __m128 signs_w = { 1.0F, -1.0F, -1.0F, -1.0F }; + // x = dot(rhs.wxyz, lhs.xwzy * signs_x) + // y = dot(rhs.wxyz, lhs.yzwx * signs_y) + // z = dot(rhs.wxyz, lhs.zyxw * signs_z) + // w = dot(rhs.wxyz, lhs.wxyz * signs_w) + __m128 rhs_wxyz = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(2, 1, 0, 3)); + __m128 lhs_xwzy = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(1, 2, 3, 0)); + __m128 lhs_yzwx = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(0, 3, 2, 1)); + __m128 lhs_zyxw = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(3, 0, 1, 2)); + __m128 lhs_wxyz = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(2, 1, 0, 3)); + __m128 x = _mm_dp_ps(rhs_wxyz, _mm_mul_ps(lhs_xwzy, signs_x), 0xFF); + __m128 y = _mm_dp_ps(rhs_wxyz, _mm_mul_ps(lhs_yzwx, signs_y), 0xFF); + __m128 z = _mm_dp_ps(rhs_wxyz, _mm_mul_ps(lhs_zyxw, signs_z), 0xFF); + __m128 w = _mm_dp_ps(rhs_wxyz, _mm_mul_ps(lhs_wxyz, signs_w), 0xFF); + __m128 xxyy = _mm_shuffle_ps(x, y, _MM_SHUFFLE(0, 0, 0, 0)); + __m128 zzww = _mm_shuffle_ps(z, w, _MM_SHUFFLE(0, 0, 0, 0)); + return _mm_shuffle_ps(xxyy, zzww, _MM_SHUFFLE(2, 0, 2, 0)); +#elif defined(RTM_SSE2_INTRINSICS) + constexpr __m128 control_wzyx = { 0.0F, -0.0F, 0.0F, -0.0F }; + constexpr __m128 control_zwxy = { 0.0F, 0.0F, -0.0F, -0.0F }; + constexpr __m128 control_yxwz = { -0.0F, 0.0F, 0.0F, -0.0F }; + + const __m128 r_xxxx = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 r_yyyy = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 r_zzzz = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(2, 2, 2, 2)); + const __m128 r_wwww = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 3, 3, 3)); + + const __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, lhs); + const __m128 l_wzyx = _mm_shuffle_ps(lhs, lhs,_MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, l_wzyx); + const __m128 l_zwxy = _mm_shuffle_ps(l_wzyx, l_wzyx,_MM_SHUFFLE(2, 3, 0, 1)); + + const __m128 lwrx_nlzrx_lyrx_nlxrx = _mm_xor_ps(lwrx_lzrx_lyrx_lxrx, control_wzyx); + + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, l_zwxy); + const __m128 l_yxwz = _mm_shuffle_ps(l_zwxy, l_zwxy,_MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lzry_lwry_nlxry_nlyry = _mm_xor_ps(lzry_lwry_lxry_lyry, control_zwxy); + + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, l_yxwz); + const __m128 result0 = _mm_add_ps(lxrw_lyrw_lzrw_lwrw, lwrx_nlzrx_lyrx_nlxrx); + + const __m128 nlyrz_lxrz_lwrz_wlzrz = _mm_xor_ps(lyrz_lxrz_lwrz_lzrz, control_yxwz); + const __m128 result1 = _mm_add_ps(lzry_lwry_nlxry_nlyry, nlyrz_lxrz_lwrz_wlzrz); + return _mm_add_ps(result0, result1); +#elif defined(RTM_NEON64_INTRINSICS) + // Use shuffles and negation instead of loading constants and doing mul/xor. + // On ARM64, this is the fastest version. + + // Dispatch rev first, if we can't dual dispatch with neg below, we won't stall it + // [l.y, l.x, l.w, l.z] + const float32x4_t y_x_w_z = vrev64q_f32(lhs); + + // [-l.x, -l.y, -l.z, -l.w] + const float32x4_t neg_lhs = vnegq_f32(lhs); + + // trn([l.y, l.x, l.w, l.z], [-l.x, -l.y, -l.z, -l.w]) = [l.y, -l.x, l.w, -l.z], [l.x, -l.y, l.z, -l.w] + float32x4x2_t y_nx_w_nz__x_ny_z_nw = vtrnq_f32(y_x_w_z, neg_lhs); + + // [l.w, -l.z, l.y, -l.x] + float32x4_t l_wzyx = vcombine_f32(vget_high_f32(y_nx_w_nz__x_ny_z_nw.val[0]), vget_low_f32(y_nx_w_nz__x_ny_z_nw.val[0])); + + // [l.z, l.w, -l.x, -l.y] + float32x4_t l_zwxy = vcombine_f32(vget_high_f32(lhs), vget_low_f32(neg_lhs)); + + // neg([l.w, -l.z, l.y, -l.x]) = [-l.w, l.z, -l.y, l.x] + float32x4_t nw_z_ny_x = vnegq_f32(l_wzyx); + + // [-l.y, l.x, l.w, -l.z] + float32x4_t l_yxwz = vcombine_f32(vget_high_f32(nw_z_ny_x), vget_low_f32(l_wzyx)); + + const float32x2_t r_xy = vget_low_f32(rhs); + const float32x2_t r_zw = vget_high_f32(rhs); + + const float32x4_t lxrw_lyrw_lzrw_lwrw = vmulq_lane_f32(lhs, r_zw, 1); + + #if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result0 = vfmaq_lane_f32(lxrw_lyrw_lzrw_lwrw, l_wzyx, r_xy, 0); + const float32x4_t result1 = vfmaq_lane_f32(result0, l_zwxy, r_xy, 1); + return vfmaq_lane_f32(result1, l_yxwz, r_zw, 0); + #else + const float32x4_t result0 = vmlaq_lane_f32(lxrw_lyrw_lzrw_lwrw, l_wzyx, r_xy, 0); + const float32x4_t result1 = vmlaq_lane_f32(result0, l_zwxy, r_xy, 1); + return vmlaq_lane_f32(result1, l_yxwz, r_zw, 0); + #endif +#else + // On ARMv7, the scalar version is often the fastest. + const float lhs_x = quat_get_x(lhs); + const float lhs_y = quat_get_y(lhs); + const float lhs_z = quat_get_z(lhs); + const float lhs_w = quat_get_w(lhs); + + const float rhs_x = quat_get_x(rhs); + const float rhs_y = quat_get_y(rhs); + const float rhs_z = quat_get_z(rhs); + const float rhs_w = quat_get_w(rhs); + + const float x = (rhs_w * lhs_x) + (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + const float y = (rhs_w * lhs_y) - (rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + const float z = (rhs_w * lhs_z) + (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + const float w = (rhs_w * lhs_w) - (rhs_x * lhs_x) - (rhs_y * lhs_y) - (rhs_z * lhs_z); + + return quat_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a quaternion and a 3D vector, rotating it. + // Multiplication order is as follow: world_position = quat_mul_vector3(local_vector, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL quat_mul_vector3(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128 inv_rotation = quat_conjugate(rotation); + + // Normally when we multiply our inverse rotation quaternion with the input vector as a quaternion with W = 0.0. + // As a result, we can strip the whole part that uses W saving a few instructions. + // Because we have the rotation and its inverse, we also can use them to avoid flipping the signs + // when lining up our SIMD additions. For the first quaternion multiplication, we can avoid 3 XORs by + // doing 2 shuffles instead. The same trick can also be used with the second quaternion multiplication. + // We also don't care about the result W lane but it comes for free. + + // temp = quat_mul(inv_rotation, vector_quat) + __m128 temp; + { + const __m128 rotation_tmp0 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(3, 0, 2, 1)); // r.y, r.z, -r.x, -r.w + const __m128 rotation_tmp1 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(3, 1, 2, 0)); // r.x, r.z, -r.y, -r.w + + const __m128 v_xxxx = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 v_yyyy = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 v_zzzz = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(2, 2, 2, 2)); + + const __m128 rotation_tmp2 = _mm_shuffle_ps(rotation_tmp0, rotation_tmp1, _MM_SHUFFLE(0, 2, 1, 3)); // -r.w, r.z, -r.y, r.x + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(v_xxxx, rotation_tmp2); + + const __m128 rotation_tmp3 = _mm_shuffle_ps(inv_rotation, rotation, _MM_SHUFFLE(1, 0, 3, 2)); // -r.z, -r.w, r.x, r.y + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(v_yyyy, rotation_tmp3); + + const __m128 rotation_tmp4 = _mm_shuffle_ps(rotation_tmp0, rotation_tmp1, _MM_SHUFFLE(1, 3, 2, 0)); // r.y, -r.x, -r.w, r.z + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(v_zzzz, rotation_tmp4); + + temp = _mm_add_ps(_mm_add_ps(lwrx_lzrx_lyrx_lxrx, lzry_lwry_lxry_lyry), lyrz_lxrz_lwrz_lzrz); + } + + // result = quat_mul(temp, rotation) + { + const __m128 rotation_tmp0 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(2, 0, 2, 0)); // r.x, r.z, -r.x, -r.z + + __m128 r_xxxx = _mm_shuffle_ps(rotation_tmp0, rotation_tmp0, _MM_SHUFFLE(2, 0, 2, 0)); // r.x, -r.x, r.x, -r.x + __m128 r_yyyy = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(1, 1, 1, 1)); // r.y, r.y, -r.y, -r.y + __m128 r_zzzz = _mm_shuffle_ps(rotation_tmp0, rotation_tmp0, _MM_SHUFFLE(3, 1, 1, 3)); // -r.z, r.z, r.z, -r.z + __m128 r_wwww = _mm_shuffle_ps(rotation, rotation, _MM_SHUFFLE(3, 3, 3, 3)); // r.w, r.w, r.w, r.w + + __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, temp); + + __m128 t_wzyx = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0, 1, 2, 3)); + __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, t_wzyx); + + __m128 t_zwxy = _mm_shuffle_ps(t_wzyx, t_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, t_zwxy); + + __m128 t_yxwz = _mm_shuffle_ps(t_zwxy, t_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, t_yxwz); + + __m128 result0 = _mm_add_ps(lxrw_lyrw_lzrw_lwrw, lwrx_lzrx_lyrx_lxrx); + __m128 result1 = _mm_add_ps(lzry_lwry_lxry_lyry, lyrz_lxrz_lwrz_lzrz); + return _mm_add_ps(result0, result1); + } +#elif defined(RTM_NEON_INTRINSICS) + // On ARMv7 and ARM64, the scalar version is often the fastest. + const float32x4_t n_rotation = vnegq_f32(rotation); + + // temp = quat_mul(inv_rotation, vector_quat) + float temp_x; + float temp_y; + float temp_z; + float temp_w; + { + const float lhs_x = quat_get_x(n_rotation); + const float lhs_y = quat_get_y(n_rotation); + const float lhs_z = quat_get_z(n_rotation); + const float lhs_w = quat_get_w(rotation); + + const float rhs_x = quat_get_x(vector); + const float rhs_y = quat_get_y(vector); + const float rhs_z = quat_get_z(vector); + + temp_x = (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + temp_y = -(rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + temp_z = (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + temp_w = -(rhs_x * lhs_x) - (rhs_y * lhs_y) - (rhs_z * lhs_z); + } + + // result = quat_mul(temp, rotation) + { + const float lhs_x = temp_x; + const float lhs_y = temp_y; + const float lhs_z = temp_z; + const float lhs_w = temp_w; + + const float rhs_x = quat_get_x(rotation); + const float rhs_y = quat_get_y(rotation); + const float rhs_z = quat_get_z(rotation); + const float rhs_w = quat_get_w(rotation); + + const float x = (rhs_w * lhs_x) + (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + const float y = (rhs_w * lhs_y) - (rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + const float z = (rhs_w * lhs_z) + (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + + return vector_set(x, y, z, z); + } +#else + quatf vector_quat = quat_set_w(vector_to_quat(vector), 0.0f); + quatf inv_rotation = quat_conjugate(rotation); + return quat_to_vector(quat_mul(quat_mul(inv_rotation, vector_quat), rotation)); +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_dot + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return _mm_cvtss_f32(_mm_dp_ps(lhs, rhs, 0xFF)); +#elif defined(RTM_SSE2_INTRINSICS) + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + return _mm_cvtss_f32(x2y2z2w2_0_0_0); +#else + return (quat_get_x(lhs) * quat_get_x(rhs)) + (quat_get_y(lhs) * quat_get_y(rhs)) + (quat_get_z(lhs) * quat_get_z(rhs)) + (quat_get_w(lhs) * quat_get_w(rhs)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return scalarf{ _mm_cvtss_f32(_mm_dp_ps(lhs, rhs, 0xFF)) }; +#else + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + return scalarf{ x2y2z2w2_0_0_0 }; +#endif + } +#endif + + quatf lhs; + quatf rhs; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Quaternion dot product: lhs . rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_dot RTM_SIMD_CALL quat_dot(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_dot{ lhs, rhs }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the squared length/norm of the quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_dot RTM_SIMD_CALL quat_length_squared(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_dot{ input, input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_length + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const scalarf len_sq = quat_length_squared(input); + return scalar_cast(scalar_sqrt(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const scalarf len_sq = quat_length_squared(input); + return scalar_sqrt(len_sq); + } +#endif + + quatf input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the length/norm of the quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_length RTM_SIMD_CALL quat_length(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_length{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct quatf_quat_length_reciprocal + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const scalarf len_sq = quat_length_squared(input); + return scalar_cast(scalar_sqrt_reciprocal(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const scalarf len_sq = quat_length_squared(input); + return scalar_sqrt_reciprocal(len_sq); + } +#endif + + quatf input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal length/norm of the quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::quatf_quat_length_reciprocal RTM_SIMD_CALL quat_length_reciprocal(quatf_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::quatf_quat_length_reciprocal{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a normalized quaternion. + // Note that if the input quaternion is invalid (pure zero or with NaN/Inf), + // the result is undefined. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_normalize(quatf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // We first calculate the dot product to get the length squared: dot(input, input) + __m128 x2_y2_z2_w2 = _mm_mul_ps(input, input); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + + // Keep the dot product result as a scalar within the first lane, it is faster to + // calculate the reciprocal square root of a single lane VS all 4 lanes + __m128 dot = x2y2z2w2_0_0_0; + + // Calculate the reciprocal square root to get the inverse length of our vector + // Perform two passes of Newton-Raphson iteration on the hardware estimate + __m128 half = _mm_set_ss(0.5F); + __m128 input_half_v = _mm_mul_ss(dot, half); + __m128 x0 = _mm_rsqrt_ss(dot); + + // First iteration + __m128 x1 = _mm_mul_ss(x0, x0); + x1 = _mm_sub_ss(half, _mm_mul_ss(input_half_v, x1)); + x1 = _mm_add_ss(_mm_mul_ss(x0, x1), x0); + + // Second iteration + __m128 x2 = _mm_mul_ss(x1, x1); + x2 = _mm_sub_ss(half, _mm_mul_ss(input_half_v, x2)); + x2 = _mm_add_ss(_mm_mul_ss(x1, x2), x1); + + // Broadcast the vector length reciprocal to all 4 lanes in order to multiply it with the vector + __m128 inv_len = _mm_shuffle_ps(x2, x2, _MM_SHUFFLE(0, 0, 0, 0)); + + // Multiply the rotation by it's inverse length in order to normalize it + return _mm_mul_ps(input, inv_len); +#elif defined (RTM_NEON_INTRINSICS) + // Use sqrt/div/mul to normalize because the sqrt/div are faster than rsqrt + float inv_len = 1.0F / scalar_sqrt(vector_length_squared(input)); + return vector_mul(input, inv_len); +#else + // Reciprocal is more accurate to normalize with + float inv_len = quat_length_reciprocal(input); + return vector_to_quat(vector_mul(quat_to_vector(input), inv_len)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation between start and end for a given alpha value. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when 'alpha' is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // Note that if 'start' and 'end' are on the opposite ends of the hypersphere, 'end' is negated + // before we interpolate. As such, when 'alpha' is 1.0, either 'end' or its negated equivalent + // is returned. Furthermore, if 'start' and 'end' aren't exactly normalized, the result might + // not match exactly when 'alpha' is 0.0 or 1.0 because we normalize the resulting quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_lerp(quatf_arg0 start, quatf_arg1 end, float alpha) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Calculate the vector4 dot product: dot(start, end) + __m128 dot; +#if defined(RTM_SSE4_INTRINSICS) + // The dpps instruction isn't as accurate but we don't care here, we only need the sign of the + // dot product. If both rotations are on opposite ends of the hypersphere, the result will be + // very negative. If we are on the edge, the rotations are nearly opposite but not quite which + // means that the linear interpolation here will have terrible accuracy to begin with. It is designed + // for interpolating rotations that are reasonably close together. The bias check is mainly necessary + // because the W component is often kept positive which flips the sign. + // Using the dpps instruction reduces the number of registers that we need and helps the function get + // inlined. + dot = _mm_dp_ps(start, end, 0xFF); +#else + { + __m128 x2_y2_z2_w2 = _mm_mul_ps(start, end); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + // Shuffle the dot product to all SIMD lanes, there is no _mm_and_ss and loading + // the constant from memory with the 'and' instruction is faster, it uses fewer registers + // and fewer instructions + dot = _mm_shuffle_ps(x2y2z2w2_0_0_0, x2y2z2w2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0)); + } +#endif + + // Calculate the bias, if the dot product is positive or zero, there is no bias + // but if it is negative, we want to flip the 'end' rotation XYZW components + __m128 bias = _mm_and_ps(dot, _mm_set_ps1(-0.0F)); + + // Lerp the rotation after applying the bias + // ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias)) + __m128 alpha_ = _mm_set_ps1(alpha); + __m128 interpolated_rotation = _mm_add_ps(_mm_sub_ps(start, _mm_mul_ps(alpha_, start)), _mm_mul_ps(alpha_, _mm_xor_ps(end, bias))); + + // Now we need to normalize the resulting rotation. We first calculate the + // dot product to get the length squared: dot(interpolated_rotation, interpolated_rotation) + __m128 x2_y2_z2_w2 = _mm_mul_ps(interpolated_rotation, interpolated_rotation); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + + // Keep the dot product result as a scalar within the first lane, it is faster to + // calculate the reciprocal square root of a single lane VS all 4 lanes + dot = x2y2z2w2_0_0_0; + + // Calculate the reciprocal square root to get the inverse length of our vector + // Perform two passes of Newton-Raphson iteration on the hardware estimate + __m128 half = _mm_set_ss(0.5F); + __m128 input_half_v = _mm_mul_ss(dot, half); + __m128 x0 = _mm_rsqrt_ss(dot); + + // First iteration + __m128 x1 = _mm_mul_ss(x0, x0); + x1 = _mm_sub_ss(half, _mm_mul_ss(input_half_v, x1)); + x1 = _mm_add_ss(_mm_mul_ss(x0, x1), x0); + + // Second iteration + __m128 x2 = _mm_mul_ss(x1, x1); + x2 = _mm_sub_ss(half, _mm_mul_ss(input_half_v, x2)); + x2 = _mm_add_ss(_mm_mul_ss(x1, x2), x1); + + // Broadcast the vector length reciprocal to all 4 lanes in order to multiply it with the vector + __m128 inv_len = _mm_shuffle_ps(x2, x2, _MM_SHUFFLE(0, 0, 0, 0)); + + // Multiply the rotation by it's inverse length in order to normalize it + return _mm_mul_ps(interpolated_rotation, inv_len); +#elif defined (RTM_NEON64_INTRINSICS) + // On ARM64 with NEON, we load 1.0 once and use it twice which is faster than + // using a AND/XOR with the bias (same number of instructions) + float dot = vector_dot(start, end); + float bias = dot >= 0.0F ? 1.0F : -1.0F; + // ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias)) + vector4f interpolated_rotation = vector_mul_add(vector_mul(end, bias), alpha, vector_neg_mul_sub(start, alpha, start)); + // Use sqrt/div/mul to normalize because the sqrt/div are faster than rsqrt + float inv_len = 1.0F / scalar_sqrt(vector_length_squared(interpolated_rotation)); + return vector_mul(interpolated_rotation, inv_len); +#elif defined(RTM_NEON_INTRINSICS) + // Calculate the vector4 dot product: dot(start, end) + float32x4_t x2_y2_z2_w2 = vmulq_f32(start, end); + float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2); + float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2); + float32x2_t x2z2_y2w2 = vadd_f32(x2_y2, z2_w2); + float32x2_t x2y2z2w2 = vpadd_f32(x2z2_y2w2, x2z2_y2w2); + + // Calculate the bias, if the dot product is positive or zero, there is no bias + // but if it is negative, we want to flip the 'end' rotation XYZW components + // On ARM-v7-A, the AND/XOR trick is faster than the cmp/fsel + uint32x2_t bias = vand_u32(vreinterpret_u32_f32(x2y2z2w2), vdup_n_u32(0x80000000)); + + // Lerp the rotation after applying the bias + // ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias)) + float32x4_t end_biased = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(end), vcombine_u32(bias, bias))); + float32x4_t interpolated_rotation = vmlaq_n_f32(vmlsq_n_f32(start, start, alpha), end_biased, alpha); + + // Now we need to normalize the resulting rotation. We first calculate the + // dot product to get the length squared: dot(interpolated_rotation, interpolated_rotation) + x2_y2_z2_w2 = vmulq_f32(interpolated_rotation, interpolated_rotation); + x2_y2 = vget_low_f32(x2_y2_z2_w2); + z2_w2 = vget_high_f32(x2_y2_z2_w2); + x2z2_y2w2 = vadd_f32(x2_y2, z2_w2); + x2y2z2w2 = vpadd_f32(x2z2_y2w2, x2z2_y2w2); + + float dot = vget_lane_f32(x2y2z2w2, 0); + + // Use sqrt/div/mul to normalize because the sqrt/div are faster than rsqrt + float inv_len = 1.0F / scalar_sqrt(dot); + return vector_mul(interpolated_rotation, inv_len); +#else + // To ensure we take the shortest path, we apply a bias if the dot product is negative + vector4f start_vector = quat_to_vector(start); + vector4f end_vector = quat_to_vector(end); + float dot = vector_dot(start_vector, end_vector); + float bias = dot >= 0.0F ? 1.0F : -1.0F; + // ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias)) + vector4f interpolated_rotation = vector_mul_add(vector_mul(end_vector, bias), alpha, vector_neg_mul_sub(start_vector, alpha, start_vector)); + return quat_normalize(vector_to_quat(interpolated_rotation)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation between start and end for a given alpha value. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when 'alpha' is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // Note that if 'start' and 'end' are on the opposite ends of the hypersphere, 'end' is negated + // before we interpolate. As such, when 'alpha' is 1.0, either 'end' or its negated equivalent + // is returned. Furthermore, if 'start' and 'end' aren't exactly normalized, the result might + // not match exactly when 'alpha' is 0.0 or 1.0 because we normalize the resulting quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_lerp(quatf_arg0 start, quatf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + // Calculate the vector4 dot product: dot(start, end) + __m128 dot; +#if defined(RTM_SSE4_INTRINSICS) + // The dpps instruction isn't as accurate but we don't care here, we only need the sign of the + // dot product. If both rotations are on opposite ends of the hypersphere, the result will be + // very negative. If we are on the edge, the rotations are nearly opposite but not quite which + // means that the linear interpolation here will have terrible accuracy to begin with. It is designed + // for interpolating rotations that are reasonably close together. The bias check is mainly necessary + // because the W component is often kept positive which flips the sign. + // Using the dpps instruction reduces the number of registers that we need and helps the function get + // inlined. + dot = _mm_dp_ps(start, end, 0xFF); +#else + { + __m128 x2_y2_z2_w2 = _mm_mul_ps(start, end); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + // Shuffle the dot product to all SIMD lanes, there is no _mm_and_ss and loading + // the constant from memory with the 'and' instruction is faster, it uses fewer registers + // and fewer instructions + dot = _mm_shuffle_ps(x2y2z2w2_0_0_0, x2y2z2w2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0)); + } +#endif + + // Calculate the bias, if the dot product is positive or zero, there is no bias + // but if it is negative, we want to flip the 'end' rotation XYZW components + __m128 bias = _mm_and_ps(dot, _mm_set_ps1(-0.0F)); + + // Lerp the rotation after applying the bias + // ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias)) + __m128 alpha_ = _mm_shuffle_ps(alpha.value, alpha.value, _MM_SHUFFLE(0, 0, 0, 0)); + __m128 interpolated_rotation = _mm_add_ps(_mm_sub_ps(start, _mm_mul_ps(alpha_, start)), _mm_mul_ps(alpha_, _mm_xor_ps(end, bias))); + + // Now we need to normalize the resulting rotation. We first calculate the + // dot product to get the length squared: dot(interpolated_rotation, interpolated_rotation) + __m128 x2_y2_z2_w2 = _mm_mul_ps(interpolated_rotation, interpolated_rotation); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + + // Keep the dot product result as a scalar within the first lane, it is faster to + // calculate the reciprocal square root of a single lane VS all 4 lanes + dot = x2y2z2w2_0_0_0; + + // Calculate the reciprocal square root to get the inverse length of our vector + // Perform two passes of Newton-Raphson iteration on the hardware estimate + __m128 half = _mm_set_ss(0.5F); + __m128 input_half_v = _mm_mul_ss(dot, half); + __m128 x0 = _mm_rsqrt_ss(dot); + + // First iteration + __m128 x1 = _mm_mul_ss(x0, x0); + x1 = _mm_sub_ss(half, _mm_mul_ss(input_half_v, x1)); + x1 = _mm_add_ss(_mm_mul_ss(x0, x1), x0); + + // Second iteration + __m128 x2 = _mm_mul_ss(x1, x1); + x2 = _mm_sub_ss(half, _mm_mul_ss(input_half_v, x2)); + x2 = _mm_add_ss(_mm_mul_ss(x1, x2), x1); + + // Broadcast the vector length reciprocal to all 4 lanes in order to multiply it with the vector + __m128 inv_len = _mm_shuffle_ps(x2, x2, _MM_SHUFFLE(0, 0, 0, 0)); + + // Multiply the rotation by it's inverse length in order to normalize it + return _mm_mul_ps(interpolated_rotation, inv_len); + } +#endif + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the spherical interpolation between start and end for a given alpha value. + // See: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm + // Perhaps try this someday: http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/ + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_slerp(quatf_arg0 start, quatf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + vector4f start_v = quat_to_vector(start); + vector4f end_v = quat_to_vector(end); + + vector4f cos_half_angle_v = vector_dot(start_v, end_v); + mask4f is_angle_negative = vector_less_than(cos_half_angle_v, vector_zero()); + + // If the two input quaternions aren't on the same half of the hypersphere, flip one and the angle sign + end_v = vector_select(is_angle_negative, vector_neg(end_v), end_v); + cos_half_angle_v = vector_select(is_angle_negative, vector_neg(cos_half_angle_v), cos_half_angle_v); + + // Clamp our half angle cosine + cos_half_angle_v = vector_clamp(cos_half_angle_v, vector_set(-1.0F), vector_set(1.0F)); + + scalarf cos_half_angle = vector_get_x(cos_half_angle_v); + scalarf half_angle = scalar_acos(cos_half_angle); + scalarf sin_half_angle = scalar_sqrt(scalar_sub(scalar_set(1.0F), scalar_mul(cos_half_angle, cos_half_angle))); + scalarf inv_sin_half_angle = scalar_reciprocal(sin_half_angle); + + scalarf start_contribution_angle = scalar_mul(scalar_sub(scalar_set(1.0F), alpha), half_angle); + scalarf end_contribution_angle = scalar_mul(alpha, half_angle); + vector4f contribution_angles = vector_set(start_contribution_angle, end_contribution_angle, start_contribution_angle, end_contribution_angle); + vector4f contributions = vector_mul(vector_sin(contribution_angles), inv_sin_half_angle); + vector4f start_contribution = vector_dup_x(contributions); + vector4f end_contribution = vector_dup_y(contributions); + + vector4f result = vector_add(vector_mul(start_v, start_contribution), vector_mul(end_v, end_contribution)); + return vector_to_quat(result); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the spherical interpolation between start and end for a given alpha value. + // See: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm + // Perhaps try this someday: http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/ + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_slerp(quatf_arg0 start, quatf_arg1 end, float alpha) RTM_NO_EXCEPT + { + vector4f start_v = quat_to_vector(start); + vector4f end_v = quat_to_vector(end); + scalarf alpha_s = scalar_set(alpha); + + vector4f cos_half_angle_v = vector_dot(start_v, end_v); + mask4f is_angle_negative = vector_less_than(cos_half_angle_v, vector_zero()); + + // If the two input quaternions aren't on the same half of the hypersphere, flip one and the angle sign + end_v = vector_select(is_angle_negative, vector_neg(end_v), end_v); + cos_half_angle_v = vector_select(is_angle_negative, vector_neg(cos_half_angle_v), cos_half_angle_v); + + // Clamp our half angle cosine + cos_half_angle_v = vector_clamp(cos_half_angle_v, vector_set(-1.0F), vector_set(1.0F)); + + scalarf cos_half_angle = vector_get_x(cos_half_angle_v); + scalarf half_angle = scalar_acos(cos_half_angle); + scalarf sin_half_angle = scalar_sqrt(scalar_sub(scalar_set(1.0F), scalar_mul(cos_half_angle, cos_half_angle))); + scalarf inv_sin_half_angle = scalar_reciprocal(sin_half_angle); + + scalarf start_contribution_angle = scalar_mul(scalar_sub(scalar_set(1.0F), alpha_s), half_angle); + scalarf end_contribution_angle = scalar_mul(alpha_s, half_angle); + vector4f contribution_angles = vector_set(start_contribution_angle, end_contribution_angle, start_contribution_angle, end_contribution_angle); + vector4f contributions = vector_mul(vector_sin(contribution_angles), inv_sin_half_angle); + vector4f start_contribution = vector_dup_x(contributions); + vector4f end_contribution = vector_dup_y(contributions); + + vector4f result = vector_add(vector_mul(start_v, start_contribution), vector_mul(end_v, end_contribution)); + return vector_to_quat(result); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a component wise negated quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_neg(quatf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -0.0F, -0.0F, -0.0F, -0.0F }; + return _mm_xor_ps(input, signs); +#elif defined(RTM_NEON_INTRINSICS) + return vnegq_f32(input); +#else + return vector_to_quat(vector_mul(quat_to_vector(input), -1.0F)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion logarithm for a 3D rotation. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_rotation_log(quatf_arg0 input) RTM_NO_EXCEPT + { + // The logarithm equation is as follow: + // log(q).xyz = (q.xyz / ||q.xyz||) * acos(q.w / ||q||) + // log(q).w = ln(||q||) + // + // If our quaternion is normalized (a rotation), our output W is always 0.0 since ln(1) = 0.0 + // Our XYZ simplifies as well down to: normalize(q.xyz) * acos(q.w) + // The length of our input XYZ can be calculated either as sqrt(dot(q.xyz, q.xyz)) or + // by taking the sine of the quaternion half angle with sin(acos(q.w)) + // + // If our input rotation is near the identity, we cannot calculate the output XYZ since we would + // divide by zero. If this happens we return a quaternion near zero which should reconstruct as the + // identity with quat_rotation_exp(..). + // + // If our quaternion isn't normalized, more math is required + + const vector4f input_v = quat_to_vector(input); + const scalarf input_w = scalar_clamp((scalarf)quat_get_w(input), scalar_set(-1.0F), scalar_set(1.0F)); + const scalarf half_angle = scalar_acos(input_w); + const scalarf xyz_inv_len = vector_length_reciprocal3(input_v); + vector4f result_xyz = vector_mul(input_v, scalar_mul(xyz_inv_len, half_angle)); + + // If we are near the identity, xyz will be set to our input xyz which should be near zero + // For a true identity input, we'll output zero + const mask4f is_input_near_identity = vector_greater_than(vector_set(input_w), vector_set(1.0F - 1.0E-6F)); + result_xyz = vector_select(is_input_near_identity, input_v, result_xyz); + + return vector_to_quat(vector_set_w(result_xyz, 0.0F)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the quaternion exponential for a 3D rotation. + // The result might not be fully normalized if the input is near zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE quatf RTM_SIMD_CALL quat_rotation_exp(quatf_arg0 input) RTM_NO_EXCEPT + { + // The exponential equation is as follow: + // exp(q).xyz = exp(q.w) * (q.xyz / ||q.xyz||) * sin(||q.xyz||) + // exp(q).w = exp(q.w) * cos(||q.xyz||) + // + // If our output (normalized) quaternion is to represent a rotation, its logarithm passed as input has a W component equal to 0.0 + // Furthermore our input XYZ length is equal to our rotation half angle + // Since exp(0.0) = 1.0, our equation simplifies as follow: + // exp(q).xyz = (q.xyz / ||q.xyz||) * sin(||q.xyz||) = normalize(q.xyz) * sin(||q.xyz||) + // exp(q).w = cos(||q.xyz||) + // + // If our half angle is 0.0, the whole input is 0,0,0,0 and the result is undefined since we can use any rotation axis. + // When this happens, we return the approximated identity. + // This is easily achieved because cos(||q.xyz||) = cos(0.0) = 1.0 + // + // If our output quaternion does not represent a rotation, more math is required + + const vector4f input_v = quat_to_vector(input); + const scalarf input_len = vector_length3(input_v); + const vector4f input_len_v = vector_set(input_len); + const vector4f input_normalized = vector_div(input_v, input_len_v); + const vector4f sincos = scalar_sincos(input_len); + + vector4f result_xyz = vector_mul(input_normalized, vector_dup_x(sincos)); + + // If we are near zero, xyz will be set to our input xyz which should be near zero + // For a true zero input, we'll output the identity + const mask4f is_input_near_zero = vector_less_than(input_len_v, vector_set(1.0E-6F)); + result_xyz = vector_select(is_input_near_zero, input_v, result_xyz); + + const vector4f result_w = vector_dup_y(sincos); + const vector4f result = vector_mix(result_xyz, result_w); + return vector_to_quat(result); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Conversion to/from axis/angle/euler + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Returns the rotation axis and rotation angle that make up the input quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void RTM_SIMD_CALL quat_to_axis_angle(quatf_arg0 input, vector4f& out_axis, float& out_angle) RTM_NO_EXCEPT + { + constexpr float epsilon = 1.0E-8F; + constexpr float epsilon_squared = epsilon * epsilon; + + const scalarf input_w = scalar_clamp((scalarf)quat_get_w(input), scalar_set(-1.0F), scalar_set(1.0F)); + out_angle = scalar_cast(scalar_acos(input_w)) * 2.0F; + + const float scale_sq = scalar_max(1.0F - quat_get_w(input) * quat_get_w(input), 0.0F); + out_axis = scale_sq >= epsilon_squared ? vector_mul(quat_to_vector(input), vector_set(scalar_sqrt_reciprocal(scale_sq))) : vector_set(1.0F, 0.0F, 0.0F); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the rotation axis part of the input quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL quat_get_axis(quatf_arg0 input) RTM_NO_EXCEPT + { + constexpr float epsilon = 1.0E-8F; + constexpr float epsilon_squared = epsilon * epsilon; + + const float scale_sq = scalar_max(1.0F - quat_get_w(input) * quat_get_w(input), 0.0F); + return scale_sq >= epsilon_squared ? vector_mul(quat_to_vector(input), vector_set(scalar_sqrt_reciprocal(scale_sq))) : vector_set(1.0F, 0.0F, 0.0F); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the rotation angle part of the input quaternion. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float RTM_SIMD_CALL quat_get_angle(quatf_arg0 input) RTM_NO_EXCEPT + { + const scalarf input_w = scalar_clamp((scalarf)quat_get_w(input), scalar_set(-1.0F), scalar_set(1.0F)); + return scalar_cast(scalar_acos(input_w)) * 2.0F; + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a quaternion from a rotation axis and a rotation angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_from_axis_angle(vector4f_arg0 axis, float angle) RTM_NO_EXCEPT + { + vector4f sincos_ = scalar_sincos(0.5F * angle); + vector4f sin_ = vector_dup_x(sincos_); + scalarf cos_ = vector_get_y(sincos_); + + return vector_to_quat(vector_set_w(vector_mul(sin_, axis), cos_)); + } + + ////////////////////////////////////////////////////////////////////////// + // Creates a quaternion from Euler Pitch/Yaw/Roll angles. + // Pitch is around the Y axis (right) + // Yaw is around the Z axis (up) + // Roll is around the X axis (forward) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline quatf RTM_SIMD_CALL quat_from_euler(float pitch, float yaw, float roll) RTM_NO_EXCEPT + { + float sp; + float sy; + float sr; + float cp; + float cy; + float cr; + + scalar_sincos(pitch * 0.5F, sp, cp); + scalar_sincos(yaw * 0.5F, sy, cy); + scalar_sincos(roll * 0.5F, sr, cr); + + return quat_set(cr * sp * sy - sr * cp * cy, + -cr * sp * cy - sr * cp * sy, + cr * cp * sy - sr * sp * cy, + cr * cp * cy + sr * sp * sy); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Comparisons and masking + ////////////////////////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input quaternion does not contain any NaN or Inf, otherwise false. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL quat_is_finite(quatf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + + const __m128 infinity = _mm_set_ps1(std::numeric_limits::infinity()); + __m128 is_infinity = _mm_cmpeq_ps(abs_input, infinity); + + __m128 is_nan = _mm_cmpneq_ps(input, input); + + __m128 is_not_finite = _mm_or_ps(is_infinity, is_nan); + return _mm_movemask_ps(is_not_finite) == 0; +#else + return scalar_is_finite(quat_get_x(input)) && scalar_is_finite(quat_get_y(input)) && scalar_is_finite(quat_get_z(input)) && scalar_is_finite(quat_get_w(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input quaternion is normalized, otherwise false. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL quat_is_normalized(quatf_arg0 input, float threshold = 0.00001F) RTM_NO_EXCEPT + { + float length_squared = quat_length_squared(input); + return scalar_abs(length_squared - 1.0F) <= threshold; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the two quaternions are nearly equal component wise, otherwise false. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL quat_near_equal(quatf_arg0 lhs, quatf_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_all_near_equal(quat_to_vector(lhs), quat_to_vector(rhs), threshold); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input quaternion is nearly equal to the identity quaternion + // by comparing its rotation angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline bool RTM_SIMD_CALL quat_near_identity(quatf_arg0 input, float threshold_angle = 0.00284714461F) RTM_NO_EXCEPT + { + // Because of floating point precision, we cannot represent very small rotations. + // The closest float to 1.0 that is not 1.0 itself yields: + // acos(0.99999994f) * 2.0f = 0.000690533954 rad + // + // An error threshold of 1.e-6f is used by default. + // acos(1.f - 1.e-6f) * 2.0f = 0.00284714461 rad + // acos(1.f - 1.e-7f) * 2.0f = 0.00097656250 rad + // + // We don't really care about the angle value itself, only if it's close to 0. + // This will happen whenever quat.w is close to 1.0. + // If the quat.w is close to -1.0, the angle will be near 2*PI which is close to + // a negative 0 rotation. By forcing quat.w to be positive, we'll end up with + // the shortest path. + const scalarf input_w = quat_get_w(input); + const scalarf input_abs_w = scalar_min(scalar_abs(input_w), scalar_set(1.0F)); + const float positive_w_angle = scalar_acos(scalar_cast(input_abs_w)) * 2.0F; + return positive_w_angle <= threshold_angle; + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/qvvd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/qvvd.h new file mode 100644 index 00000000..71725ab4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/qvvd.h @@ -0,0 +1,264 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/quatd.h" +#include "rtm/vector4d.h" +#include "rtm/matrix3x4d.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/qvv_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Casts a QVV transform float32 variant to a float64 variant. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_cast(const qvvf& input) RTM_NO_EXCEPT + { + return qvvd{ quat_cast(input.rotation), vector_cast(input.translation), vector_cast(input.scale) }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two QVV transforms. + // Multiplication order is as follow: local_to_world = qvv_mul(local_to_object, object_to_world) + // NOTE: When scale is present, multiplication will not properly handle skew/shear, + // use affine matrices if you have issues. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvd qvv_mul(const qvvd& lhs, const qvvd& rhs) RTM_NO_EXCEPT + { + const vector4d min_scale = vector_min(lhs.scale, rhs.scale); + const vector4d scale = vector_mul(lhs.scale, rhs.scale); + + if (vector_any_less_than3(min_scale, vector_zero())) + { + // If we have negative scale, we go through a matrix + const matrix3x4d lhs_mtx = matrix_from_qvv(lhs); + const matrix3x4d rhs_mtx = matrix_from_qvv(rhs); + matrix3x4d result_mtx = matrix_mul(lhs_mtx, rhs_mtx); + result_mtx = matrix_remove_scale(result_mtx); + + const vector4d sign = vector_sign(scale); + result_mtx.x_axis = vector_mul(result_mtx.x_axis, vector_dup_x(sign)); + result_mtx.y_axis = vector_mul(result_mtx.y_axis, vector_dup_y(sign)); + result_mtx.z_axis = vector_mul(result_mtx.z_axis, vector_dup_z(sign)); + + const quatd rotation = quat_from_matrix(result_mtx); + const vector4d translation = result_mtx.w_axis; + return qvv_set(rotation, translation, scale); + } + else + { + const quatd rotation = quat_mul(lhs.rotation, rhs.rotation); + const vector4d translation = vector_add(quat_mul_vector3(vector_mul(lhs.translation, rhs.scale), rhs.rotation), rhs.translation); + return qvv_set(rotation, translation, scale); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two QVV transforms ignoring 3D scale. + // The resulting QVV transform will have the LHS scale. + // Multiplication order is as follow: local_to_world = qvv_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvd qvv_mul_no_scale(const qvvd& lhs, const qvvd& rhs) RTM_NO_EXCEPT + { + const quatd rotation = quat_mul(lhs.rotation, rhs.rotation); + const vector4d translation = vector_add(quat_mul_vector3(lhs.translation, rhs.rotation), rhs.translation); + return qvv_set(rotation, translation, lhs.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a QVV transform and a 3D point. + // Multiplication order is as follow: world_position = qvv_mul_point3(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d qvv_mul_point3(const vector4d& point, const qvvd& qvv) RTM_NO_EXCEPT + { + return vector_add(quat_mul_vector3(vector_mul(point, qvv.scale), qvv.rotation), qvv.translation); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a QVV transform and a 3D point ignoring 3D scale. + // Multiplication order is as follow: world_position = qvv_mul_point3_no_scale(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d qvv_mul_point3_no_scale(const vector4d& point, const qvvd& qvv) RTM_NO_EXCEPT + { + return vector_add(quat_mul_vector3(point, qvv.rotation), qvv.translation); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the inverse of the input QVV transform. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvd qvv_inverse(const qvvd& input) RTM_NO_EXCEPT + { + const quatd inv_rotation = quat_conjugate(input.rotation); + const vector4d inv_scale = vector_reciprocal(input.scale); + const vector4d inv_translation = vector_neg(quat_mul_vector3(vector_mul(input.translation, inv_scale), inv_rotation)); + return qvv_set(inv_rotation, inv_translation, inv_scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the inverse of the input QVV transform ignoring 3D scale. + // The resulting QVV transform will have the input scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvd qvv_inverse_no_scale(const qvvd& input) RTM_NO_EXCEPT + { + const quatd inv_rotation = quat_conjugate(input.rotation); + const vector4d inv_translation = vector_neg(quat_mul_vector3(input.translation, inv_rotation)); + return qvv_set(inv_rotation, inv_translation, input.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a QVV transforms with the rotation part normalized. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_normalize(const qvvd& input) RTM_NO_EXCEPT + { + const quatd rotation = quat_normalize(input.rotation); + return qvv_set(rotation, input.translation, input.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_lerp(const qvvd& start, const qvvd& end, double alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + const vector4d scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_lerp(const qvvd& start, const qvvd& end, const scalard& alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + const vector4d scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // The resulting QVV transform will have the start scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_lerp_no_scale(const qvvd& start, const qvvd& end, double alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // The resulting QVV transform will have the start scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_lerp_no_scale(const qvvd& start, const qvvd& end, const scalard& alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_slerp(const qvvd& start, const qvvd& end, double alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + const vector4d scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_slerp(const qvvd& start, const qvvd& end, const scalard& alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + const vector4d scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_slerp_no_scale(const qvvd& start, const qvvd& end, double alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvd qvv_slerp_no_scale(const qvvd& start, const qvvd& end, const scalard& alpha) RTM_NO_EXCEPT + { + const quatd rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4d translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } +#endif + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/qvvf.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/qvvf.h new file mode 100644 index 00000000..e4a19d68 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/qvvf.h @@ -0,0 +1,273 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/quatf.h" +#include "rtm/vector4f.h" +#include "rtm/matrix3x4f.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/qvv_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Casts a QVV transform float64 variant to a float32 variant. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_cast(const qvvd& input) RTM_NO_EXCEPT + { + return qvvf{ quat_cast(input.rotation), vector_cast(input.translation), vector_cast(input.scale) }; + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two QVV transforms. + // Multiplication order is as follow: local_to_world = qvv_mul(local_to_object, object_to_world) + // NOTE: When scale is present, multiplication will not properly handle skew/shear, + // use affine matrices if you have issues. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvf RTM_SIMD_CALL qvv_mul(qvvf_arg0 lhs, qvvf_arg1 rhs) RTM_NO_EXCEPT + { + const vector4f min_scale = vector_min(lhs.scale, rhs.scale); + const vector4f scale = vector_mul(lhs.scale, rhs.scale); + + if (vector_any_less_than3(min_scale, vector_zero())) + { + // If we have negative scale, we go through a matrix + const matrix3x4f lhs_mtx = matrix_from_qvv(lhs); + const matrix3x4f rhs_mtx = matrix_from_qvv(rhs); + matrix3x4f result_mtx = matrix_mul(lhs_mtx, rhs_mtx); + result_mtx = matrix_remove_scale(result_mtx); + +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -0.0F, -0.0F, -0.0F, -0.0F }; + const __m128 sign_bits = _mm_and_ps(scale, signs); // Mask out the sign bit + + result_mtx.x_axis = _mm_xor_ps(result_mtx.x_axis, _mm_shuffle_ps(sign_bits, sign_bits, _MM_SHUFFLE(0, 0, 0, 0))); + result_mtx.y_axis = _mm_xor_ps(result_mtx.y_axis, _mm_shuffle_ps(sign_bits, sign_bits, _MM_SHUFFLE(1, 1, 1, 1))); + result_mtx.z_axis = _mm_xor_ps(result_mtx.z_axis, _mm_shuffle_ps(sign_bits, sign_bits, _MM_SHUFFLE(2, 2, 2, 2))); +#else + const vector4f sign = vector_sign(scale); + result_mtx.x_axis = vector_mul(result_mtx.x_axis, vector_dup_x(sign)); + result_mtx.y_axis = vector_mul(result_mtx.y_axis, vector_dup_y(sign)); + result_mtx.z_axis = vector_mul(result_mtx.z_axis, vector_dup_z(sign)); +#endif + + const quatf rotation = quat_from_matrix(result_mtx); + const vector4f translation = result_mtx.w_axis; + return qvv_set(rotation, translation, scale); + } + else + { + const quatf rotation = quat_mul(lhs.rotation, rhs.rotation); + const vector4f translation = vector_add(quat_mul_vector3(vector_mul(lhs.translation, rhs.scale), rhs.rotation), rhs.translation); + return qvv_set(rotation, translation, scale); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies two QVV transforms ignoring 3D scale. + // The resulting QVV transform will have the LHS scale. + // Multiplication order is as follow: local_to_world = qvv_mul(local_to_object, object_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvf RTM_SIMD_CALL qvv_mul_no_scale(qvvf_arg0 lhs, qvvf_arg1 rhs) RTM_NO_EXCEPT + { + const quatf rotation = quat_mul(lhs.rotation, rhs.rotation); + const vector4f translation = vector_add(quat_mul_vector3(lhs.translation, rhs.rotation), rhs.translation); + return qvv_set(rotation, translation, lhs.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a QVV transform and a 3D point. + // Multiplication order is as follow: world_position = qvv_mul_point3(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL qvv_mul_point3(vector4f_arg0 point, qvvf_arg1 qvv) RTM_NO_EXCEPT + { + return vector_add(quat_mul_vector3(vector_mul(qvv.scale, point), qvv.rotation), qvv.translation); + } + + ////////////////////////////////////////////////////////////////////////// + // Multiplies a QVV transform and a 3D point ignoring 3D scale. + // Multiplication order is as follow: world_position = qvv_mul_point3_no_scale(local_position, local_to_world) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL qvv_mul_point3_no_scale(vector4f_arg0 point, qvvf_arg1 qvv) RTM_NO_EXCEPT + { + return vector_add(quat_mul_vector3(point, qvv.rotation), qvv.translation); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the inverse of the input QVV transform. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvf RTM_SIMD_CALL qvv_inverse(qvvf_arg0 input) RTM_NO_EXCEPT + { + const quatf inv_rotation = quat_conjugate(input.rotation); + const vector4f inv_scale = vector_reciprocal(input.scale); + const vector4f inv_translation = vector_neg(quat_mul_vector3(vector_mul(input.translation, inv_scale), inv_rotation)); + return qvv_set(inv_rotation, inv_translation, inv_scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the inverse of the input QVV transform ignoring 3D scale. + // The resulting QVV transform will have the input scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline qvvf RTM_SIMD_CALL qvv_inverse_no_scale(qvvf_arg0 input) RTM_NO_EXCEPT + { + const quatf inv_rotation = quat_conjugate(input.rotation); + const vector4f inv_translation = vector_neg(quat_mul_vector3(input.translation, inv_rotation)); + return qvv_set(inv_rotation, inv_translation, input.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a QVV transforms with the rotation part normalized. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_normalize(qvvf_arg0 input) RTM_NO_EXCEPT + { + const quatf rotation = quat_normalize(input.rotation); + return qvv_set(rotation, input.translation, input.scale); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_lerp(qvvf_arg0 start, qvvf_arg1 end, float alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + const vector4f scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_lerp(qvvf_arg0 start, qvvf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + const vector4f scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // The resulting QVV transform will have the start scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_lerp_no_scale(qvvf_arg0 start, qvvf_arg1 end, float alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + // The resulting QVV transform will have the start scale. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_lerp_no_scale(qvvf_arg0 start, qvvf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_lerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_slerp(qvvf_arg0 start, qvvf_arg1 end, float alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + const vector4f scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_slerp(qvvf_arg0 start, qvvf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + const vector4f scale = vector_lerp(start.scale, end.scale, alpha); + return qvv_set(rotation, translation, scale); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_slerp_no_scale(qvvf_arg0 start, qvvf_arg1 end, float alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component spherical interpolation of the two inputs at the specified alpha. + // See quat_slerp(..) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE qvvf RTM_SIMD_CALL qvv_slerp_no_scale(qvvf_arg0 start, qvvf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + const quatf rotation = quat_slerp(start.rotation, end.rotation, alpha); + const vector4f translation = vector_lerp(start.translation, end.translation, alpha); + return qvv_set(rotation, translation, start.scale); + } +#endif + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/scalard.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/scalard.h new file mode 100644 index 00000000..fa60d93e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/scalard.h @@ -0,0 +1,1061 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/constants.h" +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/scalar_common.h" + +#include +#include + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Creates a scalar from a floating point value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_set(double xyzw) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalard{ _mm_set1_pd(xyzw) }; +#else + return xyzw; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Writes a scalar to memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL scalar_store(scalard input, double* output) RTM_NO_EXCEPT + { + _mm_store_sd(output, input.value); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Writes a scalar to memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void scalar_store(double input, double* output) RTM_NO_EXCEPT + { + *output = input; + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a scalar into a floating point value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double RTM_SIMD_CALL scalar_cast(scalard input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(input.value); +#else + return input; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the largest integer value not greater than the input (round towards minus infinity). + // scalar_floor(1.8) = 1.0 + // scalar_floor(-1.8) = -2.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_floor(scalard input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return scalard{ _mm_round_sd(input.value, input.value, 0x9) }; +#else + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128d abs_input = _mm_and_pd(input.value, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large = _mm_cmpge_sd(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128d is_nan = _mm_cmpneq_sd(input.value, input.value); + + // Combine our masks to determine if we should return the original value + __m128d use_original_input = _mm_or_pd(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128d integer_part = _mm_cvtepi32_pd(_mm_cvtpd_epi32(input.value)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + __m128d is_negative = _mm_cmpgt_sd(integer_part, input.value); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Positive values will yield a 0.0 bias + __m128d bias = _mm_cvtepi32_pd(_mm_castpd_si128(is_negative)); + + // Add our bias to properly handle negative values + integer_part = _mm_add_sd(integer_part, bias); + + __m128d result = _mm_or_pd(_mm_and_pd(use_original_input, input.value), _mm_andnot_pd(use_original_input, integer_part)); + return scalard{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the largest integer value not greater than the input (round towards negative infinity). + // scalar_floor(1.8) = 1.0 + // scalar_floor(-1.8) = -2.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_floor(double input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_floor(scalar_set(input))); +#else + return std::floor(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest integer value not less than the input (round towards positive infinity). + // scalar_ceil(1.8) = 2.0 + // scalar_ceil(-1.8) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_ceil(scalard input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return scalard{ _mm_round_sd(input.value, input.value, 0xA) }; +#else + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128d abs_input = _mm_and_pd(input.value, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large = _mm_cmpge_sd(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128d is_nan = _mm_cmpneq_sd(input.value, input.value); + + // Combine our masks to determine if we should return the original value + __m128d use_original_input = _mm_or_pd(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128d integer_part = _mm_cvtepi32_pd(_mm_cvtpd_epi32(input.value)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + __m128d is_positive = _mm_cmplt_sd(integer_part, input.value); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Negative values will yield a 0.0 bias + __m128d bias = _mm_cvtepi32_pd(_mm_castpd_si128(is_positive)); + + // Subtract our bias to properly handle positive values + integer_part = _mm_sub_sd(integer_part, bias); + + __m128d result = _mm_or_pd(_mm_and_pd(use_original_input, input.value), _mm_andnot_pd(use_original_input, integer_part)); + return scalard{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest integer value not less than the input (round towards positive infinity). + // scalar_ceil(1.8) = 2.0 + // scalar_ceil(-1.8) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_ceil(double input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_ceil(scalar_set(input))); +#else + return std::ceil(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the input if it is within the min/max values otherwise the + // exceeded boundary is returned. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_clamp(scalard input, scalard min, scalard max) RTM_NO_EXCEPT + { + return scalard{ _mm_min_sd(_mm_max_sd(input.value, min.value), max.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the input if it is within the min/max values otherwise the + // exceeded boundary is returned. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_clamp(double input, double min, double max) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_min_sd(_mm_max_sd(_mm_set1_pd(input), _mm_set1_pd(min)), _mm_set1_pd(max))); +#else + return std::min(std::max(input, min), max); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the absolute value of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_abs(scalard input) RTM_NO_EXCEPT + { + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + return scalard{ _mm_and_pd(input.value, _mm_castsi128_pd(abs_mask)) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the absolute value of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_abs(double input) RTM_NO_EXCEPT + { + return std::fabs(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_sqrt(scalard input) RTM_NO_EXCEPT + { + return scalard{ _mm_sqrt_sd(input.value, input.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_sqrt(double input) RTM_NO_EXCEPT + { + return std::sqrt(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_sqrt_reciprocal(scalard input) RTM_NO_EXCEPT + { + const __m128d input_sqrt = _mm_sqrt_sd(input.value, input.value); + const __m128d result = _mm_div_sd(_mm_set_sd(1.0), input_sqrt); + return scalard{ result }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_sqrt_reciprocal(double input) RTM_NO_EXCEPT + { + return 1.0 / scalar_sqrt(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_reciprocal(scalard input) RTM_NO_EXCEPT + { + return scalard{ _mm_div_sd(_mm_set1_pd(1.0), input.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_reciprocal(double input) RTM_NO_EXCEPT + { + return 1.0 / input; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the addition of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_add(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return scalard{ _mm_add_sd(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the addition of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_add(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs + rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the subtraction of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_sub(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return scalard{ _mm_sub_sd(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the subtraction of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_sub(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs - rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_mul(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return scalard{ _mm_mul_sd(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_mul(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs * rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the division of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_div(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return scalard{ _mm_div_sd(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the division of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_div(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs / rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication/addition of the three inputs: s2 + (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_mul_add(scalard s0, scalard s1, scalard s2) RTM_NO_EXCEPT + { + return scalard{ _mm_add_sd(_mm_mul_sd(s0.value, s1.value), s2.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication/addition of the three inputs: s2 + (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_mul_add(double s0, double s1, double s2) RTM_NO_EXCEPT + { +#if defined(RTM_NEON_INTRINSICS) + return std::fma(s0, s1, s2); +#else + return (s0 * s1) + s2; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the negative multiplication/subtraction of the three inputs: -((s0 * s1) - s2) + // This is mathematically equivalent to: s2 - (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_neg_mul_sub(scalard s0, scalard s1, scalard s2) RTM_NO_EXCEPT + { + return scalard{ _mm_sub_sd(s2.value, _mm_mul_sd(s0.value, s1.value)) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the negative multiplication/subtraction of the three inputs: -((s0 * s1) - s2) + // This is mathematically equivalent to: s2 - (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_neg_mul_sub(double s0, double s1, double s2) RTM_NO_EXCEPT + { + return s2 - (s0 * s1); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_lerp(scalard start, scalard end, scalard alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return scalar_mul_add(end, alpha, scalar_neg_mul_sub(start, alpha, start)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_lerp(double start, double end, double alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return scalar_mul_add(end, alpha, scalar_neg_mul_sub(start, alpha, start)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_min(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return scalard{ _mm_min_sd(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_min(double left, double right) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_min_sd(_mm_set1_pd(left), _mm_set1_pd(right))); +#else + return std::min(left, right); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the largest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_max(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return scalard{ _mm_max_sd(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the largest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_max(double left, double right) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_max_sd(_mm_set1_pd(left), _mm_set1_pd(right))); +#else + return std::max(left, right); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are equal, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_equal(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return _mm_comieq_sd(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are equal, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_equal(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs == rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs < rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_lower_than(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return _mm_comilt_sd(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs < rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_lower_than(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs < rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs <= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_lower_equal(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return _mm_comile_sd(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs <= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_lower_equal(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs <= rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs > rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_greater_than(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return _mm_comigt_sd(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs > rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_greater_than(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs > rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs >= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_greater_equal(scalard lhs, scalard rhs) RTM_NO_EXCEPT + { + return _mm_comige_sd(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs >= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_greater_equal(double lhs, double rhs) RTM_NO_EXCEPT + { + return lhs >= rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are nearly equal, otherwise false: abs(lhs - rhs) <= threshold + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_near_equal(scalard lhs, scalard rhs, scalard threshold) RTM_NO_EXCEPT + { + return scalar_lower_equal(scalar_abs(scalar_sub(lhs, rhs)), threshold); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are nearly equal, otherwise false: abs(lhs - rhs) <= threshold + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool scalar_near_equal(double lhs, double rhs, double threshold) RTM_NO_EXCEPT + { + return scalar_abs(lhs - rhs) <= threshold; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input is finite (not NaN or Inf), false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_is_finite(scalard input) RTM_NO_EXCEPT + { + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + __m128d abs_input = _mm_and_pd(input.value, _mm_castsi128_pd(abs_mask)); + + const __m128d infinity = _mm_set1_pd(std::numeric_limits::infinity()); + __m128d is_infinity = _mm_cmpeq_sd(abs_input, infinity); + + __m128d is_nan = _mm_cmpneq_sd(input.value, input.value); + + __m128d is_not_finite = _mm_or_pd(is_infinity, is_nan); + return (_mm_movemask_pd(is_not_finite) & 0x1) == 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input is finite (not NaN or Inf), false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool scalar_is_finite(double input) RTM_NO_EXCEPT + { + return std::isfinite(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using a symmetric algorithm. + // scalar_symmetric_round(1.5) = 2.0 + // scalar_symmetric_round(1.2) = 1.0 + // scalar_symmetric_round(-1.5) = -2.0 + // scalar_symmetric_round(-1.2) = -1.0 + // Note: This function relies on the default floating point rounding mode (banker's rounding). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_round_symmetric(scalard input) RTM_NO_EXCEPT + { + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + +#if defined(RTM_SSE4_INTRINSICS) + __m128d is_positive = _mm_cmpge_sd(input.value, _mm_setzero_pd()); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d sign = _mm_andnot_pd(is_positive, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128d bias = _mm_or_pd(sign, _mm_set1_pd(0.5)); + __m128d biased_input = _mm_add_sd(input.value, bias); + + __m128d floored = _mm_floor_sd(biased_input, biased_input); + __m128d ceiled = _mm_ceil_sd(biased_input, biased_input); + + __m128d result = RTM_VECTOR2D_SELECT(is_positive, floored, ceiled); + return scalard{ result }; +#else + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128d abs_input = _mm_and_pd(input.value, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large = _mm_cmpge_sd(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128d is_nan = _mm_cmpneq_sd(input.value, input.value); + + // Combine our masks to determine if we should return the original value + __m128d use_original_input = _mm_or_pd(is_input_large, is_nan); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d sign = _mm_and_pd(input.value, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128d bias = _mm_or_pd(sign, _mm_set1_pd(0.5)); + __m128d biased_input = _mm_add_sd(input.value, bias); + + // Convert to an integer with truncation and back, this rounds towards zero. + __m128d integer_part = _mm_cvtepi32_pd(_mm_cvttpd_epi32(biased_input)); + + __m128d result = _mm_or_pd(_mm_and_pd(use_original_input, input.value), _mm_andnot_pd(use_original_input, integer_part)); + + return scalard{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using a symmetric algorithm. + // scalar_round_symmetric(1.5) = 2.0 + // scalar_round_symmetric(1.2) = 1.0 + // scalar_round_symmetric(-1.5) = -2.0 + // scalar_round_symmetric(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_round_symmetric(double input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_round_symmetric(scalar_set(input))); +#else + return input >= 0.0 ? scalar_floor(input + 0.5) : scalar_ceil(input - 0.5); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using banker's rounding (half to even). + // scalar_round_bankers(2.5) = 2.0 + // scalar_round_bankers(1.5) = 2.0 + // scalar_round_bankers(1.2) = 1.0 + // scalar_round_bankers(-2.5) = -2.0 + // scalar_round_bankers(-1.5) = -2.0 + // scalar_round_bankers(-1.2) = -1.0 + // Note: This function relies on the default floating point rounding mode (banker's rounding). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalard RTM_SIMD_CALL scalar_round_bankers(scalard input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return scalard{ _mm_cvtsd_f64(_mm_round_sd(input.value, input.value, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC)) }; +#else + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d sign = _mm_and_pd(input.value, sign_mask); + + // We add the largest integer that a 64 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + __m128d truncating_offset = _mm_or_pd(sign, fractional_limit); + __m128d integer_part = _mm_sub_sd(_mm_add_sd(input.value, truncating_offset), truncating_offset); + + __m128d abs_input = _mm_and_pd(input.value, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large = _mm_cmpge_sd(abs_input, fractional_limit); + __m128d result = _mm_or_pd(_mm_and_pd(is_input_large, input.value), _mm_andnot_pd(is_input_large, integer_part)); + + return scalard{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using banker's rounding (half to even). + // scalar_round_bankers(2.5) = 2.0 + // scalar_round_bankers(1.5) = 2.0 + // scalar_round_bankers(1.2) = 1.0 + // scalar_round_bankers(-2.5) = -2.0 + // scalar_round_bankers(-1.5) = -2.0 + // scalar_round_bankers(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_round_bankers(double input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_round_bankers(scalar_set(input))); +#else + if (!scalar_is_finite(input)) + return input; + + int64_t whole = static_cast(input); + double whole_f = static_cast(whole); + double remainder = scalar_abs(input - whole_f); + if (remainder < 0.5) + return whole_f; + if (remainder > 0.5) + return input >= 0.0 ? (whole_f + 1.0) : (whole_f - 1.0); + + if ((whole % 2) == 0) + return whole_f; + else + return input >= 0.0 ? (whole_f + 1.0) : (whole_f - 1.0); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the fractional part of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_fraction(double value) RTM_NO_EXCEPT + { + return value - scalar_floor(value); + } + + ////////////////////////////////////////////////////////////////////////// + // Safely casts an integral input into a float64 output. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE double scalar_safe_to_double(SrcIntegralType input) RTM_NO_EXCEPT + { + double input_f = double(input); + RTM_ASSERT(SrcIntegralType(input_f) == input, "Conversion to double would result in truncation"); + return input_f; + } + + ////////////////////////////////////////////////////////////////////////// + // Trigonometric functions + ////////////////////////////////////////////////////////////////////////// + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the sine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_sin(scalard angle) RTM_NO_EXCEPT + { + return scalar_set(std::sin(scalar_cast(angle))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the sine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_sin(double angle) RTM_NO_EXCEPT + { + return std::sin(angle); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_cos(scalard angle) RTM_NO_EXCEPT + { + return scalar_set(std::cos(scalar_cast(angle))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_cos(double angle) RTM_NO_EXCEPT + { + return std::cos(angle); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns both sine and cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d RTM_SIMD_CALL scalar_sincos(scalard angle) RTM_NO_EXCEPT + { + scalard sin_ = scalar_sin(angle); + scalard cos_ = scalar_cos(angle); + + __m128d xy = _mm_unpacklo_pd(sin_.value, cos_.value); + return vector4d{ xy, xy }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns both sine and cosine of the input angle. + // The result's [x] component contains sin(angle). + // The result's [y] component contains cos(angle). + // [zw] are undefined. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d RTM_SIMD_CALL scalar_sincos(double angle) RTM_NO_EXCEPT + { + scalard angle_ = scalar_set(angle); + scalard sin_ = scalar_sin(angle_); + scalard cos_ = scalar_cos(angle_); + +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy = _mm_unpacklo_pd(sin_.value, cos_.value); + return vector4d{ xy, xy }; +#else + return vector4d{ sin_, cos_, sin_, cos_ }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns both sine and cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void scalar_sincos(double angle, double& out_sin, double& out_cos) RTM_NO_EXCEPT + { + out_sin = scalar_sin(angle); + out_cos = scalar_cos(angle); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-sine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_asin(scalard value) RTM_NO_EXCEPT + { + return scalar_set(std::asin(scalar_cast(value))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-sine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_asin(double value) RTM_NO_EXCEPT + { + return std::asin(value); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-cosine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_acos(scalard value) RTM_NO_EXCEPT + { + return scalar_set(std::acos(scalar_cast(value))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-cosine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_acos(double value) RTM_NO_EXCEPT + { + return std::acos(value); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the tangent of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_tan(scalard angle) RTM_NO_EXCEPT + { + return scalar_set(std::tan(scalar_cast(angle))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the tangent of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_tan(double angle) RTM_NO_EXCEPT + { + return std::tan(angle); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of the input. + // Note that due to the sign ambiguity, atan cannot determine which quadrant + // the value resides in. See scalar_atan2. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_atan(scalard value) RTM_NO_EXCEPT + { + return scalar_set(std::atan(scalar_cast(value))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of the input. + // Note that due to the sign ambiguity, atan cannot determine which quadrant + // the value resides in. See scalar_atan2. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_atan(double value) RTM_NO_EXCEPT + { + return std::atan(value); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of [y/x] using the sign of the arguments to + // determine the correct quadrant. + // Y represents the proportion of the y-coordinate. + // X represents the proportion of the x-coordinate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalard RTM_SIMD_CALL scalar_atan2(scalard y, scalard x) RTM_NO_EXCEPT + { + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + + const __m128d zero = _mm_setzero_pd(); + __m128d is_x_zero = _mm_cmpeq_sd(x.value, zero); + __m128d is_y_zero = _mm_cmpeq_sd(y.value, zero); + __m128d inputs_are_zero = _mm_and_pd(is_x_zero, is_y_zero); + + __m128d is_x_positive = _mm_cmpgt_sd(x.value, zero); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d y_sign = _mm_and_pd(y.value, sign_mask); + + // If X == 0.0, our offset is PI/2 otherwise it is PI both with the sign of Y + __m128d half_pi = _mm_set1_pd(constants::half_pi()); + __m128d pi = _mm_set1_pd(constants::pi()); + __m128d offset = _mm_or_pd(_mm_and_pd(is_x_zero, half_pi), _mm_andnot_pd(is_x_zero, pi)); + offset = _mm_or_pd(offset, y_sign); + + // If X > 0.0, our offset is 0.0 + offset = _mm_andnot_pd(is_x_positive, offset); + + // If X == 0.0 and Y == 0.0, our offset is 0.0 + offset = _mm_andnot_pd(inputs_are_zero, offset); + + __m128d angle = _mm_div_sd(y.value, x.value); + __m128d value = scalar_atan(scalard{ angle }).value; + + // If X == 0.0, our value is 0.0 otherwise it is atan(y/x) + value = _mm_or_pd(_mm_and_pd(is_x_zero, zero), _mm_andnot_pd(is_x_zero, value)); + + // If X == 0.0 and Y == 0.0, our value is 0.0 + value = _mm_andnot_pd(inputs_are_zero, value); + + __m128d result = _mm_add_sd(value, offset); + return scalard{ result }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of [y/x] using the sign of the arguments to + // determine the correct quadrant. + // Y represents the proportion of the y-coordinate. + // X represents the proportion of the x-coordinate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline double scalar_atan2(double y, double x) RTM_NO_EXCEPT + { + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + + if (x == 0.0) + { + if (y == 0.0) + return 0.0; + + return std::copysign(constants::half_pi(), y); + } + + double value = scalar_atan(y / x); + if (x > 0.0) + return value; + + double offset = std::copysign(constants::pi(), y); + return value + offset; + } + + ////////////////////////////////////////////////////////////////////////// + // Converts degrees into radians. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_deg_to_rad(double deg) RTM_NO_EXCEPT + { + return deg * constants::pi_div_one_eighty(); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts radians into degrees. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr double scalar_rad_to_deg(double rad) RTM_NO_EXCEPT + { + return rad * constants::one_eighty_div_pi(); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/scalarf.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/scalarf.h new file mode 100644 index 00000000..67f9caf8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/scalarf.h @@ -0,0 +1,1469 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/constants.h" +#include "rtm/macros.h" +#include "rtm/math.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/scalar_common.h" + +#include +#include + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Creates a scalar from a floating point value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_set(float xyzw) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalarf{ _mm_set_ps1(xyzw) }; +#else + return xyzw; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Writes a scalar to memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL scalar_store(scalarf_arg0 input, float* output) RTM_NO_EXCEPT + { + _mm_store_ss(output, input.value); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Writes a scalar to memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void scalar_store(float input, float* output) RTM_NO_EXCEPT + { + *output = input; + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a scalar into a floating point value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float RTM_SIMD_CALL scalar_cast(scalarf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(input.value); +#else + return input; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the largest integer value not greater than the input (round towards minus infinity). + // scalar_floor(1.8) = 1.0 + // scalar_floor(-1.8) = -2.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_floor(scalarf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return scalarf{ _mm_round_ss(input.value, input.value, 0x9) }; +#else + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input.value, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ss(input.value, input.value); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input.value)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + __m128 is_negative = _mm_cmpgt_ss(integer_part, input.value); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Positive values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_negative)); + + // Add our bias to properly handle negative values + integer_part = _mm_add_ss(integer_part, bias); + + __m128 result = _mm_or_ps(_mm_and_ps(use_original_input, input.value), _mm_andnot_ps(use_original_input, integer_part)); + return scalarf{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the largest integer value not greater than the input (round towards negative infinity). + // scalar_floor(1.8) = 1.0 + // scalar_floor(-1.8) = -2.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_floor(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_floor(scalar_set(input))); +#else + return std::floor(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest integer value not less than the input (round towards positive infinity). + // scalar_ceil(1.8) = 2.0 + // scalar_ceil(-1.8) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_ceil(scalarf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return scalarf{ _mm_round_ss(input.value, input.value, 0xA) }; +#else + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input.value, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ss(input.value, input.value); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input.value)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + __m128 is_positive = _mm_cmplt_ss(integer_part, input.value); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Negative values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_positive)); + + // Subtract our bias to properly handle positive values + integer_part = _mm_sub_ss(integer_part, bias); + + __m128 result = _mm_or_ps(_mm_and_ps(use_original_input, input.value), _mm_andnot_ps(use_original_input, integer_part)); + return scalarf{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest integer value not less than the input (round towards positive infinity). + // scalar_ceil(1.8) = 2.0 + // scalar_ceil(-1.8) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_ceil(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_ceil(scalar_set(input))); +#else + return std::ceil(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the input if it is within the min/max values otherwise the + // exceeded boundary is returned. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_clamp(scalarf_arg0 input, scalarf_arg1 min, scalarf_arg2 max) RTM_NO_EXCEPT + { + return scalarf{ _mm_min_ss(_mm_max_ss(input.value, min.value), max.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the input if it is within the min/max values otherwise the + // exceeded boundary is returned. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_clamp(float input, float min, float max) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_min_ss(_mm_max_ss(_mm_set_ps1(input), _mm_set_ps1(min)), _mm_set_ps1(max))); +#else + return std::min(std::max(input, min), max); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the absolute value of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_abs(scalarf_arg0 input) RTM_NO_EXCEPT + { + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + return scalarf{ _mm_and_ps(input.value, _mm_castsi128_ps(abs_mask)) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the absolute value of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_abs(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + return _mm_cvtss_f32(_mm_and_ps(_mm_set_ps1(input), _mm_castsi128_ps(abs_mask))); +#else + return std::fabs(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_sqrt(scalarf_arg0 input) RTM_NO_EXCEPT + { + return scalarf{ _mm_sqrt_ss(input.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float RTM_SIMD_CALL scalar_sqrt(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ps1(input))); +#else + return std::sqrt(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal square root of the input. + ////////////////////////////////////////////////////////////////////////// + #if defined(RTM_COMPILER_MSVC) && _MSC_VER >= 1920 && _MSC_VER < 1925 && defined(RTM_ARCH_X64) && !defined(RTM_AVX_INTRINSICS) + // HACK!!! Visual Studio 2019 has a code generation bug triggered by the code below, disable optimizations for now + // Bug only happens with x64 SSE2, not with AVX nor with x86 + // Fixed in 16.5.4, see https://github.com/nfrechette/rtm/issues/35 + // TODO: Remove this hack sometime in 2022 or later once the fix is old enough that we no longer have to support the hack + #pragma optimize("", off) + #endif + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_sqrt_reciprocal(scalarf_arg0 input) RTM_NO_EXCEPT + { + // Perform two passes of Newton-Raphson iteration on the hardware estimate + const __m128 half = _mm_set_ss(0.5F); + const __m128 input_half = _mm_mul_ss(input.value, half); + const __m128 x0 = _mm_rsqrt_ss(input.value); + + // First iteration + __m128 x1 = _mm_mul_ss(x0, x0); + x1 = _mm_sub_ss(half, _mm_mul_ss(input_half, x1)); + x1 = _mm_add_ss(_mm_mul_ss(x0, x1), x0); + + // Second iteration + __m128 x2 = _mm_mul_ss(x1, x1); + x2 = _mm_sub_ss(half, _mm_mul_ss(input_half, x2)); + x2 = _mm_add_ss(_mm_mul_ss(x1, x2), x1); + + return scalarf{ x2 }; + } + #if defined(RTM_COMPILER_MSVC) && _MSC_VER >= 1920 && _MSC_VER < 1925 && defined(RTM_ARCH_X64) && !defined(RTM_AVX_INTRINSICS) + // HACK!!! See comment above + #pragma optimize("", on) + #endif +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal square root of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float RTM_SIMD_CALL scalar_sqrt_reciprocal(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_sqrt_reciprocal(scalar_set(input))); +#else + return 1.0F / scalar_sqrt(input); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_reciprocal(scalarf_arg0 input) RTM_NO_EXCEPT + { + return scalarf{ _mm_div_ss(_mm_set_ps1(1.0F), input.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float RTM_SIMD_CALL scalar_reciprocal(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_reciprocal(scalar_set(input))); +#else + return 1.0f / input; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the addition of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_add(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return scalarf{ _mm_add_ss(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the addition of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_add(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs + rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the subtraction of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_sub(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return scalarf{ _mm_sub_ss(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the subtraction of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_sub(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs - rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_mul(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return scalarf{ _mm_mul_ss(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_mul(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs * rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the division of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_div(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return scalarf{ _mm_div_ss(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the division of the two scalar inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_div(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs / rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication/addition of the three inputs: s2 + (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_mul_add(scalarf_arg0 s0, scalarf_arg1 s1, scalarf_arg2 s2) RTM_NO_EXCEPT + { + return scalarf{ _mm_add_ss(_mm_mul_ss(s0.value, s1.value), s2.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the multiplication/addition of the three inputs: s2 + (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_mul_add(float s0, float s1, float s2) RTM_NO_EXCEPT + { +#if defined(RTM_NEON_INTRINSICS) + return std::fma(s0, s1, s2); +#else + return (s0 * s1) + s2; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the negative multiplication/subtraction of the three inputs: -((s0 * s1) - s2) + // This is mathematically equivalent to: s2 - (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_neg_mul_sub(scalarf_arg0 s0, scalarf_arg1 s1, scalarf_arg2 s2) RTM_NO_EXCEPT + { + return scalarf{ _mm_sub_ss(s2.value, _mm_mul_ss(s0.value, s1.value)) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the negative multiplication/subtraction of the three inputs: -((s0 * s1) - s2) + // This is mathematically equivalent to: s2 - (s0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_neg_mul_sub(float s0, float s1, float s2) RTM_NO_EXCEPT + { + return s2 - (s0 * s1); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_lerp(scalarf_arg0 start, scalarf_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return scalar_mul_add(end, alpha, scalar_neg_mul_sub(start, alpha, start)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_lerp(float start, float end, float alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return scalar_mul_add(end, alpha, scalar_neg_mul_sub(start, alpha, start)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_min(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return scalarf{ _mm_min_ss(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_min(float left, float right) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_min_ss(_mm_set_ps1(left), _mm_set_ps1(right))); +#else + return std::min(left, right); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the largest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_max(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return scalarf{ _mm_max_ss(lhs.value, rhs.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the largest of the two inputs. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_max(float left, float right) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_max_ss(_mm_set_ps1(left), _mm_set_ps1(right))); +#else + return std::max(left, right); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are equal, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_equal(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return _mm_comieq_ss(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are equal, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_equal(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs == rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs < rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_lower_than(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return _mm_comilt_ss(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs < rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_lower_than(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs < rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs <= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_lower_equal(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return _mm_comile_ss(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs <= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_lower_equal(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs <= rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs > rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_greater_than(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return _mm_comigt_ss(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs > rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_greater_than(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs > rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs >= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_greater_equal(scalarf_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return _mm_comige_ss(lhs.value, rhs.value) != 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if lhs >= rhs, false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr bool scalar_greater_equal(float lhs, float rhs) RTM_NO_EXCEPT + { + return lhs >= rhs; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are nearly equal, otherwise false: abs(lhs - rhs) <= threshold + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_near_equal(scalarf_arg0 lhs, scalarf_arg1 rhs, scalarf_arg2 threshold) RTM_NO_EXCEPT + { + return scalar_lower_equal(scalar_abs(scalar_sub(lhs, rhs)), threshold); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if both inputs are nearly equal, otherwise false: abs(lhs - rhs) <= threshold + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool scalar_near_equal(float lhs, float rhs, float threshold) RTM_NO_EXCEPT + { + return scalar_abs(lhs - rhs) <= threshold; + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input is finite (not NaN or Inf), false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL scalar_is_finite(scalarf_arg0 input) RTM_NO_EXCEPT + { + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input.value, _mm_castsi128_ps(abs_mask)); + + const __m128 infinity = _mm_set_ps1(std::numeric_limits::infinity()); + __m128 is_infinity = _mm_cmpeq_ss(abs_input, infinity); + + __m128 is_nan = _mm_cmpneq_ss(input.value, input.value); + + __m128 is_not_finite = _mm_or_ps(is_infinity, is_nan); + return (_mm_movemask_ps(is_not_finite) & 0x1) == 0; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the input is finite (not NaN or Inf), false otherwise. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool scalar_is_finite(float input) RTM_NO_EXCEPT + { + return std::isfinite(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using a symmetric algorithm. + // scalar_symmetric_round(1.5) = 2.0 + // scalar_symmetric_round(1.2) = 1.0 + // scalar_symmetric_round(-1.5) = -2.0 + // scalar_symmetric_round(-1.2) = -1.0 + // Note: This function relies on the default floating point rounding mode (banker's rounding). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_round_symmetric(scalarf_arg0 input) RTM_NO_EXCEPT + { + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + +#if defined(RTM_SSE4_INTRINSICS) + __m128 is_positive = _mm_cmpge_ss(input.value, _mm_setzero_ps()); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_andnot_ps(is_positive, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128 bias = _mm_or_ps(sign, _mm_set_ps1(0.5F)); + __m128 biased_input = _mm_add_ss(input.value, bias); + + __m128 floored = _mm_floor_ss(biased_input, biased_input); + __m128 ceiled = _mm_ceil_ss(biased_input, biased_input); + + __m128 result = RTM_VECTOR4F_SELECT(is_positive, floored, ceiled); + return scalarf{ result }; +#else + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input.value, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ss(input.value, input.value); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input.value, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128 bias = _mm_or_ps(sign, _mm_set_ps1(0.5F)); + __m128 biased_input = _mm_add_ss(input.value, bias); + + // Convert to an integer with truncation and back, this rounds towards zero. + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvttps_epi32(biased_input)); + + __m128 result = _mm_or_ps(_mm_and_ps(use_original_input, input.value), _mm_andnot_ps(use_original_input, integer_part)); + + return scalarf{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using a symmetric algorithm. + // scalar_round_symmetric(1.5) = 2.0 + // scalar_round_symmetric(1.2) = 1.0 + // scalar_round_symmetric(-1.5) = -2.0 + // scalar_round_symmetric(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float scalar_round_symmetric(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_round_symmetric(scalar_set(input))); +#elif defined(RTM_NEON64_INTRINSICS) + // arm64 has floor/ceil instructions + return input >= 0.0F ? scalar_floor(input + 0.5F) : scalar_ceil(input - 0.5F); +#else + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const float fractional_limit = 8388608.0F; // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + float abs_input = scalar_abs(input); + bool is_input_large = abs_input >= fractional_limit; + + // Test if our input is NaN with (value != value), it is only true for NaN + bool is_nan = input != input; + + // Combine our masks to determine if we should return the original value + bool use_original_input = is_input_large | is_nan; + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + float bias = input >= 0.0F ? 0.5F : -0.5F; + float biased_input = input + bias; + + // Convert to an integer with truncation and back, this rounds towards zero. + float integer_part = static_cast(static_cast(biased_input)); + + return use_original_input ? input : integer_part; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using banker's rounding (half to even). + // scalar_round_bankers(2.5) = 2.0 + // scalar_round_bankers(1.5) = 2.0 + // scalar_round_bankers(1.2) = 1.0 + // scalar_round_bankers(-2.5) = -2.0 + // scalar_round_bankers(-1.5) = -2.0 + // scalar_round_bankers(-1.2) = -1.0 + // Note: This function relies on the default floating point rounding mode (banker's rounding). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE scalarf RTM_SIMD_CALL scalar_round_bankers(scalarf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return scalarf{ _mm_round_ss(input.value, input.value, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC) }; +#else + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input.value, sign_mask); + + // We add the largest integer that a 32 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + __m128 truncating_offset = _mm_or_ps(sign, fractional_limit); + __m128 integer_part = _mm_sub_ss(_mm_add_ss(input.value, truncating_offset), truncating_offset); + + // If our input was so large that it had no fractional part, return it unchanged + // Otherwise return our integer part + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input.value, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + __m128 result = _mm_or_ps(_mm_and_ps(is_input_large, input.value), _mm_andnot_ps(is_input_large, integer_part)); + + return scalarf{ result }; +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the rounded input using banker's rounding (half to even). + // scalar_round_bankers(2.5) = 2.0 + // scalar_round_bankers(1.5) = 2.0 + // scalar_round_bankers(1.2) = 1.0 + // scalar_round_bankers(-2.5) = -2.0 + // scalar_round_bankers(-1.5) = -2.0 + // scalar_round_bankers(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_round_bankers(float input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_round_bankers(scalar_set(input))); +#elif defined(RTM_NEON64_INTRINSICS) && defined(RTM_IMPL_VRNDNS_SUPPORTED) + return vrndns_f32(input); +#else + if (!scalar_is_finite(input)) + return input; + + int32_t whole = static_cast(input); + float whole_f = static_cast(whole); + float remainder = scalar_abs(input - whole_f); + if (remainder < 0.5F) + return whole_f; + if (remainder > 0.5F) + return input >= 0.0F ? (whole_f + 1.0F) : (whole_f - 1.0F); + + if ((whole % 2) == 0) + return whole_f; + else + return input >= 0.0F ? (whole_f + 1.0F) : (whole_f - 1.0F); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the fractional part of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_fraction(float value) RTM_NO_EXCEPT + { + return value - scalar_floor(value); + } + + ////////////////////////////////////////////////////////////////////////// + // Safely casts an integral input into a float64 output. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE float scalar_safe_to_float(SrcIntegralType input) RTM_NO_EXCEPT + { + float input_f = float(input); + RTM_ASSERT(SrcIntegralType(input_f) == input, "Conversion to float would result in truncation"); + return input_f; + } + + ////////////////////////////////////////////////////////////////////////// + // Trigonometric functions + ////////////////////////////////////////////////////////////////////////// + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the sine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_sin(scalarf_arg0 angle) RTM_NO_EXCEPT + { + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ss(angle.value, _mm_set_ps1(constants::one_div_two_pi())); + quotient = scalar_round_bankers(scalarf{ quotient }).value; + quotient = _mm_mul_ss(quotient, _mm_set_ps1(constants::two_pi())); + __m128 x = _mm_sub_ss(angle.value, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(sign, _mm_set_ps1(constants::pi())); + + const __m128 reflection = _mm_sub_ss(reference, x); + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + + __m128 is_less_equal_than_half_pi = _mm_cmple_ss(x_abs, _mm_set_ps1(constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const float x2 = _mm_cvtss_f32(_mm_mul_ss(x, x)); + float result = (x2 * -2.3828544692960918e-8F) + 2.7521557770526783e-6F; + result = (result * x2) - 1.9840782426250314e-4F; + result = (result * x2) + 8.3333303183525942e-3F; + result = (result * x2) - 1.6666666601721269e-1F; + result = (result * x2) + 1.0F; + result = result * _mm_cvtss_f32(x); + return scalar_set(result); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the sine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float RTM_SIMD_CALL scalar_sin(float angle) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_sin(scalar_set(angle))); +#elif defined(RTM_NEON_INTRINSICS) + return std::sin(angle); +#else + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float quotient = angle * constants::one_div_two_pi(); + quotient = scalar_round_bankers(quotient); + quotient = quotient * constants::two_pi(); + float x = angle - quotient; + + // Remap our input in the [-pi/2, pi/2] range + const float reference = std::copysign(constants::pi(), x); + const float reflection = reference - x; + const float x_abs = scalar_abs(x); + x = x_abs <= constants::half_pi() ? x : reflection; + + // Calculate our value + const float x2 = x * x; + float result = (x2 * -2.3828544692960918e-8F) + 2.7521557770526783e-6F; + result = (result * x2) - 1.9840782426250314e-4F; + result = (result * x2) + 8.3333303183525942e-3F; + result = (result * x2) - 1.6666666601721269e-1F; + result = (result * x2) + 1.0F; + result = result * x; + return result; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_cos(scalarf_arg0 angle) RTM_NO_EXCEPT + { + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ss(angle.value, _mm_set_ps1(constants::one_div_two_pi())); + quotient = scalar_round_bankers(scalarf{ quotient }).value; + quotient = _mm_mul_ss(quotient, _mm_set_ps1(constants::two_pi())); + __m128 x = _mm_sub_ss(angle.value, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 x_sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(x_sign, _mm_set_ps1(constants::pi())); + const __m128 reflection = _mm_sub_ss(reference, x); + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + __m128 is_less_equal_than_half_pi = _mm_cmple_ss(x_abs, _mm_set_ps1(constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const float x2 = _mm_cvtss_f32(_mm_mul_ss(x, x)); + float result = (x2 * -2.6051615464872668e-7F) + 2.4760495088926859e-5F; + result = (result * x2) - 1.3888377661039897e-3F; + result = (result * x2) + 4.1666638865338612e-2F; + result = (result * x2) - 4.9999999508695869e-1F; + result = (result * x2) + 1.0F; + + // Remap into [-pi, pi] + __m128 result_v = _mm_set_ps1(result); + __m128 cosine = _mm_or_ps(result_v, _mm_andnot_ps(is_less_equal_than_half_pi, sign_mask)); + return scalarf{ cosine }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float RTM_SIMD_CALL scalar_cos(float angle) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_cos(scalar_set(angle))); +#elif defined(RTM_NEON_INTRINSICS) + return std::cos(angle); +#else + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float quotient = angle * constants::one_div_two_pi(); + quotient = scalar_round_bankers(quotient); + quotient = quotient * constants::two_pi(); + float x = angle - quotient; + + // Remap our input in the [-pi/2, pi/2] range + const float reference = std::copysign(constants::pi(), x); + const float reflection = reference - x; + const float x_abs = scalar_abs(x); + x = x_abs <= constants::half_pi() ? x : reflection; + + // Calculate our value + const float x2 = x * x; + float result = (x2 * -2.6051615464872668e-7F) + 2.4760495088926859e-5F; + result = (result * x2) - 1.3888377661039897e-3F; + result = (result * x2) + 4.1666638865338612e-2F; + result = (result * x2) - 4.9999999508695869e-1F; + result = (result * x2) + 1.0F; + + // Remap into [-pi, pi] + if (x_abs <= constants::half_pi()) + return result; + else + return -result; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns both sine and cosine of the input angle. + // The result's [x] component contains sin(angle). + // The result's [y] component contains cos(angle). + // [zw] are undefined. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL scalar_sincos(scalarf angle) RTM_NO_EXCEPT + { + scalarf sin_ = scalar_sin(angle); + scalarf cos_ = scalar_cos(angle); + + return _mm_unpacklo_ps(sin_.value, cos_.value); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns both sine and cosine of the input angle. + // The result's [x] component contains sin(angle). + // The result's [y] component contains cos(angle). + // [zw] are undefined. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL scalar_sincos(float angle) RTM_NO_EXCEPT + { + scalarf angle_ = scalar_set(angle); + scalarf sin_ = scalar_sin(angle_); + scalarf cos_ = scalar_cos(angle_); + +#if defined(RTM_SSE2_INTRINSICS) + return _mm_unpacklo_ps(sin_.value, cos_.value); +#elif defined(RTM_NEON_INTRINSICS) + float32x2_t xy = vcreate_f32(((uint64_t)*(const uint32_t*)&sin_) | ((uint64_t)(*(const uint32_t*)&cos_) << 32)); + return vcombine_f32(xy, xy); +#else + return vector4f{ sin_, cos_, sin_, cos_ }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns both sine and cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void scalar_sincos(float angle, float& out_sin, float& out_cos) RTM_NO_EXCEPT + { + out_sin = scalar_sin(angle); + out_cos = scalar_cos(angle); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-sine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_asin(scalarf_arg0 value) RTM_NO_EXCEPT + { + // Use a degree 7 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_value = _mm_and_ps(value.value, _mm_castsi128_ps(abs_mask)); + + // Calculate our value + const float x = _mm_cvtss_f32(abs_value); + float result = (x * -1.2690614339589956e-3F) + 6.7072304676685235e-3F; + result = (result * x) - 1.7162031184398074e-2F; + result = (result * x) + 3.0961594977611639e-2F; + result = (result * x) - 5.0207843052845647e-2F; + result = (result * x) + 8.8986946573346160e-2F; + result = (result * x) - 2.1459960076929829e-1F; + result = (result * x) + 1.5707963267948966F; + + // Scale our result + const __m128 scale = _mm_sqrt_ss(_mm_sub_ss(_mm_set_ps1(1.0F), abs_value)); + result = result * _mm_cvtss_f32(scale); + + // Handle negative values through reflection + if (_mm_cvtss_f32(value.value) < 0.0F) + result = constants::pi() - result; + + // Shift our final result + const float offset = constants::half_pi(); + result = offset - result; + return scalarf{ _mm_set_ps1(result) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-sine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float scalar_asin(float value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_asin(scalar_set(value))); +#elif defined(RTM_NEON_INTRINSICS) + return std::asin(value); +#else + // Use a degree 7 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + const float abs_value = scalar_abs(value); + + // Calculate our value + float result = (abs_value * -1.2690614339589956e-3F) + 6.7072304676685235e-3F; + result = (result * abs_value) - 1.7162031184398074e-2F; + result = (result * abs_value) + 3.0961594977611639e-2F; + result = (result * abs_value) - 5.0207843052845647e-2F; + result = (result * abs_value) + 8.8986946573346160e-2F; + result = (result * abs_value) - 2.1459960076929829e-1F; + result = (result * abs_value) + 1.5707963267948966F; + + // Scale our result + const float scale = scalar_sqrt(1.0F - abs_value); + result = result * scale; + + // Handle negative values through reflection + if (value < 0.0F) + result = constants::pi() - result; + + // Shift our final result + const float offset = constants::half_pi(); + result = offset - result; + return result; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-cosine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_acos(scalarf_arg0 value) RTM_NO_EXCEPT + { + // Use the identity: acos(value) + asin(value) = PI/2 + // This ends up being: acos(value) = PI/2 - asin(value) + // Since asin(value) = PI/2 - sqrt(1.0 - polynomial(value)) + // Our end result is acos(value) = sqrt(1.0 - polynomial(value)) + // This means we can re-use the same polynomial as asin() + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_value = _mm_and_ps(value.value, _mm_castsi128_ps(abs_mask)); + + // Calculate our value + const float x = _mm_cvtss_f32(abs_value); + float result = (x * -1.2690614339589956e-3F) + 6.7072304676685235e-3F; + result = (result * x) - 1.7162031184398074e-2F; + result = (result * x) + 3.0961594977611639e-2F; + result = (result * x) - 5.0207843052845647e-2F; + result = (result * x) + 8.8986946573346160e-2F; + result = (result * x) - 2.1459960076929829e-1F; + result = (result * x) + 1.5707963267948966F; + + // Scale our result + const __m128 scale = _mm_sqrt_ss(_mm_sub_ss(_mm_set_ps1(1.0F), abs_value)); + result = result * _mm_cvtss_f32(scale); + + // Handle negative values through reflection + if (_mm_cvtss_f32(value.value) < 0.0F) + result = constants::pi() - result; + + return scalarf{ _mm_set_ps1(result) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-cosine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float scalar_acos(float value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_acos(scalar_set(value))); +#elif defined(RTM_NEON_INTRINSICS) + return std::acos(value); +#else + // Use the identity: acos(value) + asin(value) = PI/2 + // This ends up being: acos(value) = PI/2 - asin(value) + // Since asin(value) = PI/2 - sqrt(1.0 - polynomial(value)) + // Our end result is acos(value) = sqrt(1.0 - polynomial(value)) + // This means we can re-use the same polynomial as asin() + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + const float abs_value = scalar_abs(value); + + // Calculate our value + float result = (abs_value * -1.2690614339589956e-3F) + 6.7072304676685235e-3F; + result = (result * abs_value) - 1.7162031184398074e-2F; + result = (result * abs_value) + 3.0961594977611639e-2F; + result = (result * abs_value) - 5.0207843052845647e-2F; + result = (result * abs_value) + 8.8986946573346160e-2F; + result = (result * abs_value) - 2.1459960076929829e-1F; + result = (result * abs_value) + 1.5707963267948966F; + + // Scale our result + const float scale = scalar_sqrt(1.0F - abs_value); + result = result * scale; + + // Handle negative values through reflection + if (value < 0.0F) + result = constants::pi() - result; + + return result; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the tangent of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_tan(scalarf_arg0 angle) RTM_NO_EXCEPT + { + // Use the identity: tan(angle) = sin(angle) / cos(angle) + scalarf sin_ = scalar_sin(angle); + scalarf cos_ = scalar_cos(angle); + if (scalar_cast(cos_) == 0.0F) + return scalar_set(std::copysign(std::numeric_limits::infinity(), scalar_cast(angle))); + + return scalar_div(sin_, cos_); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the tangent of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float scalar_tan(float angle) RTM_NO_EXCEPT + { +#if defined(RTM_NEON_INTRINSICS) + return std::tan(angle); +#else + // Use the identity: tan(angle) = sin(angle) / cos(angle) + scalarf angle_ = scalar_set(angle); + scalarf sin_ = scalar_sin(angle_); + scalarf cos_ = scalar_cos(angle_); + if (scalar_cast(cos_) == 0.0F) + return std::copysign(std::numeric_limits::infinity(), angle); + + return scalar_cast(scalar_div(sin_, cos_)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of the input. + // Note that due to the sign ambiguity, atan cannot determine which quadrant + // the value resides in. See scalar_atan2. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_atan(scalarf_arg0 value) RTM_NO_EXCEPT + { + // Use a degree 13 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Discard our sign, we'll restore it later + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_value = _mm_and_ps(value.value, _mm_castsi128_ps(abs_mask)); + + // Compute our value + __m128 is_larger_than_one = _mm_cmpgt_ss(abs_value, _mm_set_ps1(1.0F)); + __m128 reciprocal = scalar_reciprocal(scalarf{ abs_value }).value; + + __m128 x = RTM_VECTOR4F_SELECT(is_larger_than_one, reciprocal, abs_value); + + float x_s = _mm_cvtss_f32(x); + float x2 = x_s * x_s; + + float result = (x2 * 7.2128853633444123e-3F) - 3.5059680836411644e-2F; + result = (result * x2) + 8.1675882859940430e-2F; + result = (result * x2) - 1.3374657325451267e-1F; + result = (result * x2) + 1.9856563505717162e-1F; + result = (result * x2) - 3.3324998579202170e-1F; + result = (result * x2) + 1.0F; + result = result * x_s; + + __m128 result_s = _mm_set_ps1(result); + __m128 remapped = _mm_sub_ss(_mm_set_ps1(constants::half_pi()), result_s); + + // pi/2 - result + result_s = RTM_VECTOR4F_SELECT(is_larger_than_one, remapped, result_s); + + // Keep the original sign + result_s = _mm_or_ps(result_s, _mm_and_ps(value.value, _mm_set_ps1(-0.0F))); + + return scalarf{ result_s }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of the input. + // Note that due to the sign ambiguity, atan cannot determine which quadrant + // the value resides in. See scalar_atan2. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float RTM_SIMD_CALL scalar_atan(float value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_atan(scalar_set(value))); +#elif defined(RTM_NEON_INTRINSICS) + return std::atan(value); +#else + // Use a degree 13 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Discard our sign, we'll restore it later + float abs_value = scalar_abs(value); + + // Compute our value + float x = abs_value > 1.0F ? scalar_reciprocal(abs_value) : abs_value; + float x2 = x * x; + + float result = (x2 * 7.2128853633444123e-3F) - 3.5059680836411644e-2F; + result = (result * x2) + 8.1675882859940430e-2F; + result = (result * x2) - 1.3374657325451267e-1F; + result = (result * x2) + 1.9856563505717162e-1F; + result = (result * x2) - 3.3324998579202170e-1F; + result = (result * x2) + 1.0F; + result = result * x; + + if (abs_value > 1.0f) + result = constants::half_pi() - result; // pi/2 - result + + // Keep the original sign + result = value >= 0.0F ? result : -result; + + return result; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of [y/x] using the sign of the arguments to + // determine the correct quadrant. + // Y represents the proportion of the y-coordinate. + // X represents the proportion of the x-coordinate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline scalarf RTM_SIMD_CALL scalar_atan2(scalarf y, scalarf x) RTM_NO_EXCEPT + { + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + + const __m128 zero = _mm_setzero_ps(); + __m128 is_x_zero = _mm_cmpeq_ss(x.value, zero); + __m128 is_y_zero = _mm_cmpeq_ss(y.value, zero); + __m128 inputs_are_zero = _mm_and_ps(is_x_zero, is_y_zero); + + __m128 is_x_positive = _mm_cmpgt_ss(x.value, zero); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 y_sign = _mm_and_ps(y.value, sign_mask); + + // If X == 0.0, our offset is PI/2 otherwise it is PI both with the sign of Y + __m128 half_pi = _mm_set_ps1(constants::half_pi()); + __m128 pi = _mm_set_ps1(constants::pi()); + __m128 offset = _mm_or_ps(_mm_and_ps(is_x_zero, half_pi), _mm_andnot_ps(is_x_zero, pi)); + offset = _mm_or_ps(offset, y_sign); + + // If X > 0.0, our offset is 0.0 + offset = _mm_andnot_ps(is_x_positive, offset); + + // If X == 0.0 and Y == 0.0, our offset is 0.0 + offset = _mm_andnot_ps(inputs_are_zero, offset); + + __m128 angle = _mm_div_ss(y.value, x.value); + __m128 value = scalar_atan(scalarf{ angle }).value; + + // If X == 0.0, our value is 0.0 otherwise it is atan(y/x) + value = _mm_or_ps(_mm_and_ps(is_x_zero, zero), _mm_andnot_ps(is_x_zero, value)); + + // If X == 0.0 and Y == 0.0, our value is 0.0 + value = _mm_andnot_ps(inputs_are_zero, value); + + __m128 result = _mm_add_ss(value, offset); + return scalarf{ result }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns the arc-tangent of [y/x] using the sign of the arguments to + // determine the correct quadrant. + // Y represents the proportion of the y-coordinate. + // X represents the proportion of the x-coordinate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline float scalar_atan2(float y, float x) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return scalar_cast(scalar_atan2(scalar_set(y), scalar_set(x))); +#elif defined(RTM_NEON_INTRINSICS) + return std::atan2(y, x); +#else + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + + if (x == 0.0F) + { + if (y == 0.0F) + return 0.0F; + + return std::copysign(constants::half_pi(), y); + } + + float value = scalar_atan(y / x); + if (x > 0.0F) + return value; + + float offset = std::copysign(constants::pi(), y); + return value + offset; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Converts degrees into radians. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_deg_to_rad(float deg) RTM_NO_EXCEPT + { + return deg * constants::pi_div_one_eighty(); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts radians into degrees. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr float scalar_rad_to_deg(float rad) RTM_NO_EXCEPT + { + return rad * constants::one_eighty_div_pi(); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/type_traits.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/type_traits.h new file mode 100644 index 00000000..dc661bcb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/type_traits.h @@ -0,0 +1,85 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/types.h" +#include "rtm/version.h" + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Returns the proper types for a floating point type. + ////////////////////////////////////////////////////////////////////////// + template + struct float_traits {}; + + template<> + struct float_traits + { + using mask4 = mask4f; + + using scalar = scalarf; + using vector4 = vector4f; + using quat = quatf; + using qvv = qvvf; + + using matrix3x3 = matrix3x3f; + using matrix3x4 = matrix3x4f; + using matrix4x4 = matrix4x4f; + + using float1 = float; + using float2 = float2f; + using float3 = float3f; + using float4 = float4f; + + using int1 = uint32_t; + }; + + template<> + struct float_traits + { + using mask4 = mask4d; + + using scalar = scalard; + using vector4 = vector4d; + using quat = quatd; + using qvv = qvvd; + + using matrix3x3 = matrix3x3d; + using matrix3x4 = matrix3x4d; + using matrix4x4 = matrix4x4d; + + using float1 = double; + using float2 = float2d; + using float3 = float3d; + using float4 = float4d; + + using int1 = uint64_t; + }; + + RTM_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/types.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/types.h new file mode 100644 index 00000000..2a31c29b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/types.h @@ -0,0 +1,555 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/version.h" + +#include + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // A quaternion (4D complex number) where the imaginary part is the [w] component. + // It accurately represents a 3D rotation with no gimbal lock as long as it is kept normalized. + ////////////////////////////////////////////////////////////////////////// + using quatf = __m128; + + ////////////////////////////////////////////////////////////////////////// + // A quaternion (4D complex number) where the imaginary part is the [w] component. + // It accurately represents a 3D rotation with no gimbal lock as long as it is kept normalized. + ////////////////////////////////////////////////////////////////////////// + struct quatd + { + __m128d xy; + __m128d zw; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4D vector. + ////////////////////////////////////////////////////////////////////////// + using vector4f = __m128; + + ////////////////////////////////////////////////////////////////////////// + // A 4D vector. + ////////////////////////////////////////////////////////////////////////// + struct vector4d + { + __m128d xy; + __m128d zw; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask for 32 bit floats: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + using mask4f = __m128; + + ////////////////////////////////////////////////////////////////////////// + // A 4x64 bit vector comparison mask for 64 bit floats: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct mask4d + { + __m128d xy; + __m128d zw; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + using mask4i = __m128i; + + ////////////////////////////////////////////////////////////////////////// + // A 4x64 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct mask4q + { + __m128i xy; + __m128i zw; + }; +#elif defined(RTM_NEON_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // A quaternion (4D complex number) where the imaginary part is the [w] component. + // It accurately represents a 3D rotation with no gimbal lock as long as it is kept normalized. + ////////////////////////////////////////////////////////////////////////// + using quatf = float32x4_t; + + ////////////////////////////////////////////////////////////////////////// + // A quaternion (4D complex number) where the imaginary part is the [w] component. + // It accurately represents a 3D rotation with no gimbal lock as long as it is kept normalized. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) quatd + { + double x; + double y; + double z; + double w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4D vector. + ////////////////////////////////////////////////////////////////////////// + using vector4f = float32x4_t; + + ////////////////////////////////////////////////////////////////////////// + // A 4D vector. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) vector4d + { + double x; + double y; + double z; + double w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask for 32 bit floats: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + using mask4f = float32x4_t; + + ////////////////////////////////////////////////////////////////////////// + // A 4x64 bit vector comparison mask for 64 bit floats: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4d + { + uint64_t x; + uint64_t y; + uint64_t z; + uint64_t w; + }; + +#if defined(RTM_COMPILER_MSVC) + // MSVC uses a simple typedef to an identical underlying type for uint32x4_t and float32x4_t + // To avoid issues of duplicate symbols, we introduce a concrete type + + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4i + { + uint32x4_t value; + }; + + // Helper macros to simplify usage + #define RTM_IMPL_MASK4i_GET(mask) mask.value + #define RTM_IMPL_MASK4i_SET(mask) mask4i{ mask } +#else + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + using mask4i = uint32x4_t; + + // Helper macros to simplify usage + #define RTM_IMPL_MASK4i_GET(mask) mask + #define RTM_IMPL_MASK4i_SET(mask) mask +#endif + + ////////////////////////////////////////////////////////////////////////// + // A 4x64 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4q + { + uint64_t x; + uint64_t y; + uint64_t z; + uint64_t w; + }; +#else + ////////////////////////////////////////////////////////////////////////// + // A quaternion (4D complex number) where the imaginary part is the [w] component. + // It accurately represents a 3D rotation with no gimbal lock as long as it is kept normalized. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) quatf + { + float x; + float y; + float z; + float w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A quaternion (4D complex number) where the imaginary part is the [w] component. + // It accurately represents a 3D rotation with no gimbal lock as long as it is kept normalized. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) quatd + { + double x; + double y; + double z; + double w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4D vector. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) vector4f + { + float x; + float y; + float z; + float w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4D vector. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) vector4d + { + double x; + double y; + double z; + double w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask for 32 bit floats: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4f + { + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x64 bit vector comparison mask for 64 bit floats: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4d + { + uint64_t x; + uint64_t y; + uint64_t z; + uint64_t w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x32 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4i + { + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; + }; + + ////////////////////////////////////////////////////////////////////////// + // A 4x64 bit vector comparison mask: ~0 if true, 0 otherwise. + ////////////////////////////////////////////////////////////////////////// + struct alignas(16) mask4q + { + uint64_t x; + uint64_t y; + uint64_t z; + uint64_t w; + }; +#endif + +#if defined(RTM_SSE2_INTRINSICS) + // With SSE2, we use a concrete type for scalarf/scalard unlike other platforms and other types + // like vector4f and quatf. We don't use a concrete type when we can avoid it to help the compiler + // optimize as much as possible. But we must be able to tell a scalar apart from a vector for + // return type overloading and argument overloading. + // For example, we want to support vector_mul(vec4, vec4) and vector_mul(vec4, scalar). + // When scalarf is a 'float', the type is distinct and everything works as expected + // but if we use __m128, the type is the same as vector4f and we won't be able to tell + // them apart. + // Another example is vector_dot where we want to support returning a float, a scalarf, and + // a vector4f depending on what the user expects. We could always return a float/scalarf but + // if we need a vector4f it is less efficient if _mm_dp_ps is used: we would have an extra + // shuffle. + // Using a concrete type here allows us to tell the types apart and properly overload them + // when required. The compiler should still be able to optimize properly. + + ////////////////////////////////////////////////////////////////////////// + // A SIMD friendly scalar type. Different architectures have an easier or harder time + // working with scalar floating point numbers. For example, older PowerPC processors + // had to write to memory and reload from it to transfer from one register file into + // another (e.g convert from a float to a SIMD vector). Modern processors handle + // this much better but inefficiencies remain, especially with SSE. While it is + // free to convert a SIMD scalar into a float with _mm_cvtss_f32(..) the reverse generally + // requires the compiler to fill the unused SIMD lanes with known values (either zero or the same). + // This introduces an extra instruction that isn't always required when only the first lane is used + // such as with scalar_sqrt_reciprocal(..). By introducing a type for SIMD scalar values, + // each platform is free to make an optimal choice. + ////////////////////////////////////////////////////////////////////////// + struct scalarf + { + __m128 value; + }; + + ////////////////////////////////////////////////////////////////////////// + // A SIMD friendly scalar type. Different architectures have an easier or harder time + // working with scalar floating point numbers. For example, older PowerPC processors + // had to write to memory and reload from it to transfer from one register file into + // another (e.g convert from a float to a SIMD vector). Modern processors handle + // this much better but inefficiencies remain, especially with SSE. While it is + // free to convert a SIMD scalar into a float with _mm_cvtss_f32(..) the reverse generally + // requires the compiler to fill the unused SIMD lanes with known values (either zero or the same). + // This introduces an extra instruction that isn't always required when only the first lane is used + // such as with scalar_sqrt_reciprocal(..). By introducing a type for SIMD scalar values, + // each platform is free to make an optimal choice. + ////////////////////////////////////////////////////////////////////////// + struct scalard + { + __m128d value; + }; +#else + ////////////////////////////////////////////////////////////////////////// + // A SIMD friendly scalar type. Different architectures have an easier or harder time + // working with scalar floating point numbers. For example, older PowerPC processors + // had to write to memory and reload from it to transfer from one register file into + // another (e.g convert from a float to a SIMD vector). Modern processors handle + // this much better but inefficiencies remain, especially with SSE. While it is + // free to convert a SIMD scalar into a float with _mm_cvtss_f32(..) the reverse generally + // requires the compiler to fill the unused SIMD lanes with known values (either zero or the same). + // This introduces an extra instruction that isn't always required when only the first lane is used + // such as with scalar_sqrt_reciprocal(..). By introducing a type for SIMD scalar values, + // each platform is free to make an optimal choice. + ////////////////////////////////////////////////////////////////////////// + using scalarf = float; + + ////////////////////////////////////////////////////////////////////////// + // A SIMD friendly scalar type. Different architectures have an easier or harder time + // working with scalar floating point numbers. For example, older PowerPC processors + // had to write to memory and reload from it to transfer from one register file into + // another (e.g convert from a float to a SIMD vector). Modern processors handle + // this much better but inefficiencies remain, especially with SSE. While it is + // free to convert a SIMD scalar into a float with _mm_cvtss_f32(..) the reverse generally + // requires the compiler to fill the unused SIMD lanes with known values (either zero or the same). + // This introduces an extra instruction that isn't always required when only the first lane is used + // such as with scalar_sqrt_reciprocal(..). By introducing a type for SIMD scalar values, + // each platform is free to make an optimal choice. + ////////////////////////////////////////////////////////////////////////// + using scalard = double; +#endif + + ////////////////////////////////////////////////////////////////////////// + // A QVV transform represents a 3D rotation (quaternion), 3D translation (vector), and 3D scale (vector). + // It properly handles positive scaling but negative scaling is a bit more problematic. + // A best effort is made by converting the quaternion to a matrix during those operations. + // If scale fidelity is important, consider using an affine matrix 3x4 instead. + ////////////////////////////////////////////////////////////////////////// + struct qvvf + { + quatf rotation; + vector4f translation; + vector4f scale; + }; + + ////////////////////////////////////////////////////////////////////////// + // A QVV transform represents a 3D rotation (quaternion), 3D translation (vector), and 3D scale (vector). + // It properly handles positive scaling but negative scaling is a bit more problematic. + // A best effort is made by converting the quaternion to a matrix during those operations. + // If scale fidelity is important, consider using an affine matrix 3x4 instead. + ////////////////////////////////////////////////////////////////////////// + struct qvvd + { + quatd rotation; + vector4d translation; + vector4d scale; + }; + + ////////////////////////////////////////////////////////////////////////// + // A generic 3x3 matrix. + // Note: The [w] component of every column vector is undefined. + ////////////////////////////////////////////////////////////////////////// + struct matrix3x3f + { + vector4f x_axis; + vector4f y_axis; + vector4f z_axis; + }; + + ////////////////////////////////////////////////////////////////////////// + // A generic 3x3 matrix. + // Note: The [w] component of every column vector is undefined. + ////////////////////////////////////////////////////////////////////////// + struct matrix3x3d + { + vector4d x_axis; + vector4d y_axis; + vector4d z_axis; + }; + + ////////////////////////////////////////////////////////////////////////// + // An 3x4 affine matrix represents a 3D rotation, 3D translation, and 3D scale. + // It properly deals with skew/shear when present but once scale with mirroring is combined, + // it cannot be safely extracted back. + // + // Affine matrices are 4x4 but have their last row always equal to [0, 0, 0, 1] which is why it is 3x4. + // Note: We do not track the implicit last row and it is thus undefined. + // + // Left handed coordinate system: + // X axis == forward + // Y axis == right + // Z axis == up + ////////////////////////////////////////////////////////////////////////// + struct matrix3x4f + { + vector4f x_axis; + vector4f y_axis; + vector4f z_axis; + vector4f w_axis; + }; + + ////////////////////////////////////////////////////////////////////////// + // An 3x4 affine matrix represents a 3D rotation, 3D translation, and 3D scale. + // It properly deals with skew/shear when present but once scale with mirroring is combined, + // it cannot be safely extracted back. + // + // Affine matrices are 4x4 but have their last row always equal to [0, 0, 0, 1] which is why it is 3x4. + // Note: We do not track the implicit last row and it is thus undefined. + // + // Left handed coordinate system: + // X axis == forward + // Y axis == right + // Z axis == up + ////////////////////////////////////////////////////////////////////////// + struct matrix3x4d + { + vector4d x_axis; + vector4d y_axis; + vector4d z_axis; + vector4d w_axis; + }; + + ////////////////////////////////////////////////////////////////////////// + // A generic 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + struct matrix4x4f + { + vector4f x_axis; + vector4f y_axis; + vector4f z_axis; + vector4f w_axis; + }; + + ////////////////////////////////////////////////////////////////////////// + // A generic 4x4 matrix. + ////////////////////////////////////////////////////////////////////////// + struct matrix4x4d + { + vector4d x_axis; + vector4d y_axis; + vector4d z_axis; + vector4d w_axis; + }; + + ////////////////////////////////////////////////////////////////////////// + // Represents a component when mixing/shuffling/permuting vectors. + // [xyzw] are used to refer to the first input while [abcd] refer to the second input. + ////////////////////////////////////////////////////////////////////////// + enum class mix4 + { + x = 0, + y = 1, + z = 2, + w = 3, + + a = 4, + b = 5, + c = 6, + d = 7, + }; + + ////////////////////////////////////////////////////////////////////////// + // Represents an axis in 3D. + ////////////////////////////////////////////////////////////////////////// + enum class axis3 + { + x = 0, + y = 1, + z = 2, + }; + + ////////////////////////////////////////////////////////////////////////// + // Represents an axis in 4D. + ////////////////////////////////////////////////////////////////////////// + enum class axis4 + { + x = 0, + y = 1, + z = 2, + w = 3, + }; + + + ////////////////////////////////////////////////////////////////////////// + // Various unaligned types suitable for interop. with GPUs, etc. + ////////////////////////////////////////////////////////////////////////// + + + struct float2f + { + float x; + float y; + }; + + struct float3f + { + float x; + float y; + float z; + }; + + struct float4f + { + float x; + float y; + float z; + float w; + }; + + struct float2d + { + double x; + double y; + }; + + struct float3d + { + double x; + double y; + double z; + }; + + struct float4d + { + double x; + double y; + double z; + double w; + }; + + RTM_IMPL_VERSION_NAMESPACE_END +} + +// Always include the register passing typedefs +#include "rtm/impl/type_args.h" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/vector4d.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/vector4d.h new file mode 100644 index 00000000..996edada --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/vector4d.h @@ -0,0 +1,2266 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/math.h" +#include "rtm/scalard.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/memory_utils.h" +#include "rtm/impl/vector_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Setters, getters, and casts + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector4 from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load(const double* input) RTM_NO_EXCEPT + { + return vector_set(input[0], input[1], input[2], input[3]); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an input scalar from memory into the [x] component and sets the [yzw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load1(const double* input) RTM_NO_EXCEPT + { + return vector_set(input[0], 0.0, 0.0, 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector2 from memory and sets the [zw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load2(const double* input) RTM_NO_EXCEPT + { + return vector_set(input[0], input[1], 0.0, 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector3 from memory and sets the [w] component to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load3(const double* input) RTM_NO_EXCEPT + { + return vector_set(input[0], input[1], input[2], 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector4 from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load(const float4d* input) RTM_NO_EXCEPT + { + return vector_set(input->x, input->y, input->z, input->w); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector2 from memory and sets the [zw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load2(const float2d* input) RTM_NO_EXCEPT + { + return vector_set(input->x, input->y, 0.0, 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector3 from memory and sets the [w] component to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_load3(const float3d* input) RTM_NO_EXCEPT + { + return vector_set(input->x, input->y, input->z, 0.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an input scalar from memory into the [xyzw] components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_broadcast(const double* input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128d value = _mm_load1_pd(input); + return vector4d{ value, value }; +#else + return vector_set(*input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a quaternion to a vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d quat_to_vector(const quatd& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ input.xy, input.zw }; +#else + return vector4d{ input.x, input.y, input.z, input.w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a vector4 float32 variant to a float64 variant. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_cast(const vector4f& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_cvtps_pd(input), _mm_cvtps_pd(_mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 2, 3, 2))) }; +#elif defined(RTM_NEON_INTRINSICS) + return vector4d{ double(vgetq_lane_f32(input, 0)), double(vgetq_lane_f32(input, 1)), double(vgetq_lane_f32(input, 2)), double(vgetq_lane_f32(input, 3)) }; +#else + return vector4d{ double(input.x), double(input.y), double(input.z), double(input.w) }; +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_get_x + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(input.xy); +#else + return input.x; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ input.xy }; + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [x] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_get_x vector_get_x(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_get_x{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_get_y + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_shuffle_pd(input.xy, input.xy, 1)); +#else + return input.y; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ _mm_shuffle_pd(input.xy, input.xy, 1) }; + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [y] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_get_y vector_get_y(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_get_y{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_get_z + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(input.zw); +#else + return input.z; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ input.zw }; + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [z] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_get_z vector_get_z(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_get_z{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_get_w + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtsd_f64(_mm_shuffle_pd(input.zw, input.zw, 1)); +#else + return input.w; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + return scalard{ _mm_shuffle_pd(input.zw, input.zw, 1) }; + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [w] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_get_w vector_get_w(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_get_w{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + template + struct vector4d_vector_get_component_static + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (rtm_impl::static_condition::test()) + return vector_get_x(input); + else if (rtm_impl::static_condition::test()) + return vector_get_y(input); + else if (rtm_impl::static_condition::test()) + return vector_get_z(input); + else + return vector_get_w(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (rtm_impl::static_condition::test()) + return vector_get_x(input); + else if (rtm_impl::static_condition::test()) + return vector_get_y(input); + else if (rtm_impl::static_condition::test()) + return vector_get_z(input); + else + return vector_get_w(input); + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 desired component. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_get_component_static vector_get_component(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_get_component_static{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_get_component + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (xyzw == mix4::x) + return vector_get_x(input); + else if (xyzw == mix4::y) + return vector_get_y(input); + else if (xyzw == mix4::z) + return vector_get_z(input); + else + return vector_get_w(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (xyzw == mix4::x) + return vector_get_x(input); + else if (xyzw == mix4::y) + return vector_get_y(input); + else if (xyzw == mix4::z) + return vector_get_z(input); + else + return vector_get_w(input); + } +#endif + + vector4d input; + mix4 component; + int padding[3]; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 desired component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_get_component vector_get_component(const vector4d& input, mix4 component) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_get_component{ input, component, { 0 } }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest component in the input vector as a scalar. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_get_min_component vector_get_min_component(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_get_min_component{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the largest component in the input vector as a scalar. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_get_max_component vector_get_max_component(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_get_max_component{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [x] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_x(const vector4d& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_move_sd(input.xy, _mm_set_sd(lane_value)), input.zw }; +#else + return vector4d{ lane_value, input.y, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [x] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_x(const vector4d& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return vector4d{ _mm_move_sd(input.xy, lane_value.value), input.zw }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [y] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_y(const vector4d& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_shuffle_pd(input.xy, _mm_set_sd(lane_value), 0), input.zw }; +#else + return vector4d{ input.x, lane_value, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [y] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_y(const vector4d& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return vector4d{ _mm_shuffle_pd(input.xy, lane_value.value, 0), input.zw }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [z] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_z(const vector4d& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ input.xy, _mm_move_sd(input.zw, _mm_set_sd(lane_value)) }; +#else + return vector4d{ input.x, input.y, lane_value, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [z] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_z(const vector4d& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return vector4d{ input.xy, _mm_move_sd(input.zw, lane_value.value) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [w] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_w(const vector4d& input, double lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ input.xy, _mm_shuffle_pd(input.zw, _mm_set_sd(lane_value), 0) }; +#else + return vector4d{ input.x, input.y, input.z, lane_value }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [w] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_set_w(const vector4d& input, const scalard& lane_value) RTM_NO_EXCEPT + { + return vector4d{ input.xy, _mm_shuffle_pd(input.zw, lane_value.value, 0) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns a floating point pointer to the vector4 data. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE const double* vector_to_pointer(const vector4d& input) RTM_NO_EXCEPT + { + return reinterpret_cast(&input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector4 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store(const vector4d& input, double* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + output[1] = vector_get_y(input); + output[2] = vector_get_z(input); + output[3] = vector_get_w(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector1 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store1(const vector4d& input, double* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector2 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store2(const vector4d& input, double* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + output[1] = vector_get_y(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector3 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store3(const vector4d& input, double* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + output[1] = vector_get_y(input); + output[2] = vector_get_z(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector4 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store(const vector4d& input, uint8_t* output) RTM_NO_EXCEPT + { + std::memcpy(output, &input, sizeof(vector4d)); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector1 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store1(const vector4d& input, uint8_t* output) + { + std::memcpy(output, &input, sizeof(double) * 1); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector2 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store2(const vector4d& input, uint8_t* output) + { + std::memcpy(output, &input, sizeof(double) * 2); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector3 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store3(const vector4d& input, uint8_t* output) + { + std::memcpy(output, &input, sizeof(double) * 3); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector4 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store(const vector4d& input, float4d* output) RTM_NO_EXCEPT + { + output->x = vector_get_x(input); + output->y = vector_get_y(input); + output->z = vector_get_z(input); + output->w = vector_get_w(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector2 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store2(const vector4d& input, float2d* output) RTM_NO_EXCEPT + { + output->x = vector_get_x(input); + output->y = vector_get_y(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector3 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void vector_store3(const vector4d& input, float3d* output) RTM_NO_EXCEPT + { + output->x = vector_get_x(input); + output->y = vector_get_y(input); + output->z = vector_get_z(input); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Arithmetic + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Per component addition of the two inputs: lhs + rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_add(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_add_pd(lhs.xy, rhs.xy), _mm_add_pd(lhs.zw, rhs.zw) }; +#else + return vector_set(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component subtraction of the two inputs: lhs - rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_sub(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_sub_pd(lhs.xy, rhs.xy), _mm_sub_pd(lhs.zw, rhs.zw) }; +#else + return vector_set(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication of the two inputs: lhs * rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mul(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_mul_pd(lhs.xy, rhs.xy), _mm_mul_pd(lhs.zw, rhs.zw) }; +#else + return vector_set(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication of the vector by a scalar: lhs * rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mul(const vector4d& lhs, double rhs) RTM_NO_EXCEPT + { + return vector_mul(lhs, vector_set(rhs)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication of the vector by a scalar: lhs * rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mul(const vector4d& lhs, const scalard& rhs) RTM_NO_EXCEPT + { + const __m128d rhs_xx = _mm_shuffle_pd(rhs.value, rhs.value, 0); + return vector4d{ _mm_mul_pd(lhs.xy, rhs_xx), _mm_mul_pd(lhs.zw, rhs_xx) }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component division of the two inputs: lhs / rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_div(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_div_pd(lhs.xy, rhs.xy), _mm_div_pd(lhs.zw, rhs.zw) }; +#else + return vector_set(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component maximum of the two inputs: max(lhs, rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_max(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_max_pd(lhs.xy, rhs.xy), _mm_max_pd(lhs.zw, rhs.zw) }; +#else + return vector_set(scalar_max(lhs.x, rhs.x), scalar_max(lhs.y, rhs.y), scalar_max(lhs.z, rhs.z), scalar_max(lhs.w, rhs.w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component minimum of the two inputs: min(lhs, rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_min(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_min_pd(lhs.xy, rhs.xy), _mm_min_pd(lhs.zw, rhs.zw) }; +#else + return vector_set(scalar_min(lhs.x, rhs.x), scalar_min(lhs.y, rhs.y), scalar_min(lhs.z, rhs.z), scalar_min(lhs.w, rhs.w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component clamping of an input between a minimum and a maximum value: min(max_value, max(min_value, input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_clamp(const vector4d& input, const vector4d& min_value, const vector4d& max_value) RTM_NO_EXCEPT + { + return vector_min(max_value, vector_max(min_value, input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component absolute of the input: abs(input) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_abs(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + vector4d zero{ _mm_setzero_pd(), _mm_setzero_pd() }; + return vector_max(vector_sub(zero, input), input); +#else + return vector_set(scalar_abs(input.x), scalar_abs(input.y), scalar_abs(input.z), scalar_abs(input.w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component negation of the input: -input + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_neg(const vector4d& input) RTM_NO_EXCEPT + { + return vector_mul(input, -1.0); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component reciprocal of the input: 1.0 / input + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_reciprocal(const vector4d& input) RTM_NO_EXCEPT + { + return vector_div(vector_set(1.0), input); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component square root of the input: sqrt(input) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_sqrt(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return vector4d{ _mm_sqrt_pd(input.xy), _mm_sqrt_pd(input.zw) }; +#else + scalard x = vector_get_x(input); + scalard y = vector_get_y(input); + scalard z = vector_get_z(input); + scalard w = vector_get_w(input); + return vector_set(scalar_sqrt(x), scalar_sqrt(y), scalar_sqrt(z), scalar_sqrt(w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component returns the smallest integer value not less than the input (round towards positive infinity). + // vector_ceil([1.8, 1.0, -1.8, -1.0]) = [2.0, 1.0, -1.0, -1.0] + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_ceil(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large_xy = _mm_cmpge_pd(abs_input_xy, fractional_limit); + __m128d is_input_large_zw = _mm_cmpge_pd(abs_input_zw, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + __m128d is_nan_zw = _mm_cmpneq_pd(input.zw, input.zw); + + // Combine our masks to determine if we should return the original value + __m128d use_original_input_xy = _mm_or_pd(is_input_large_xy, is_nan_xy); + __m128d use_original_input_zw = _mm_or_pd(is_input_large_zw, is_nan_zw); + + // Convert to an integer and back + __m128d integer_part_xy = _mm_cvtepi32_pd(_mm_cvtpd_epi32(input.xy)); + __m128d integer_part_zw = _mm_cvtepi32_pd(_mm_cvtpd_epi32(input.zw)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + __m128d is_positive_xy = _mm_cmplt_pd(integer_part_xy, input.xy); + __m128d is_positive_zw = _mm_cmplt_pd(integer_part_zw, input.zw); + + // Our mask output is 64 bit wide but to convert to a bias, we need 32 bit integers + is_positive_xy = _mm_castps_pd(_mm_shuffle_ps(_mm_castpd_ps(is_positive_xy), _mm_castpd_ps(is_positive_xy), _MM_SHUFFLE(2, 0, 2, 0))); + is_positive_zw = _mm_castps_pd(_mm_shuffle_ps(_mm_castpd_ps(is_positive_zw), _mm_castpd_ps(is_positive_zw), _MM_SHUFFLE(2, 0, 2, 0))); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Negative values will yield a 0.0 bias + __m128d bias_xy = _mm_cvtepi32_pd(_mm_castpd_si128(is_positive_xy)); + __m128d bias_zw = _mm_cvtepi32_pd(_mm_castpd_si128(is_positive_zw)); + + // Subtract our bias to properly handle positive values + integer_part_xy = _mm_sub_pd(integer_part_xy, bias_xy); + integer_part_zw = _mm_sub_pd(integer_part_zw, bias_zw); + + __m128d result_xy = _mm_or_pd(_mm_and_pd(use_original_input_xy, input.xy), _mm_andnot_pd(use_original_input_xy, integer_part_xy)); + __m128d result_zw = _mm_or_pd(_mm_and_pd(use_original_input_zw, input.zw), _mm_andnot_pd(use_original_input_zw, integer_part_zw)); + return vector4d{ result_xy, result_zw }; +#else + return vector_set(scalar_ceil(vector_get_x(input)), scalar_ceil(vector_get_y(input)), scalar_ceil(vector_get_z(input)), scalar_ceil(vector_get_w(input))); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component returns the largest integer value not greater than the input (round towards negative infinity). + // vector_floor([1.8, 1.0, -1.8, -1.0]) = [1.0, 1.0, -2.0, -1.0] + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_floor(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return vector4d{ _mm_floor_pd(input.xy), _mm_floor_pd(input.zw) }; +#elif defined(RTM_SSE2_INTRINSICS) + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large_xy = _mm_cmpge_pd(abs_input_xy, fractional_limit); + __m128d is_input_large_zw = _mm_cmpge_pd(abs_input_zw, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + __m128d is_nan_zw = _mm_cmpneq_pd(input.zw, input.zw); + + // Combine our masks to determine if we should return the original value + __m128d use_original_input_xy = _mm_or_pd(is_input_large_xy, is_nan_xy); + __m128d use_original_input_zw = _mm_or_pd(is_input_large_zw, is_nan_zw); + + // Convert to an integer and back + __m128d integer_part_xy = _mm_cvtepi32_pd(_mm_cvtpd_epi32(input.xy)); + __m128d integer_part_zw = _mm_cvtepi32_pd(_mm_cvtpd_epi32(input.zw)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + __m128d is_negative_xy = _mm_cmpgt_pd(integer_part_xy, input.xy); + __m128d is_negative_zw = _mm_cmpgt_pd(integer_part_zw, input.zw); + + // Our mask output is 64 bit wide but to convert to a bias, we need 32 bit integers + is_negative_xy = _mm_castps_pd(_mm_shuffle_ps(_mm_castpd_ps(is_negative_xy), _mm_castpd_ps(is_negative_xy), _MM_SHUFFLE(2, 0, 2, 0))); + is_negative_zw = _mm_castps_pd(_mm_shuffle_ps(_mm_castpd_ps(is_negative_zw), _mm_castpd_ps(is_negative_zw), _MM_SHUFFLE(2, 0, 2, 0))); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Positive values will yield a 0.0 bias + __m128d bias_xy = _mm_cvtepi32_pd(_mm_castpd_si128(is_negative_xy)); + __m128d bias_zw = _mm_cvtepi32_pd(_mm_castpd_si128(is_negative_zw)); + + // Add our bias to properly handle negative values + integer_part_xy = _mm_add_pd(integer_part_xy, bias_xy); + integer_part_zw = _mm_add_pd(integer_part_zw, bias_zw); + + __m128d result_xy = _mm_or_pd(_mm_and_pd(use_original_input_xy, input.xy), _mm_andnot_pd(use_original_input_xy, integer_part_xy)); + __m128d result_zw = _mm_or_pd(_mm_and_pd(use_original_input_zw, input.zw), _mm_andnot_pd(use_original_input_zw, integer_part_zw)); + return vector4d{ result_xy, result_zw }; +#else + return vector_set(scalar_floor(vector_get_x(input)), scalar_floor(vector_get_y(input)), scalar_floor(vector_get_z(input)), scalar_floor(vector_get_w(input))); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // 3D cross product: lhs x rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_cross3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { + // cross(a, b) = (a.yzx * b.zxy) - (a.zxy * b.yzx) + const double lhs_x = vector_get_x(lhs); + const double lhs_y = vector_get_y(lhs); + const double lhs_z = vector_get_z(lhs); + const double rhs_x = vector_get_x(rhs); + const double rhs_y = vector_get_y(rhs); + const double rhs_z = vector_get_z(rhs); + return vector_set((lhs_y * rhs_z) - (lhs_z * rhs_y), (lhs_z * rhs_x) - (lhs_x * rhs_z), (lhs_x * rhs_y) - (lhs_y * rhs_x)); + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_dot + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard lhs_x = vector_get_x(lhs); + const scalard lhs_y = vector_get_y(lhs); + const scalard lhs_z = vector_get_z(lhs); + const scalard lhs_w = vector_get_w(lhs); + const scalard rhs_x = vector_get_x(rhs); + const scalard rhs_y = vector_get_y(rhs); + const scalard rhs_z = vector_get_z(rhs); + const scalard rhs_w = vector_get_w(rhs); + const scalard xx = scalar_mul(lhs_x, rhs_x); + const scalard yy = scalar_mul(lhs_y, rhs_y); + const scalard zz = scalar_mul(lhs_z, rhs_z); + const scalard ww = scalar_mul(lhs_w, rhs_w); + return scalar_cast(scalar_add(scalar_add(xx, yy), scalar_add(zz, ww))); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard lhs_x = vector_get_x(lhs); + const scalard lhs_y = vector_get_y(lhs); + const scalard lhs_z = vector_get_z(lhs); + const scalard lhs_w = vector_get_w(lhs); + const scalard rhs_x = vector_get_x(rhs); + const scalard rhs_y = vector_get_y(rhs); + const scalard rhs_z = vector_get_z(rhs); + const scalard rhs_w = vector_get_w(rhs); + const scalard xx = scalar_mul(lhs_x, rhs_x); + const scalard yy = scalar_mul(lhs_y, rhs_y); + const scalard zz = scalar_mul(lhs_z, rhs_z); + const scalard ww = scalar_mul(lhs_w, rhs_w); + return scalar_add(scalar_add(xx, yy), scalar_add(zz, ww)); + } +#endif + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4d() const RTM_NO_EXCEPT + { + const scalard dot = *this; + return vector_set(dot); + } + + vector4d lhs; + vector4d rhs; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // 4D dot product: lhs . rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_dot vector_dot(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_dot{ lhs, rhs }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_dot3 + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d x2_y2 = _mm_mul_pd(lhs.xy, rhs.xy); + __m128d z2_w2 = _mm_mul_pd(lhs.zw, rhs.zw); + __m128d y2 = _mm_shuffle_pd(x2_y2, x2_y2, 1); + __m128d x2y2 = _mm_add_sd(x2_y2, y2); + return _mm_cvtsd_f64(_mm_add_sd(x2y2, z2_w2)); +#else + return (vector_get_x(lhs) * vector_get_x(rhs)) + (vector_get_y(lhs) * vector_get_y(rhs)) + (vector_get_z(lhs) * vector_get_z(rhs)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + __m128d x2_y2 = _mm_mul_pd(lhs.xy, rhs.xy); + __m128d z2_w2 = _mm_mul_pd(lhs.zw, rhs.zw); + __m128d y2 = _mm_shuffle_pd(x2_y2, x2_y2, 1); + __m128d x2y2 = _mm_add_sd(x2_y2, y2); + return scalard{ _mm_add_sd(x2y2, z2_w2) }; + } +#endif + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4d() const RTM_NO_EXCEPT + { + const scalard dot = *this; + return vector_set(dot); + } + + vector4d lhs; + vector4d rhs; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // 3D dot product: lhs . rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_dot3 vector_dot3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_dot3{ lhs, rhs }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the squared length/norm of the vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_dot vector_length_squared(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_dot{ input, input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the squared length/norm of the vector3. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_dot3 vector_length_squared3(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_dot3{ input, input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_length + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared(input); + return scalar_cast(scalar_sqrt(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared(input); + return scalar_sqrt(len_sq); + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the length/norm of the vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_length vector_length(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_length{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_length3 + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared3(input); + return scalar_cast(scalar_sqrt(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared3(input); + return scalar_sqrt(len_sq); + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the length/norm of the vector3. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_length3 vector_length3(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_length3{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_length_reciprocal + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared(input); + return scalar_cast(scalar_sqrt_reciprocal(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared(input); + return scalar_sqrt_reciprocal(len_sq); + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal length/norm of the vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_length_reciprocal vector_length_reciprocal(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_length_reciprocal{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4d_vector_length_reciprocal3 + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator double() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared3(input); + return scalar_cast(scalar_sqrt_reciprocal(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalard() const RTM_NO_EXCEPT + { + const scalard len_sq = vector_length_squared3(input); + return scalar_sqrt_reciprocal(len_sq); + } +#endif + + vector4d input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal length/norm of the vector3. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4d_vector_length_reciprocal3 vector_length_reciprocal3(const vector4d& input) RTM_NO_EXCEPT + { + return rtm_impl::vector4d_vector_length_reciprocal3{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the distance between two 3D points. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE rtm_impl::vector4d_vector_length3 vector_distance3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { + const vector4d difference = vector_sub(lhs, rhs); + return rtm_impl::vector4d_vector_length3{ difference }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a normalized vector3. + // If the length of the input is not finite or zero, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_normalize3(const vector4d& input) RTM_NO_EXCEPT + { + // Reciprocal is more accurate to normalize with + const scalard len_sq = vector_length_squared3(input); + return vector_mul(input, scalar_sqrt_reciprocal(len_sq)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a normalized vector3. + // If the length of the input is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_normalize3(const vector4d& input, const vector4d& fallback, double threshold = 1.0E-8) RTM_NO_EXCEPT + { + // Reciprocal is more accurate to normalize with + const scalard len_sq = vector_length_squared3(input); + if (scalar_cast(len_sq) >= threshold) + return vector_mul(input, scalar_sqrt_reciprocal(len_sq)); + else + return fallback; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the fractional part of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_fraction(const vector4d& input) RTM_NO_EXCEPT + { + return vector_set(scalar_fraction(vector_get_x(input)), scalar_fraction(vector_get_y(input)), scalar_fraction(vector_get_z(input)), scalar_fraction(vector_get_w(input))); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * v1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mul_add(const vector4d& v0, const vector4d& v1, const vector4d& v2) RTM_NO_EXCEPT + { + return vector_add(vector_mul(v0, v1), v2); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mul_add(const vector4d& v0, double s1, const vector4d& v2) RTM_NO_EXCEPT + { + return vector_add(vector_mul(v0, s1), v2); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mul_add(const vector4d& v0, const scalard& s1, const vector4d& v2) RTM_NO_EXCEPT + { + return vector_add(vector_mul(v0, s1), v2); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * v1) - v2) + // This is mathematically equivalent to: v2 - (v0 * v1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_neg_mul_sub(const vector4d& v0, const vector4d& v1, const vector4d& v2) RTM_NO_EXCEPT + { + return vector_sub(v2, vector_mul(v0, v1)); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_neg_mul_sub(const vector4d& v0, double s1, const vector4d& v2) RTM_NO_EXCEPT + { + return vector_sub(v2, vector_mul(v0, s1)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_neg_mul_sub(const vector4d& v0, const scalard& s1, const vector4d& v2) RTM_NO_EXCEPT + { + return vector_sub(v2, vector_mul(v0, s1)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_lerp(const vector4d& start, const vector4d& end, double alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return vector_mul_add(end, alpha, vector_neg_mul_sub(start, alpha, start)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_lerp(const vector4d& start, const vector4d& end, const scalard& alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + const vector4d alpha_v = vector_set(alpha); + return vector_mul_add(end, alpha_v, vector_neg_mul_sub(start, alpha_v, start)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_lerp(const vector4d& start, const vector4d& end, const vector4d& alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return vector_mul_add(end, alpha, vector_neg_mul_sub(start, alpha, start)); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Comparisons and masking + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if equal, otherwise 0: lhs == rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4d vector_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmpeq_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmpeq_pd(lhs.zw, rhs.zw); + return mask4d{ xy_lt_pd, zw_lt_pd }; +#else + return mask4d{ rtm_impl::get_mask_value(lhs.x == rhs.x), rtm_impl::get_mask_value(lhs.y == rhs.y), rtm_impl::get_mask_value(lhs.z == rhs.z), rtm_impl::get_mask_value(lhs.w == rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if less than, otherwise 0: lhs < rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4d vector_less_than(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmplt_pd(lhs.zw, rhs.zw); + return mask4d{xy_lt_pd, zw_lt_pd}; +#else + return mask4d{rtm_impl::get_mask_value(lhs.x < rhs.x), rtm_impl::get_mask_value(lhs.y < rhs.y), rtm_impl::get_mask_value(lhs.z < rhs.z), rtm_impl::get_mask_value(lhs.w < rhs.w)}; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if less equal, otherwise 0: lhs <= rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4d vector_less_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmple_pd(lhs.zw, rhs.zw); + return mask4d{ xy_lt_pd, zw_lt_pd }; +#else + return mask4d{ rtm_impl::get_mask_value(lhs.x <= rhs.x), rtm_impl::get_mask_value(lhs.y <= rhs.y), rtm_impl::get_mask_value(lhs.z <= rhs.z), rtm_impl::get_mask_value(lhs.w <= rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if greater than, otherwise 0: lhs > rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4d vector_greater_than(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpgt_pd(lhs.zw, rhs.zw); + return mask4d{ xy_ge_pd, zw_ge_pd }; +#else + return mask4d{ rtm_impl::get_mask_value(lhs.x > rhs.x), rtm_impl::get_mask_value(lhs.y > rhs.y), rtm_impl::get_mask_value(lhs.z > rhs.z), rtm_impl::get_mask_value(lhs.w > rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if greater equal, otherwise 0: lhs >= rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4d vector_greater_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpge_pd(lhs.zw, rhs.zw); + return mask4d{ xy_ge_pd, zw_ge_pd }; +#else + return mask4d{ rtm_impl::get_mask_value(lhs.x >= rhs.x), rtm_impl::get_mask_value(lhs.y >= rhs.y), rtm_impl::get_mask_value(lhs.z >= rhs.z), rtm_impl::get_mask_value(lhs.w >= rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are less than, otherwise false: all(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_less_than(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmplt_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_lt_pd) & _mm_movemask_pd(zw_lt_pd)) == 3; +#else + return lhs.x < rhs.x && lhs.y < rhs.y && lhs.z < rhs.z && lhs.w < rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are less than, otherwise false: all(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_less_than2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_lt_pd) == 3; +#else + return lhs.x < rhs.x && lhs.y < rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are less than, otherwise false: all(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_less_than3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmplt_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_lt_pd) == 3 && (_mm_movemask_pd(zw_lt_pd) & 1) == 1; +#else + return lhs.x < rhs.x && lhs.y < rhs.y && lhs.z < rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are less than, otherwise false: any(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_less_than(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmplt_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_lt_pd) | _mm_movemask_pd(zw_lt_pd)) != 0; +#else + return lhs.x < rhs.x || lhs.y < rhs.y || lhs.z < rhs.z || lhs.w < rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are less than, otherwise false: any(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_less_than2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_lt_pd) != 0; +#else + return lhs.x < rhs.x || lhs.y < rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are less than, otherwise false: any(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_less_than3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_lt_pd = _mm_cmplt_pd(lhs.xy, rhs.xy); + __m128d zw_lt_pd = _mm_cmplt_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_lt_pd) != 0 || (_mm_movemask_pd(zw_lt_pd) & 0x1) != 0; +#else + return lhs.x < rhs.x || lhs.y < rhs.y || lhs.z < rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are less equal, otherwise false: all(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_less_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_le_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + __m128d zw_le_pd = _mm_cmple_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_le_pd) & _mm_movemask_pd(zw_le_pd)) == 3; +#else + return lhs.x <= rhs.x && lhs.y <= rhs.y && lhs.z <= rhs.z && lhs.w <= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are less equal, otherwise false: all(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_less_equal2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_le_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_le_pd) == 3; +#else + return lhs.x <= rhs.x && lhs.y <= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are less equal, otherwise false: all(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_less_equal3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_le_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + __m128d zw_le_pd = _mm_cmple_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_le_pd) == 3 && (_mm_movemask_pd(zw_le_pd) & 1) != 0; +#else + return lhs.x <= rhs.x && lhs.y <= rhs.y && lhs.z <= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are less equal, otherwise false: any(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_less_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_le_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + __m128d zw_le_pd = _mm_cmple_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_le_pd) | _mm_movemask_pd(zw_le_pd)) != 0; +#else + return lhs.x <= rhs.x || lhs.y <= rhs.y || lhs.z <= rhs.z || lhs.w <= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are less equal, otherwise false: any(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_less_equal2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_le_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_le_pd) != 0; +#else + return lhs.x <= rhs.x || lhs.y <= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are less equal, otherwise false: any(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_less_equal3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_le_pd = _mm_cmple_pd(lhs.xy, rhs.xy); + __m128d zw_le_pd = _mm_cmple_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_le_pd) != 0 || (_mm_movemask_pd(zw_le_pd) & 1) != 0; +#else + return lhs.x <= rhs.x || lhs.y <= rhs.y || lhs.z <= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are greater than, otherwise false: all(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_greater_than(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpgt_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_ge_pd) & _mm_movemask_pd(zw_ge_pd)) == 3; +#else + return lhs.x > rhs.x && lhs.y > rhs.y && lhs.z > rhs.z && lhs.w > rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are greater than, otherwise false: all(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_greater_than2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_ge_pd) == 3; +#else + return lhs.x > rhs.x && lhs.y > rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are greater than, otherwise false: all(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_greater_than3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpgt_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_ge_pd) == 3 && (_mm_movemask_pd(zw_ge_pd) & 1) != 0; +#else + return lhs.x > rhs.x && lhs.y > rhs.y && lhs.z > rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are greater than, otherwise false: any(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_greater_than(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpgt_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_ge_pd) | _mm_movemask_pd(zw_ge_pd)) != 0; +#else + return lhs.x > rhs.x || lhs.y > rhs.y || lhs.z > rhs.z || lhs.w > rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are greater than, otherwise false: any(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_greater_than2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_ge_pd) != 0; +#else + return lhs.x > rhs.x || lhs.y > rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are greater than, otherwise false: any(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_greater_than3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpgt_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpgt_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_ge_pd) != 0 || (_mm_movemask_pd(zw_ge_pd) & 1) != 0; +#else + return lhs.x > rhs.x || lhs.y > rhs.y || lhs.z > rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are greater equal, otherwise false: all(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_greater_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpge_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_ge_pd) & _mm_movemask_pd(zw_ge_pd)) == 3; +#else + return lhs.x >= rhs.x && lhs.y >= rhs.y && lhs.z >= rhs.z && lhs.w >= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are greater equal, otherwise false: all(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_greater_equal2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_ge_pd) == 3; +#else + return lhs.x >= rhs.x && lhs.y >= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are greater equal, otherwise false: all(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_greater_equal3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpge_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_ge_pd) == 3 && (_mm_movemask_pd(zw_ge_pd) & 1) != 0; +#else + return lhs.x >= rhs.x && lhs.y >= rhs.y && lhs.z >= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are greater equal, otherwise false: any(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_greater_equal(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpge_pd(lhs.zw, rhs.zw); + return (_mm_movemask_pd(xy_ge_pd) | _mm_movemask_pd(zw_ge_pd)) != 0; +#else + return lhs.x >= rhs.x || lhs.y >= rhs.y || lhs.z >= rhs.z || lhs.w >= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are greater equal, otherwise false: any(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_greater_equal2(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + return _mm_movemask_pd(xy_ge_pd) != 0; +#else + return lhs.x >= rhs.x || lhs.y >= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are greater equal, otherwise false: any(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_greater_equal3(const vector4d& lhs, const vector4d& rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy_ge_pd = _mm_cmpge_pd(lhs.xy, rhs.xy); + __m128d zw_ge_pd = _mm_cmpge_pd(lhs.zw, rhs.zw); + return _mm_movemask_pd(xy_ge_pd) != 0 || (_mm_movemask_pd(zw_ge_pd) & 1) != 0; +#else + return lhs.x >= rhs.x || lhs.y >= rhs.y || lhs.z >= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are near equal, otherwise false: all(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_near_equal(const vector4d& lhs, const vector4d& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_all_less_equal(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are near equal, otherwise false: all(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_near_equal2(const vector4d& lhs, const vector4d& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_all_less_equal2(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are near equal, otherwise false: all(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_all_near_equal3(const vector4d& lhs, const vector4d& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_all_less_equal3(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are near equal, otherwise false: any(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_near_equal(const vector4d& lhs, const vector4d& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_any_less_equal(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are near equal, otherwise false: any(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_near_equal2(const vector4d& lhs, const vector4d& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_any_less_equal2(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are near equal, otherwise false: any(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_any_near_equal3(const vector4d& lhs, const vector4d& rhs, double threshold = 0.00001) RTM_NO_EXCEPT + { + return vector_any_less_equal3(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are finite (not NaN/Inf), otherwise false: all(finite(input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_is_finite(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + + const __m128d infinity = _mm_set1_pd(std::numeric_limits::infinity()); + __m128d is_infinity_xy = _mm_cmpeq_pd(abs_input_xy, infinity); + __m128d is_infinity_zw = _mm_cmpeq_pd(abs_input_zw, infinity); + + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + __m128d is_nan_zw = _mm_cmpneq_pd(input.zw, input.zw); + + __m128d is_not_finite_xy = _mm_or_pd(is_infinity_xy, is_nan_xy); + __m128d is_not_finite_zw = _mm_or_pd(is_infinity_zw, is_nan_zw); + __m128d is_not_finite = _mm_or_pd(is_not_finite_xy, is_not_finite_zw); + return _mm_movemask_pd(is_not_finite) == 0x0; +#else + return scalar_is_finite(vector_get_x(input)) && scalar_is_finite(vector_get_y(input)) && scalar_is_finite(vector_get_z(input)) && scalar_is_finite(vector_get_w(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are finite (not NaN/Inf), otherwise false: all(finite(input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_is_finite2(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + + const __m128d infinity = _mm_set1_pd(std::numeric_limits::infinity()); + __m128d is_infinity_xy = _mm_cmpeq_pd(abs_input_xy, infinity); + + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + + __m128d is_not_finite_xy = _mm_or_pd(is_infinity_xy, is_nan_xy); + return _mm_movemask_pd(is_not_finite_xy) == 0x0; +#else + return scalar_is_finite(vector_get_x(input)) && scalar_is_finite(vector_get_y(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are finite (not NaN/Inf), otherwise false: all(finite(input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool vector_is_finite3(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + + const __m128d infinity = _mm_set1_pd(std::numeric_limits::infinity()); + __m128d is_infinity_xy = _mm_cmpeq_pd(abs_input_xy, infinity); + __m128d is_infinity_zw = _mm_cmpeq_pd(abs_input_zw, infinity); + + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + __m128d is_nan_zw = _mm_cmpneq_pd(input.zw, input.zw); + + __m128d is_not_finite_xy = _mm_or_pd(is_infinity_xy, is_nan_xy); + __m128d is_not_finite_zw = _mm_or_pd(is_infinity_zw, is_nan_zw); + return _mm_movemask_pd(is_not_finite_xy) == 0 && (_mm_movemask_pd(is_not_finite_zw) & 0x1) == 0; +#else + return scalar_is_finite(vector_get_x(input)) && scalar_is_finite(vector_get_y(input)) && scalar_is_finite(vector_get_z(input)); +#endif + } + + + + ////////////////////////////////////////////////////////////////////////// + // Swizzling, permutations, and mixing + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_select(const mask4d& mask, const vector4d& if_true, const vector4d& if_false) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy = RTM_VECTOR2D_SELECT(mask.xy, if_true.xy, if_false.xy); + __m128d zw = RTM_VECTOR2D_SELECT(mask.zw, if_true.zw, if_false.zw); + return vector4d{ xy, zw }; +#else + return vector4d{ rtm_impl::select(mask.x, if_true.x, if_false.x), rtm_impl::select(mask.y, if_true.y, if_false.y), rtm_impl::select(mask.z, if_true.z, if_false.z), rtm_impl::select(mask.w, if_true.w, if_false.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Mixes two inputs and returns the desired components. + // [xyzw] indexes into the first input while [abcd] indexes in the second. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_mix(const vector4d& input0, const vector4d& input1) RTM_NO_EXCEPT + { + // Slow code path, not yet optimized or not using intrinsics + const double x = rtm_impl::is_mix_xyzw(comp0) ? vector_get_component(input0) : vector_get_component(input1); + const double y = rtm_impl::is_mix_xyzw(comp1) ? vector_get_component(input0) : vector_get_component(input1); + const double z = rtm_impl::is_mix_xyzw(comp2) ? vector_get_component(input0) : vector_get_component(input1); + const double w = rtm_impl::is_mix_xyzw(comp3) ? vector_get_component(input0) : vector_get_component(input1); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [x] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_dup_x(const vector4d& input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [y] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_dup_y(const vector4d& input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [z] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_dup_z(const vector4d& input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [w] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_dup_w(const vector4d& input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + + ////////////////////////////////////////////////////////////////////////// + // Logical + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // Per component logical AND between the inputs: input0 & input1 + ////////////////////////////////////////////////////////////////////////// + inline vector4d vector_and(const vector4d& input0, const vector4d& input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy = _mm_and_pd(input0.xy, input1.xy); + __m128d zw = _mm_and_pd(input0.zw, input1.zw); + return vector4d{ xy, zw }; +#else + const uint64_t* input0_ = reinterpret_cast(&input0); + const uint64_t* input1_ = reinterpret_cast(&input1); + + vector4d result; + uint64_t* result_ = reinterpret_cast(&result); + + result_[0] = input0_[0] & input1_[0]; + result_[1] = input0_[1] & input1_[1]; + result_[2] = input0_[2] & input1_[2]; + result_[3] = input0_[3] & input1_[3]; + + return result; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component logical OR between the inputs: input0 | input1 + ////////////////////////////////////////////////////////////////////////// + inline vector4d vector_or(const vector4d& input0, const vector4d& input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy = _mm_or_pd(input0.xy, input1.xy); + __m128d zw = _mm_or_pd(input0.zw, input1.zw); + return vector4d{ xy, zw }; +#else + const uint64_t* input0_ = reinterpret_cast(&input0); + const uint64_t* input1_ = reinterpret_cast(&input1); + + vector4d result; + uint64_t* result_ = reinterpret_cast(&result); + + result_[0] = input0_[0] | input1_[0]; + result_[1] = input0_[1] | input1_[1]; + result_[2] = input0_[2] | input1_[2]; + result_[3] = input0_[3] | input1_[3]; + + return result; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component logical XOR between the inputs: input0 ^ input1 + ////////////////////////////////////////////////////////////////////////// + inline vector4d vector_xor(const vector4d& input0, const vector4d& input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + __m128d xy = _mm_xor_pd(input0.xy, input1.xy); + __m128d zw = _mm_xor_pd(input0.zw, input1.zw); + return vector4d{ xy, zw }; +#else + const uint64_t* input0_ = reinterpret_cast(&input0); + const uint64_t* input1_ = reinterpret_cast(&input1); + + vector4d result; + uint64_t* result_ = reinterpret_cast(&result); + + result_[0] = input0_[0] ^ input1_[0]; + result_[1] = input0_[1] ^ input1_[1]; + result_[2] = input0_[2] ^ input1_[2]; + result_[3] = input0_[3] ^ input1_[3]; + + return result; +#endif + } + + + ////////////////////////////////////////////////////////////////////////// + // Miscellaneous + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the sign of the input vector: input >= 0.0 ? 1.0 : -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_sign(const vector4d& input) RTM_NO_EXCEPT + { + const mask4d mask = vector_greater_equal(input, vector_zero()); + return vector_select(mask, vector_set(1.0), vector_set(-1.0)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the input with the sign of the control value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_copy_sign(const vector4d& input, const vector4d& control_sign) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128d sign_bit = _mm_set1_pd(-0.0); + __m128d signs_xy = _mm_and_pd(sign_bit, control_sign.xy); + __m128d signs_zw = _mm_and_pd(sign_bit, control_sign.zw); + __m128d abs_input_xy = _mm_andnot_pd(sign_bit, input.xy); + __m128d abs_input_zw = _mm_andnot_pd(sign_bit, input.zw); + __m128d xy = _mm_or_pd(abs_input_xy, signs_xy); + __m128d zw = _mm_or_pd(abs_input_zw, signs_zw); + return vector4d{ xy, zw }; +#else + double x = vector_get_x(input); + double y = vector_get_y(input); + double z = vector_get_z(input); + double w = vector_get_w(input); + + double x_sign = vector_get_x(control_sign); + double y_sign = vector_get_y(control_sign); + double z_sign = vector_get_z(control_sign); + double w_sign = vector_get_w(control_sign); + + return vector_set(std::copysign(x, x_sign), std::copysign(y, y_sign), std::copysign(z, z_sign), std::copysign(w, w_sign)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the rounded input using a symmetric algorithm. + // vector_round_symmetric(1.5) = 2.0 + // vector_round_symmetric(1.2) = 1.0 + // vector_round_symmetric(-1.5) = -2.0 + // vector_round_symmetric(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_round_symmetric(const vector4d& input) RTM_NO_EXCEPT + { + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + +#if defined(RTM_SSE4_INTRINSICS) + __m128d zero = _mm_setzero_pd(); + __m128d is_positive_xy = _mm_cmpge_pd(input.xy, zero); + __m128d is_positive_zw = _mm_cmpge_pd(input.zw, zero); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d sign_xy = _mm_andnot_pd(is_positive_xy, sign_mask); + __m128d sign_zw = _mm_andnot_pd(is_positive_zw, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128d half = _mm_set1_pd(0.5); + __m128d bias_xy = _mm_or_pd(sign_xy, half); + __m128d bias_zw = _mm_or_pd(sign_zw, half); + __m128d biased_input_xy = _mm_add_pd(input.xy, bias_xy); + __m128d biased_input_zw = _mm_add_pd(input.zw, bias_zw); + + __m128d floored_xy = _mm_floor_pd(biased_input_xy); + __m128d floored_zw = _mm_floor_pd(biased_input_zw); + __m128d ceiled_xy = _mm_ceil_pd(biased_input_xy); + __m128d ceiled_zw = _mm_ceil_pd(biased_input_zw); + + __m128d result_xy = RTM_VECTOR2D_SELECT(is_positive_xy, floored_xy, ceiled_xy); + __m128d result_zw = RTM_VECTOR2D_SELECT(is_positive_zw, floored_zw, ceiled_zw); + return vector4d{ result_xy, result_zw }; +#elif defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large_xy = _mm_cmpge_pd(abs_input_xy, fractional_limit); + __m128d is_input_large_zw = _mm_cmpge_pd(abs_input_zw, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128d is_nan_xy = _mm_cmpneq_pd(input.xy, input.xy); + __m128d is_nan_zw = _mm_cmpneq_pd(input.zw, input.zw); + + // Combine our masks to determine if we should return the original value + __m128d use_original_input_xy = _mm_or_pd(is_input_large_xy, is_nan_xy); + __m128d use_original_input_zw = _mm_or_pd(is_input_large_zw, is_nan_zw); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d sign_xy = _mm_and_pd(input.xy, sign_mask); + __m128d sign_zw = _mm_and_pd(input.zw, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128d half = _mm_set1_pd(0.5); + __m128d bias_xy = _mm_or_pd(sign_xy, half); + __m128d bias_zw = _mm_or_pd(sign_zw, half); + __m128d biased_input_xy = _mm_add_pd(input.xy, bias_xy); + __m128d biased_input_zw = _mm_add_pd(input.zw, bias_zw); + + // Convert to an integer with truncation and back, this rounds towards zero. + __m128d integer_part_xy = _mm_cvtepi32_pd(_mm_cvttpd_epi32(biased_input_xy)); + __m128d integer_part_zw = _mm_cvtepi32_pd(_mm_cvttpd_epi32(biased_input_zw)); + + __m128d result_xy = _mm_or_pd(_mm_and_pd(use_original_input_xy, input.xy), _mm_andnot_pd(use_original_input_xy, integer_part_xy)); + __m128d result_zw = _mm_or_pd(_mm_and_pd(use_original_input_zw, input.zw), _mm_andnot_pd(use_original_input_zw, integer_part_zw)); + + return vector4d{ result_xy, result_zw }; +#else + const vector4d half = vector_set(0.5); + const vector4d floored = vector_floor(vector_add(input, half)); + const vector4d ceiled = vector_ceil(vector_sub(input, half)); + const mask4d is_greater_equal = vector_greater_equal(input, vector_zero()); + return vector_select(is_greater_equal, floored, ceiled); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the rounded input using banker's rounding (half to even). + // vector_round_bankers(2.5) = 2.0 + // vector_round_bankers(1.5) = 2.0 + // vector_round_bankers(1.2) = 1.0 + // vector_round_bankers(-2.5) = -2.0 + // vector_round_bankers(-1.5) = -2.0 + // vector_round_bankers(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4d vector_round_bankers(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return vector4d{ _mm_round_pd(input.xy, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC), _mm_round_pd(input.zw, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC) }; +#elif defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi64x(0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL); + + const __m128d sign_mask = _mm_set_pd(-0.0, -0.0); + __m128d sign_xy = _mm_and_pd(input.xy, sign_mask); + __m128d sign_zw = _mm_and_pd(input.zw, sign_mask); + + // We add the largest integer that a 64 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + const __m128d fractional_limit = _mm_set1_pd(4503599627370496.0); // 2^52 + __m128d truncating_offset_xy = _mm_or_pd(sign_xy, fractional_limit); + __m128d truncating_offset_zw = _mm_or_pd(sign_zw, fractional_limit); + __m128d integer_part_xy = _mm_sub_pd(_mm_add_pd(input.xy, truncating_offset_xy), truncating_offset_xy); + __m128d integer_part_zw = _mm_sub_pd(_mm_add_pd(input.zw, truncating_offset_zw), truncating_offset_zw); + + __m128d abs_input_xy = _mm_and_pd(input.xy, _mm_castsi128_pd(abs_mask)); + __m128d abs_input_zw = _mm_and_pd(input.zw, _mm_castsi128_pd(abs_mask)); + __m128d is_input_large_xy = _mm_cmpge_pd(abs_input_xy, fractional_limit); + __m128d is_input_large_zw = _mm_cmpge_pd(abs_input_zw, fractional_limit); + __m128d result_xy = _mm_or_pd(_mm_and_pd(is_input_large_xy, input.xy), _mm_andnot_pd(is_input_large_xy, integer_part_xy)); + __m128d result_zw = _mm_or_pd(_mm_and_pd(is_input_large_zw, input.zw), _mm_andnot_pd(is_input_large_zw, integer_part_zw)); + return vector4d{ result_xy, result_zw }; +#else + scalard x = scalar_round_bankers(scalard(vector_get_x(input))); + scalard y = scalar_round_bankers(scalard(vector_get_y(input))); + scalard z = scalar_round_bankers(scalard(vector_get_z(input))); + scalard w = scalar_round_bankers(scalard(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the sine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_sin(const vector4d& input) RTM_NO_EXCEPT + { + scalard x = scalar_sin(scalard(vector_get_x(input))); + scalard y = scalar_sin(scalard(vector_get_y(input))); + scalard z = scalar_sin(scalard(vector_get_z(input))); + scalard w = scalar_sin(scalard(vector_get_w(input))); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-sine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_asin(const vector4d& input) RTM_NO_EXCEPT + { + scalard x = scalar_asin(scalard(vector_get_x(input))); + scalard y = scalar_asin(scalard(vector_get_y(input))); + scalard z = scalar_asin(scalard(vector_get_z(input))); + scalard w = scalar_asin(scalard(vector_get_w(input))); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_cos(const vector4d& input) RTM_NO_EXCEPT + { + scalard x = scalar_cos(scalard(vector_get_x(input))); + scalard y = scalar_cos(scalard(vector_get_y(input))); + scalard z = scalar_cos(scalard(vector_get_z(input))); + scalard w = scalar_cos(scalard(vector_get_w(input))); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-cosine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_acos(const vector4d& input) RTM_NO_EXCEPT + { + scalard x = scalar_acos(scalard(vector_get_x(input))); + scalard y = scalar_acos(scalard(vector_get_y(input))); + scalard z = scalar_acos(scalard(vector_get_z(input))); + scalard w = scalar_acos(scalard(vector_get_w(input))); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the sine and cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void vector_sincos(const vector4d& input, vector4d& out_sine, vector4d& out_cosine) RTM_NO_EXCEPT + { + const vector4d x = scalar_sincos(scalard(vector_get_x(input))); + const vector4d y = scalar_sincos(scalard(vector_get_y(input))); + const vector4d z = scalar_sincos(scalard(vector_get_z(input))); + const vector4d w = scalar_sincos(scalard(vector_get_w(input))); + + const vector4d cos_xy = vector_mix(x, y); + const vector4d cos_zw = vector_mix(z, w); + out_cosine = vector_mix(cos_xy, cos_zw); + + const vector4d sin_xy = vector_mix(x, y); + const vector4d sin_zw = vector_mix(z, w); + out_sine = vector_mix(sin_xy, sin_zw); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the tangent of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_tan(const vector4d& angle) RTM_NO_EXCEPT + { + // Use the identity: tan(angle) = sin(angle) / cos(angle) + vector4d sin_; + vector4d cos_; + vector_sincos(angle, sin_, cos_); + + const mask4d is_cos_zero = vector_equal(cos_, vector_zero()); + const vector4d signed_infinity = vector_copy_sign(vector_set(std::numeric_limits::infinity()), angle); + const vector4d result = vector_div(sin_, cos_); + return vector_select(is_cos_zero, signed_infinity, result); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-tangent of the input. + // Note that due to the sign ambiguity, atan cannot determine which quadrant + // the value resides in. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_atan(const vector4d& input) RTM_NO_EXCEPT + { + scalard x = scalar_atan(scalard(vector_get_x(input))); + scalard y = scalar_atan(scalard(vector_get_y(input))); + scalard z = scalar_atan(scalard(vector_get_z(input))); + scalard w = scalar_atan(scalard(vector_get_w(input))); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-tangent of [y/x] using the sign of the arguments to + // determine the correct quadrant. + // Y represents the proportion of the y-coordinate. + // X represents the proportion of the x-coordinate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4d vector_atan2(const vector4d& y, const vector4d& x) RTM_NO_EXCEPT + { + scalard x_ = scalar_atan2(scalard(vector_get_x(y)), scalard(vector_get_x(x))); + scalard y_ = scalar_atan2(scalard(vector_get_y(y)), scalard(vector_get_y(x))); + scalard z_ = scalar_atan2(scalard(vector_get_z(y)), scalard(vector_get_z(x))); + scalard w_ = scalar_atan2(scalard(vector_get_w(y)), scalard(vector_get_w(x))); + return vector_set(x_, y_, z_, w_); + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/vector4f.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/vector4f.h new file mode 100644 index 00000000..69fec4e5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/vector4f.h @@ -0,0 +1,3075 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/macros.h" +#include "rtm/math.h" +#include "rtm/scalarf.h" +#include "rtm/version.h" +#include "rtm/impl/compiler_utils.h" +#include "rtm/impl/memory_utils.h" +#include "rtm/impl/vector_common.h" + +RTM_IMPL_FILE_PRAGMA_PUSH + +namespace rtm +{ + RTM_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Setters, getters, and casts + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector4 from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load(const float* input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_loadu_ps(input); +#elif defined(RTM_NEON_INTRINSICS) + return vld1q_f32(input); +#else + return vector_set(input[0], input[1], input[2], input[3]); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an input scalar from memory into the [x] component and sets the [yzw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load1(const float* input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_load_ss(input); +#elif defined(RTM_NEON_INTRINSICS) + return vld1q_lane_f32(input, vdupq_n_f32(0.0F), 0); +#else + return vector_set(input[0], 0.0F, 0.0F, 0.0F); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector2 from memory and sets the [zw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load2(const float* input) RTM_NO_EXCEPT + { + return vector_set(input[0], input[1], 0.0F, 0.0F); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector3 from memory and sets the [w] component to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load3(const float* input) RTM_NO_EXCEPT + { + return vector_set(input[0], input[1], input[2], 0.0F); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector4 from memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load(const float4f* input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_loadu_ps(&input->x); +#elif defined(RTM_NEON_INTRINSICS) + return vld1q_f32(&input->x); +#else + return vector_set(input->x, input->y, input->z, input->w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector2 from memory and sets the [zw] components to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load2(const float2f* input) RTM_NO_EXCEPT + { + return vector_set(input->x, input->y, 0.0F, 0.0F); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an unaligned vector3 from memory and sets the [w] component to zero. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_load3(const float3f* input) RTM_NO_EXCEPT + { + return vector_set(input->x, input->y, input->z, 0.0F); + } + + ////////////////////////////////////////////////////////////////////////// + // Loads an input scalar from memory into the [xyzw] components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_broadcast(const float* input) RTM_NO_EXCEPT + { +#if defined(RTM_AVX_INTRINSICS) + return _mm_broadcast_ss(input); +#elif defined(RTM_SSE2_INTRINSICS) + return _mm_load_ps1(input); +#elif defined(RTM_NEON_INTRINSICS) + return vld1q_dup_f32(input); +#else + return vector_set(*input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a quaternion to a vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL quat_to_vector(quatf_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) || defined(RTM_NEON_INTRINSICS) + return input; +#else + return vector4f{ input.x, input.y, input.z, input.w }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Casts a vector4 float64 variant to a float32 variant. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_cast(const vector4d& input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_shuffle_ps(_mm_cvtpd_ps(input.xy), _mm_cvtpd_ps(input.zw), _MM_SHUFFLE(1, 0, 1, 0)); +#else + return vector_set(float(input.x), float(input.y), float(input.z), float(input.w)); +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_get_x + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(input); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 0); +#else + return input.x; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ input }; + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [x] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_get_x RTM_SIMD_CALL vector_get_x(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_get_x{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_get_y + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_shuffle_ps(input, input, _MM_SHUFFLE(1, 1, 1, 1))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 1); +#else + return input.y; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_shuffle_ps(input, input, _MM_SHUFFLE(1, 1, 1, 1)) }; + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [y] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_get_y RTM_SIMD_CALL vector_get_y(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_get_y{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_get_z + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_shuffle_ps(input, input, _MM_SHUFFLE(2, 2, 2, 2))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 2); +#else + return input.z; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_shuffle_ps(input, input, _MM_SHUFFLE(2, 2, 2, 2)) }; + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [z] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_get_z RTM_SIMD_CALL vector_get_z(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_get_z{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_get_w + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cvtss_f32(_mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 3, 3, 3))); +#elif defined(RTM_NEON_INTRINSICS) + return vgetq_lane_f32(input, 3); +#else + return input.w; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + return scalarf{ _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 3, 3, 3)) }; + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 [w] component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_get_w RTM_SIMD_CALL vector_get_w(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_get_w{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + template + struct vector4f_vector_get_component_static + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (rtm_impl::static_condition::test()) + return vector_get_x(input); + else if (rtm_impl::static_condition::test()) + return vector_get_y(input); + else if (rtm_impl::static_condition::test()) + return vector_get_z(input); + else + return vector_get_w(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (rtm_impl::static_condition::test()) + return vector_get_x(input); + else if (rtm_impl::static_condition::test()) + return vector_get_y(input); + else if (rtm_impl::static_condition::test()) + return vector_get_z(input); + else + return vector_get_w(input); + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 desired component. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_get_component_static RTM_SIMD_CALL vector_get_component(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_get_component_static{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_get_component + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (xyzw == mix4::x) + return vector_get_x(input); + else if (xyzw == mix4::y) + return vector_get_y(input); + else if (xyzw == mix4::z) + return vector_get_z(input); + else + return vector_get_w(input); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const mix4 xyzw = mix4(int(component) % 4); + if (xyzw == mix4::x) + return vector_get_x(input); + else if (xyzw == mix4::y) + return vector_get_y(input); + else if (xyzw == mix4::z) + return vector_get_z(input); + else + return vector_get_w(input); + } +#endif + + vector4f input; + mix4 component; + int padding[3]; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the vector4 desired component. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_get_component RTM_SIMD_CALL vector_get_component(vector4f_arg0 input, mix4 component) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_get_component{ input, component, { 0 } }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the smallest component in the input vector as a scalar. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_get_min_component RTM_SIMD_CALL vector_get_min_component(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_get_min_component{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the largest component in the input vector as a scalar. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_get_max_component RTM_SIMD_CALL vector_get_max_component(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_get_max_component{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [x] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_x(vector4f_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_move_ss(input, _mm_set_ss(lane_value)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 0); +#else + return vector4f{ lane_value, input.y, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [x] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_x(vector4f_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { + return _mm_move_ss(input, lane_value.value); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [y] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_y(vector4f_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, _mm_set_ss(lane_value), 0x10); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 yxzw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 2, 0, 1)); + const __m128 vxzw = _mm_move_ss(yxzw, _mm_set_ss(lane_value)); + return _mm_shuffle_ps(vxzw, vxzw, _MM_SHUFFLE(3, 2, 0, 1)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 1); +#else + return vector4f{ input.x, lane_value, input.z, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [y] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_y(vector4f_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, lane_value.value, 0x10); +#else + const __m128 yxzw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 2, 0, 1)); + const __m128 vxzw = _mm_move_ss(yxzw, lane_value.value); + return _mm_shuffle_ps(vxzw, vxzw, _MM_SHUFFLE(3, 2, 0, 1)); +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [z] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_z(vector4f_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, _mm_set_ss(lane_value), 0x20); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 zyxw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 0, 1, 2)); + const __m128 vyxw = _mm_move_ss(zyxw, _mm_set_ss(lane_value)); + return _mm_shuffle_ps(vyxw, vyxw, _MM_SHUFFLE(3, 0, 1, 2)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 2); +#else + return vector4f{ input.x, input.y, lane_value, input.w }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [z] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_z(vector4f_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, lane_value.value, 0x20); +#else + const __m128 yxzw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(3, 0, 1, 2)); + const __m128 vxzw = _mm_move_ss(yxzw, lane_value.value); + return _mm_shuffle_ps(vxzw, vxzw, _MM_SHUFFLE(3, 0, 1, 2)); +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [w] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_w(vector4f_arg0 input, float lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, _mm_set_ss(lane_value), 0x30); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 wyzx = _mm_shuffle_ps(input, input, _MM_SHUFFLE(0, 2, 1, 3)); + const __m128 vyzx = _mm_move_ss(wyzx, _mm_set_ss(lane_value)); + return _mm_shuffle_ps(vyzx, vyzx, _MM_SHUFFLE(0, 2, 1, 3)); +#elif defined(RTM_NEON_INTRINSICS) + return vsetq_lane_f32(lane_value, input, 3); +#else + return vector4f{ input.x, input.y, input.z, lane_value }; +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Sets the vector4 [w] component and returns the new value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_set_w(vector4f_arg0 input, scalarf_arg1 lane_value) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_insert_ps(input, lane_value.value, 0x30); +#else + const __m128 yxzw = _mm_shuffle_ps(input, input, _MM_SHUFFLE(0, 2, 1, 3)); + const __m128 vxzw = _mm_move_ss(yxzw, lane_value.value); + return _mm_shuffle_ps(vxzw, vxzw, _MM_SHUFFLE(0, 2, 1, 3)); +#endif + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Returns a floating point pointer to the vector4 data. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE const float* RTM_SIMD_CALL vector_to_pointer(const vector4f& input) RTM_NO_EXCEPT + { + return reinterpret_cast(&input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector4 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store(vector4f_arg0 input, float* output) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + _mm_storeu_ps(output, input); +#else + output[0] = vector_get_x(input); + output[1] = vector_get_y(input); + output[2] = vector_get_z(input); + output[3] = vector_get_w(input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector1 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store1(vector4f_arg0 input, float* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector2 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store2(vector4f_arg0 input, float* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + output[1] = vector_get_y(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector3 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store3(vector4f_arg0 input, float* output) RTM_NO_EXCEPT + { + output[0] = vector_get_x(input); + output[1] = vector_get_y(input); + output[2] = vector_get_z(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector4 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store(vector4f_arg0 input, uint8_t* output) RTM_NO_EXCEPT + { + std::memcpy(output, &input, sizeof(vector4f)); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector1 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store1(vector4f_arg0 input, uint8_t* output) RTM_NO_EXCEPT + { + std::memcpy(output, &input, sizeof(float) * 1); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector2 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store2(vector4f_arg0 input, uint8_t* output) RTM_NO_EXCEPT + { + std::memcpy(output, &input, sizeof(float) * 2); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector3 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store3(vector4f_arg0 input, uint8_t* output) RTM_NO_EXCEPT + { + std::memcpy(output, &input, sizeof(float) * 3); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector4 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store(vector4f_arg0 input, float4f* output) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + _mm_storeu_ps(&output->x, input); +#else + output->x = vector_get_x(input); + output->y = vector_get_y(input); + output->z = vector_get_z(input); + output->w = vector_get_w(input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector2 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store2(vector4f_arg0 input, float2f* output) RTM_NO_EXCEPT + { + output->x = vector_get_x(input); + output->y = vector_get_y(input); + } + + ////////////////////////////////////////////////////////////////////////// + // Writes a vector3 to unaligned memory. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE void RTM_SIMD_CALL vector_store3(vector4f_arg0 input, float3f* output) RTM_NO_EXCEPT + { + output->x = vector_get_x(input); + output->y = vector_get_y(input); + output->z = vector_get_z(input); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Arithmetic + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Per component addition of the two inputs: lhs + rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_add(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_add_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vaddq_f32(lhs, rhs); +#else + return vector_set(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component subtraction of the two inputs: lhs - rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_sub(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_sub_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vsubq_f32(lhs, rhs); +#else + return vector_set(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication of the two inputs: lhs * rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mul(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_mul_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vmulq_f32(lhs, rhs); +#else + return vector_set(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication of the vector by a scalar: lhs * rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mul(vector4f_arg0 lhs, float rhs) RTM_NO_EXCEPT + { +#if defined(RTM_NEON_INTRINSICS) + return vmulq_n_f32(lhs, rhs); +#else + return vector_mul(lhs, vector_set(rhs)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication of the vector by a scalar: lhs * rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mul(vector4f_arg0 lhs, scalarf_arg1 rhs) RTM_NO_EXCEPT + { + return _mm_mul_ps(lhs, _mm_shuffle_ps(rhs.value, rhs.value, _MM_SHUFFLE(0, 0, 0, 0))); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component division of the two inputs: lhs / rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_div(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_div_ps(lhs, rhs); +#elif defined (RTM_NEON64_INTRINSICS) + return vdivq_f32(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + // Use scalar division on ARMv7, slow but accurate + float x = vgetq_lane_f32(lhs, 0) / vgetq_lane_f32(rhs, 0); + float y = vgetq_lane_f32(lhs, 1) / vgetq_lane_f32(rhs, 1); + float z = vgetq_lane_f32(lhs, 2) / vgetq_lane_f32(rhs, 2); + float w = vgetq_lane_f32(lhs, 3) / vgetq_lane_f32(rhs, 3); + + float32x4_t result; + result = vsetq_lane_f32(x, result, 0); + result = vsetq_lane_f32(y, result, 1); + result = vsetq_lane_f32(z, result, 2); + result = vsetq_lane_f32(w, result, 3); + return result; +#else + return vector_set(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component maximum of the two inputs: max(lhs, rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_max(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_max_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vmaxq_f32(lhs, rhs); +#else + return vector_set(scalar_max(lhs.x, rhs.x), scalar_max(lhs.y, rhs.y), scalar_max(lhs.z, rhs.z), scalar_max(lhs.w, rhs.w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component minimum of the two inputs: min(lhs, rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_min(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_min_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vminq_f32(lhs, rhs); +#else + return vector_set(scalar_min(lhs.x, rhs.x), scalar_min(lhs.y, rhs.y), scalar_min(lhs.z, rhs.z), scalar_min(lhs.w, rhs.w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component clamping of an input between a minimum and a maximum value: min(max_value, max(min_value, input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_clamp(vector4f_arg0 input, vector4f_arg1 min_value, vector4f_arg2 max_value) RTM_NO_EXCEPT + { + return vector_min(max_value, vector_max(min_value, input)); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component absolute of the input: abs(input) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_abs(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + return _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); +#elif defined(RTM_NEON_INTRINSICS) + return vabsq_f32(input); +#else + return vector_set(scalar_abs(input.x), scalar_abs(input.y), scalar_abs(input.z), scalar_abs(input.w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component negation of the input: -input + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_neg(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -0.0F, -0.0F, -0.0F, -0.0F }; + return _mm_xor_ps(input, signs); +#elif defined(RTM_NEON_INTRINSICS) + return vnegq_f32(input); +#else + return vector_mul(input, -1.0f); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component reciprocal of the input: 1.0 / input + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_reciprocal(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_div_ps(_mm_set_ps1(1.0F), input); +#elif defined(RTM_NEON64_INTRINSICS) + return vdivq_f32(vdupq_n_f32(1.0F), input); +#elif defined(RTM_NEON_INTRINSICS) + // Perform two passes of Newton-Raphson iteration on the hardware estimate + float32x4_t x0 = vrecpeq_f32(input); + + // First iteration + float32x4_t x1 = vmulq_f32(x0, vrecpsq_f32(x0, input)); + + // Second iteration + float32x4_t x2 = vmulq_f32(x1, vrecpsq_f32(x1, input)); + return x2; +#else + return vector_div(vector_set(1.0F), input); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component square root of the input: sqrt(input) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_sqrt(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_sqrt_ps(input); +#elif defined(RTM_NEON64_INTRINSICS) && defined(RTM_IMPL_VSQRT_SUPPORTED) + return vsqrtq_f32(input); +#else + scalarf x = vector_get_x(input); + scalarf y = vector_get_y(input); + scalarf z = vector_get_z(input); + scalarf w = vector_get_w(input); + return vector_set(scalar_sqrt(x), scalar_sqrt(y), scalar_sqrt(z), scalar_sqrt(w)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component returns the smallest integer value not less than the input (round towards positive infinity). + // vector_ceil([1.8, 1.0, -1.8, -1.0]) = [2.0, 1.0, -1.0, -1.0] + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_ceil(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_ceil_ps(input); +#elif defined(RTM_SSE2_INTRINSICS) + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ps(input, input); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + __m128 is_positive = _mm_cmplt_ps(integer_part, input); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Negative values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_positive)); + + // Subtract our bias to properly handle positive values + integer_part = _mm_sub_ps(integer_part, bias); + + return _mm_or_ps(_mm_and_ps(use_original_input, input), _mm_andnot_ps(use_original_input, integer_part)); +#elif defined(RTM_NEON64_INTRINSICS) + return vrndpq_f32(input); +#elif defined(RTM_NEON_INTRINSICS) + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(input)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + uint32x4_t is_positive = vcltq_f32(integer_part, input); + + float32x4_t bias = vcvtq_f32_s32(is_positive); + + // Subtract our bias to properly handle positive values + integer_part = vsubq_f32(integer_part, bias); + + return vbslq_f32(use_original_input, input, integer_part); +#else + return vector_set(scalar_ceil(vector_get_x(input)), scalar_ceil(vector_get_y(input)), scalar_ceil(vector_get_z(input)), scalar_ceil(vector_get_w(input))); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component returns the largest integer value not greater than the input (round towards negative infinity). + // vector_floor([1.8, 1.0, -1.8, -1.0]) = [1.0, 1.0, -2.0, -1.0] + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_floor(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_floor_ps(input); +#elif defined(RTM_SSE2_INTRINSICS) + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ps(input, input); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + __m128 is_negative = _mm_cmpgt_ps(integer_part, input); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Positive values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_negative)); + + // Add our bias to properly handle negative values + integer_part = _mm_add_ps(integer_part, bias); + + return _mm_or_ps(_mm_and_ps(use_original_input, input), _mm_andnot_ps(use_original_input, integer_part)); +#elif defined(RTM_NEON64_INTRINSICS) + return vrndmq_f32(input); +#elif defined(RTM_NEON_INTRINSICS) + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(input)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + uint32x4_t is_negative = vcgtq_f32(integer_part, input); + + float32x4_t bias = vcvtq_f32_s32(is_negative); + + // Add our bias to properly handle negative values + integer_part = vaddq_f32(integer_part, bias); + + return vbslq_f32(use_original_input, input, integer_part); +#else + return vector_set(scalar_floor(vector_get_x(input)), scalar_floor(vector_get_y(input)), scalar_floor(vector_get_z(input)), scalar_floor(vector_get_w(input))); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // 3D cross product: lhs x rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_cross3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // cross(a, b).zxy = (a * b.yzx) - (a.yzx * b) + __m128 lhs_yzx = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(3, 0, 2, 1)); + __m128 rhs_yzx = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 0, 2, 1)); + __m128 tmp_zxy = _mm_sub_ps(_mm_mul_ps(lhs, rhs_yzx), _mm_mul_ps(lhs_yzx, rhs)); + + // cross(a, b) = ((a * b.yzx) - (a.yzx * b)).yzx + return _mm_shuffle_ps(tmp_zxy, tmp_zxy, _MM_SHUFFLE(3, 0, 2, 1)); +#else + // cross(a, b) = (a.yzx * b.zxy) - (a.zxy * b.yzx) + const float lhs_x = vector_get_x(lhs); + const float lhs_y = vector_get_y(lhs); + const float lhs_z = vector_get_z(lhs); + const float rhs_x = vector_get_x(rhs); + const float rhs_y = vector_get_y(rhs); + const float rhs_z = vector_get_z(rhs); + return vector_set((lhs_y * rhs_z) - (lhs_z * rhs_y), (lhs_z * rhs_x) - (lhs_x * rhs_z), (lhs_x * rhs_y) - (lhs_y * rhs_x)); +#endif + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_dot + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return _mm_cvtss_f32(_mm_dp_ps(lhs, rhs, 0xFF)); +#elif defined(RTM_SSE2_INTRINSICS) + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + return _mm_cvtss_f32(x2y2z2w2_0_0_0); +#else + return (vector_get_x(lhs) * vector_get_x(rhs)) + (vector_get_y(lhs) * vector_get_y(rhs)) + (vector_get_z(lhs) * vector_get_z(rhs)) + (vector_get_w(lhs) * vector_get_w(rhs)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return scalarf{ _mm_cvtss_f32(_mm_dp_ps(lhs, rhs, 0xFF)) }; +#else + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + return scalarf{ x2y2z2w2_0_0_0 }; +#endif + } +#endif + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4f() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return _mm_dp_ps(lhs, rhs, 0xFF); +#elif defined(RTM_SSE2_INTRINSICS) + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + return _mm_shuffle_ps(x2y2z2w2_0_0_0, x2y2z2w2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0)); +#elif defined(RTM_NEON_INTRINSICS) + float32x4_t x2_y2_z2_w2 = vmulq_f32(lhs, rhs); + float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2); + float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2); + float32x2_t x2z2_y2w2 = vadd_f32(x2_y2, z2_w2); + float32x2_t x2y2z2w2 = vpadd_f32(x2z2_y2w2, x2z2_y2w2); + return vcombine_f32(x2y2z2w2, x2y2z2w2); +#else + scalarf result = *this; + return vector_set(result); +#endif + } + + vector4f lhs; + vector4f rhs; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // 4D dot product: lhs . rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_dot RTM_SIMD_CALL vector_dot(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_dot{ lhs, rhs }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_dot3 + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return _mm_cvtss_f32(_mm_dp_ps(lhs, rhs, 0x7F)); +#elif defined(RTM_SSE2_INTRINSICS) + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 y2_0_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2_0_0_0 = _mm_add_ss(x2_y2_z2_w2, y2_0_0_0); + __m128 z2_0_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 0, 2)); + __m128 x2y2z2_0_0_0 = _mm_add_ss(x2y2_0_0_0, z2_0_0_0); + return _mm_cvtss_f32(x2y2z2_0_0_0); +#elif defined(RTM_NEON_INTRINSICS) + float32x4_t x2_y2_z2_w2 = vmulq_f32(lhs, rhs); + float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2); + float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2); + float32x2_t x2y2_x2y2 = vpadd_f32(x2_y2, x2_y2); + float32x2_t z2_z2 = vdup_lane_f32(z2_w2, 0); + float32x2_t x2y2z2_x2y2z2 = vadd_f32(x2y2_x2y2, z2_z2); + return vget_lane_f32(x2y2z2_x2y2z2, 0); +#else + return (vector_get_x(lhs) * vector_get_x(rhs)) + (vector_get_y(lhs) * vector_get_y(rhs)) + (vector_get_z(lhs) * vector_get_z(rhs)); +#endif + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 y2_0_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2_0_0_0 = _mm_add_ss(x2_y2_z2_w2, y2_0_0_0); + __m128 z2_0_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 0, 2)); + return scalarf{ _mm_add_ss(x2y2_0_0_0, z2_0_0_0) }; + } +#endif + + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator vector4f() const RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) && 0 + // SSE4 dot product instruction isn't precise enough + return _mm_cvtss_f32(_mm_dp_ps(lhs, rhs, 0xFF)); +#elif defined(RTM_SSE2_INTRINSICS) + __m128 x2_y2_z2_w2 = _mm_mul_ps(lhs, rhs); + __m128 y2_0_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2_0_0_0 = _mm_add_ss(x2_y2_z2_w2, y2_0_0_0); + __m128 z2_0_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 0, 2)); + __m128 x2y2z2_0_0_0 = _mm_add_ss(x2y2_0_0_0, z2_0_0_0); + return _mm_shuffle_ps(x2y2z2_0_0_0, x2y2z2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0)); +#elif defined(RTM_NEON_INTRINSICS) + float32x4_t x2_y2_z2_w2 = vmulq_f32(lhs, rhs); + float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2); + float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2); + float32x2_t x2y2_x2y2 = vpadd_f32(x2_y2, x2_y2); + float32x2_t z2_z2 = vdup_lane_f32(z2_w2, 0); + float32x2_t x2y2z2_x2y2z2 = vadd_f32(x2y2_x2y2, z2_z2); + return vdupq_lane_f32(x2y2z2_x2y2z2, 0); +#else + scalarf result = *this; + return vector_set(result); +#endif + } + + vector4f lhs; + vector4f rhs; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // 3D dot product: lhs . rhs + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_dot3 RTM_SIMD_CALL vector_dot3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_dot3{ lhs, rhs }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the squared length/norm of the vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_dot RTM_SIMD_CALL vector_length_squared(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_dot{ input, input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the squared length/norm of the vector3. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_dot3 RTM_SIMD_CALL vector_length_squared3(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_dot3{ input, input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_length + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared(input); + return scalar_cast(scalar_sqrt(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared(input); + return scalar_sqrt(len_sq); + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the length/norm of the vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_length RTM_SIMD_CALL vector_length(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_length{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_length3 + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared3(input); + return scalar_cast(scalar_sqrt(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared3(input); + return scalar_sqrt(len_sq); + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the length/norm of the vector3. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_length3 RTM_SIMD_CALL vector_length3(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_length3{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_length_reciprocal + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared(input); + return scalar_cast(scalar_sqrt_reciprocal(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared(input); + return scalar_sqrt_reciprocal(len_sq); + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal length/norm of the vector4. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_length_reciprocal RTM_SIMD_CALL vector_length_reciprocal(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_length_reciprocal{ input }; + } + + namespace rtm_impl + { + ////////////////////////////////////////////////////////////////////////// + // This is a helper struct to allow a single consistent API between + // various vector types when the semantics are identical but the return + // type differs. Implicit coercion is used to return the desired value + // at the call site. + ////////////////////////////////////////////////////////////////////////// + struct vector4f_vector_length_reciprocal3 + { + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator float() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared3(input); + return scalar_cast(scalar_sqrt_reciprocal(len_sq)); + } + +#if defined(RTM_SSE2_INTRINSICS) + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE RTM_SIMD_CALL operator scalarf() const RTM_NO_EXCEPT + { + const scalarf len_sq = vector_length_squared3(input); + return scalar_sqrt_reciprocal(len_sq); + } +#endif + + vector4f input; + }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the reciprocal length/norm of the vector3. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE constexpr rtm_impl::vector4f_vector_length_reciprocal3 RTM_SIMD_CALL vector_length_reciprocal3(vector4f_arg0 input) RTM_NO_EXCEPT + { + return rtm_impl::vector4f_vector_length_reciprocal3{ input }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the distance between two 3D points. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE rtm_impl::vector4f_vector_length3 RTM_SIMD_CALL vector_distance3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { + const vector4f difference = vector_sub(lhs, rhs); + return rtm_impl::vector4f_vector_length3{ difference }; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a normalized vector3. + // If the length of the input is not finite or zero, the result is undefined. + // For a safe alternative, supply a fallback value and a threshold. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_normalize3(vector4f_arg0 input) RTM_NO_EXCEPT + { + // Reciprocal is more accurate to normalize with + const scalarf len_sq = vector_length_squared3(input); + return vector_mul(input, scalar_sqrt_reciprocal(len_sq)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a normalized vector3. + // If the length of the input is below the supplied threshold, the + // fall back value is returned instead. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_normalize3(vector4f_arg0 input, vector4f_arg1 fallback, float threshold = 1.0E-8F) RTM_NO_EXCEPT + { + // Reciprocal is more accurate to normalize with + const scalarf len_sq = vector_length_squared3(input); + if (scalar_cast(len_sq) >= threshold) + return vector_mul(input, scalar_sqrt_reciprocal(len_sq)); + else + return fallback; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the fractional part of the input. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_fraction(vector4f_arg0 input) RTM_NO_EXCEPT + { + return vector_set(scalar_fraction(vector_get_x(input)), scalar_fraction(vector_get_y(input)), scalar_fraction(vector_get_z(input)), scalar_fraction(vector_get_w(input))); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * v1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mul_add(vector4f_arg0 v0, vector4f_arg1 v1, vector4f_arg2 v2) RTM_NO_EXCEPT + { + return RTM_VECTOR4F_MULV_ADD(v0, v1, v2); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mul_add(vector4f_arg0 v0, float s1, vector4f_arg2 v2) RTM_NO_EXCEPT + { + return RTM_VECTOR4F_MULS_ADD(v0, s1, v2); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component multiplication/addition of the three inputs: v2 + (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mul_add(vector4f_arg0 v0, scalarf_arg1 s1, vector4f_arg2 v2) RTM_NO_EXCEPT + { + return vector_add(vector_mul(v0, s1), v2); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * v1) - v2) + // This is mathematically equivalent to: v2 - (v0 * v1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_neg_mul_sub(vector4f_arg0 v0, vector4f_arg1 v1, vector4f_arg2 v2) RTM_NO_EXCEPT + { + return RTM_VECTOR4F_NEG_MULV_SUB(v0, v1, v2); + } + + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_neg_mul_sub(vector4f_arg0 v0, float s1, vector4f_arg2 v2) RTM_NO_EXCEPT + { + return RTM_VECTOR4F_NEG_MULS_SUB(v0, s1, v2); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component negative multiplication/subtraction of the three inputs: -((v0 * s1) - v2) + // This is mathematically equivalent to: v2 - (v0 * s1) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_neg_mul_sub(vector4f_arg0 v0, scalarf_arg1 s1, vector4f_arg2 v2) RTM_NO_EXCEPT + { + return vector_sub(v2, vector_mul(v0, s1)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_lerp(vector4f_arg0 start, vector4f_arg1 end, float alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return vector_mul_add(end, alpha, vector_neg_mul_sub(start, alpha, start)); + } + +#if defined(RTM_SSE2_INTRINSICS) + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_lerp(vector4f_arg0 start, vector4f_arg1 end, scalarf_arg2 alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + const vector4f alpha_v = vector_set(alpha); + return vector_mul_add(end, alpha_v, vector_neg_mul_sub(start, alpha_v, start)); + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Per component linear interpolation of the two inputs at the specified alpha. + // The formula used is: ((1.0 - alpha) * start) + (alpha * end). + // Interpolation is stable and will return 'start' when alpha is 0.0 and 'end' when it is 1.0. + // This is the same instruction count when FMA is present but it might be slightly slower + // due to the extra multiplication compared to: start + (alpha * (end - start)). + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_lerp(vector4f_arg0 start, vector4f_arg1 end, vector4f_arg2 alpha) RTM_NO_EXCEPT + { + // ((1.0 - alpha) * start) + (alpha * end) == (start - alpha * start) + (alpha * end) + return vector_mul_add(end, alpha, vector_neg_mul_sub(start, alpha, start)); + } + + + + ////////////////////////////////////////////////////////////////////////// + // Comparisons and masking + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if equal, otherwise 0: lhs == rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4f RTM_SIMD_CALL vector_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cmpeq_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vceqq_f32(lhs, rhs)); +#else + return mask4f{ rtm_impl::get_mask_value(lhs.x == rhs.x), rtm_impl::get_mask_value(lhs.y == rhs.y), rtm_impl::get_mask_value(lhs.z == rhs.z), rtm_impl::get_mask_value(lhs.w == rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if less than, otherwise 0: lhs < rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4f RTM_SIMD_CALL vector_less_than(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cmplt_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vcltq_f32(lhs, rhs)); +#else + return mask4f{ rtm_impl::get_mask_value(lhs.x < rhs.x), rtm_impl::get_mask_value(lhs.y < rhs.y), rtm_impl::get_mask_value(lhs.z < rhs.z), rtm_impl::get_mask_value(lhs.w < rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if less equal, otherwise 0: lhs <= rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4f RTM_SIMD_CALL vector_less_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cmple_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vcleq_f32(lhs, rhs)); +#else + return mask4f{ rtm_impl::get_mask_value(lhs.x <= rhs.x), rtm_impl::get_mask_value(lhs.y <= rhs.y), rtm_impl::get_mask_value(lhs.z <= rhs.z), rtm_impl::get_mask_value(lhs.w <= rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if greater than, otherwise 0: lhs > rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4f RTM_SIMD_CALL vector_greater_than(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cmpgt_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vcgtq_f32(lhs, rhs)); +#else + return mask4f{ rtm_impl::get_mask_value(lhs.x > rhs.x), rtm_impl::get_mask_value(lhs.y > rhs.y), rtm_impl::get_mask_value(lhs.z > rhs.z), rtm_impl::get_mask_value(lhs.w > rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component ~0 if greater equal, otherwise 0: lhs >= rhs ? ~0 : 0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE mask4f RTM_SIMD_CALL vector_greater_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_cmpge_ps(lhs, rhs); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vcgeq_f32(lhs, rhs)); +#else + return mask4f{ rtm_impl::get_mask_value(lhs.x >= rhs.x), rtm_impl::get_mask_value(lhs.y >= rhs.y), rtm_impl::get_mask_value(lhs.z >= rhs.z), rtm_impl::get_mask_value(lhs.w >= rhs.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are less than, otherwise false: all(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_less_than(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmplt_ps(lhs, rhs)) == 0xF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcltq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) == 0xFFFFFFFFU; +#else + return lhs.x < rhs.x && lhs.y < rhs.y && lhs.z < rhs.z && lhs.w < rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are less than, otherwise false: all(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_less_than2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmplt_ps(lhs, rhs)) & 0x3) == 0x3; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vclt_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) == 0xFFFFFFFFFFFFFFFFu; +#else + return lhs.x < rhs.x && lhs.y < rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are less than, otherwise false: all(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_less_than3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmplt_ps(lhs, rhs)) & 0x7) == 0x7; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcltq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) == 0x00FFFFFFU; +#else + return lhs.x < rhs.x && lhs.y < rhs.y && lhs.z < rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are less than, otherwise false: any(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_less_than(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmplt_ps(lhs, rhs)) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcltq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) != 0; +#else + return lhs.x < rhs.x || lhs.y < rhs.y || lhs.z < rhs.z || lhs.w < rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are less than, otherwise false: any(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_less_than2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmplt_ps(lhs, rhs)) & 0x3) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vclt_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) != 0; +#else + return lhs.x < rhs.x || lhs.y < rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are less than, otherwise false: any(lhs < rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_less_than3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmplt_ps(lhs, rhs)) & 0x7) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcltq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) != 0; +#else + return lhs.x < rhs.x || lhs.y < rhs.y || lhs.z < rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are less equal, otherwise false: all(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_less_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmple_ps(lhs, rhs)) == 0xF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcleq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) == 0xFFFFFFFFU; +#else + return lhs.x <= rhs.x && lhs.y <= rhs.y && lhs.z <= rhs.z && lhs.w <= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are less equal, otherwise false: all(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_less_equal2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmple_ps(lhs, rhs)) & 0x3) == 0x3; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vcle_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) == 0xFFFFFFFFFFFFFFFFULL; +#else + return lhs.x <= rhs.x && lhs.y <= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are less equal, otherwise false: all(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_less_equal3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmple_ps(lhs, rhs)) & 0x7) == 0x7; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcleq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) == 0x00FFFFFFU; +#else + return lhs.x <= rhs.x && lhs.y <= rhs.y && lhs.z <= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are less equal, otherwise false: any(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_less_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmple_ps(lhs, rhs)) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcleq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) != 0; +#else + return lhs.x <= rhs.x || lhs.y <= rhs.y || lhs.z <= rhs.z || lhs.w <= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are less equal, otherwise false: any(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_less_equal2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmple_ps(lhs, rhs)) & 0x3) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vcle_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) != 0; +#else + return lhs.x <= rhs.x || lhs.y <= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are less equal, otherwise false: any(lhs <= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_less_equal3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmple_ps(lhs, rhs)) & 0x7) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcleq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) != 0; +#else + return lhs.x <= rhs.x || lhs.y <= rhs.y || lhs.z <= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are greater than, otherwise false: all(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_greater_than(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)) == 0xF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgtq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) == 0xFFFFFFFFU; +#else + return lhs.x > rhs.x && lhs.y > rhs.y && lhs.z > rhs.z && lhs.w > rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are greater than, otherwise false: all(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_greater_than2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)) & 0x3) == 0x3; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vcgt_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) == 0xFFFFFFFFFFFFFFFFULL; +#else + return lhs.x > rhs.x && lhs.y > rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are greater than, otherwise false: all(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_greater_than3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)) & 0x7) == 0x7; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgtq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) == 0x00FFFFFFU; +#else + return lhs.x > rhs.x && lhs.y > rhs.y && lhs.z > rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are greater than, otherwise false: any(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_greater_than(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgtq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) != 0; +#else + return lhs.x > rhs.x || lhs.y > rhs.y || lhs.z > rhs.z || lhs.w > rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are greater than, otherwise false: any(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_greater_than2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)) & 0x3) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vcgt_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) != 0; +#else + return lhs.x > rhs.x || lhs.y > rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are greater than, otherwise false: any(lhs > rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_greater_than3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)) & 0x7) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgtq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) != 0; +#else + return lhs.x > rhs.x || lhs.y > rhs.y || lhs.z > rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are greater equal, otherwise false: all(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_greater_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmpge_ps(lhs, rhs)) == 0xF; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgeq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) == 0xFFFFFFFFU; +#else + return lhs.x >= rhs.x && lhs.y >= rhs.y && lhs.z >= rhs.z && lhs.w >= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are greater equal, otherwise false: all(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_greater_equal2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpge_ps(lhs, rhs)) & 0x3) == 0x3; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vcge_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) == 0xFFFFFFFFFFFFFFFFULL; +#else + return lhs.x >= rhs.x && lhs.y >= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are greater equal, otherwise false: all(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_greater_equal3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpge_ps(lhs, rhs)) & 0x7) == 0x7; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgeq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) == 0x00FFFFFFU; +#else + return lhs.x >= rhs.x && lhs.y >= rhs.y && lhs.z >= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are greater equal, otherwise false: any(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_greater_equal(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_movemask_ps(_mm_cmpge_ps(lhs, rhs)) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgeq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) != 0; +#else + return lhs.x >= rhs.x || lhs.y >= rhs.y || lhs.z >= rhs.z || lhs.w >= rhs.w; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are greater equal, otherwise false: any(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_greater_equal2(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpge_ps(lhs, rhs)) & 0x3) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x2_t mask = vcge_f32(vget_low_f32(lhs), vget_low_f32(rhs)); + return vget_lane_u64(mask, 0) != 0; +#else + return lhs.x >= rhs.x || lhs.y >= rhs.y; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are greater equal, otherwise false: any(lhs >= rhs) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_greater_equal3(vector4f_arg0 lhs, vector4f_arg1 rhs) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return (_mm_movemask_ps(_mm_cmpge_ps(lhs, rhs)) & 0x7) != 0; +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t mask = vcgeq_f32(lhs, rhs); + uint8x8x2_t mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15 = vzip_u8(vget_low_u8(mask), vget_high_u8(mask)); + uint16x4x2_t mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15 = vzip_u16(mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[0], mask_0_8_1_9_2_10_3_11_4_12_5_13_6_14_7_15.val[1]); + return (vget_lane_u32(mask_0_8_4_12_1_9_5_13_2_10_6_14_3_11_7_15.val[0], 0) & 0x00FFFFFFU) != 0; +#else + return lhs.x >= rhs.x || lhs.y >= rhs.y || lhs.z >= rhs.z; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are near equal, otherwise false: all(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_near_equal(vector4f_arg0 lhs, vector4f_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_all_less_equal(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are near equal, otherwise false: all(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_near_equal2(vector4f_arg0 lhs, vector4f_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_all_less_equal2(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are near equal, otherwise false: all(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_all_near_equal3(vector4f_arg0 lhs, vector4f_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_all_less_equal3(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any 4 components are near equal, otherwise false: any(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_near_equal(vector4f_arg0 lhs, vector4f_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_any_less_equal(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xy] components are near equal, otherwise false: any(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_near_equal2(vector4f_arg0 lhs, vector4f_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_any_less_equal2(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if any [xyz] components are near equal, otherwise false: any(abs(lhs - rhs) <= threshold) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_any_near_equal3(vector4f_arg0 lhs, vector4f_arg1 rhs, float threshold = 0.00001F) RTM_NO_EXCEPT + { + return vector_any_less_equal3(vector_abs(vector_sub(lhs, rhs)), vector_set(threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all 4 components are finite (not NaN/Inf), otherwise false: all(finite(input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_is_finite(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + + const __m128 infinity = _mm_set_ps1(std::numeric_limits::infinity()); + __m128 is_infinity = _mm_cmpeq_ps(abs_input, infinity); + + __m128 is_nan = _mm_cmpneq_ps(input, input); + + __m128 is_not_finite = _mm_or_ps(is_infinity, is_nan); + return _mm_movemask_ps(is_not_finite) == 0; +#else + return scalar_is_finite(vector_get_x(input)) && scalar_is_finite(vector_get_y(input)) && scalar_is_finite(vector_get_z(input)) && scalar_is_finite(vector_get_w(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xy] components are finite (not NaN/Inf), otherwise false: all(finite(input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_is_finite2(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + + const __m128 infinity = _mm_set_ps1(std::numeric_limits::infinity()); + __m128 is_infinity = _mm_cmpeq_ps(abs_input, infinity); + + __m128 is_nan = _mm_cmpneq_ps(input, input); + + __m128 is_not_finite = _mm_or_ps(is_infinity, is_nan); + return (_mm_movemask_ps(is_not_finite) & 0x3) == 0; +#else + return scalar_is_finite(vector_get_x(input)) && scalar_is_finite(vector_get_y(input)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if all [xyz] components are finite (not NaN/Inf), otherwise false: all(finite(input)) + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE bool RTM_SIMD_CALL vector_is_finite3(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + + const __m128 infinity = _mm_set_ps1(std::numeric_limits::infinity()); + __m128 is_infinity = _mm_cmpeq_ps(abs_input, infinity); + + __m128 is_nan = _mm_cmpneq_ps(input, input); + + __m128 is_not_finite = _mm_or_ps(is_infinity, is_nan); + return (_mm_movemask_ps(is_not_finite) & 0x7) == 0; +#else + return scalar_is_finite(vector_get_x(input)) && scalar_is_finite(vector_get_y(input)) && scalar_is_finite(vector_get_z(input)); +#endif + } + + + + ////////////////////////////////////////////////////////////////////////// + // Swizzling, permutations, and mixing + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Per component selection depending on the mask: mask != 0 ? if_true : if_false + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_select(mask4f_arg0 mask, vector4f_arg1 if_true, vector4f_arg2 if_false) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) || defined(RTM_NEON_INTRINSICS) + return RTM_VECTOR4F_SELECT(mask, if_true, if_false); +#else + return vector4f{ rtm_impl::select(mask.x, if_true.x, if_false.x), rtm_impl::select(mask.y, if_true.y, if_false.y), rtm_impl::select(mask.z, if_true.z, if_false.z), rtm_impl::select(mask.w, if_true.w, if_false.w) }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Mixes two inputs and returns the desired components. + // [xyzw] indexes into the first input while [abcd] indexes in the second. + ////////////////////////////////////////////////////////////////////////// + template + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_mix(vector4f_arg0 input0, vector4f_arg1 input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // All four components come from input 0 + if (rtm_impl::static_condition::test()) + return _mm_shuffle_ps(input0, input0, _MM_SHUFFLE(int(comp3) % 4, int(comp2) % 4, int(comp1) % 4, int(comp0) % 4)); + + // All four components come from input 1 + if (rtm_impl::static_condition::test()) + return _mm_shuffle_ps(input1, input1, _MM_SHUFFLE(int(comp3) % 4, int(comp2) % 4, int(comp1) % 4, int(comp0) % 4)); + + // First two components come from input 0, second two come from input 1 + if (rtm_impl::static_condition::test()) + return _mm_shuffle_ps(input0, input1, _MM_SHUFFLE(int(comp3) % 4, int(comp2) % 4, int(comp1) % 4, int(comp0) % 4)); + + // First two components come from input 1, second two come from input 0 + if (rtm_impl::static_condition::test()) + return _mm_shuffle_ps(input1, input0, _MM_SHUFFLE(int(comp3) % 4, int(comp2) % 4, int(comp1) % 4, int(comp0) % 4)); + + // Low words from both inputs are interleaved + if (rtm_impl::static_condition::test()) + return _mm_unpacklo_ps(input0, input1); + + // Low words from both inputs are interleaved + if (rtm_impl::static_condition::test()) + return _mm_unpacklo_ps(input1, input0); + + // High words from both inputs are interleaved + if (rtm_impl::static_condition::test()) + return _mm_unpackhi_ps(input0, input1); + + // High words from both inputs are interleaved + if (rtm_impl::static_condition::test()) + return _mm_unpackhi_ps(input1, input0); +#endif // defined(RTM_SSE2_INTRINSICS) + + // Slow code path, not yet optimized or not using intrinsics + const float x = rtm_impl::is_mix_xyzw(comp0) ? vector_get_component(input0) : vector_get_component(input1); + const float y = rtm_impl::is_mix_xyzw(comp1) ? vector_get_component(input0) : vector_get_component(input1); + const float z = rtm_impl::is_mix_xyzw(comp2) ? vector_get_component(input0) : vector_get_component(input1); + const float w = rtm_impl::is_mix_xyzw(comp3) ? vector_get_component(input0) : vector_get_component(input1); + return vector_set(x, y, z, w); + } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [x] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_dup_x(vector4f_arg0 input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [y] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_dup_y(vector4f_arg0 input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [z] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_dup_z(vector4f_arg0 input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Replicates the [w] component in all components. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_dup_w(vector4f_arg0 input) RTM_NO_EXCEPT { return vector_mix(input, input); } + + ////////////////////////////////////////////////////////////////////////// + // Logical + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // Per component logical AND between the inputs: input0 & input1 + ////////////////////////////////////////////////////////////////////////// + inline vector4f RTM_SIMD_CALL vector_and(vector4f_arg0 input0, vector4f_arg1 input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_and_ps(input0, input1); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(input0), vreinterpretq_u32_f32(input1))); +#else + +#if defined(RTM_COMPILER_GCC) + // GCC complains 'result' is used uninitialized but that is not true, ignore it + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuninitialized" +#endif + + const uint32_t* input0_ = reinterpret_cast(&input0); + const uint32_t* input1_ = reinterpret_cast(&input1); + + vector4f result; + uint32_t* result_ = reinterpret_cast(&result); + + result_[0] = input0_[0] & input1_[0]; + result_[1] = input0_[1] & input1_[1]; + result_[2] = input0_[2] & input1_[2]; + result_[3] = input0_[3] & input1_[3]; + + return result; + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component logical OR between the inputs: input0 | input1 + ////////////////////////////////////////////////////////////////////////// + inline vector4f RTM_SIMD_CALL vector_or(vector4f_arg0 input0, vector4f_arg1 input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_or_ps(input0, input1); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(input0), vreinterpretq_u32_f32(input1))); +#else + +#if defined(RTM_COMPILER_GCC) + // GCC complains 'result' is used uninitialized but that is not true, ignore it + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuninitialized" +#endif + + const uint32_t* input0_ = reinterpret_cast(&input0); + const uint32_t* input1_ = reinterpret_cast(&input1); + + vector4f result; + uint32_t* result_ = reinterpret_cast(&result); + + result_[0] = input0_[0] | input1_[0]; + result_[1] = input0_[1] | input1_[1]; + result_[2] = input0_[2] | input1_[2]; + result_[3] = input0_[3] | input1_[3]; + + return result; + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Per component logical XOR between the inputs: input0 ^ input1 + ////////////////////////////////////////////////////////////////////////// + inline vector4f RTM_SIMD_CALL vector_xor(vector4f_arg0 input0, vector4f_arg1 input1) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + return _mm_xor_ps(input0, input1); +#elif defined(RTM_NEON_INTRINSICS) + return vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(input0), vreinterpretq_u32_f32(input1))); +#else + +#if defined(RTM_COMPILER_GCC) + // GCC complains 'result' is used uninitialized but that is not true, ignore it + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuninitialized" +#endif + + const uint32_t* input0_ = reinterpret_cast(&input0); + const uint32_t* input1_ = reinterpret_cast(&input1); + + vector4f result; + uint32_t* result_ = reinterpret_cast(&result); + + result_[0] = input0_[0] ^ input1_[0]; + result_[1] = input0_[1] ^ input1_[1]; + result_[2] = input0_[2] ^ input1_[2]; + result_[3] = input0_[3] ^ input1_[3]; + + return result; + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif +#endif + } + + + ////////////////////////////////////////////////////////////////////////// + // Miscellaneous + ////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the sign of the input vector: input >= 0.0 ? 1.0 : -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_sign(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -0.0F, -0.0F, -0.0F, -0.0F }; + constexpr __m128 one = { 1.0F, 1.0F, 1.0F, 1.0F }; + const __m128 sign_bits = _mm_and_ps(input, signs); // Mask out the sign bit + return _mm_or_ps(sign_bits, one); // Copy the sign bit onto +-1.0f +#else + const mask4f mask = vector_greater_equal(input, vector_zero()); + return vector_select(mask, vector_set(1.0F), vector_set(-1.0F)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the input with the sign of the control value. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_copy_sign(vector4f_arg0 input, vector4f_arg1 control_sign) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128 sign_bit = _mm_set_ps1(-0.0F); + __m128 signs = _mm_and_ps(sign_bit, control_sign); + __m128 abs_input = _mm_andnot_ps(sign_bit, input); + return _mm_or_ps(abs_input, signs); +#else + float x = vector_get_x(input); + float y = vector_get_y(input); + float z = vector_get_z(input); + float w = vector_get_w(input); + + float x_sign = vector_get_x(control_sign); + float y_sign = vector_get_y(control_sign); + float z_sign = vector_get_z(control_sign); + float w_sign = vector_get_w(control_sign); + + return vector_set(std::copysign(x, x_sign), std::copysign(y, y_sign), std::copysign(z, z_sign), std::copysign(w, w_sign)); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the rounded input using a symmetric algorithm. + // vector_round_symmetric(1.5) = 2.0 + // vector_round_symmetric(1.2) = 1.0 + // vector_round_symmetric(-1.5) = -2.0 + // vector_round_symmetric(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_round_symmetric(vector4f_arg0 input) RTM_NO_EXCEPT + { + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + +#if defined(RTM_SSE4_INTRINSICS) + __m128 is_positive = _mm_cmpge_ps(input, _mm_setzero_ps()); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_andnot_ps(is_positive, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128 bias = _mm_or_ps(sign, _mm_set_ps1(0.5F)); + __m128 biased_input = _mm_add_ps(input, bias); + + __m128 floored = _mm_floor_ps(biased_input); + __m128 ceiled = _mm_ceil_ps(biased_input); + + return vector_select(is_positive, floored, ceiled); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ps(input, input); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128 bias = _mm_or_ps(sign, _mm_set_ps1(0.5F)); + __m128 biased_input = _mm_add_ps(input, bias); + + // Convert to an integer with truncation and back, this rounds towards zero. + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvttps_epi32(biased_input)); + + return _mm_or_ps(_mm_and_ps(use_original_input, input), _mm_andnot_ps(use_original_input, integer_part)); +#elif defined(RTM_NEON_INTRINSICS) && !defined(RTM_NEON64_INTRINSICS) // arm64 is faster with scalar code + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(input), vdupq_n_f32(-0.0F)); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + float32x4_t bias = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(0.5F)))); + float32x4_t biased_input = vaddq_f32(input, bias); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(biased_input)); + + return vbslq_f32(use_original_input, input, integer_part); +#else + const vector4f half = vector_set(0.5F); + const vector4f floored = vector_floor(vector_add(input, half)); + const vector4f ceiled = vector_ceil(vector_sub(input, half)); + const mask4f is_greater_equal = vector_greater_equal(input, vector_zero()); + return vector_select(is_greater_equal, floored, ceiled); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the rounded input using banker's rounding (half to even). + // vector_round_bankers(2.5) = 2.0 + // vector_round_bankers(1.5) = 2.0 + // vector_round_bankers(1.2) = 1.0 + // vector_round_bankers(-2.5) = -2.0 + // vector_round_bankers(-1.5) = -2.0 + // vector_round_bankers(-1.2) = -1.0 + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK RTM_FORCE_INLINE vector4f RTM_SIMD_CALL vector_round_bankers(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE4_INTRINSICS) + return _mm_round_ps(input, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); +#elif defined(RTM_SSE2_INTRINSICS) + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input, sign_mask); + + // We add the largest integer that a 32 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + __m128 truncating_offset = _mm_or_ps(sign, fractional_limit); + __m128 integer_part = _mm_sub_ps(_mm_add_ps(input, truncating_offset), truncating_offset); + + // If our input was so large that it had no fractional part, return it unchanged + // Otherwise return our integer part + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + return _mm_or_ps(_mm_and_ps(is_input_large, input), _mm_andnot_ps(is_input_large, integer_part)); +#elif defined(RTM_NEON64_INTRINSICS) + return vrndnq_f32(input); +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(input), vdupq_n_f32(-0.0F)); + + // We add the largest integer that a 32 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + float32x4_t truncating_offset = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(fractional_limit))); + float32x4_t integer_part = vsubq_f32(vaddq_f32(input, truncating_offset), truncating_offset); + + // If our input was so large that it had no fractional part, return it unchanged + // Otherwise return our integer part + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + return vbslq_f32(is_input_large, input, integer_part); +#else + scalarf x = scalar_round_bankers(scalarf(vector_get_x(input))); + scalarf y = scalar_round_bankers(scalarf(vector_get_y(input))); + scalarf z = scalar_round_bankers(scalarf(vector_get_z(input))); + scalarf w = scalar_round_bankers(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the sine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_sin(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ps(input, _mm_set_ps1(constants::one_div_two_pi())); + quotient = vector_round_bankers(quotient); + quotient = _mm_mul_ps(quotient, _mm_set_ps1(constants::two_pi())); + __m128 x = _mm_sub_ps(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(sign, _mm_set_ps1(constants::pi())); + + const __m128 reflection = _mm_sub_ps(reference, x); + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + + __m128 is_less_equal_than_half_pi = _mm_cmple_ps(x_abs, _mm_set_ps1(constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const __m128 x2 = _mm_mul_ps(x, x); + __m128 result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(-2.3828544692960918e-8F)), _mm_set_ps1(2.7521557770526783e-6F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.9840782426250314e-4F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(8.3333303183525942e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.6666666601721269e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.0F)); + result = _mm_mul_ps(result, x); + return result; +#elif defined(RTM_NEON_INTRINSICS) + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float32x4_t quotient = vmulq_n_f32(input, constants::one_div_two_pi()); + quotient = vector_round_bankers(quotient); + quotient = vmulq_n_f32(quotient, constants::two_pi()); + float32x4_t x = vsubq_f32(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), sign_mask); + float32x4_t reference = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(constants::pi())))); + + float32x4_t reflection = vsubq_f32(reference, x); +#if !defined(RTM_IMPL_VCA_SUPPORTED) + float32x4_t is_less_equal_than_half_pi = vcleq_f32(vabsq_f32(x), vdupq_n_f32(constants::half_pi())); +#else + float32x4_t is_less_equal_than_half_pi = vcaleq_f32(x, vdupq_n_f32(constants::half_pi())); +#endif + x = vbslq_f32(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + float32x4_t x2 = vmulq_f32(x, x); + + float32x4_t result = RTM_VECTOR4F_MULS_ADD(x2, -2.3828544692960918e-8F, vdupq_n_f32(2.7521557770526783e-6F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(-1.9840782426250314e-4F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(8.3333303183525942e-3F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(-1.6666666601721269e-1F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(1.0F)); + + result = vmulq_f32(result, x); + return result; +#else + scalarf x = scalar_sin(scalarf(vector_get_x(input))); + scalarf y = scalar_sin(scalarf(vector_get_y(input))); + scalarf z = scalar_sin(scalarf(vector_get_z(input))); + scalarf w = scalar_sin(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-sine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_asin(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Use a degree 7 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + // Use the sign bit to generate our absolute value since we'll re-use that constant + const __m128 sign_bit = _mm_set_ps1(-0.0F); + __m128 abs_value = _mm_andnot_ps(sign_bit, input); + + // Calculate our value + __m128 result = _mm_add_ps(_mm_mul_ps(abs_value, _mm_set_ps1(-1.2690614339589956e-3F)), _mm_set_ps1(6.7072304676685235e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-1.7162031184398074e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(3.0961594977611639e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-5.0207843052845647e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(8.8986946573346160e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-2.1459960076929829e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(1.5707963267948966F)); + + // Scale our result + __m128 scale = _mm_sqrt_ps(_mm_sub_ps(_mm_set_ps1(1.0F), abs_value)); + result = _mm_mul_ps(result, scale); + + // Normally the math is as follow: + // If input is positive: PI/2 - result + // If input is negative: PI/2 - (PI - result) = PI/2 - PI + result = -PI/2 + result + + // As such, the offset is PI/2 and it takes the sign of the input + // This allows us to load a single constant from memory directly + __m128 input_sign = _mm_and_ps(input, sign_bit); + __m128 offset = _mm_or_ps(input_sign, _mm_set_ps1(constants::half_pi())); + + // And our result has the opposite sign of the input + result = _mm_xor_ps(result, _mm_xor_ps(input_sign, sign_bit)); + return _mm_add_ps(result, offset); +#else + scalarf x = scalar_asin(scalarf(vector_get_x(input))); + scalarf y = scalar_asin(scalarf(vector_get_y(input))); + scalarf z = scalar_asin(scalarf(vector_get_z(input))); + scalarf w = scalar_asin(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_cos(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ps(input, _mm_set_ps1(constants::one_div_two_pi())); + quotient = vector_round_bankers(quotient); + quotient = _mm_mul_ps(quotient, _mm_set_ps1(constants::two_pi())); + __m128 x = _mm_sub_ps(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 x_sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(x_sign, _mm_set_ps1(constants::pi())); + const __m128 reflection = _mm_sub_ps(reference, x); + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + __m128 is_less_equal_than_half_pi = _mm_cmple_ps(x_abs, _mm_set_ps1(constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const __m128 x2 = _mm_mul_ps(x, x); + __m128 result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(-2.6051615464872668e-7F)), _mm_set_ps1(2.4760495088926859e-5F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.3888377661039897e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(4.1666638865338612e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-4.9999999508695869e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.0F)); + + // Remap into [-pi, pi] + return _mm_or_ps(result, _mm_andnot_ps(is_less_equal_than_half_pi, sign_mask)); +#elif defined(RTM_NEON_INTRINSICS) + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float32x4_t quotient = vmulq_n_f32(input, constants::one_div_two_pi()); + quotient = vector_round_bankers(quotient); + quotient = vmulq_n_f32(quotient, constants::two_pi()); + float32x4_t x = vsubq_f32(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), sign_mask); + float32x4_t reference = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(constants::pi())))); + + float32x4_t reflection = vsubq_f32(reference, x); +#if !defined(RTM_IMPL_VCA_SUPPORTED) + float32x4_t is_less_equal_than_half_pi = vcleq_f32(vabsq_f32(x), vdupq_n_f32(constants::half_pi())); +#else + float32x4_t is_less_equal_than_half_pi = vcaleq_f32(x, vdupq_n_f32(constants::half_pi())); +#endif + x = vbslq_f32(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + float32x4_t x2 = vmulq_f32(x, x); + + float32x4_t result = RTM_VECTOR4F_MULS_ADD(x2, -2.6051615464872668e-7F, vdupq_n_f32(2.4760495088926859e-5F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(-1.3888377661039897e-3F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(4.1666638865338612e-2F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(-4.9999999508695869e-1F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(1.0F)); + + // Remap into [-pi, pi] + return vbslq_f32(is_less_equal_than_half_pi, result, vnegq_f32(result)); +#else + scalarf x = scalar_cos(scalarf(vector_get_x(input))); + scalarf y = scalar_cos(scalarf(vector_get_y(input))); + scalarf z = scalar_cos(scalarf(vector_get_z(input))); + scalarf w = scalar_cos(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-cosine of the input. + // Input value must be in the range [-1.0, 1.0]. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_acos(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Use the identity: acos(value) + asin(value) = PI/2 + // This ends up being: acos(value) = PI/2 - asin(value) + // Since asin(value) = PI/2 - sqrt(1.0 - polynomial(value)) + // Our end result is acos(value) = sqrt(1.0 - polynomial(value)) + // This means we can re-use the same polynomial as asin() + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + // Use the sign bit to generate our absolute value since we'll re-use that constant + const __m128 sign_bit = _mm_set_ps1(-0.0F); + __m128 abs_value = _mm_andnot_ps(sign_bit, input); + + // Calculate our value + __m128 result = _mm_add_ps(_mm_mul_ps(abs_value, _mm_set_ps1(-1.2690614339589956e-3F)), _mm_set_ps1(6.7072304676685235e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-1.7162031184398074e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(3.0961594977611639e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-5.0207843052845647e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(8.8986946573346160e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-2.1459960076929829e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(1.5707963267948966F)); + + // Scale our result + __m128 scale = _mm_sqrt_ps(_mm_sub_ps(_mm_set_ps1(1.0F), abs_value)); + result = _mm_mul_ps(result, scale); + + // Normally the math is as follow: + // If input is positive: result + // If input is negative: PI - result = -result + PI + + // As such, the offset is 0.0 when the input is positive and PI when negative + __m128 is_input_negative = _mm_cmplt_ps(input, _mm_setzero_ps()); + __m128 offset = _mm_and_ps(is_input_negative, _mm_set_ps1(constants::pi())); + + // And our result has the same sign of the input + __m128 input_sign = _mm_and_ps(input, sign_bit); + result = _mm_or_ps(result, input_sign); + return _mm_add_ps(result, offset); +#else + scalarf x = scalar_acos(scalarf(vector_get_x(input))); + scalarf y = scalar_acos(scalarf(vector_get_y(input))); + scalarf z = scalar_acos(scalarf(vector_get_z(input))); + scalarf w = scalar_acos(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the sine and cosine of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline void RTM_SIMD_CALL vector_sincos(vector4f_arg0 input, vector4f& out_sine, vector4f& out_cosine) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ps(input, _mm_set_ps1(constants::one_div_two_pi())); + quotient = vector_round_bankers(quotient); + quotient = _mm_mul_ps(quotient, _mm_set_ps1(constants::two_pi())); + __m128 x = _mm_sub_ps(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 x_sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(x_sign, _mm_set_ps1(constants::pi())); + const __m128 reflection = _mm_sub_ps(reference, x); + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + __m128 is_less_equal_than_half_pi = _mm_cmple_ps(x_abs, _mm_set_ps1(constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + const __m128 x2 = _mm_mul_ps(x, x); + + // Calculate our cosine value + __m128 cosine_result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(-2.6051615464872668e-7F)), _mm_set_ps1(2.4760495088926859e-5F)); + cosine_result = _mm_add_ps(_mm_mul_ps(cosine_result, x2), _mm_set_ps1(-1.3888377661039897e-3F)); + cosine_result = _mm_add_ps(_mm_mul_ps(cosine_result, x2), _mm_set_ps1(4.1666638865338612e-2F)); + cosine_result = _mm_add_ps(_mm_mul_ps(cosine_result, x2), _mm_set_ps1(-4.9999999508695869e-1F)); + cosine_result = _mm_add_ps(_mm_mul_ps(cosine_result, x2), _mm_set_ps1(1.0F)); + + // Remap into [-pi, pi] + out_cosine = _mm_or_ps(cosine_result, _mm_andnot_ps(is_less_equal_than_half_pi, sign_mask)); + + // Calculate our sine value + __m128 sine_result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(-2.3828544692960918e-8F)), _mm_set_ps1(2.7521557770526783e-6F)); + sine_result = _mm_add_ps(_mm_mul_ps(sine_result, x2), _mm_set_ps1(-1.9840782426250314e-4F)); + sine_result = _mm_add_ps(_mm_mul_ps(sine_result, x2), _mm_set_ps1(8.3333303183525942e-3F)); + sine_result = _mm_add_ps(_mm_mul_ps(sine_result, x2), _mm_set_ps1(-1.6666666601721269e-1F)); + sine_result = _mm_add_ps(_mm_mul_ps(sine_result, x2), _mm_set_ps1(1.0F)); + out_sine = _mm_mul_ps(sine_result, x); +#elif defined(RTM_NEON_INTRINSICS) + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float32x4_t quotient = vmulq_n_f32(input, constants::one_div_two_pi()); + quotient = vector_round_bankers(quotient); + quotient = vmulq_n_f32(quotient, constants::two_pi()); + float32x4_t x = vsubq_f32(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + const uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), sign_mask); + const float32x4_t reference = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(constants::pi())))); + + const float32x4_t reflection = vsubq_f32(reference, x); +#if !defined(RTM_IMPL_VCA_SUPPORTED) + const float32x4_t is_less_equal_than_half_pi = vcleq_f32(vabsq_f32(x), vdupq_n_f32(constants::half_pi())); +#else + const float32x4_t is_less_equal_than_half_pi = vcaleq_f32(x, vdupq_n_f32(constants::half_pi())); +#endif + x = vbslq_f32(is_less_equal_than_half_pi, x, reflection); + const float32x4_t x2 = vmulq_f32(x, x); + + // Calculate our cosine value + float32x4_t cosine_result = RTM_VECTOR4F_MULS_ADD(x2, -2.6051615464872668e-7F, vdupq_n_f32(2.4760495088926859e-5F)); + cosine_result = RTM_VECTOR4F_MULV_ADD(cosine_result, x2, vdupq_n_f32(-1.3888377661039897e-3F)); + cosine_result = RTM_VECTOR4F_MULV_ADD(cosine_result, x2, vdupq_n_f32(4.1666638865338612e-2F)); + cosine_result = RTM_VECTOR4F_MULV_ADD(cosine_result, x2, vdupq_n_f32(-4.9999999508695869e-1F)); + cosine_result = RTM_VECTOR4F_MULV_ADD(cosine_result, x2, vdupq_n_f32(1.0F)); + + // Remap into [-pi, pi] + out_cosine = vbslq_f32(is_less_equal_than_half_pi, cosine_result, vnegq_f32(cosine_result)); + + // Calculate our sine value + float32x4_t sine_result = RTM_VECTOR4F_MULS_ADD(x2, -2.3828544692960918e-8F, vdupq_n_f32(2.7521557770526783e-6F)); + sine_result = RTM_VECTOR4F_MULV_ADD(sine_result, x2, vdupq_n_f32(-1.9840782426250314e-4F)); + sine_result = RTM_VECTOR4F_MULV_ADD(sine_result, x2, vdupq_n_f32(8.3333303183525942e-3F)); + sine_result = RTM_VECTOR4F_MULV_ADD(sine_result, x2, vdupq_n_f32(-1.6666666601721269e-1F)); + sine_result = RTM_VECTOR4F_MULV_ADD(sine_result, x2, vdupq_n_f32(1.0F)); + + out_sine = vmulq_f32(sine_result, x); +#else + const vector4f x = scalar_sincos(scalarf(vector_get_x(input))); + const vector4f y = scalar_sincos(scalarf(vector_get_y(input))); + const vector4f z = scalar_sincos(scalarf(vector_get_z(input))); + const vector4f w = scalar_sincos(scalarf(vector_get_w(input))); + + out_cosine = vector4f{ x.y, y.y, z.y, w.y }; + out_sine = vector4f{ x.x, y.x, z.x, w.x }; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the tangent of the input angle. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_tan(vector4f_arg0 angle) RTM_NO_EXCEPT + { + // Use the identity: tan(angle) = sin(angle) / cos(angle) + vector4f sin_; + vector4f cos_; + vector_sincos(angle, sin_, cos_); + + const mask4f is_cos_zero = vector_equal(cos_, vector_zero()); + const vector4f signed_infinity = vector_copy_sign(vector_set(std::numeric_limits::infinity()), angle); + const vector4f result = vector_div(sin_, cos_); + return vector_select(is_cos_zero, signed_infinity, result); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-tangent of the input. + // Note that due to the sign ambiguity, atan cannot determine which quadrant + // the value resides in. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_atan(vector4f_arg0 input) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // Use a degree 13 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Discard our sign, we'll restore it later + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_value = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + + // Compute our value + __m128 is_larger_than_one = _mm_cmpgt_ps(abs_value, _mm_set_ps1(1.0F)); + __m128 reciprocal = vector_reciprocal(abs_value); + + __m128 x = vector_select(is_larger_than_one, reciprocal, abs_value); + + __m128 x2 = _mm_mul_ps(x, x); + + __m128 result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(7.2128853633444123e-3F)), _mm_set_ps1(-3.5059680836411644e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(8.1675882859940430e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.3374657325451267e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.9856563505717162e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-3.3324998579202170e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.0F)); + result = _mm_mul_ps(result, x); + + __m128 remapped = _mm_sub_ps(_mm_set_ps1(constants::half_pi()), result); + + // pi/2 - result + result = vector_select(is_larger_than_one, remapped, result); + + // Keep the original sign + return _mm_or_ps(result, _mm_and_ps(input, _mm_set_ps1(-0.0F))); +#elif defined(RTM_NEON_INTRINSICS) + // Use a degree 13 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Discard our sign, we'll restore it later + float32x4_t abs_value = vabsq_f32(input); + + // Compute our value +#if !defined(RTM_IMPL_VCA_SUPPORTED) + uint32x4_t is_larger_than_one = vcgtq_f32(vabsq_f32(input), vdupq_n_f32(1.0F)); +#else + uint32x4_t is_larger_than_one = vcagtq_f32(input, vdupq_n_f32(1.0F)); +#endif + float32x4_t reciprocal = vector_reciprocal(abs_value); + + float32x4_t x = vector_select(is_larger_than_one, reciprocal, abs_value); + + float32x4_t x2 = vmulq_f32(x, x); + + float32x4_t result = RTM_VECTOR4F_MULS_ADD(x2, 7.2128853633444123e-3F, vdupq_n_f32(-3.5059680836411644e-2F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(8.1675882859940430e-2F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(-1.3374657325451267e-1F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(1.9856563505717162e-1F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(-3.3324998579202170e-1F)); + result = RTM_VECTOR4F_MULV_ADD(result, x2, vdupq_n_f32(1.0F)); + + result = vmulq_f32(result, x); + + float32x4_t remapped = vsubq_f32(vdupq_n_f32(constants::half_pi()), result); + + // pi/2 - result + result = vector_select(is_larger_than_one, remapped, result); + + // Keep the original sign + return vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(result), vandq_u32(vreinterpretq_u32_f32(input), vreinterpretq_u32_f32(vdupq_n_f32(-0.0F))))); +#else + scalarf x = scalar_atan(scalarf(vector_get_x(input))); + scalarf y = scalar_atan(scalarf(vector_get_y(input))); + scalarf z = scalar_atan(scalarf(vector_get_z(input))); + scalarf w = scalar_atan(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Returns per component the arc-tangent of [y/x] using the sign of the arguments to + // determine the correct quadrant. + // Y represents the proportion of the y-coordinate. + // X represents the proportion of the x-coordinate. + ////////////////////////////////////////////////////////////////////////// + RTM_DISABLE_SECURITY_COOKIE_CHECK inline vector4f RTM_SIMD_CALL vector_atan2(vector4f_arg0 y, vector4f_arg1 x) RTM_NO_EXCEPT + { +#if defined(RTM_SSE2_INTRINSICS) + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + + const __m128 zero = _mm_setzero_ps(); + __m128 is_x_zero = _mm_cmpeq_ps(x, zero); + __m128 is_y_zero = _mm_cmpeq_ps(y, zero); + __m128 inputs_are_zero = _mm_and_ps(is_x_zero, is_y_zero); + + __m128 is_x_positive = _mm_cmpgt_ps(x, zero); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 y_sign = _mm_and_ps(y, sign_mask); + + // If X == 0.0, our offset is PI/2 otherwise it is PI both with the sign of Y + __m128 half_pi = _mm_set_ps1(constants::half_pi()); + __m128 pi = _mm_set_ps1(constants::pi()); + __m128 offset = _mm_or_ps(_mm_and_ps(is_x_zero, half_pi), _mm_andnot_ps(is_x_zero, pi)); + offset = _mm_or_ps(offset, y_sign); + + // If X > 0.0, our offset is 0.0 + offset = _mm_andnot_ps(is_x_positive, offset); + + // If X == 0.0 and Y == 0.0, our offset is 0.0 + offset = _mm_andnot_ps(inputs_are_zero, offset); + + __m128 angle = _mm_div_ps(y, x); + __m128 value = vector_atan(angle); + + // If X == 0.0, our value is 0.0 otherwise it is atan(y/x) + value = _mm_andnot_ps(is_x_zero, value); + + // If X == 0.0 and Y == 0.0, our value is 0.0 + value = _mm_andnot_ps(inputs_are_zero, value); + + return _mm_add_ps(value, offset); +#elif defined(RTM_NEON64_INTRINSICS) + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + +#if !defined(RTM_IMPL_VCZ_SUPPORTED) + float32x4_t zero = vdupq_n_f32(0.0F); + uint32x4_t is_x_zero = vceqq_f32(x, zero); + uint32x4_t is_y_zero = vceqq_f32(y, zero); + uint32x4_t inputs_are_zero = vandq_u32(is_x_zero, is_y_zero); + + uint32x4_t is_x_positive = vcgtq_f32(x, zero); +#else + uint32x4_t is_x_zero = vceqzq_f32(x); + uint32x4_t is_y_zero = vceqzq_f32(y); + uint32x4_t inputs_are_zero = vandq_u32(is_x_zero, is_y_zero); + + uint32x4_t is_x_positive = vcgtzq_f32(x); +#endif + + uint32x4_t y_sign = vandq_u32(vreinterpretq_u32_f32(y), vreinterpretq_u32_f32(vdupq_n_f32(-0.0F))); + + // If X == 0.0, our offset is PI/2 otherwise it is PI both with the sign of Y + float32x4_t half_pi = vdupq_n_f32(constants::half_pi()); + float32x4_t pi = vdupq_n_f32(constants::pi()); + float32x4_t offset = vreinterpretq_f32_u32(vorrq_u32(vandq_u32(is_x_zero, vreinterpretq_u32_f32(half_pi)), vandq_u32(vmvnq_u32(is_x_zero), vreinterpretq_u32_f32(pi)))); + offset = vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(offset), y_sign)); + + // If X > 0.0, our offset is 0.0 + offset = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(is_x_positive), vreinterpretq_u32_f32(offset))); + + // If X == 0.0 and Y == 0.0, our offset is 0.0 + offset = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(inputs_are_zero), vreinterpretq_u32_f32(offset))); + + float32x4_t angle = vector_div(y, x); + float32x4_t value = vector_atan(angle); + + // If X == 0.0, our value is 0.0 otherwise it is atan(y/x) + value = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(is_x_zero), vreinterpretq_u32_f32(value))); + + // If X == 0.0 and Y == 0.0, our value is 0.0 + value = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(inputs_are_zero), vreinterpretq_u32_f32(value))); + + return vaddq_f32(value, offset); +#else + scalarf x_ = scalar_atan2(scalarf(vector_get_x(y)), scalarf(vector_get_x(x))); + scalarf y_ = scalar_atan2(scalarf(vector_get_y(y)), scalarf(vector_get_y(x))); + scalarf z_ = scalar_atan2(scalarf(vector_get_z(y)), scalarf(vector_get_z(x))); + scalarf w_ = scalar_atan2(scalarf(vector_get_w(y)), scalarf(vector_get_w(x))); + return vector_set(x_, y_, z_, w_); +#endif + } + + RTM_IMPL_VERSION_NAMESPACE_END +} + +RTM_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/version.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/version.h new file mode 100644 index 00000000..f8048753 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/includes/rtm/version.h @@ -0,0 +1,90 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "rtm/impl/detect_compiler.h" + +//////////////////////////////////////////////////////////////////////////////// +// Macros to detect the Realtime Math version +//////////////////////////////////////////////////////////////////////////////// + +#define RTM_VERSION_MAJOR 2 +#define RTM_VERSION_MINOR 1 +#define RTM_VERSION_PATCH 3 + +//////////////////////////////////////////////////////////////////////////////// +// In order to allow multiple versions of this library to coexist side by side +// within the same executable/library, the symbols have to be unique per version. +// We achieve this by using a versioned namespace that we optionally inline. +// To disable namespace inlining, define RTM_NO_INLINE_NAMESPACE before including +// any Realtime Math header. To disable the versioned namespace altogether, +// define RTM_NO_VERSION_NAMESPACE before including any Realtime Math header. +//////////////////////////////////////////////////////////////////////////////// + +#if !defined(RTM_NO_VERSION_NAMESPACE) + #if defined(RTM_COMPILER_MSVC) && RTM_COMPILER_MSVC == RTM_COMPILER_MSVC_2015 + // VS2015 struggles with type resolution when inline namespaces are used + // For that reason, we disable it explicitly + #define RTM_NO_VERSION_NAMESPACE + #endif +#endif + +// Name of the namespace, e.g. v21 +#define RTM_IMPL_VERSION_NAMESPACE_NAME v ## RTM_VERSION_MAJOR ## RTM_VERSION_MINOR + +// Because this is being introduced in a patch release, as caution, it is disabled +// by default. It does break ABI if host runtimes forward declare types but that +// is something they shouldn't do with a 3rd party library. Now, we offer forward +// declaration headers to help prepare the migration in the next minor release. +#if defined(RTM_NO_VERSION_NAMESPACE) || !defined(RTM_ENABLE_VERSION_NAMESPACE) + // Namespace is inlined, its usage does not need to be qualified with the + // full version everywhere + #define RTM_IMPL_NAMESPACE rtm + + #define RTM_IMPL_VERSION_NAMESPACE_BEGIN + #define RTM_IMPL_VERSION_NAMESPACE_END +#elif defined(RTM_NO_INLINE_NAMESPACE) + // Namespace won't be inlined, its usage will have to be qualified with the + // full version everywhere + #define RTM_IMPL_NAMESPACE rtm::RTM_IMPL_VERSION_NAMESPACE_NAME + + #define RTM_IMPL_VERSION_NAMESPACE_BEGIN \ + namespace RTM_IMPL_VERSION_NAMESPACE_NAME \ + { + + #define RTM_IMPL_VERSION_NAMESPACE_END \ + } +#else + // Namespace is inlined, its usage does not need to be qualified with the + // full version everywhere + #define RTM_IMPL_NAMESPACE rtm + + #define RTM_IMPL_VERSION_NAMESPACE_BEGIN \ + inline namespace RTM_IMPL_VERSION_NAMESPACE_NAME \ + { + + #define RTM_IMPL_VERSION_NAMESPACE_END \ + } +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/make.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/make.py new file mode 100644 index 00000000..ef1c24d0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/make.py @@ -0,0 +1,492 @@ +import argparse +import multiprocessing +import os +import platform +import shutil +import subprocess +import sys + +def parse_argv(): + parser = argparse.ArgumentParser(add_help=False) + + actions = parser.add_argument_group(title='Actions', description='If no action is specified, on Windows, OS X, and Linux the solution/make files are generated. Multiple actions can be used simultaneously.') + actions.add_argument('-build', action='store_true') + actions.add_argument('-clean', action='store_true') + actions.add_argument('-clean_only', action='store_true') + actions.add_argument('-unit_test', action='store_true') + actions.add_argument('-bench', action='store_true') + actions.add_argument('-run_bench', action='store_true') + actions.add_argument('-pull_bench', action='store_true') # Android only + + target = parser.add_argument_group(title='Target') + target.add_argument('-compiler', choices=['vs2015', 'vs2017', 'vs2019', 'vs2019-clang', 'android', 'clang4', 'clang5', 'clang6', 'clang7', 'clang8', 'clang9', 'clang10', 'clang11', 'gcc5', 'gcc6', 'gcc7', 'gcc8', 'gcc9', 'gcc10', 'osx', 'ios', 'emscripten'], help='Defaults to the host system\'s default compiler') + target.add_argument('-config', choices=['Debug', 'Release'], type=str.capitalize) + target.add_argument('-cpu', choices=['x86', 'x64', 'armv7', 'arm64', 'wasm'], help='Defaults to the host system\'s architecture') + + misc = parser.add_argument_group(title='Miscellaneous') + misc.add_argument('-avx', dest='use_avx', action='store_true', help='Compile using AVX instructions on Windows, OS X, and Linux') + misc.add_argument('-avx2', dest='use_avx2', action='store_true', help='Compile using AVX2 instructions on Windows, OS X, and Linux') + misc.add_argument('-nosimd', dest='use_simd', action='store_false', help='Compile without SIMD instructions') + misc.add_argument('-num_threads', help='No. to use while compiling and regressing') + misc.add_argument('-tests_matching', help='Only run tests whose names match this regex') + misc.add_argument('-vector_mix_test', action='store_true', help='Enable the vector_mix unit tests') + misc.add_argument('-help', action='help', help='Display this usage information') + + num_threads = multiprocessing.cpu_count() + if platform.system() == 'Linux' and sys.version_info >= (3, 4): + num_threads = len(os.sched_getaffinity(0)) + if not num_threads or num_threads == 0: + num_threads = 4 + + parser.set_defaults(build=False, clean=False, clean_only=False, unit_test=False, + compiler=None, config='Release', cpu=None, use_avx=False, use_avx2=False, use_simd=True, num_threads=num_threads, tests_matching='', vector_mix_test=False, + bench=False, run_bench=False, pull_bench=False) + + args = parser.parse_args() + + # Sanitize and validate our options + if (args.use_avx or args.use_avx2) and not args.use_simd: + print('SIMD is explicitly disabled, AVX and AVX2 will not be used') + args.use_avx = False + args.use_avx2 = False + + if args.compiler == 'android': + if not args.cpu: + args.cpu = 'arm64' + + if not platform.system() == 'Windows': + print('Android is only supported on Windows') + sys.exit(1) + + if args.use_avx or args.use_avx2: + print('AVX and AVX2 are not supported on Android') + sys.exit(1) + + if not args.cpu in ['armv7', 'arm64']: + print('{} cpu architecture not in supported list [armv7, arm64] for Android'.format(args.cpu)) + sys.exit(1) + elif args.compiler == 'ios': + if not args.cpu: + args.cpu = 'arm64' + + if not platform.system() == 'Darwin': + print('iOS is only supported on OS X') + sys.exit(1) + + if args.use_avx or args.use_avx2: + print('AVX and AVX2 are not supported on iOS') + sys.exit(1) + + if args.unit_test: + print('Unit tests cannot run from the command line on iOS') + sys.exit(1) + + if not args.cpu in ['arm64']: + print('{} cpu architecture not in supported list [arm64] for iOS'.format(args.cpu)) + sys.exit(1) + elif args.compiler == 'emscripten': + if not args.cpu: + args.cpu = 'wasm' + + if not platform.system() == 'Darwin' and not platform.system() == 'Linux': + print('Emscripten is only supported on OS X and Linux') + sys.exit(1) + + if args.use_avx or args.use_avx2: + print('AVX and AVX2 are not supported on Emscripten') + sys.exit(1) + + if not args.cpu in ['wasm']: + print('{} cpu architecture not in supported list [wasm] for Emscripten'.format(args.cpu)) + sys.exit(1) + else: + if not args.cpu: + args.cpu = 'x64' + + if args.cpu == 'arm64': + if not args.compiler in ['vs2017', 'vs2019', 'ios', 'android']: + print('arm64 is only supported with VS2017, VS2019, Android, and iOS') + sys.exit(1) + elif args.cpu == 'armv7': + if not args.compiler == 'android': + print('armv7 is only supported with Android') + sys.exit(1) + elif args.cpu == 'wasm': + if not args.compiler == 'emscripten': + print('wasm is only supported with Emscripten') + sys.exit(1) + + if platform.system() == 'Darwin' and args.cpu == 'x86': + result = subprocess.check_output(['xcodebuild', '-version']).decode("utf-8") + if 'Xcode 11' in result: + print('Versions of Xcode 11 and up no longer support x86') + sys.exit(1) + + return args + +def get_generator(compiler, cpu): + if compiler == None: + return None + + if platform.system() == 'Windows': + if compiler == 'vs2015': + if cpu == 'x86': + return 'Visual Studio 14' + elif cpu == 'x64': + return 'Visual Studio 14 Win64' + elif compiler == 'vs2017': + if cpu == 'x86': + return 'Visual Studio 15' + elif cpu == 'x64': + return 'Visual Studio 15 Win64' + elif cpu == 'arm64': + # VS2017 ARM/ARM64 support only works with cmake 3.13 and up and the architecture must be specified with + # the -A cmake switch + return 'Visual Studio 15 2017' + elif compiler == 'vs2019' or compiler == 'vs2019-clang': + return 'Visual Studio 16 2019' + elif compiler == 'android': + # For Android, we use the default generator since we don't build with CMake + return None + elif platform.system() == 'Darwin': + if compiler == 'osx' or compiler == 'ios': + return 'Xcode' + elif compiler == 'emscripten': + # Emscripten uses the default generator + return None + elif platform.system() == 'Linux': + if compiler == 'emscripten': + # Emscripten uses the default generator + return None + + return 'Unix Makefiles' + + print('Unknown compiler: {}'.format(compiler)) + print('See help with: python make.py -help') + sys.exit(1) + +def get_architecture(compiler, cpu): + if compiler == None: + return None + + if platform.system() == 'Windows': + if compiler == 'vs2017': + if cpu == 'arm64': + return 'ARM64' + elif compiler == 'vs2019' or compiler == 'vs2019-clang': + if cpu == 'x86': + return 'Win32' + else: + return cpu + + # This compiler/cpu pair does not need the architecture switch + return None + +def get_toolchain(compiler, cmake_script_dir): + if platform.system() == 'Windows' and compiler == 'android': + return os.path.join(cmake_script_dir, 'Toolchain-Android.cmake') + elif platform.system() == 'Darwin' and compiler == 'ios': + return os.path.join(cmake_script_dir, 'Toolchain-iOS.cmake') + + # No toolchain + return None + +def set_compiler_env(compiler, args): + if platform.system() == 'Linux': + if compiler == 'clang4': + os.environ['CC'] = 'clang-4.0' + os.environ['CXX'] = 'clang++-4.0' + elif compiler == 'clang5': + os.environ['CC'] = 'clang-5.0' + os.environ['CXX'] = 'clang++-5.0' + elif compiler == 'clang6': + os.environ['CC'] = 'clang-6.0' + os.environ['CXX'] = 'clang++-6.0' + elif compiler == 'clang7': + os.environ['CC'] = 'clang-7' + os.environ['CXX'] = 'clang++-7' + elif compiler == 'clang8': + os.environ['CC'] = 'clang-8' + os.environ['CXX'] = 'clang++-8' + elif compiler == 'clang9': + os.environ['CC'] = 'clang-9' + os.environ['CXX'] = 'clang++-9' + elif compiler == 'clang10': + os.environ['CC'] = 'clang-10' + os.environ['CXX'] = 'clang++-10' + elif compiler == 'clang11': + os.environ['CC'] = 'clang-11' + os.environ['CXX'] = 'clang++-11' + elif compiler == 'gcc5': + os.environ['CC'] = 'gcc-5' + os.environ['CXX'] = 'g++-5' + elif compiler == 'gcc6': + os.environ['CC'] = 'gcc-6' + os.environ['CXX'] = 'g++-6' + elif compiler == 'gcc7': + os.environ['CC'] = 'gcc-7' + os.environ['CXX'] = 'g++-7' + elif compiler == 'gcc8': + os.environ['CC'] = 'gcc-8' + os.environ['CXX'] = 'g++-8' + elif compiler == 'gcc9': + os.environ['CC'] = 'gcc-9' + os.environ['CXX'] = 'g++-9' + elif compiler == 'gcc10': + os.environ['CC'] = 'gcc-10' + os.environ['CXX'] = 'g++-10' + elif compiler == 'emscripten': + # Nothing to do for Emscripten + return + else: + print('Unknown compiler: {}'.format(compiler)) + print('See help with: python make.py -help') + sys.exit(1) + +def do_generate_solution(build_dir, cmake_script_dir, args): + compiler = args.compiler + cpu = args.cpu + config = args.config + + if compiler: + set_compiler_env(compiler, args) + + extra_switches = ['--no-warn-unused-cli'] + extra_switches.append('-DCPU_INSTRUCTION_SET:STRING={}'.format(cpu)) + + if args.use_avx: + print('Enabling AVX usage') + extra_switches.append('-DUSE_AVX_INSTRUCTIONS:BOOL=true') + + if args.use_avx2: + print('Enabling AVX2 usage') + extra_switches.append('-DUSE_AVX2_INSTRUCTIONS:BOOL=true') + + if not args.use_simd: + print('Disabling SIMD instruction usage') + extra_switches.append('-DUSE_SIMD_INSTRUCTIONS:BOOL=false') + + if args.vector_mix_test: + print('Enabling vector_mix unit tests') + extra_switches.append('-DWITH_VECTOR_MIX_TESTS:BOOL=true') + + if args.bench: + extra_switches.append('-DBUILD_BENCHMARK_EXE:BOOL=true') + + if not platform.system() == 'Windows': + extra_switches.append('-DCMAKE_BUILD_TYPE={}'.format(config.upper())) + + toolchain = get_toolchain(compiler, cmake_script_dir) + if toolchain: + extra_switches.append('-DCMAKE_TOOLCHAIN_FILE={}'.format(toolchain)) + + # Generate IDE solution + print('Generating build files ...') + if compiler == 'emscripten': + cmake_cmd = 'emcmake cmake .. -DCMAKE_INSTALL_PREFIX="{}" {}'.format(build_dir, ' '.join(extra_switches)) + else: + cmake_generator = get_generator(compiler, cpu) + if not cmake_generator: + print('Using default generator') + else: + generator_suffix = '' + if compiler == 'vs2019-clang': + extra_switches.append('-T ClangCL') + generator_suffix = 'Clang CL' + + print('Using generator: {} {}'.format(cmake_generator, generator_suffix)) + extra_switches.append('-G "{}"'.format(cmake_generator)) + + cmake_arch = get_architecture(compiler, cpu) + if cmake_arch: + print('Using architecture: {}'.format(cmake_arch)) + extra_switches.append('-A {}'.format(cmake_arch)) + + cmake_cmd = 'cmake .. -DCMAKE_INSTALL_PREFIX="{}" {}'.format(build_dir, ' '.join(extra_switches)) + + result = subprocess.call(cmake_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_build(args): + config = args.config + + print('Building ...') + cmake_cmd = 'cmake --build .' + if platform.system() == 'Windows': + if args.compiler == 'android': + cmake_cmd += ' --config {}'.format(config) + else: + cmake_cmd += ' --config {} --target INSTALL'.format(config) + elif platform.system() == 'Darwin': + if args.compiler == 'ios': + cmake_cmd += ' --config {}'.format(config) + else: + cmake_cmd += ' --config {} --target install'.format(config) + else: + cmake_cmd += ' --target install' + + result = subprocess.call(cmake_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_tests_android(build_dir, args): + # Switch our working directory to where we built everything + working_dir = os.path.join(build_dir, 'tests', 'main_android') + os.chdir(working_dir) + + gradlew_exe = os.path.join(working_dir, 'gradlew.bat') + + # We uninstall first and then install + if args.config == 'Debug': + install_cmd = 'uninstallAll installDebug' + elif args.config == 'Release': + install_cmd = 'uninstallAll installRelease' + + # Install our app + test_cmd = '"{}" {}'.format(gradlew_exe, install_cmd) + result = subprocess.call(test_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Execute through ADB + run_cmd = 'adb shell am start -n "com.rtm.unit_tests/com.rtm.unit_tests.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER' + result = subprocess.call(run_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Restore working directory + os.chdir(build_dir) + +def do_tests_cmake(args): + ctest_cmd = 'ctest --output-on-failure --parallel {}'.format(args.num_threads) + + if platform.system() == 'Windows' or platform.system() == 'Darwin': + ctest_cmd += ' -C {}'.format(args.config) + if args.tests_matching: + ctest_cmd += ' --tests-regex {}'.format(args.tests_matching) + + result = subprocess.call(ctest_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_tests(build_dir, args): + print('Running unit tests ...') + + if args.compiler == 'android': + do_tests_android(build_dir, args) + else: + do_tests_cmake(args) + +def do_run_bench_android(build_dir, args): + # Switch our working directory to where we built everything + working_dir = os.path.join(build_dir, 'tools', 'bench', 'main_android') + os.chdir(working_dir) + + gradlew_exe = os.path.join(working_dir, 'gradlew.bat') + + # We uninstall first and then install + if args.config == 'Debug': + install_cmd = 'uninstallAll installDebug' + elif args.config == 'Release': + install_cmd = 'uninstallAll installRelease' + + # Install our app + test_cmd = '"{}" {}'.format(gradlew_exe, install_cmd) + result = subprocess.call(test_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Execute through ADB + run_cmd = 'adb shell am start -n "com.rtm.benchmark/com.rtm.benchmark.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER' + result = subprocess.call(run_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Restore working directory + os.chdir(build_dir) + +def do_pull_bench_android(build_dir): + # Grab the android directory we wrote the results to + output = str(subprocess.check_output('adb logcat -s acl -e "Benchmark results will be written to:" -m 1 -d')) + matches = re.search('Benchmark results will be written to: ([/\.\w]+)', output) + if matches == None: + print('Failed to find Android source directory from ADB') + android_src_dir = '/storage/emulated/0/Android/data/com.rtm.benchmark/files' + print('{} will be used instead'.format(android_src_dir)) + else: + android_src_dir = matches.group(1) + + # Grab the benchmark results from the android device + dst_filename = os.path.join(build_dir, 'benchmark_results.json') + src_filename = '{}/benchmark_results.json'.format(android_src_dir) + cmd = 'adb pull "{}" "{}"'.format(src_filename, dst_filename) + os.system(cmd) + +def do_run_bench_native(build_dir): + if platform.system() == 'Windows': + bench_exe = os.path.join(os.getcwd(), 'bin/rtm_bench.exe') + else: + bench_exe = os.path.join(os.getcwd(), 'bin/rtm_bench') + + benchmark_output_filename = os.path.join(build_dir, 'benchmark_results.json') + bench_cmd = '{} --benchmark_out={} --benchmark_out_format=json'.format(bench_exe, benchmark_output_filename) + + result = subprocess.call(bench_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_run_bench(build_dir, args): + if args.compiler == 'ios': + return # Not supported on iOS + + print('Running benchmark ...') + + if args.compiler == 'android': + do_run_bench_android(build_dir, args) + else: + do_run_bench_native(build_dir) + +if __name__ == "__main__": + args = parse_argv() + + build_dir = os.path.join(os.getcwd(), 'build') + cmake_script_dir = os.path.join(os.getcwd(), 'cmake') + + is_clean_requested = args.clean or args.clean_only + if is_clean_requested and os.path.exists(build_dir): + print('Cleaning previous build ...') + shutil.rmtree(build_dir) + + if args.clean_only: + sys.exit(0) + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + + os.chdir(build_dir) + + print('Using config: {}'.format(args.config)) + print('Using cpu: {}'.format(args.cpu)) + if args.compiler: + print('Using compiler: {}'.format(args.compiler)) + print('Using {} threads'.format(args.num_threads)) + + # Make sure 'make' runs with all available cores + os.environ['MAKEFLAGS'] = '-j{}'.format(args.num_threads) + + do_generate_solution(build_dir, cmake_script_dir, args) + + if args.build: + do_build(args) + + if args.unit_test: + do_tests(build_dir, args) + + if args.run_bench: + do_run_bench(build_dir, args) + + if args.pull_bench: + do_pull_bench_android(build_dir) + + sys.exit(0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/sonar-project.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/sonar-project.properties new file mode 100644 index 00000000..04409ce6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.organization=nfrechette-github + +sonar.projectKey=nfrechette_rtm +sonar.projectName=Realtime Math +sonar.projectVersion=2.1.3 + +sonar.sources=includes,tests/sources,tests/main_generic + +sonar.cfamily.build-wrapper-output=bw_output +sonar.cfamily.threads=2 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/CMakeLists.txt new file mode 100644 index 00000000..dca259c8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.2) +project(rtm_unit_tests_root NONE) + +if(PLATFORM_ANDROID) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_android") +elseif(PLATFORM_IOS) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_ios") +elseif(PLATFORM_EMSCRIPTEN) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_emscripten") +else() + add_subdirectory("${PROJECT_SOURCE_DIR}/main_generic") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/CMakeLists.txt new file mode 100644 index 00000000..7754934c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.2) +project(rtm_unit_tests_gradle_shim NONE) + +# Set our project root since our gradle files used to build live in the binary output directory +# but the actual source files live in the source/current directory. +set(RTM_PROJECT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/app) +file(RELATIVE_PATH RTM_PROJECT_ROOT ${RTM_PROJECT_ROOT} ${PROJECT_SOURCE_DIR}/app) + +# Setup our state +set(RTM_ANDROID_PROJECT_NAME "RTM Unit Tests") +set(RTM_ANDROID_PROJECT_PACKAGE_NAME "com.rtm.unit_tests") + +# Configure gradle for our build configuration +configure_file(${RTM_ANDROID_MISC_DIR}/app/build.gradle.in app/build.gradle @ONLY) +configure_file(${RTM_ANDROID_MISC_DIR}/app/src/main/AndroidManifest.xml.in app/src/main/AndroidManifest.xml @ONLY) +configure_file(${RTM_ANDROID_MISC_DIR}/app/src/main/res/values/strings.xml.in app/src/main/res/values/strings.xml @ONLY) +configure_file(${RTM_ANDROID_MISC_DIR}/settings.gradle.in settings.gradle @ONLY) + +# Copy gradle related files +file(COPY + ${RTM_ANDROID_MISC_DIR}/build.gradle + ${RTM_ANDROID_MISC_DIR}/gradle.properties + ${RTM_ANDROID_MISC_DIR}/gradle + ${RTM_ANDROID_MISC_DIR}/gradlew + ${RTM_ANDROID_MISC_DIR}/gradlew.bat + DESTINATION .) + +file(COPY + ${RTM_ANDROID_MISC_DIR}/app/proguard-rules.pro + DESTINATION app) + +add_custom_target(${PROJECT_NAME} ALL + COMMAND gradlew.bat + # Decide whether we should build Debug or Release + $<$:assembleDebug> + $<$:assembleRelease>) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..e9ffe595 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.2) +project(rtm_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Project root is \tests\main_android +set(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") + +include_directories("${PROJECT_ROOT_DIR}/../../includes") +include_directories("${PROJECT_ROOT_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_ROOT_DIR}/../sources/*.h + ${PROJECT_ROOT_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_library(${PROJECT_NAME} SHARED ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Enable exceptions +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) + +# Enable debug symbols +target_compile_options(${PROJECT_NAME} PRIVATE -g) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DRTM_ON_ASSERT_THROW) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +if(WITH_VECTOR_MIX_TESTS) + # Enable vector_mix unit tests + add_definitions(-DRTM_IMPL_WITH_VECTOR_MIX_TESTS) +endif() + +target_include_directories(${PROJECT_NAME} PUBLIC jni) + +target_link_libraries(${PROJECT_NAME} m log) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/cpp/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/cpp/main.cpp new file mode 100644 index 00000000..fa5264c5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/cpp/main.cpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +#include + +extern "C" jint Java_com_rtm_unit_1tests_MainActivity_getNumUnitTestCases(JNIEnv* env, jobject caller) +{ + return Catch::getRegistryHub().getTestCaseRegistry().getAllTests().size(); +} + +extern "C" jint Java_com_rtm_unit_1tests_MainActivity_runUnitTests(JNIEnv* env, jobject caller) +{ + int result = Catch::Session().run(); + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/java/com/rtm/unit_tests/MainActivity.java b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/java/com/rtm/unit_tests/MainActivity.java new file mode 100644 index 00000000..01dfb66d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_android/app/src/main/java/com/rtm/unit_tests/MainActivity.java @@ -0,0 +1,31 @@ +package com.rtm.unit_tests; + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class MainActivity extends Activity { + static { + System.loadLibrary("rtm_unit_tests"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView resultTextView = new TextView(this); + + int numUnitTestCases = getNumUnitTestCases(); + int numFailed = runUnitTests(); + + if (numFailed == 0) + resultTextView.setText("All " + numUnitTestCases + " test cases ran successfully!"); + else + resultTextView.setText(numFailed + " test cases failed!"); + + setContentView(resultTextView); + } + + public native int getNumUnitTestCases(); + public native int runUnitTests(); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_emscripten/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_emscripten/CMakeLists.txt new file mode 100644 index 00000000..c43d87e5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_emscripten/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required (VERSION 3.2) +project(rtm_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_executable(${PROJECT_NAME} ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DRTM_ON_ASSERT_THROW) + +if(WITH_VECTOR_MIX_TESTS) + # Enable vector_mix unit tests + add_definitions(-DRTM_IMPL_WITH_VECTOR_MIX_TESTS) +endif() + +target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) # Enable all warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow) # Enable shadowing warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Werror) # Treat warnings as errors + +# Exceptions are not enabled by default, enable them +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) +target_link_libraries(${PROJECT_NAME} PRIVATE "-s DISABLE_EXCEPTION_CATCHING=0") + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s NODERAWFS=1") # Enable the raw node file system +target_link_libraries(${PROJECT_NAME} PRIVATE -lnodefs.js) # Link the node file system + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s ENVIRONMENT=node") # Force the environment to node + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s ALLOW_MEMORY_GROWTH=1") # Allow dynamic memory allocation + +# Setup Catch2 so we can find and execute the unit tests with CTest +set(OptionalCatchTestLauncher node) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../external/catch2/contrib") +include(CTest) +include(ParseAndAddCatchTests) +ParseAndAddCatchTests(${PROJECT_NAME}) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.js + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wasm + DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_emscripten/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_emscripten/main.cpp new file mode 100644 index 00000000..332c229b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_emscripten/main.cpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_generic/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_generic/CMakeLists.txt new file mode 100644 index 00000000..b15bbbd7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_generic/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required (VERSION 3.2) +project(rtm_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_TEST_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../external/catch2/contrib") +include(CTest) +include(Catch) +catch_discover_tests(${PROJECT_NAME}) + +setup_default_compiler_flags(${PROJECT_NAME}) + +if(MSVC) + if(CPU_INSTRUCTION_SET MATCHES "arm64") + # Exceptions are not enabled by default for ARM targets, enable them + target_compile_options(${PROJECT_NAME} PRIVATE /EHsc) + endif() +endif() + +# Throw on failure to allow us to catch them and recover +add_definitions(-DRTM_ON_ASSERT_THROW) + +if(WITH_VECTOR_MIX_TESTS) + # Enable vector_mix unit tests + add_definitions(-DRTM_IMPL_WITH_VECTOR_MIX_TESTS) +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_generic/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_generic/main.cpp new file mode 100644 index 00000000..6657d255 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_generic/main.cpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +#ifdef _WIN32 +#include +#endif + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + +#ifdef _WIN32 + if (IsDebuggerPresent()) + { + printf("Press any key to continue...\n"); + while (_kbhit() == 0); + } +#endif + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_ios/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_ios/CMakeLists.txt new file mode 100644 index 00000000..60d1266e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_ios/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required (VERSION 3.2) +project(rtm_unit_tests) + +# iOS cmake toolchain does not support CMAKE_CXX_STANDARD +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Force enable debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + +# Enable optimizations in Release +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +set(MACOSX_BUNDLE_EXECUTABLE_NAME ${PROJECT_NAME}) +set(MACOSX_BUNDLE_INFO_STRING "com.rtm.rtm-unit-tests") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.rtm.rtm-unit-tests") +set(MACOSX_BUNDLE_BUNDLE_NAME "rtm-unit-tests") + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_TEST_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DRTM_ON_ASSERT_THROW) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +if(WITH_VECTOR_MIX_TESTS) + # Enable vector_mix unit tests + add_definitions(-DRTM_IMPL_WITH_VECTOR_MIX_TESTS) +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_ios/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_ios/main.cpp new file mode 100644 index 00000000..3142a165 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/main_ios/main.cpp @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_constants.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_constants.cpp new file mode 100644 index 00000000..1871b4f8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_constants.cpp @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +using namespace rtm; + +TEST_CASE("constants", "[math][constants]") +{ + // Float + CHECK(scalar_near_equal(float(constants::pi()), 3.141592653589793238462643383279502884F, 1.0e-6F)); + CHECK(scalar_near_equal(float(-constants::pi()), -3.141592653589793238462643383279502884F, 1.0e-6F)); + CHECK(scalar_near_equal(float(+constants::pi()), +3.141592653589793238462643383279502884F, 1.0e-6F)); + CHECK(scalar_near_equal(float(constants::pi()) * 2.0F, 3.141592653589793238462643383279502884F * 2.0F, 1.0e-6F)); + CHECK(scalar_near_equal(float(2.0F * constants::pi()), 2.0F * 3.141592653589793238462643383279502884F, 1.0e-6F)); + CHECK(scalar_near_equal(float(constants::pi()) / 2.0F, 3.141592653589793238462643383279502884F / 2.0F, 1.0e-6F)); + CHECK(scalar_near_equal(float(2.0F / constants::pi()), 2.0F / 3.141592653589793238462643383279502884F, 1.0e-6F)); + CHECK(scalar_near_equal(float(constants::pi()) + 1.0F, 3.141592653589793238462643383279502884F + 1.0F, 1.0e-6F)); + CHECK(scalar_near_equal(float(1.0F + constants::pi()), 1.0F + 3.141592653589793238462643383279502884F, 1.0e-6F)); + CHECK(scalar_near_equal(float(constants::pi()) - 1.0F, 3.141592653589793238462643383279502884F - 1.0F, 1.0e-6F)); + CHECK(scalar_near_equal(float(1.0F - constants::pi()), 1.0F - 3.141592653589793238462643383279502884F, 1.0e-6F)); + + // Double + CHECK(scalar_near_equal(double(constants::pi()), 3.141592653589793238462643383279502884, 1.0e-6)); + CHECK(scalar_near_equal(double(-constants::pi()), -3.141592653589793238462643383279502884, 1.0e-6)); + CHECK(scalar_near_equal(double(+constants::pi()), +3.141592653589793238462643383279502884, 1.0e-6)); + CHECK(scalar_near_equal(double(constants::pi() * 2.0), 3.141592653589793238462643383279502884 * 2.0, 1.0e-6)); + CHECK(scalar_near_equal(double(2.0 * constants::pi()), 2.0 * 3.141592653589793238462643383279502884, 1.0e-6)); + CHECK(scalar_near_equal(double(constants::pi() / 2.0), 3.141592653589793238462643383279502884 / 2.0, 1.0e-6)); + CHECK(scalar_near_equal(double(2.0 / constants::pi()), 2.0 / 3.141592653589793238462643383279502884, 1.0e-6)); + CHECK(scalar_near_equal(double(constants::pi() + 1.0), 3.141592653589793238462643383279502884 + 1.0, 1.0e-6)); + CHECK(scalar_near_equal(double(1.0 + constants::pi()), 1.0 + 3.141592653589793238462643383279502884, 1.0e-6)); + CHECK(scalar_near_equal(double(constants::pi() - 1.0), 3.141592653589793238462643383279502884 - 1.0, 1.0e-6)); + CHECK(scalar_near_equal(double(1.0 - constants::pi()), 1.0 - 3.141592653589793238462643383279502884, 1.0e-6)); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_header_fwd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_header_fwd.cpp new file mode 100644 index 00000000..0c54ed51 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_header_fwd.cpp @@ -0,0 +1,25 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_macros.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_macros.cpp new file mode 100644 index 00000000..d24f7740 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_macros.cpp @@ -0,0 +1,129 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +TEST_CASE("macros", "[math][macros]") +{ + const float threshold = 0.0F; // Result must be binary exact! + + { + rtm::vector4f xyz0 = rtm::vector_set(1.0F, 2.0F, 3.0F); + rtm::vector4f xyz1 = rtm::vector_set(4.0F, 5.0F, 6.0F); + rtm::vector4f xyz2 = rtm::vector_set(7.0F, 8.0F, 9.0F); + + rtm::vector4f xxx; + rtm::vector4f yyy; + rtm::vector4f zzz; + RTM_MATRIXF_TRANSPOSE_3X3(xyz0, xyz1, xyz2, xxx, yyy, zzz); + + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(1.0F, 4.0F, 7.0F), xxx, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(2.0F, 5.0F, 8.0F), yyy, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(3.0F, 6.0F, 9.0F), zzz, threshold)); + + // Test when input == output + RTM_MATRIXF_TRANSPOSE_3X3(xyz0, xyz1, xyz2, xyz0, xyz1, xyz2); + + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(1.0F, 4.0F, 7.0F), xyz0, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(2.0F, 5.0F, 8.0F), xyz1, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(3.0F, 6.0F, 9.0F), xyz2, threshold)); + } + + { + rtm::vector4f xyzw0 = rtm::vector_set(1.0F, 2.0F, 3.0F, 20.0F); + rtm::vector4f xyzw1 = rtm::vector_set(4.0F, 5.0F, 6.0F, 21.0F); + rtm::vector4f xyzw2 = rtm::vector_set(7.0F, 8.0F, 9.0F, 22.0F); + rtm::vector4f xyzw3 = rtm::vector_set(10.0F, 11.0F, 12.0F, 23.0F); + + rtm::vector4f xxxx; + rtm::vector4f yyyy; + rtm::vector4f zzzz; + rtm::vector4f wwww; + RTM_MATRIXF_TRANSPOSE_4X4(xyzw0, xyzw1, xyzw2, xyzw3, xxxx, yyyy, zzzz, wwww); + + CHECK(rtm::vector_all_near_equal(rtm::vector_set(1.0F, 4.0F, 7.0F, 10.0F), xxxx, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(2.0F, 5.0F, 8.0F, 11.0F), yyyy, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(3.0F, 6.0F, 9.0F, 12.0F), zzzz, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(20.0F, 21.0F, 22.0F, 23.0F), wwww, threshold)); + + // Test when input == output + RTM_MATRIXF_TRANSPOSE_4X4(xyzw0, xyzw1, xyzw2, xyzw3, xyzw0, xyzw1, xyzw2, xyzw3); + + CHECK(rtm::vector_all_near_equal(rtm::vector_set(1.0F, 4.0F, 7.0F, 10.0F), xyzw0, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(2.0F, 5.0F, 8.0F, 11.0F), xyzw1, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(3.0F, 6.0F, 9.0F, 12.0F), xyzw2, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(20.0F, 21.0F, 22.0F, 23.0F), xyzw3, threshold)); + } + + { + rtm::vector4f xyz0 = rtm::vector_set(1.0F, 2.0F, 3.0F, 20.0F); + rtm::vector4f xyz1 = rtm::vector_set(4.0F, 5.0F, 6.0F, 21.0F); + rtm::vector4f xyz2 = rtm::vector_set(7.0F, 8.0F, 9.0F, 22.0F); + rtm::vector4f xyz3 = rtm::vector_set(10.0F, 11.0F, 12.0F, 23.0F); + + rtm::vector4f xxxx; + rtm::vector4f yyyy; + rtm::vector4f zzzz; + RTM_MATRIXF_TRANSPOSE_4X3(xyz0, xyz1, xyz2, xyz3, xxxx, yyyy, zzzz); + + CHECK(rtm::vector_all_near_equal(rtm::vector_set(1.0F, 4.0F, 7.0F, 10.0F), xxxx, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(2.0F, 5.0F, 8.0F, 11.0F), yyyy, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(3.0F, 6.0F, 9.0F, 12.0F), zzzz, threshold)); + + // Test when input == output + RTM_MATRIXF_TRANSPOSE_4X3(xyz0, xyz1, xyz2, xyz3, xyz0, xyz1, xyz2); + + CHECK(rtm::vector_all_near_equal(rtm::vector_set(1.0F, 4.0F, 7.0F, 10.0F), xyz0, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(2.0F, 5.0F, 8.0F, 11.0F), xyz1, threshold)); + CHECK(rtm::vector_all_near_equal(rtm::vector_set(3.0F, 6.0F, 9.0F, 12.0F), xyz2, threshold)); + } + + { + rtm::vector4f xyzw0 = rtm::vector_set(1.0F, 2.0F, 3.0F, 20.0F); + rtm::vector4f xyzw1 = rtm::vector_set(4.0F, 5.0F, 6.0F, 21.0F); + rtm::vector4f xyzw2 = rtm::vector_set(7.0F, 8.0F, 9.0F, 22.0F); + + rtm::vector4f xxx; + rtm::vector4f yyy; + rtm::vector4f zzz; + rtm::vector4f www; + RTM_MATRIXF_TRANSPOSE_3X4(xyzw0, xyzw1, xyzw2, xxx, yyy, zzz, www); + + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(1.0F, 4.0F, 7.0F), xxx, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(2.0F, 5.0F, 8.0F), yyy, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(3.0F, 6.0F, 9.0F), zzz, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(20.0F, 21.0F, 22.0F), www, threshold)); + + // Test when input == output + RTM_MATRIXF_TRANSPOSE_3X4(xyzw0, xyzw1, xyzw2, xyzw0, xyzw1, xyzw2, xxx); + + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(1.0F, 4.0F, 7.0F), xyzw0, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(2.0F, 5.0F, 8.0F), xyzw1, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(3.0F, 6.0F, 9.0F), xyzw2, threshold)); + CHECK(rtm::vector_all_near_equal3(rtm::vector_set(20.0F, 21.0F, 22.0F), xxx, threshold)); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_mask4.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_mask4.cpp new file mode 100644 index 00000000..bcde6824 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_mask4.cpp @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +using namespace rtm; + +template +static void test_mask_impl() +{ + { + const MaskType mask = mask_set(IntType(0), ~IntType(0), IntType(0), ~IntType(0)); + CHECK(mask_get_x(mask) == IntType(0)); + CHECK(mask_get_y(mask) == ~IntType(0)); + CHECK(mask_get_z(mask) == IntType(0)); + CHECK(mask_get_w(mask) == ~IntType(0)); + } + + { + const MaskType mask = mask_set(false, true, false, true); + CHECK(mask_get_x(mask) == IntType(0)); + CHECK(mask_get_y(mask) == ~IntType(0)); + CHECK(mask_get_z(mask) == IntType(0)); + CHECK(mask_get_w(mask) == ~IntType(0)); + } + + { + const MaskType mask0 = mask_set(IntType(0), ~IntType(0), IntType(0), ~IntType(0)); + const MaskType mask1 = mask_set(IntType(0), IntType(0), IntType(0), IntType(0)); + const MaskType mask2 = mask_set(IntType(0), IntType(0), IntType(0), ~IntType(0)); + const MaskType mask3 = mask_set(IntType(0), IntType(0), ~IntType(0), ~IntType(0)); + const MaskType mask4 = mask_set(IntType(0), ~IntType(0), ~IntType(0), ~IntType(0)); + const MaskType mask5 = mask_set(~IntType(0), ~IntType(0), ~IntType(0), ~IntType(0)); + const MaskType mask6 = mask_set(~IntType(0), IntType(0), IntType(0), IntType(0)); + const MaskType mask7 = mask_set(~IntType(0), ~IntType(0), IntType(0), IntType(0)); + const MaskType mask8 = mask_set(~IntType(0), ~IntType(0), ~IntType(0), IntType(0)); + + CHECK(mask_all_true(mask0) == false); + CHECK(mask_all_true(mask1) == false); + CHECK(mask_all_true(mask2) == false); + CHECK(mask_all_true(mask3) == false); + CHECK(mask_all_true(mask4) == false); + CHECK(mask_all_true(mask5) == true); + CHECK(mask_all_true(mask6) == false); + CHECK(mask_all_true(mask7) == false); + CHECK(mask_all_true(mask8) == false); + + CHECK(mask_all_true2(mask0) == false); + CHECK(mask_all_true2(mask1) == false); + CHECK(mask_all_true2(mask2) == false); + CHECK(mask_all_true2(mask3) == false); + CHECK(mask_all_true2(mask4) == false); + CHECK(mask_all_true2(mask5) == true); + CHECK(mask_all_true2(mask6) == false); + CHECK(mask_all_true2(mask7) == true); + CHECK(mask_all_true2(mask8) == true); + + CHECK(mask_all_true3(mask0) == false); + CHECK(mask_all_true3(mask1) == false); + CHECK(mask_all_true3(mask2) == false); + CHECK(mask_all_true3(mask3) == false); + CHECK(mask_all_true3(mask4) == false); + CHECK(mask_all_true3(mask5) == true); + CHECK(mask_all_true3(mask6) == false); + CHECK(mask_all_true3(mask7) == false); + CHECK(mask_all_true3(mask8) == true); + + CHECK(mask_any_true(mask0) == true); + CHECK(mask_any_true(mask1) == false); + CHECK(mask_any_true(mask2) == true); + CHECK(mask_any_true(mask3) == true); + CHECK(mask_any_true(mask4) == true); + CHECK(mask_any_true(mask5) == true); + CHECK(mask_any_true(mask6) == true); + CHECK(mask_any_true(mask7) == true); + CHECK(mask_any_true(mask8) == true); + + CHECK(mask_any_true2(mask0) == true); + CHECK(mask_any_true2(mask1) == false); + CHECK(mask_any_true2(mask2) == false); + CHECK(mask_any_true2(mask3) == false); + CHECK(mask_any_true2(mask4) == true); + CHECK(mask_any_true2(mask5) == true); + CHECK(mask_any_true2(mask6) == true); + CHECK(mask_any_true2(mask7) == true); + CHECK(mask_any_true2(mask8) == true); + + CHECK(mask_any_true3(mask0) == true); + CHECK(mask_any_true3(mask1) == false); + CHECK(mask_any_true3(mask2) == false); + CHECK(mask_any_true3(mask3) == true); + CHECK(mask_any_true3(mask4) == true); + CHECK(mask_any_true3(mask5) == true); + CHECK(mask_any_true3(mask6) == true); + CHECK(mask_any_true3(mask7) == true); + CHECK(mask_any_true3(mask8) == true); + } +} + +TEST_CASE("mask4f math", "[math][mask]") +{ + test_mask_impl(); +} + +TEST_CASE("mask4d math", "[math][mask]") +{ + test_mask_impl(); +} + +TEST_CASE("mask4i math", "[math][mask]") +{ + test_mask_impl(); +} + +TEST_CASE("mask4q math", "[math][mask]") +{ + test_mask_impl(); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3_impl.h new file mode 100644 index 00000000..4f84ed3c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3_impl.h @@ -0,0 +1,308 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +using namespace rtm; + +template +static void test_matrix3x3_setters(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x3Type = typename float_traits::matrix3x3; + + const Matrix3x3Type identity = matrix_identity(); + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0), FloatType(0.0)); + Vector4Type z_axis = vector_set(FloatType(7.0), FloatType(8.0), FloatType(9.0), FloatType(0.0)); + Matrix3x3Type mtx = matrix_set(x_axis, y_axis, z_axis); + CHECK(vector_all_near_equal3(x_axis, mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(y_axis, mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(z_axis, mtx.z_axis, threshold)); + } + + { + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), identity.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), identity.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), identity.z_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x3Type mtx = matrix_from_quat(rotation_around_z); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx.z_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x3Type mtx = matrix_from_rotation(rotation_around_z); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx.z_axis, threshold)); + } + + { + Matrix3x3Type mtx = matrix_from_scale(vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0))); + CHECK(vector_all_near_equal3(vector_set(FloatType(4.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(5.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(6.0)), mtx.z_axis, threshold)); + } +} + +template +static void test_matrix3x3_arithmetic(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x3Type = typename float_traits::matrix3x3; + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x3Type mtx_a = matrix_from_quat(rotation_around_z); + Vector4Type result = matrix_mul_vector3(x_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = matrix_mul_vector3(y_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + Matrix3x3Type mtx_b = matrix_from_quat(rotation_around_x); + result = matrix_mul_vector3(x_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), threshold)); + result = matrix_mul_vector3(y_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + + Matrix3x3Type mtx_ab = matrix_mul(mtx_a, mtx_b); + Matrix3x3Type mtx_ba = matrix_mul(mtx_b, mtx_a); + result = matrix_mul_vector3(x_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(x_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_vector3(y_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(y_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_vector3(x_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(x_axis, mtx_b), mtx_a), threshold)); + result = matrix_mul_vector3(y_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(y_axis, mtx_b), mtx_a), threshold)); + } +} + +template +static void test_matrix3x3_transformations(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x3Type = typename float_traits::matrix3x3; + + const Matrix3x3Type identity = matrix_identity(); + const Vector4Type zero = vector_zero(); + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0), FloatType(0.0)); + Vector4Type z_axis = vector_set(FloatType(7.0), FloatType(8.0), FloatType(9.0), FloatType(0.0)); + Matrix3x3Type mtx0 = matrix_set(x_axis, y_axis, z_axis); + Matrix3x3Type mtx1 = matrix_transpose(mtx0); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(4.0), FloatType(7.0)), mtx1.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(2.0), FloatType(5.0), FloatType(8.0)), mtx1.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(3.0), FloatType(6.0), FloatType(9.0)), mtx1.z_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x3Type mtx = matrix_from_quat(rotation_around_z); + Matrix3x3Type inv_mtx = matrix_inverse(mtx); + Matrix3x3Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.z_axis, threshold)); + } + + { + QuatType rotation = quat_from_euler(scalar_deg_to_rad(FloatType(12.3)), scalar_deg_to_rad(FloatType(42.8)), scalar_deg_to_rad(FloatType(33.41))); + Matrix3x3Type mtx = matrix_from_quat(rotation); + Matrix3x3Type inv_mtx = matrix_inverse(mtx); + Matrix3x3Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.z_axis, threshold)); + } + + { + QuatType rotation = quat_from_euler(scalar_deg_to_rad(FloatType(12.3)), scalar_deg_to_rad(FloatType(42.8)), scalar_deg_to_rad(FloatType(33.41))); + Matrix3x3Type mtx = matrix_from_quat(rotation); + Matrix3x3Type inv_mtx = matrix_inverse(mtx, mtx); + Matrix3x3Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.z_axis, threshold)); + } + + { + Matrix3x3Type mtx = matrix_set(zero, zero, zero); + Matrix3x3Type inv_mtx = matrix_inverse(mtx, identity); + CHECK(vector_all_near_equal(identity.x_axis, inv_mtx.x_axis, threshold)); + CHECK(vector_all_near_equal(identity.y_axis, inv_mtx.y_axis, threshold)); + CHECK(vector_all_near_equal(identity.z_axis, inv_mtx.z_axis, threshold)); + } +} + +template +static void test_matrix3x3_misc(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x3Type = typename float_traits::matrix3x3; + using Matrix3x4Type = typename float_traits::matrix3x4; + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x3Type mtx = matrix_from_quat(rotation_around_z); + QuatType rotation = quat_from_matrix(mtx); + CHECK(quat_near_equal(rotation_around_z, rotation, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx0_3x4 = matrix_from_qvv(rotation_around_z, translation, scale); + Matrix3x3Type mtx0 = matrix_cast(mtx0_3x4); + Matrix3x3Type mtx0_no_scale = matrix_remove_scale(mtx0); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx0_no_scale.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), mtx0_no_scale.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx0_no_scale.z_axis, threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x3Type mtx = matrix_set(x_axis, y_axis, z_axis); + FloatType det = scalar_cast(matrix_determinant(mtx)); + CHECK(scalar_near_equal(det, FloatType(381.95681179092484), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x3Type mtx = matrix_set(x_axis, y_axis, z_axis); + + FloatType det_xx = scalar_cast(matrix_minor(mtx, axis3::x, axis3::x)); + CHECK(scalar_near_equal(det_xx, FloatType(38.4967), threshold)); + FloatType det_xy = scalar_cast(matrix_minor(mtx, axis3::x, axis3::y)); + CHECK(scalar_near_equal(det_xy, FloatType(-55.2182), threshold)); + FloatType det_xz = scalar_cast(matrix_minor(mtx, axis3::x, axis3::z)); + CHECK(scalar_near_equal(det_xz, FloatType(-62.0643), threshold)); + + FloatType det_yx = scalar_cast(matrix_minor(mtx, axis3::y, axis3::x)); + CHECK(scalar_near_equal(det_yx, FloatType(49.9981), threshold)); + FloatType det_yy = scalar_cast(matrix_minor(mtx, axis3::y, axis3::y)); + CHECK(scalar_near_equal(det_yy, FloatType(12.5136), threshold)); + FloatType det_yz = scalar_cast(matrix_minor(mtx, axis3::y, axis3::z)); + CHECK(scalar_near_equal(det_yz, FloatType(-41.7163), threshold)); + + FloatType det_zx = scalar_cast(matrix_minor(mtx, axis3::z, axis3::x)); + CHECK(scalar_near_equal(det_zx, FloatType(22.7625), threshold)); + FloatType det_zy = scalar_cast(matrix_minor(mtx, axis3::z, axis3::y)); + CHECK(scalar_near_equal(det_zy, FloatType(39.085), threshold)); + FloatType det_zz = scalar_cast(matrix_minor(mtx, axis3::z, axis3::z)); + CHECK(scalar_near_equal(det_zz, FloatType(41.4168), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, x_axis); + Matrix3x3Type cof = matrix_cofactor(mtx); + + FloatType cof_xx = scalar_cast(vector_get_x(cof.x_axis)); + CHECK(scalar_near_equal(cof_xx, FloatType(38.4967), threshold)); + FloatType cof_xy = scalar_cast(vector_get_y(cof.x_axis)); + CHECK(scalar_near_equal(cof_xy, FloatType(55.2182), threshold)); + FloatType cof_xz = scalar_cast(vector_get_z(cof.x_axis)); + CHECK(scalar_near_equal(cof_xz, FloatType(-62.0643), threshold)); + + FloatType cof_yx = scalar_cast(vector_get_x(cof.y_axis)); + CHECK(scalar_near_equal(cof_yx, FloatType(-49.9981), threshold)); + FloatType cof_yy = scalar_cast(vector_get_y(cof.y_axis)); + CHECK(scalar_near_equal(cof_yy, FloatType(12.5136), threshold)); + FloatType cof_yz = scalar_cast(vector_get_z(cof.y_axis)); + CHECK(scalar_near_equal(cof_yz, FloatType(41.7163), threshold)); + + FloatType cof_zx = scalar_cast(vector_get_x(cof.z_axis)); + CHECK(scalar_near_equal(cof_zx, FloatType(22.7625), threshold)); + FloatType cof_zy = scalar_cast(vector_get_y(cof.z_axis)); + CHECK(scalar_near_equal(cof_zy, FloatType(-39.085), threshold)); + FloatType cof_zz = scalar_cast(vector_get_z(cof.z_axis)); + CHECK(scalar_near_equal(cof_zz, FloatType(41.4168), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, x_axis); + Matrix3x3Type adj = matrix_adjugate(mtx); + + FloatType adj_xx = scalar_cast(vector_get_x(adj.x_axis)); + CHECK(scalar_near_equal(adj_xx, FloatType(38.4967), threshold)); + FloatType adj_xy = scalar_cast(vector_get_y(adj.x_axis)); + CHECK(scalar_near_equal(adj_xy, FloatType(-49.9981), threshold)); + FloatType adj_xz = scalar_cast(vector_get_z(adj.x_axis)); + CHECK(scalar_near_equal(adj_xz, FloatType(22.7625), threshold)); + + FloatType adj_yx = scalar_cast(vector_get_x(adj.y_axis)); + CHECK(scalar_near_equal(adj_yx, FloatType(55.2182), threshold)); + FloatType adj_yy = scalar_cast(vector_get_y(adj.y_axis)); + CHECK(scalar_near_equal(adj_yy, FloatType(12.5136), threshold)); + FloatType adj_yz = scalar_cast(vector_get_z(adj.y_axis)); + CHECK(scalar_near_equal(adj_yz, FloatType(-39.085), threshold)); + + FloatType adj_zx = scalar_cast(vector_get_x(adj.z_axis)); + CHECK(scalar_near_equal(adj_zx, FloatType(-62.0643), threshold)); + FloatType adj_zy = scalar_cast(vector_get_y(adj.z_axis)); + CHECK(scalar_near_equal(adj_zy, FloatType(41.7163), threshold)); + FloatType adj_zz = scalar_cast(vector_get_z(adj.z_axis)); + CHECK(scalar_near_equal(adj_zz, FloatType(41.4168), threshold)); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3d.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3d.cpp new file mode 100644 index 00000000..bf3bbad0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3d.cpp @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_matrix3x3_impl.h" + +TEST_CASE("matrix3x3d math get/set", "[math][matrix3x3]") +{ + test_matrix3x3_setters(1.0E-4); +} + +TEST_CASE("matrix3x3d math arithmetic", "[math][matrix3x3]") +{ + test_matrix3x3_arithmetic(1.0E-4); +} + +TEST_CASE("matrix3x3d math transformations", "[math][matrix3x3]") +{ + test_matrix3x3_transformations(1.0E-4); +} + +TEST_CASE("matrix3x3d math misc", "[math][matrix3x3]") +{ + test_matrix3x3_misc(1.0E-4); + + { + quatd rotation_around_z = quat_from_euler(scalar_deg_to_rad(0.0), scalar_deg_to_rad(90.0), scalar_deg_to_rad(0.0)); + matrix3x3d src = matrix_from_quat(rotation_around_z); + matrix3x3f dst = matrix_cast(src); + CHECK(vector_all_near_equal3(vector_cast(src.x_axis), dst.x_axis, 1.0E-4F)); + CHECK(vector_all_near_equal3(vector_cast(src.y_axis), dst.y_axis, 1.0E-4F)); + CHECK(vector_all_near_equal3(vector_cast(src.z_axis), dst.z_axis, 1.0E-4F)); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3f.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3f.cpp new file mode 100644 index 00000000..e16ac559 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x3f.cpp @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_matrix3x3_impl.h" + +TEST_CASE("matrix3x3f math get/set", "[math][matrix3x3]") +{ + test_matrix3x3_setters(1.0E-4F); +} + +TEST_CASE("matrix3x3f math arithmetic", "[math][matrix3x3]") +{ + test_matrix3x3_arithmetic(1.0E-4F); +} + +TEST_CASE("matrix3x3f math transformations", "[math][matrix3x3]") +{ + test_matrix3x3_transformations(1.0E-4F); +} + +TEST_CASE("matrix3x3f math misc", "[math][matrix3x3]") +{ + test_matrix3x3_misc(1.0E-4F); + + { + quatf rotation_around_z = quat_from_euler(scalar_deg_to_rad(0.0F), scalar_deg_to_rad(90.0F), scalar_deg_to_rad(0.0F)); + matrix3x3f src = matrix_from_quat(rotation_around_z); + + // Work around clang5 compiler crash by marking this variable as static + static matrix3x3d dst = matrix_cast(src); + CHECK(vector_all_near_equal3(vector_cast(src.x_axis), dst.x_axis, 1.0E-4)); + CHECK(vector_all_near_equal3(vector_cast(src.y_axis), dst.y_axis, 1.0E-4)); + CHECK(vector_all_near_equal3(vector_cast(src.z_axis), dst.z_axis, 1.0E-4)); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x4.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x4.cpp new file mode 100644 index 00000000..4511b3d0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix3x4.cpp @@ -0,0 +1,466 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +using namespace rtm; + +template +static void test_affine_matrix_setters(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using QVVType = typename float_traits::qvv; + using Matrix3x3Type = typename float_traits::matrix3x3; + using Matrix3x4Type = typename float_traits::matrix3x4; + + const Matrix3x4Type identity = matrix_identity(); + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0), FloatType(0.0)); + Vector4Type z_axis = vector_set(FloatType(7.0), FloatType(8.0), FloatType(9.0), FloatType(0.0)); + Vector4Type w_axis = vector_set(FloatType(10.0), FloatType(11.0), FloatType(12.0), FloatType(1.0)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, w_axis); + CHECK(vector_all_near_equal3(x_axis, mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(y_axis, mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(z_axis, mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(w_axis, mtx.w_axis, threshold)); + } + + { + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), identity.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), identity.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), identity.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), identity.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Matrix3x4Type mtx = matrix_from_qvv(rotation_around_z, translation, vector_set(FloatType(1.0))); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(1.0)), mtx.w_axis, threshold)); + + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + mtx = matrix_from_qvv(rotation_around_z, translation, scale); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(4.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-5.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(6.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x4Type mtx = matrix_from_quat(rotation_around_z); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x4Type mtx = matrix_from_rotation(rotation_around_z); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x3Type rotation_mtx = matrix_from_quat(rotation_around_z); + Matrix3x4Type mtx = matrix_from_rotation(rotation_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + Matrix3x4Type mtx = matrix_from_translation(vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0))); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + Matrix3x4Type mtx = matrix_from_scale(vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0))); + CHECK(vector_all_near_equal3(vector_set(FloatType(4.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(5.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(6.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + QVVType transform = qvv_set(rotation_around_z, translation, scale); + Matrix3x4Type mtx = matrix_from_qvv(transform); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(4.0), FloatType(0.0), FloatType(0.0)), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-5.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(6.0), FloatType(0.0)), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(1.0)), mtx.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx = matrix_from_qvv(rotation_around_z, translation, scale); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx, axis4::x), mtx.x_axis, threshold)); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx, axis4::y), mtx.y_axis, threshold)); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx, axis4::z), mtx.z_axis, threshold)); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx, axis4::w), mtx.w_axis, threshold)); + + const Matrix3x4Type mtx2 = mtx; + CHECK(vector_all_near_equal3(matrix_get_axis(mtx2, axis4::x), mtx2.x_axis, threshold)); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx2, axis4::y), mtx2.y_axis, threshold)); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx2, axis4::z), mtx2.z_axis, threshold)); + CHECK(vector_all_near_equal3(matrix_get_axis(mtx2, axis4::w), mtx2.w_axis, threshold)); + } +} + +template +static void test_affine_matrix_arithmetic(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x4Type = typename float_traits::matrix3x4; + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x4Type mtx_a = matrix_from_qvv(rotation_around_z, x_axis, vector_set(FloatType(1.0))); + Vector4Type result = matrix_mul_point3(x_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = matrix_mul_point3(y_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + Matrix3x4Type mtx_b = matrix_from_qvv(rotation_around_x, y_axis, vector_set(FloatType(1.0))); + result = matrix_mul_point3(x_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = matrix_mul_point3(y_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(-1.0)), threshold)); + + Matrix3x4Type mtx_ab = matrix_mul(mtx_a, mtx_b); + Matrix3x4Type mtx_ba = matrix_mul(mtx_b, mtx_a); + result = matrix_mul_point3(x_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_point3(matrix_mul_point3(x_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_point3(y_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_point3(matrix_mul_point3(y_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_point3(x_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_point3(matrix_mul_point3(x_axis, mtx_b), mtx_a), threshold)); + result = matrix_mul_point3(y_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_point3(matrix_mul_point3(y_axis, mtx_b), mtx_a), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x4Type mtx_a = matrix_from_qvv(rotation_around_z, x_axis, vector_set(FloatType(1.0))); + Vector4Type result = matrix_mul_vector3(x_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = matrix_mul_vector3(y_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + Matrix3x4Type mtx_b = matrix_from_qvv(rotation_around_x, y_axis, vector_set(FloatType(1.0))); + result = matrix_mul_vector3(x_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), threshold)); + result = matrix_mul_vector3(y_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + + Matrix3x4Type mtx_ab = matrix_mul(mtx_a, mtx_b); + Matrix3x4Type mtx_ba = matrix_mul(mtx_b, mtx_a); + result = matrix_mul_vector3(x_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(x_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_vector3(y_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(y_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_vector3(x_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(x_axis, mtx_b), mtx_a), threshold)); + result = matrix_mul_vector3(y_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector3(matrix_mul_vector3(y_axis, mtx_b), mtx_a), threshold)); + } +} + +template +static void test_affine_matrix_transformations(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x3Type = typename float_traits::matrix3x3; + using Matrix3x4Type = typename float_traits::matrix3x4; + + const Matrix3x4Type identity = matrix_identity(); + const Vector4Type zero = vector_zero(); + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0), FloatType(0.0)); + Vector4Type z_axis = vector_set(FloatType(7.0), FloatType(8.0), FloatType(9.0), FloatType(0.0)); + Vector4Type w_axis = vector_set(FloatType(10.0), FloatType(11.0), FloatType(12.0), FloatType(1.0)); + Matrix3x4Type mtx0 = matrix_set(x_axis, y_axis, z_axis, w_axis); + Matrix3x3Type mtx1 = matrix_transpose(mtx0); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(4.0), FloatType(7.0), FloatType(10.0)), mtx1.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(2.0), FloatType(5.0), FloatType(8.0), FloatType(11.0)), mtx1.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(3.0), FloatType(6.0), FloatType(9.0), FloatType(12.0)), mtx1.z_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx = matrix_from_qvv(rotation_around_z, translation, scale); + Matrix3x4Type inv_mtx = matrix_inverse(mtx); + Matrix3x4Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx = matrix_from_qvv(rotation_around_z, translation, scale); + Matrix3x4Type inv_mtx = matrix_inverse(mtx, mtx); + Matrix3x4Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.w_axis, threshold)); + } + + { + Matrix3x4Type mtx = matrix_set(zero, zero, zero, zero); + Matrix3x4Type inv_mtx = matrix_inverse(mtx, identity); + CHECK(vector_all_near_equal(identity.x_axis, inv_mtx.x_axis, threshold)); + CHECK(vector_all_near_equal(identity.y_axis, inv_mtx.y_axis, threshold)); + CHECK(vector_all_near_equal(identity.z_axis, inv_mtx.z_axis, threshold)); + CHECK(vector_all_near_equal(identity.w_axis, inv_mtx.w_axis, threshold)); + } + + { + QuatType rotation = quat_from_euler(scalar_deg_to_rad(FloatType(12.3)), scalar_deg_to_rad(FloatType(42.8)), scalar_deg_to_rad(FloatType(33.41))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx = matrix_from_qvv(rotation, translation, scale); + Matrix3x4Type inv_mtx = matrix_inverse(mtx); + Matrix3x4Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.w_axis, threshold)); + } +} + +template +static void test_affine_matrix_misc(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x3Type = typename float_traits::matrix3x3; + using Matrix3x4Type = typename float_traits::matrix3x4; + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x4Type mtx = matrix_from_quat(rotation_around_z); + QuatType rotation = quat_from_matrix(mtx); + CHECK(quat_near_equal(rotation_around_z, rotation, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx0 = matrix_from_qvv(rotation_around_z, translation, scale); + Matrix3x4Type mtx0_no_scale = matrix_remove_scale(mtx0); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), mtx0_no_scale.x_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), mtx0_no_scale.y_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), mtx0_no_scale.z_axis, threshold)); + CHECK(vector_all_near_equal3(vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(1.0)), mtx0_no_scale.w_axis, threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, x_axis); + FloatType det = scalar_cast(matrix_determinant(mtx)); + CHECK(scalar_near_equal(det, FloatType(381.95681179092484), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, x_axis); + + FloatType det_xx = scalar_cast(matrix_minor(mtx, axis3::x, axis3::x)); + CHECK(scalar_near_equal(det_xx, FloatType(38.4967), threshold)); + FloatType det_xy = scalar_cast(matrix_minor(mtx, axis3::x, axis3::y)); + CHECK(scalar_near_equal(det_xy, FloatType(-55.2182), threshold)); + FloatType det_xz = scalar_cast(matrix_minor(mtx, axis3::x, axis3::z)); + CHECK(scalar_near_equal(det_xz, FloatType(-62.0643), threshold)); + + FloatType det_yx = scalar_cast(matrix_minor(mtx, axis3::y, axis3::x)); + CHECK(scalar_near_equal(det_yx, FloatType(49.9981), threshold)); + FloatType det_yy = scalar_cast(matrix_minor(mtx, axis3::y, axis3::y)); + CHECK(scalar_near_equal(det_yy, FloatType(12.5136), threshold)); + FloatType det_yz = scalar_cast(matrix_minor(mtx, axis3::y, axis3::z)); + CHECK(scalar_near_equal(det_yz, FloatType(-41.7163), threshold)); + + FloatType det_zx = scalar_cast(matrix_minor(mtx, axis3::z, axis3::x)); + CHECK(scalar_near_equal(det_zx, FloatType(22.7625), threshold)); + FloatType det_zy = scalar_cast(matrix_minor(mtx, axis3::z, axis3::y)); + CHECK(scalar_near_equal(det_zy, FloatType(39.085), threshold)); + FloatType det_zz = scalar_cast(matrix_minor(mtx, axis3::z, axis3::z)); + CHECK(scalar_near_equal(det_zz, FloatType(41.4168), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, x_axis); + Matrix3x3Type cof = matrix_cofactor(mtx); + + FloatType cof_xx = scalar_cast(vector_get_x(cof.x_axis)); + CHECK(scalar_near_equal(cof_xx, FloatType(38.4967), threshold)); + FloatType cof_xy = scalar_cast(vector_get_y(cof.x_axis)); + CHECK(scalar_near_equal(cof_xy, FloatType(55.2182), threshold)); + FloatType cof_xz = scalar_cast(vector_get_z(cof.x_axis)); + CHECK(scalar_near_equal(cof_xz, FloatType(-62.0643), threshold)); + + FloatType cof_yx = scalar_cast(vector_get_x(cof.y_axis)); + CHECK(scalar_near_equal(cof_yx, FloatType(-49.9981), threshold)); + FloatType cof_yy = scalar_cast(vector_get_y(cof.y_axis)); + CHECK(scalar_near_equal(cof_yy, FloatType(12.5136), threshold)); + FloatType cof_yz = scalar_cast(vector_get_z(cof.y_axis)); + CHECK(scalar_near_equal(cof_yz, FloatType(41.7163), threshold)); + + FloatType cof_zx = scalar_cast(vector_get_x(cof.z_axis)); + CHECK(scalar_near_equal(cof_zx, FloatType(22.7625), threshold)); + FloatType cof_zy = scalar_cast(vector_get_y(cof.z_axis)); + CHECK(scalar_near_equal(cof_zy, FloatType(-39.085), threshold)); + FloatType cof_zz = scalar_cast(vector_get_z(cof.z_axis)); + CHECK(scalar_near_equal(cof_zz, FloatType(41.4168), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(5.62565), FloatType(7.90751), FloatType(4.37048), FloatType(99999.9999)); + Vector4Type y_axis = vector_set(FloatType(0.36345), FloatType(7.87300), FloatType(7.23000), FloatType(99999.9999)); + Vector4Type z_axis = vector_set(FloatType(8.06413), FloatType(3.91970), FloatType(8.48928), FloatType(99999.9999)); + Matrix3x4Type mtx = matrix_set(x_axis, y_axis, z_axis, x_axis); + Matrix3x3Type adj = matrix_adjugate(mtx); + + FloatType adj_xx = scalar_cast(vector_get_x(adj.x_axis)); + CHECK(scalar_near_equal(adj_xx, FloatType(38.4967), threshold)); + FloatType adj_xy = scalar_cast(vector_get_y(adj.x_axis)); + CHECK(scalar_near_equal(adj_xy, FloatType(-49.9981), threshold)); + FloatType adj_xz = scalar_cast(vector_get_z(adj.x_axis)); + CHECK(scalar_near_equal(adj_xz, FloatType(22.7625), threshold)); + + FloatType adj_yx = scalar_cast(vector_get_x(adj.y_axis)); + CHECK(scalar_near_equal(adj_yx, FloatType(55.2182), threshold)); + FloatType adj_yy = scalar_cast(vector_get_y(adj.y_axis)); + CHECK(scalar_near_equal(adj_yy, FloatType(12.5136), threshold)); + FloatType adj_yz = scalar_cast(vector_get_z(adj.y_axis)); + CHECK(scalar_near_equal(adj_yz, FloatType(-39.085), threshold)); + + FloatType adj_zx = scalar_cast(vector_get_x(adj.z_axis)); + CHECK(scalar_near_equal(adj_zx, FloatType(-62.0643), threshold)); + FloatType adj_zy = scalar_cast(vector_get_y(adj.z_axis)); + CHECK(scalar_near_equal(adj_zy, FloatType(41.7163), threshold)); + FloatType adj_zz = scalar_cast(vector_get_z(adj.z_axis)); + CHECK(scalar_near_equal(adj_zz, FloatType(41.4168), threshold)); + } +} + +TEST_CASE("matrix3x4f math", "[math][matrix3x4]") +{ + test_affine_matrix_setters(1.0E-4F); + test_affine_matrix_arithmetic(1.0E-4F); + test_affine_matrix_transformations(1.0E-4F); + test_affine_matrix_misc(1.0E-4F); + + { + quatf rotation_around_z = quat_from_euler(scalar_deg_to_rad(0.0F), scalar_deg_to_rad(90.0F), scalar_deg_to_rad(0.0F)); + vector4f translation = vector_set(1.0F, 2.0F, 3.0F); + vector4f scale = vector_set(4.0F, 5.0F, 6.0F); + matrix3x4f src = matrix_from_qvv(rotation_around_z, translation, scale); + matrix3x4d dst = matrix_cast(src); + CHECK(vector_all_near_equal3(vector_cast(src.x_axis), dst.x_axis, 1.0E-4)); + CHECK(vector_all_near_equal3(vector_cast(src.y_axis), dst.y_axis, 1.0E-4)); + CHECK(vector_all_near_equal3(vector_cast(src.z_axis), dst.z_axis, 1.0E-4)); + CHECK(vector_all_near_equal3(vector_cast(src.w_axis), dst.w_axis, 1.0E-4)); + } +} + +TEST_CASE("matrix3x4d math", "[math][matrix3x4]") +{ + test_affine_matrix_setters(1.0E-4); + test_affine_matrix_arithmetic(1.0E-4F); + test_affine_matrix_transformations(1.0E-4F); + test_affine_matrix_misc(1.0E-4F); + + { + quatd rotation_around_z = quat_from_euler(scalar_deg_to_rad(0.0), scalar_deg_to_rad(90.0), scalar_deg_to_rad(0.0)); + vector4d translation = vector_set(1.0, 2.0, 3.0); + vector4d scale = vector_set(4.0, 5.0, 6.0); + matrix3x4d src = matrix_from_qvv(rotation_around_z, translation, scale); + matrix3x4f dst = matrix_cast(src); + CHECK(vector_all_near_equal3(vector_cast(src.x_axis), dst.x_axis, 1.0E-4F)); + CHECK(vector_all_near_equal3(vector_cast(src.y_axis), dst.y_axis, 1.0E-4F)); + CHECK(vector_all_near_equal3(vector_cast(src.z_axis), dst.z_axis, 1.0E-4F)); + CHECK(vector_all_near_equal3(vector_cast(src.w_axis), dst.w_axis, 1.0E-4F)); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix4x4.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix4x4.cpp new file mode 100644 index 00000000..6d89dc1b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_matrix4x4.cpp @@ -0,0 +1,385 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +using namespace rtm; + +template +static void test_matrix4x4_setters(const FloatType threshold) +{ + using Vector4Type = typename float_traits::vector4; + using Matrix4x4Type = typename float_traits::matrix4x4; + + const Matrix4x4Type identity = matrix_identity(); + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0), FloatType(0.0)); + Vector4Type z_axis = vector_set(FloatType(7.0), FloatType(8.0), FloatType(9.0), FloatType(0.0)); + Vector4Type w_axis = vector_set(FloatType(10.0), FloatType(11.0), FloatType(12.0), FloatType(1.0)); + Matrix4x4Type mtx = matrix_set(x_axis, y_axis, z_axis, w_axis); + CHECK(vector_all_near_equal(x_axis, mtx.x_axis, threshold)); + CHECK(vector_all_near_equal(y_axis, mtx.y_axis, threshold)); + CHECK(vector_all_near_equal(z_axis, mtx.z_axis, threshold)); + CHECK(vector_all_near_equal(w_axis, mtx.w_axis, threshold)); + } + + { + CHECK(vector_all_near_equal(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), identity.x_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), identity.y_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), identity.z_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), identity.w_axis, threshold)); + } +} + +template +static void test_matrix4x4_arithmetic(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x4Type = typename float_traits::matrix3x4; + using Matrix4x4Type = typename float_traits::matrix4x4; + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Matrix3x4Type mtx_a3x4 = matrix_from_qvv(rotation_around_z, x_axis, vector_set(FloatType(1.0))); + Matrix4x4Type mtx_a = matrix_cast(mtx_a3x4); + Vector4Type result = matrix_mul_vector(x_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), threshold)); + result = matrix_mul_vector(y_axis, mtx_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + Matrix3x4Type mtx_b3x4 = matrix_from_qvv(rotation_around_x, y_axis, vector_set(FloatType(1.0))); + Matrix4x4Type mtx_b = matrix_cast(mtx_b3x4); + result = matrix_mul_vector(x_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), threshold)); + result = matrix_mul_vector(y_axis, mtx_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), threshold)); + + Matrix4x4Type mtx_ab = matrix_mul(mtx_a, mtx_b); + Matrix4x4Type mtx_ba = matrix_mul(mtx_b, mtx_a); + result = matrix_mul_vector(x_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector(matrix_mul_vector(x_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_vector(y_axis, mtx_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector(matrix_mul_vector(y_axis, mtx_a), mtx_b), threshold)); + result = matrix_mul_vector(x_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector(matrix_mul_vector(x_axis, mtx_b), mtx_a), threshold)); + result = matrix_mul_vector(y_axis, mtx_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, matrix_mul_vector(matrix_mul_vector(y_axis, mtx_b), mtx_a), threshold)); + } +} + +template +static void test_matrix4x4_transformations(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Matrix3x4Type = typename float_traits::matrix3x4; + using Matrix4x4Type = typename float_traits::matrix4x4; + + const Matrix4x4Type identity = matrix_identity(); + const Vector4Type zero = vector_zero(); + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0), FloatType(0.0)); + Vector4Type z_axis = vector_set(FloatType(7.0), FloatType(8.0), FloatType(9.0), FloatType(0.0)); + Vector4Type w_axis = vector_set(FloatType(10.0), FloatType(11.0), FloatType(12.0), FloatType(1.0)); + Matrix4x4Type mtx0 = matrix_set(x_axis, y_axis, z_axis, w_axis); + Matrix4x4Type mtx1 = matrix_transpose(mtx0); + CHECK(vector_all_near_equal(vector_set(FloatType(1.0), FloatType(4.0), FloatType(7.0), FloatType(10.0)), mtx1.x_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(2.0), FloatType(5.0), FloatType(8.0), FloatType(11.0)), mtx1.y_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(3.0), FloatType(6.0), FloatType(9.0), FloatType(12.0)), mtx1.z_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), mtx1.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx3x4 = matrix_from_qvv(rotation_around_z, translation, scale); + mtx3x4.x_axis = vector_set(vector_get_x(mtx3x4.x_axis), vector_get_y(mtx3x4.x_axis), vector_get_z(mtx3x4.x_axis), FloatType(0.0)); + mtx3x4.y_axis = vector_set(vector_get_x(mtx3x4.y_axis), vector_get_y(mtx3x4.y_axis), vector_get_z(mtx3x4.y_axis), FloatType(0.0)); + mtx3x4.z_axis = vector_set(vector_get_x(mtx3x4.z_axis), vector_get_y(mtx3x4.z_axis), vector_get_z(mtx3x4.z_axis), FloatType(0.0)); + mtx3x4.w_axis = vector_set(vector_get_x(mtx3x4.w_axis), vector_get_y(mtx3x4.w_axis), vector_get_z(mtx3x4.w_axis), FloatType(1.0)); + Matrix4x4Type mtx = matrix_cast(mtx3x4); + Matrix4x4Type inv_mtx = matrix_inverse(mtx); + Matrix4x4Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.z_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.w_axis, threshold)); + } + + { + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type translation = vector_set(FloatType(1.0), FloatType(2.0), FloatType(3.0)); + Vector4Type scale = vector_set(FloatType(4.0), FloatType(5.0), FloatType(6.0)); + Matrix3x4Type mtx3x4 = matrix_from_qvv(rotation_around_z, translation, scale); + mtx3x4.x_axis = vector_set(vector_get_x(mtx3x4.x_axis), vector_get_y(mtx3x4.x_axis), vector_get_z(mtx3x4.x_axis), FloatType(0.0)); + mtx3x4.y_axis = vector_set(vector_get_x(mtx3x4.y_axis), vector_get_y(mtx3x4.y_axis), vector_get_z(mtx3x4.y_axis), FloatType(0.0)); + mtx3x4.z_axis = vector_set(vector_get_x(mtx3x4.z_axis), vector_get_y(mtx3x4.z_axis), vector_get_z(mtx3x4.z_axis), FloatType(0.0)); + mtx3x4.w_axis = vector_set(vector_get_x(mtx3x4.w_axis), vector_get_y(mtx3x4.w_axis), vector_get_z(mtx3x4.w_axis), FloatType(1.0)); + Matrix4x4Type mtx = matrix_cast(mtx3x4); + Matrix4x4Type inv_mtx = matrix_inverse(mtx, mtx); + Matrix4x4Type result = matrix_mul(mtx, inv_mtx); + CHECK(vector_all_near_equal(vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), result.x_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0)), result.y_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0)), result.z_axis, threshold)); + CHECK(vector_all_near_equal(vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), result.w_axis, threshold)); + } + + { + Matrix4x4Type mtx = matrix_set(zero, zero, zero, zero); + Matrix4x4Type inv_mtx = matrix_inverse(mtx, identity); + CHECK(vector_all_near_equal(identity.x_axis, inv_mtx.x_axis, threshold)); + CHECK(vector_all_near_equal(identity.y_axis, inv_mtx.y_axis, threshold)); + CHECK(vector_all_near_equal(identity.z_axis, inv_mtx.z_axis, threshold)); + CHECK(vector_all_near_equal(identity.w_axis, inv_mtx.w_axis, threshold)); + } +} + +template +static void test_matrix4x4_misc(const FloatType threshold) +{ + using Vector4Type = typename float_traits::vector4; + using Matrix4x4Type = typename float_traits::matrix4x4; + + { + Vector4Type x_axis = vector_set(FloatType(1.65424), FloatType(0.22921), FloatType(5.73038), FloatType(4.46541)); + Vector4Type y_axis = vector_set(FloatType(1.90220), FloatType(0.82590), FloatType(6.61556), FloatType(4.46383)); + Vector4Type z_axis = vector_set(FloatType(7.36288), FloatType(7.09841), FloatType(0.33519), FloatType(7.43985)); + Vector4Type w_axis = vector_set(FloatType(4.42391), FloatType(4.03858), FloatType(2.49537), FloatType(0.11255)); + Matrix4x4Type mtx = matrix_set(x_axis, y_axis, z_axis, w_axis); + FloatType det = scalar_cast(matrix_determinant(mtx)); + CHECK(scalar_near_equal(det, FloatType(120.68779956246105324363), threshold)); + } + + { + FloatType threshold2 = FloatType(1.0E-3F); + Vector4Type x_axis = vector_set(FloatType(1.65424), FloatType(0.22921), FloatType(5.73038), FloatType(4.46541)); + Vector4Type y_axis = vector_set(FloatType(1.90220), FloatType(0.82590), FloatType(6.61556), FloatType(4.46383)); + Vector4Type z_axis = vector_set(FloatType(7.36288), FloatType(7.09841), FloatType(0.33519), FloatType(7.43985)); + Vector4Type w_axis = vector_set(FloatType(4.42391), FloatType(4.03858), FloatType(2.49537), FloatType(0.11255)); + Matrix4x4Type mtx = matrix_set(x_axis, y_axis, z_axis, w_axis); + + FloatType det_xx = scalar_cast(matrix_minor(mtx, axis4::x, axis4::x)); + CHECK(scalar_near_equal(det_xx, FloatType(251.213), threshold2)); + FloatType det_xy = scalar_cast(matrix_minor(mtx, axis4::x, axis4::y)); + CHECK(scalar_near_equal(det_xy, FloatType(252.409), threshold2)); + FloatType det_xz = scalar_cast(matrix_minor(mtx, axis4::x, axis4::z)); + CHECK(scalar_near_equal(det_xz, FloatType(-36.5778), threshold2)); + FloatType det_xw = scalar_cast(matrix_minor(mtx, axis4::x, axis4::w)); + CHECK(scalar_near_equal(det_xw, FloatType(6.1402), threshold2)); + + FloatType det_yx = scalar_cast(matrix_minor(mtx, axis4::y, axis4::x)); + CHECK(scalar_near_equal(det_yx, FloatType(236.404), threshold2)); + FloatType det_yy = scalar_cast(matrix_minor(mtx, axis4::y, axis4::y)); + CHECK(scalar_near_equal(det_yy, FloatType(228.63), threshold2)); + FloatType det_yz = scalar_cast(matrix_minor(mtx, axis4::y, axis4::z)); + CHECK(scalar_near_equal(det_yz, FloatType(-48.4728), threshold2)); + FloatType det_yw = scalar_cast(matrix_minor(mtx, axis4::y, axis4::w)); + CHECK(scalar_near_equal(det_yw, FloatType(13.6377), threshold2)); + + FloatType det_zx = scalar_cast(matrix_minor(mtx, axis4::z, axis4::x)); + CHECK(scalar_near_equal(det_zx, FloatType(-9.7121), threshold2)); + FloatType det_zy = scalar_cast(matrix_minor(mtx, axis4::z, axis4::y)); + CHECK(scalar_near_equal(det_zy, FloatType(-14.752), threshold2)); + FloatType det_zz = scalar_cast(matrix_minor(mtx, axis4::z, axis4::z)); + CHECK(scalar_near_equal(det_zz, FloatType(-7.20201), threshold2)); + FloatType det_zw = scalar_cast(matrix_minor(mtx, axis4::z, axis4::w)); + CHECK(scalar_near_equal(det_zw, FloatType(-12.0829), threshold2)); + + FloatType det_wx = scalar_cast(matrix_minor(mtx, axis4::w, axis4::x)); + CHECK(scalar_near_equal(det_wx, FloatType(-51.1582), threshold2)); + FloatType det_wy = scalar_cast(matrix_minor(mtx, axis4::w, axis4::y)); + CHECK(scalar_near_equal(det_wy, FloatType(-28.475), threshold2)); + FloatType det_wz = scalar_cast(matrix_minor(mtx, axis4::w, axis4::z)); + CHECK(scalar_near_equal(det_wz, FloatType(-4.82179), threshold2)); + FloatType det_ww = scalar_cast(matrix_minor(mtx, axis4::w, axis4::w)); + CHECK(scalar_near_equal(det_ww, FloatType(-23.678), threshold2)); + } + + { + FloatType threshold2 = FloatType(1.0E-3F); + Vector4Type x_axis = vector_set(FloatType(1.65424), FloatType(0.22921), FloatType(5.73038), FloatType(4.46541)); + Vector4Type y_axis = vector_set(FloatType(1.90220), FloatType(0.82590), FloatType(6.61556), FloatType(4.46383)); + Vector4Type z_axis = vector_set(FloatType(7.36288), FloatType(7.09841), FloatType(0.33519), FloatType(7.43985)); + Vector4Type w_axis = vector_set(FloatType(4.42391), FloatType(4.03858), FloatType(2.49537), FloatType(0.11255)); + Matrix4x4Type mtx = matrix_set(x_axis, y_axis, z_axis, w_axis); + Matrix4x4Type cof = matrix_cofactor(mtx); + + FloatType cof_xx = scalar_cast(vector_get_x(cof.x_axis)); + CHECK(scalar_near_equal(cof_xx, FloatType(251.213), threshold2)); + FloatType cof_xy = scalar_cast(vector_get_y(cof.x_axis)); + CHECK(scalar_near_equal(cof_xy, FloatType(-252.409), threshold2)); + FloatType cof_xz = scalar_cast(vector_get_z(cof.x_axis)); + CHECK(scalar_near_equal(cof_xz, FloatType(-36.5778), threshold2)); + FloatType cof_xw = scalar_cast(vector_get_w(cof.x_axis)); + CHECK(scalar_near_equal(cof_xw, FloatType(-6.1402), threshold2)); + + FloatType cof_yx = scalar_cast(vector_get_x(cof.y_axis)); + CHECK(scalar_near_equal(cof_yx, FloatType(-236.404), threshold2)); + FloatType cof_yy = scalar_cast(vector_get_y(cof.y_axis)); + CHECK(scalar_near_equal(cof_yy, FloatType(228.63), threshold2)); + FloatType cof_yz = scalar_cast(vector_get_z(cof.y_axis)); + CHECK(scalar_near_equal(cof_yz, FloatType(48.4728), threshold2)); + FloatType cof_yw = scalar_cast(vector_get_w(cof.y_axis)); + CHECK(scalar_near_equal(cof_yw, FloatType(13.6377), threshold2)); + + FloatType cof_zx = scalar_cast(vector_get_x(cof.z_axis)); + CHECK(scalar_near_equal(cof_zx, FloatType(-9.7121), threshold2)); + FloatType cof_zy = scalar_cast(vector_get_y(cof.z_axis)); + CHECK(scalar_near_equal(cof_zy, FloatType(14.752), threshold2)); + FloatType cof_zz = scalar_cast(vector_get_z(cof.z_axis)); + CHECK(scalar_near_equal(cof_zz, FloatType(-7.20201), threshold2)); + FloatType cof_zw = scalar_cast(vector_get_w(cof.z_axis)); + CHECK(scalar_near_equal(cof_zw, FloatType(12.0829), threshold2)); + + FloatType cof_wx = scalar_cast(vector_get_x(cof.w_axis)); + CHECK(scalar_near_equal(cof_wx, FloatType(51.1582), threshold2)); + FloatType cof_wy = scalar_cast(vector_get_y(cof.w_axis)); + CHECK(scalar_near_equal(cof_wy, FloatType(-28.475), threshold2)); + FloatType cof_wz = scalar_cast(vector_get_z(cof.w_axis)); + CHECK(scalar_near_equal(cof_wz, FloatType(4.82179), threshold2)); + FloatType cof_ww = scalar_cast(vector_get_w(cof.w_axis)); + CHECK(scalar_near_equal(cof_ww, FloatType(-23.678), threshold2)); + } + + { + FloatType threshold2 = FloatType(1.0E-3F); + Vector4Type x_axis = vector_set(FloatType(1.65424), FloatType(0.22921), FloatType(5.73038), FloatType(4.46541)); + Vector4Type y_axis = vector_set(FloatType(1.90220), FloatType(0.82590), FloatType(6.61556), FloatType(4.46383)); + Vector4Type z_axis = vector_set(FloatType(7.36288), FloatType(7.09841), FloatType(0.33519), FloatType(7.43985)); + Vector4Type w_axis = vector_set(FloatType(4.42391), FloatType(4.03858), FloatType(2.49537), FloatType(0.11255)); + Matrix4x4Type mtx = matrix_set(x_axis, y_axis, z_axis, w_axis); + Matrix4x4Type adj = matrix_adjugate(mtx); + + FloatType adj_xx = scalar_cast(vector_get_x(adj.x_axis)); + CHECK(scalar_near_equal(adj_xx, FloatType(251.213), threshold2)); + FloatType adj_xy = scalar_cast(vector_get_y(adj.x_axis)); + CHECK(scalar_near_equal(adj_xy, FloatType(-236.404), threshold2)); + FloatType adj_xz = scalar_cast(vector_get_z(adj.x_axis)); + CHECK(scalar_near_equal(adj_xz, FloatType(-9.7121), threshold2)); + FloatType adj_xw = scalar_cast(vector_get_w(adj.x_axis)); + CHECK(scalar_near_equal(adj_xw, FloatType(51.1582), threshold2)); + + FloatType adj_yx = scalar_cast(vector_get_x(adj.y_axis)); + CHECK(scalar_near_equal(adj_yx, FloatType(-252.409), threshold2)); + FloatType adj_yy = scalar_cast(vector_get_y(adj.y_axis)); + CHECK(scalar_near_equal(adj_yy, FloatType(228.63), threshold2)); + FloatType adj_yz = scalar_cast(vector_get_z(adj.y_axis)); + CHECK(scalar_near_equal(adj_yz, FloatType(14.752), threshold2)); + FloatType adj_yw = scalar_cast(vector_get_w(adj.y_axis)); + CHECK(scalar_near_equal(adj_yw, FloatType(-28.475), threshold2)); + + FloatType adj_zx = scalar_cast(vector_get_x(adj.z_axis)); + CHECK(scalar_near_equal(adj_zx, FloatType(-36.5778), threshold2)); + FloatType adj_zy = scalar_cast(vector_get_y(adj.z_axis)); + CHECK(scalar_near_equal(adj_zy, FloatType(48.4728), threshold2)); + FloatType adj_zz = scalar_cast(vector_get_z(adj.z_axis)); + CHECK(scalar_near_equal(adj_zz, FloatType(-7.20201), threshold2)); + FloatType adj_zw = scalar_cast(vector_get_w(adj.z_axis)); + CHECK(scalar_near_equal(adj_zw, FloatType(4.82179), threshold2)); + + FloatType adj_wx = scalar_cast(vector_get_x(adj.w_axis)); + CHECK(scalar_near_equal(adj_wx, FloatType(-6.1402), threshold2)); + FloatType adj_wy = scalar_cast(vector_get_y(adj.w_axis)); + CHECK(scalar_near_equal(adj_wy, FloatType(13.6377), threshold2)); + FloatType adj_wz = scalar_cast(vector_get_z(adj.w_axis)); + CHECK(scalar_near_equal(adj_wz, FloatType(12.0829), threshold2)); + FloatType adj_ww = scalar_cast(vector_get_w(adj.w_axis)); + CHECK(scalar_near_equal(adj_ww, FloatType(-23.678), threshold2)); + } +} + +TEST_CASE("matrix4x4f math", "[math][matrix4x4]") +{ + test_matrix4x4_setters(1.0E-4F); + test_matrix4x4_arithmetic(1.0E-4F); + test_matrix4x4_transformations(1.0E-4F); + test_matrix4x4_misc(1.0E-4F); + + { + quatf rotation_around_z = quat_from_euler(scalar_deg_to_rad(0.0F), scalar_deg_to_rad(90.0F), scalar_deg_to_rad(0.0F)); + vector4f translation = vector_set(1.0F, 2.0F, 3.0F); + vector4f scale = vector_set(4.0F, 5.0F, 6.0F); + matrix3x4f src3x4 = matrix_from_qvv(rotation_around_z, translation, scale); + + matrix4x4f src = matrix_cast(src3x4); + CHECK((float)vector_get_w(src.x_axis) == 0.0F); + CHECK((float)vector_get_w(src.y_axis) == 0.0F); + CHECK((float)vector_get_w(src.z_axis) == 0.0F); + CHECK((float)vector_get_w(src.w_axis) == 1.0F); + + matrix4x4d dst = matrix_cast(src); + CHECK(vector_all_near_equal(vector_cast(src.x_axis), dst.x_axis, 1.0E-4)); + CHECK(vector_all_near_equal(vector_cast(src.y_axis), dst.y_axis, 1.0E-4)); + CHECK(vector_all_near_equal(vector_cast(src.z_axis), dst.z_axis, 1.0E-4)); + CHECK(vector_all_near_equal(vector_cast(src.w_axis), dst.w_axis, 1.0E-4)); + } +} + +TEST_CASE("matrix4x4d math", "[math][matrix4x4]") +{ + test_matrix4x4_setters(1.0E-4); + test_matrix4x4_arithmetic(1.0E-4); + test_matrix4x4_transformations(1.0E-4); + test_matrix4x4_misc(1.0E-4); + + { + quatd rotation_around_z = quat_from_euler(scalar_deg_to_rad(0.0), scalar_deg_to_rad(90.0), scalar_deg_to_rad(0.0)); + vector4d translation = vector_set(1.0, 2.0, 3.0); + vector4d scale = vector_set(4.0, 5.0, 6.0); + matrix3x4d src3x4 = matrix_from_qvv(rotation_around_z, translation, scale); + + matrix4x4d src = matrix_cast(src3x4); + CHECK((double)vector_get_w(src.x_axis) == 0.0); + CHECK((double)vector_get_w(src.y_axis) == 0.0); + CHECK((double)vector_get_w(src.z_axis) == 0.0); + CHECK((double)vector_get_w(src.w_axis) == 1.0); + + matrix4x4f dst = matrix_cast(src); + CHECK(vector_all_near_equal(vector_cast(src.x_axis), dst.x_axis, 1.0E-4F)); + CHECK(vector_all_near_equal(vector_cast(src.y_axis), dst.y_axis, 1.0E-4F)); + CHECK(vector_all_near_equal(vector_cast(src.z_axis), dst.z_axis, 1.0E-4F)); + CHECK(vector_all_near_equal(vector_cast(src.w_axis), dst.w_axis, 1.0E-4F)); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_memory_utils.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_memory_utils.cpp new file mode 100644 index 00000000..ae21b977 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_memory_utils.cpp @@ -0,0 +1,229 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include +#include +#include + +using namespace rtm; +using namespace rtm::rtm_impl; + +TEST_CASE("misc tests", "[core][memory]") +{ + size_t num_powers_of_two = 0; + for (size_t i = 0; i <= 65536; ++i) + { + if (is_power_of_two(i)) + num_powers_of_two++; + } + + CHECK(num_powers_of_two == 17); + CHECK(is_power_of_two(1) == true); + CHECK(is_power_of_two(2) == true); + CHECK(is_power_of_two(4) == true); + CHECK(is_power_of_two(8) == true); + CHECK(is_power_of_two(16) == true); + CHECK(is_power_of_two(32) == true); + CHECK(is_power_of_two(64) == true); + CHECK(is_power_of_two(128) == true); + CHECK(is_power_of_two(256) == true); + CHECK(is_power_of_two(512) == true); + CHECK(is_power_of_two(1024) == true); + CHECK(is_power_of_two(2048) == true); + CHECK(is_power_of_two(4096) == true); + CHECK(is_power_of_two(8192) == true); + CHECK(is_power_of_two(16384) == true); + CHECK(is_power_of_two(32768) == true); + CHECK(is_power_of_two(65536) == true); + + CHECK(is_alignment_valid(0) == false); + CHECK(is_alignment_valid(4) == true); + CHECK(is_alignment_valid(8) == true); + CHECK(is_alignment_valid(2) == false); + CHECK(is_alignment_valid(5) == false); + CHECK(is_alignment_valid(8) == true); + CHECK(is_alignment_valid(16) == true); + + struct alignas(8) Tmp + { + int32_t padding; // Aligned to 8 bytes + int32_t value; // Aligned to 4 bytes + }; + Tmp tmp; + CHECK(is_aligned_to(&tmp.padding, 8) == true); + CHECK(is_aligned_to(&tmp.value, 4) == true); + CHECK(is_aligned_to(&tmp.value, 2) == true); + CHECK(is_aligned_to(&tmp.value, 1) == true); + CHECK(is_aligned_to(&tmp.value, 8) == false); + + CHECK(is_aligned_to(4, 4) == true); + CHECK(is_aligned_to(4, 2) == true); + CHECK(is_aligned_to(4, 1) == true); + CHECK(is_aligned_to(4, 8) == false); + CHECK(is_aligned_to(6, 4) == false); + CHECK(is_aligned_to(6, 2) == true); + CHECK(is_aligned_to(6, 1) == true); + + CHECK(is_aligned_to(align_to(5, 4), 4) == true); + CHECK(align_to(5, 4) == 8); + CHECK(is_aligned_to(align_to(8, 4), 4) == true); + CHECK(align_to(8, 4) == 8); + + void* ptr = (void*)0x00000000; + CHECK(align_to(ptr, 4) == (void*)0x00000000); + CHECK(align_to(ptr, 8) == (void*)0x00000000); + ptr = (void*)0x00000001; + CHECK(align_to(ptr, 4) == (void*)0x00000004); + CHECK(align_to(ptr, 8) == (void*)0x00000008); + ptr = (void*)0x00000004; + CHECK(align_to(ptr, 4) == (void*)0x00000004); + CHECK(align_to(ptr, 8) == (void*)0x00000008); + + int32_t array[8]; + CHECK(get_array_size(array) == (sizeof(array) / sizeof(array[0]))); +} + +TEST_CASE("raw memory support", "[core][memory]") +{ + uint32_t value32 = 0xAB78FE04; + uint8_t unaligned_value_buffer[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; + std::memcpy(&unaligned_value_buffer[1], &value32, sizeof(uint32_t)); + CHECK(unaligned_read(&unaligned_value_buffer[1]) == value32); +} + +enum class UnsignedEnum : uint32_t +{ + ZERO = 0, + U16_MAX = std::numeric_limits::max(), + U32_MAX = std::numeric_limits::max(), +}; + +enum class SignedEnum : int32_t +{ + I32_MIN = std::numeric_limits::min(), + I16_MIN = std::numeric_limits::min(), + I16_MAX = std::numeric_limits::max(), + I32_MAX = std::numeric_limits::max(), +}; + +TEST_CASE("safe_static_cast from unsigned enum", "[core][memory]") +{ + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK(safe_static_cast(UnsignedEnum::U16_MAX) == std::numeric_limits::max()); + CHECK(safe_static_cast(UnsignedEnum::U32_MAX) == std::numeric_limits::max()); + + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK(safe_static_cast(UnsignedEnum::U16_MAX)); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U32_MAX)); + + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK(safe_static_cast(UnsignedEnum::U16_MAX) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U32_MAX)); + + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U16_MAX)); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U32_MAX)); +} + +TEST_CASE("safe_static_cast from signed enum", "[core][memory]") +{ + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MIN)); + CHECK_THROWS(safe_static_cast(SignedEnum::I16_MIN)); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == safe_static_cast(std::numeric_limits::max())); + CHECK(safe_static_cast(SignedEnum::I32_MAX) == safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(SignedEnum::I32_MIN) == std::numeric_limits::min()); + CHECK(safe_static_cast(SignedEnum::I16_MIN) == std::numeric_limits::min()); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == std::numeric_limits::max()); + CHECK(safe_static_cast(SignedEnum::I32_MAX) == std::numeric_limits::max()); + + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MIN)); + CHECK_THROWS(safe_static_cast(SignedEnum::I16_MIN)); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MAX)); + + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MIN)); + CHECK(safe_static_cast(SignedEnum::I16_MIN) == std::numeric_limits::min()); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MAX)); +} + +TEST_CASE("safe_static_cast from signed int", "[core][memory]") +{ + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK(safe_static_cast(std::numeric_limits::max()) == safe_static_cast(std::numeric_limits::max())); + CHECK(safe_static_cast(std::numeric_limits::max()) == safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(std::numeric_limits::min()) == std::numeric_limits::min()); + CHECK(safe_static_cast(std::numeric_limits::min()) == std::numeric_limits::min()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK(safe_static_cast(std::numeric_limits::min()) == std::numeric_limits::min()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); +} + +TEST_CASE("safe_static_cast from unsigned int", "[core][memory]") +{ + CHECK(safe_static_cast(0U) == 0); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + + CHECK(safe_static_cast(0U) == 0); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(0U) == 0); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(0U) == 0); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); +} + +TEST_CASE("safe_static_cast from double", "[core][memory]") +{ + // Use volatile to avoid a constant being truncated warning + volatile double value = -std::numeric_limits::max(); + CHECK_THROWS(safe_static_cast(value)); + value = std::numeric_limits::max(); + CHECK_THROWS(safe_static_cast(value)); + + CHECK(safe_static_cast(-std::numeric_limits::max()) == -std::numeric_limits::max()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_packing_quat.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_packing_quat.cpp new file mode 100644 index 00000000..74bf4d52 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_packing_quat.cpp @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +using namespace rtm; + +template +static void test_quat_impl(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + + { + QuatType quat0 = quat_set(FloatType(0.39564531008956383), FloatType(0.044254239301713752), FloatType(0.22768840967675355), FloatType(-0.88863059760894492)); + QuatType quat1 = quat_ensure_positive_w(quat0); + QuatType quat2 = quat_ensure_positive_w(quat1); + + CHECK(FloatType(quat_get_x(quat0)) == -FloatType(quat_get_x(quat1))); + CHECK(FloatType(quat_get_y(quat0)) == -FloatType(quat_get_y(quat1))); + CHECK(FloatType(quat_get_z(quat0)) == -FloatType(quat_get_z(quat1))); + CHECK(FloatType(quat_get_w(quat0)) == -FloatType(quat_get_w(quat1))); + + CHECK(FloatType(quat_get_x(quat2)) == FloatType(quat_get_x(quat1))); + CHECK(FloatType(quat_get_y(quat2)) == FloatType(quat_get_y(quat1))); + CHECK(FloatType(quat_get_z(quat2)) == FloatType(quat_get_z(quat1))); + CHECK(FloatType(quat_get_w(quat2)) == FloatType(quat_get_w(quat1))); + + Vector4Type vec1 = quat_to_vector(quat1); + QuatType quat3 = quat_from_positive_w(vec1); + CHECK(FloatType(quat_get_x(quat1)) == FloatType(quat_get_x(quat3))); + CHECK(FloatType(quat_get_y(quat1)) == FloatType(quat_get_y(quat3))); + CHECK(FloatType(quat_get_z(quat1)) == FloatType(quat_get_z(quat3))); + CHECK(scalar_near_equal(FloatType(quat_get_w(quat1)), FloatType(quat_get_w(quat3)), threshold)); + } +} + +TEST_CASE("quatf packing math", "[math][quat][packing]") +{ + test_quat_impl(1.0E-4F); +} + +TEST_CASE("quatd packing math", "[math][quat][packing]") +{ + test_quat_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_quat.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_quat.cpp new file mode 100644 index 00000000..bc0e36f5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_quat.cpp @@ -0,0 +1,498 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace rtm; + +template +static Vector4Type quat_rotate_scalar(const Vector4Type& vector, const QuatType& rotation) +{ + // (q.W*q.W-qv.qv)v + 2(qv.v)qv + 2 q.W (qv x v) + FloatType rotation_x = quat_get_x(rotation); + FloatType rotation_y = quat_get_y(rotation); + FloatType rotation_z = quat_get_z(rotation); + Vector4Type qv = vector_set(rotation_x, rotation_y, rotation_z); + Vector4Type vOut = vector_mul(vector_cross3(qv, vector), FloatType(2.0) * quat_get_w(rotation)); + vOut = vector_add(vOut, vector_mul(vector, (quat_get_w(rotation) * quat_get_w(rotation)) - vector_dot(qv, qv))); + vOut = vector_add(vOut, vector_mul(qv, FloatType(2.0) * vector_dot(qv, vector))); + return vOut; +} + +template +static QuatType quat_mul_scalar(const QuatType& lhs, const QuatType& rhs) +{ + FloatType lhs_raw[4] = { quat_get_x(lhs), quat_get_y(lhs), quat_get_z(lhs), quat_get_w(lhs) }; + FloatType rhs_raw[4] = { quat_get_x(rhs), quat_get_y(rhs), quat_get_z(rhs), quat_get_w(rhs) }; + + FloatType x = (rhs_raw[3] * lhs_raw[0]) + (rhs_raw[0] * lhs_raw[3]) + (rhs_raw[1] * lhs_raw[2]) - (rhs_raw[2] * lhs_raw[1]); + FloatType y = (rhs_raw[3] * lhs_raw[1]) - (rhs_raw[0] * lhs_raw[2]) + (rhs_raw[1] * lhs_raw[3]) + (rhs_raw[2] * lhs_raw[0]); + FloatType z = (rhs_raw[3] * lhs_raw[2]) + (rhs_raw[0] * lhs_raw[1]) - (rhs_raw[1] * lhs_raw[0]) + (rhs_raw[2] * lhs_raw[3]); + FloatType w = (rhs_raw[3] * lhs_raw[3]) - (rhs_raw[0] * lhs_raw[0]) - (rhs_raw[1] * lhs_raw[1]) - (rhs_raw[2] * lhs_raw[2]); + + return quat_set(x, y, z, w); +} + +template +static FloatType scalar_dot(const QuatType& lhs, const QuatType& rhs) +{ + return (quat_get_x(lhs) * quat_get_x(rhs)) + (quat_get_y(lhs) * quat_get_y(rhs)) + (quat_get_z(lhs) * quat_get_z(rhs)) + (quat_get_w(lhs) * quat_get_w(rhs)); +} + +template +static QuatType scalar_normalize(const QuatType& input) +{ + FloatType inv_len = FloatType(1.0) / rtm::scalar_sqrt(scalar_dot(input, input)); + return quat_set(quat_get_x(input) * inv_len, quat_get_y(input) * inv_len, quat_get_z(input) * inv_len, quat_get_w(input) * inv_len); +} + +template +static QuatType scalar_lerp(const QuatType& start, const QuatType& end, FloatType alpha) +{ + FloatType dot = scalar_dot(start, end); + FloatType bias = dot >= FloatType(0.0) ? FloatType(1.0) : FloatType(-1.0); + FloatType x = quat_get_x(start) + ((quat_get_x(end) * bias) - quat_get_x(start)) * alpha; + FloatType y = quat_get_y(start) + ((quat_get_y(end) * bias) - quat_get_y(start)) * alpha; + FloatType z = quat_get_z(start) + ((quat_get_z(end) * bias) - quat_get_z(start)) * alpha; + FloatType w = quat_get_w(start) + ((quat_get_w(end) * bias) - quat_get_w(start)) * alpha; + return quat_normalize(quat_set(x, y, z, w)); +} + +template +static void test_quat_impl(const FloatType threshold) +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using Float4Type = typename float_traits::float4; + using ScalarType = typename float_traits::scalar; + + const Vector4Type zero = vector_zero(); + const QuatType identity = quat_identity(); + + ////////////////////////////////////////////////////////////////////////// + // Setters, getters, and casts + + CHECK(FloatType(quat_get_x(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(FloatType(quat_get_y(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(FloatType(quat_get_z(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(FloatType(quat_get_w(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(scalar_cast(quat_get_x(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(scalar_cast(quat_get_y(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(scalar_cast(quat_get_z(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(scalar_cast(quat_get_w(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(FloatType(quat_get_x(identity)) == FloatType(0.0)); + CHECK(FloatType(quat_get_y(identity)) == FloatType(0.0)); + CHECK(FloatType(quat_get_z(identity)) == FloatType(0.0)); + CHECK(FloatType(quat_get_w(identity)) == FloatType(1.0)); + + CHECK(quat_near_equal(quat_set_x(identity, FloatType(4.0)), quat_set(FloatType(4.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), FloatType(0.0))); + CHECK(quat_near_equal(quat_set_y(identity, FloatType(4.0)), quat_set(FloatType(0.0), FloatType(4.0), FloatType(0.0), FloatType(1.0)), FloatType(0.0))); + CHECK(quat_near_equal(quat_set_z(identity, FloatType(4.0)), quat_set(FloatType(0.0), FloatType(0.0), FloatType(4.0), FloatType(1.0)), FloatType(0.0))); + CHECK(quat_near_equal(quat_set_w(identity, FloatType(4.0)), quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(4.0)), FloatType(0.0))); + + CHECK(quat_near_equal(quat_set_x(identity, scalar_set(FloatType(4.0))), quat_set(FloatType(4.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)), FloatType(0.0))); + CHECK(quat_near_equal(quat_set_y(identity, scalar_set(FloatType(4.0))), quat_set(FloatType(0.0), FloatType(4.0), FloatType(0.0), FloatType(1.0)), FloatType(0.0))); + CHECK(quat_near_equal(quat_set_z(identity, scalar_set(FloatType(4.0))), quat_set(FloatType(0.0), FloatType(0.0), FloatType(4.0), FloatType(1.0)), FloatType(0.0))); + CHECK(quat_near_equal(quat_set_w(identity, scalar_set(FloatType(4.0))), quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(4.0)), FloatType(0.0))); + + { + struct alignas(16) Tmp + { + uint8_t padding0[8]; // 8 | 8 + FloatType values[4]; // 24 | 40 + uint8_t padding1[8]; // 32 | 48 + }; + + Tmp tmp = { { 0 }, { FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0) }, {} }; + CHECK(FloatType(quat_get_x(quat_load(&tmp.values[0]))) == tmp.values[0]); + CHECK(FloatType(quat_get_y(quat_load(&tmp.values[0]))) == tmp.values[1]); + CHECK(FloatType(quat_get_z(quat_load(&tmp.values[0]))) == tmp.values[2]); + CHECK(FloatType(quat_get_w(quat_load(&tmp.values[0]))) == tmp.values[3]); + } + + { + Float4Type tmpf4 = { FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0) }; + CHECK(FloatType(quat_get_x(quat_load(&tmpf4))) == tmpf4.x); + CHECK(FloatType(quat_get_y(quat_load(&tmpf4))) == tmpf4.y); + CHECK(FloatType(quat_get_z(quat_load(&tmpf4))) == tmpf4.z); + CHECK(FloatType(quat_get_w(quat_load(&tmpf4))) == tmpf4.w); + } + + { + const Vector4Type vec = vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)); + CHECK(FloatType(quat_get_x(vector_to_quat(vec))) == FloatType(vector_get_x(vec))); + CHECK(FloatType(quat_get_y(vector_to_quat(vec))) == FloatType(vector_get_y(vec))); + CHECK(FloatType(quat_get_z(vector_to_quat(vec))) == FloatType(vector_get_z(vec))); + CHECK(FloatType(quat_get_w(vector_to_quat(vec))) == FloatType(vector_get_w(vec))); + } + + { + struct alignas(16) Tmp + { + uint8_t padding0[8]; // 8 | 8 + FloatType values[4]; // 24 | 40 + uint8_t padding1[8]; // 32 | 48 + }; + + Tmp tmp = { { 0 }, { FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0) }, {} }; + quat_store(quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), &tmp.values[0]); + CHECK(tmp.values[0] == FloatType(0.0)); + CHECK(tmp.values[1] == FloatType(2.34)); + CHECK(tmp.values[2] == FloatType(-3.12)); + CHECK(tmp.values[3] == FloatType(10000.0)); + } + + { + QuatType quat = quat_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)); + Float4Type tmpf4 = { FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(0.0) }; + + quat_store(quat, &tmpf4); + CHECK(FloatType(quat_get_x(quat)) == tmpf4.x); + CHECK(FloatType(quat_get_y(quat)) == tmpf4.y); + CHECK(FloatType(quat_get_z(quat)) == tmpf4.z); + CHECK(FloatType(quat_get_w(quat)) == tmpf4.w); + } + + ////////////////////////////////////////////////////////////////////////// + // Arithmetic + + { + QuatType quat = quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0))); + QuatType quat_conj = quat_conjugate(quat); + CHECK(FloatType(quat_get_x(quat_conj)) == -FloatType(quat_get_x(quat))); + CHECK(FloatType(quat_get_y(quat_conj)) == -FloatType(quat_get_y(quat))); + CHECK(FloatType(quat_get_z(quat_conj)) == -FloatType(quat_get_z(quat))); + CHECK(FloatType(quat_get_w(quat_conj)) == FloatType(quat_get_w(quat))); + } + + { + QuatType quat0 = quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0))); + QuatType quat1 = quat_from_euler(scalar_deg_to_rad(FloatType(45.0)), scalar_deg_to_rad(FloatType(60.0)), scalar_deg_to_rad(FloatType(120.0))); + QuatType result = quat_mul(quat0, quat1); + QuatType result_ref = quat_mul_scalar(quat0, quat1); + CHECK(quat_near_equal(result, result_ref, threshold)); + + quat0 = quat_set(FloatType(0.39564531008956383), FloatType(0.044254239301713752), FloatType(0.22768840967675355), FloatType(0.88863059760894492)); + quat1 = quat_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)); + result = quat_mul(quat0, quat1); + result_ref = quat_mul_scalar(quat0, quat1); + CHECK(quat_near_equal(result, result_ref, threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type result = quat_mul_vector3(x_axis, rotation_around_z); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = quat_mul_vector3(y_axis, rotation_around_z); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + result = quat_mul_vector3(x_axis, rotation_around_x); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), threshold)); + result = quat_mul_vector3(y_axis, rotation_around_x); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + + QuatType rotation_xz = quat_mul(rotation_around_x, rotation_around_z); + QuatType rotation_zx = quat_mul(rotation_around_z, rotation_around_x); + result = quat_mul_vector3(x_axis, rotation_xz); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = quat_mul_vector3(y_axis, rotation_xz); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + result = quat_mul_vector3(x_axis, rotation_zx); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + result = quat_mul_vector3(y_axis, rotation_zx); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), threshold)); + } + + { + const QuatType test_rotations[] = { + identity, + quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0))), + quat_from_euler(scalar_deg_to_rad(FloatType(45.0)), scalar_deg_to_rad(FloatType(60.0)), scalar_deg_to_rad(FloatType(120.0))), + quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(180.0)), scalar_deg_to_rad(FloatType(45.0))), + quat_from_euler(scalar_deg_to_rad(FloatType(-120.0)), scalar_deg_to_rad(FloatType(-90.0)), scalar_deg_to_rad(FloatType(0.0))), + quat_from_euler(scalar_deg_to_rad(FloatType(-0.01)), scalar_deg_to_rad(FloatType(0.02)), scalar_deg_to_rad(FloatType(-0.03))), + }; + + const Vector4Type test_vectors[] = { + zero, + vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)), + vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), + vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), + vector_set(FloatType(45.0), FloatType(-60.0), FloatType(120.0)), + vector_set(FloatType(-45.0), FloatType(60.0), FloatType(-120.0)), + vector_set(FloatType(0.57735026918962576451), FloatType(0.57735026918962576451), FloatType(0.57735026918962576451)), + vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0)), + }; + + for (size_t quat_index = 0; quat_index < rtm_impl::get_array_size(test_rotations); ++quat_index) + { + const QuatType& rotation = test_rotations[quat_index]; + for (size_t vector_index = 0; vector_index < rtm_impl::get_array_size(test_vectors); ++vector_index) + { + const Vector4Type& vector = test_vectors[vector_index]; + Vector4Type result = quat_mul_vector3(vector, rotation); + Vector4Type result_ref = quat_rotate_scalar(vector, rotation); + CHECK(vector_all_near_equal3(result, result_ref, threshold)); + } + } + } + + { + const FloatType test_value10_flt[4] = { FloatType(-0.001138), FloatType(0.91623), FloatType(-1.624598), FloatType(0.715671) }; + const FloatType test_value11_flt[4] = { FloatType(0.1138), FloatType(-0.623), FloatType(1.4598), FloatType(-0.5671) }; + const QuatType test_value10 = quat_set(test_value10_flt[0], test_value10_flt[1], test_value10_flt[2], test_value10_flt[3]); + const QuatType test_value11 = quat_set(test_value11_flt[0], test_value11_flt[1], test_value11_flt[2], test_value11_flt[3]); + const FloatType scalar_dot_result = scalar_dot(test_value10, test_value11); + const FloatType quat_dot_result = quat_dot(test_value10, test_value11); + CHECK(scalar_near_equal(quat_dot_result, scalar_dot_result, threshold)); + const ScalarType quat_sdot_result = quat_dot(test_value10, test_value11); + CHECK(scalar_near_equal(scalar_cast(quat_sdot_result), scalar_dot_result, threshold)); + } + + { + QuatType quat = quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0))); + Vector4Type vec = quat_to_vector(quat); + + CHECK(scalar_near_equal(FloatType(quat_length_squared(quat)), FloatType(vector_length_squared(vec)), threshold)); + CHECK(scalar_near_equal(scalar_cast(quat_length_squared(quat)), scalar_cast(vector_length_squared(vec)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_length(quat)), FloatType(vector_length(vec)), threshold)); + CHECK(scalar_near_equal(scalar_cast(quat_length(quat)), scalar_cast(vector_length(vec)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_length_reciprocal(quat)), FloatType(vector_length_reciprocal(vec)), threshold)); + CHECK(scalar_near_equal(scalar_cast(quat_length_reciprocal(quat)), scalar_cast(vector_length_reciprocal(vec)), threshold)); + } + + { + QuatType quat = quat_set(FloatType(-0.001138), FloatType(0.91623), FloatType(-1.624598), FloatType(0.715671)); + const QuatType scalar_normalize_result = scalar_normalize(quat); + const QuatType quat_normalize_result = quat_normalize(quat); + CHECK(scalar_near_equal(FloatType(quat_get_x(quat_normalize_result)), FloatType(quat_get_x(scalar_normalize_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_y(quat_normalize_result)), FloatType(quat_get_y(scalar_normalize_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_z(quat_normalize_result)), FloatType(quat_get_z(scalar_normalize_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_w(quat_normalize_result)), FloatType(quat_get_w(scalar_normalize_result)), threshold)); + } + + { + QuatType quat0 = quat_normalize(quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0)))); + QuatType quat1 = quat_normalize(quat_from_euler(scalar_deg_to_rad(FloatType(45.0)), scalar_deg_to_rad(FloatType(60.0)), scalar_deg_to_rad(FloatType(120.0)))); + + QuatType scalar_result = scalar_lerp(quat0, quat1, FloatType(0.33)); + + CHECK(scalar_near_equal(FloatType(quat_get_x(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_x(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_y(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_y(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_z(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_z(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_w(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_w(scalar_result)), threshold)); + + CHECK(scalar_near_equal(FloatType(quat_get_x(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_x(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_y(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_y(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_z(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_z(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_w(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_w(scalar_result)), threshold)); + + quat1 = quat_neg(quat1); + CHECK(scalar_near_equal(FloatType(quat_get_x(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_x(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_y(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_y(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_z(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_z(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_w(quat_lerp(quat0, quat1, FloatType(0.33)))), FloatType(quat_get_w(scalar_result)), threshold)); + + CHECK(scalar_near_equal(FloatType(quat_get_x(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_x(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_y(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_y(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_z(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_z(scalar_result)), threshold)); + CHECK(scalar_near_equal(FloatType(quat_get_w(quat_lerp(quat0, quat1, scalar_set(FloatType(0.33))))), FloatType(quat_get_w(scalar_result)), threshold)); + + CHECK(vector_all_near_equal(quat_to_vector(quat_lerp(quat0, quat1, FloatType(0.0))), quat_to_vector(quat0), threshold)); + CHECK(vector_all_near_equal(quat_to_vector(quat_lerp(quat0, quat1, FloatType(1.0))), quat_to_vector(quat_neg(quat1)), threshold)); + CHECK(vector_all_near_equal(quat_to_vector(quat_lerp(quat0, quat1, scalar_set(FloatType(0.0)))), quat_to_vector(quat0), threshold)); + CHECK(vector_all_near_equal(quat_to_vector(quat_lerp(quat0, quat1, scalar_set(FloatType(1.0)))), quat_to_vector(quat_neg(quat1)), threshold)); + } + + { + QuatType quat0 = quat_normalize(quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0)))); + QuatType quat1 = quat_normalize(quat_from_euler(scalar_deg_to_rad(FloatType(45.0)), scalar_deg_to_rad(FloatType(60.0)), scalar_deg_to_rad(FloatType(120.0)))); + + CHECK(vector_all_near_equal(quat_to_vector(quat_slerp(quat0, quat1, FloatType(0.0))), quat_to_vector(quat0), threshold)); + CHECK(vector_all_near_equal(quat_to_vector(quat_slerp(quat0, quat1, FloatType(1.0))), quat_to_vector(quat1), threshold)); + + // Remove quat0 from quat1 + QuatType delta_0_to_1 = quat_normalize(quat_mul(quat1, quat_conjugate(quat0))); + + Vector4Type axis; + FloatType angle; + quat_to_axis_angle(delta_0_to_1, axis, angle); + + FloatType alpha = FloatType(0.33); + QuatType interp_delta = quat_from_axis_angle(axis, angle * alpha); + + QuatType result_ref = quat_mul(interp_delta, quat0); + QuatType result0 = quat_slerp(quat0, quat1, alpha); + QuatType result0_s = quat_slerp(quat0, quat1, scalar_set(alpha)); + + CHECK(vector_all_near_equal(quat_to_vector(result0), quat_to_vector(result_ref), threshold)); + CHECK(vector_all_near_equal(quat_to_vector(result0_s), quat_to_vector(result_ref), threshold)); + + quat1 = quat_neg(quat1); + QuatType result1 = quat_slerp(quat0, quat1, alpha); + QuatType result1_s = quat_slerp(quat0, quat1, scalar_set(alpha)); + + CHECK(vector_all_near_equal(quat_to_vector(result1), quat_to_vector(result_ref), threshold)); + CHECK(vector_all_near_equal(quat_to_vector(result1_s), quat_to_vector(result_ref), threshold)); + } + + { + QuatType quat0 = quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0))); + QuatType quat1 = quat_neg(quat0); + + CHECK(FloatType(quat_get_x(quat0)) == -FloatType(quat_get_x(quat1))); + CHECK(FloatType(quat_get_y(quat0)) == -FloatType(quat_get_y(quat1))); + CHECK(FloatType(quat_get_z(quat0)) == -FloatType(quat_get_z(quat1))); + CHECK(FloatType(quat_get_w(quat0)) == -FloatType(quat_get_w(quat1))); + } + + { + QuatType quat0 = quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0))); + QuatType quat_log = quat_rotation_log(quat0); + QuatType quat_exp = quat_rotation_exp(quat_log); + QuatType quat_log_identity = quat_rotation_log(identity); + QuatType quat_exp_identity = quat_rotation_exp(quat_log_identity); + + CHECK(quat_near_equal(quat0, quat_exp, threshold)); + CHECK(vector_all_near_equal(quat_to_vector(quat_log_identity), zero, threshold)); + CHECK(quat_near_equal(identity, quat_exp_identity, threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Conversion to/from axis/angle/euler + + { + QuatType rotation = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type axis; + FloatType angle; + quat_to_axis_angle(rotation, axis, angle); + CHECK(vector_all_near_equal3(axis, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), threshold)); + CHECK(vector_all_near_equal3(quat_get_axis(rotation), vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0)), threshold)); + CHECK(scalar_near_equal(quat_get_angle(rotation), scalar_deg_to_rad(FloatType(90.0)), threshold)); + } + + { + QuatType rotation = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + Vector4Type axis; + FloatType angle; + quat_to_axis_angle(rotation, axis, angle); + QuatType rotation_new = quat_from_axis_angle(axis, angle); + CHECK(quat_near_equal(rotation, rotation_new, threshold)); + } + + { + QuatType rotation = quat_set(FloatType(0.39564531008956383), FloatType(0.044254239301713752), FloatType(0.22768840967675355), FloatType(0.88863059760894492)); + Vector4Type axis_ref = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + axis_ref = quat_mul_vector3(axis_ref, rotation); + FloatType angle_ref = scalar_deg_to_rad(FloatType(57.0)); + QuatType result = quat_from_axis_angle(axis_ref, angle_ref); + Vector4Type axis; + FloatType angle; + quat_to_axis_angle(result, axis, angle); + CHECK(vector_all_near_equal3(axis, axis_ref, threshold)); + CHECK(scalar_near_equal(angle, angle_ref, threshold)); + } + + ////////////////////////////////////////////////////////////////////////// + // Comparisons and masking + + { + const FloatType inf = std::numeric_limits::infinity(); + const FloatType nan = std::numeric_limits::quiet_NaN(); + CHECK(quat_is_finite(identity) == true); + CHECK(quat_is_finite(quat_set(inf, inf, inf, inf)) == false); + CHECK(quat_is_finite(quat_set(inf, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(quat_is_finite(quat_set(FloatType(1.0), FloatType(inf), FloatType(1.0), FloatType(1.0))) == false); + CHECK(quat_is_finite(quat_set(FloatType(1.0), FloatType(1.0), FloatType(inf), FloatType(1.0))) == false); + CHECK(quat_is_finite(quat_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(inf))) == false); + CHECK(quat_is_finite(quat_set(nan, nan, nan, nan)) == false); + CHECK(quat_is_finite(quat_set(nan, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(quat_is_finite(quat_set(FloatType(1.0), FloatType(nan), FloatType(1.0), FloatType(1.0))) == false); + CHECK(quat_is_finite(quat_set(FloatType(1.0), FloatType(1.0), FloatType(nan), FloatType(1.0))) == false); + CHECK(quat_is_finite(quat_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(nan))) == false); + } + + { + QuatType quat0 = quat_set(FloatType(0.39564531008956383), FloatType(0.044254239301713752), FloatType(0.22768840967675355), FloatType(0.88863059760894492)); + FloatType quat_len = quat_length(quat0); + CHECK(scalar_near_equal(quat_len, FloatType(1.0), threshold)); + CHECK(quat_is_normalized(quat0) == true); + CHECK(quat_is_normalized(identity, FloatType(0.0)) == true); + + QuatType quat1 = vector_to_quat(vector_mul(quat_to_vector(quat0), FloatType(1.1))); + CHECK(quat_is_normalized(quat1) == false); + } + + { + CHECK(quat_near_equal(identity, identity, FloatType(0.0)) == true); + CHECK(quat_near_equal(identity, quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(quat_near_equal(identity, quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(quat_near_equal(identity, quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(2.0)), FloatType(0.9999)) == false); + } + + { + CHECK(quat_near_identity(identity, FloatType(0.0)) == true); + CHECK(quat_near_identity(quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(0.9999999)), FloatType(0.001)) == true); + CHECK(quat_near_identity(quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(0.98)), FloatType(0.001)) == false); + } +} + +TEST_CASE("quatf math", "[math][quat]") +{ + test_quat_impl(1.0E-4F); + + const quatf src = quat_set(0.39564531008956383F, 0.044254239301713752F, 0.22768840967675355F, 0.88863059760894492F); + const quatd dst = quat_cast(src); + CHECK(scalar_near_equal(double(quat_get_x(dst)), 0.39564531008956383, 1.0E-6)); + CHECK(scalar_near_equal(double(quat_get_y(dst)), 0.044254239301713752, 1.0E-6)); + CHECK(scalar_near_equal(double(quat_get_z(dst)), 0.22768840967675355, 1.0E-6)); + CHECK(scalar_near_equal(double(quat_get_w(dst)), 0.88863059760894492, 1.0E-6)); +} + +TEST_CASE("quatd math", "[math][quat]") +{ + test_quat_impl(1.0E-6); + + const quatd src = quat_set(0.39564531008956383, 0.044254239301713752, 0.22768840967675355, 0.88863059760894492); + const quatf dst = quat_cast(src); + CHECK(scalar_near_equal(float(quat_get_x(dst)), 0.39564531008956383F, 1.0E-6F)); + CHECK(scalar_near_equal(float(quat_get_y(dst)), 0.044254239301713752F, 1.0E-6F)); + CHECK(scalar_near_equal(float(quat_get_z(dst)), 0.22768840967675355F, 1.0E-6F)); + CHECK(scalar_near_equal(float(quat_get_w(dst)), 0.88863059760894492F, 1.0E-6F)); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_qvv.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_qvv.cpp new file mode 100644 index 00000000..bba9b7b1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_qvv.cpp @@ -0,0 +1,267 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +using namespace rtm; + +template +static void test_qvv_impl(const TransformType& identity, const FloatType threshold) +{ + using QuatType = decltype(TransformType::rotation); + using Vector4Type = decltype(TransformType::translation); + using ScalarType = typename float_traits::scalar; + + { + Vector4Type zero = vector_set(FloatType(0.0)); + Vector4Type one = vector_set(FloatType(1.0)); + QuatType q_identity = quat_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0)); + TransformType tmp = qvv_set(q_identity, zero, one); + CHECK(quat_near_equal(identity.rotation, tmp.rotation, threshold)); + CHECK(vector_all_near_equal3(identity.translation, tmp.translation, threshold)); + CHECK(vector_all_near_equal3(identity.scale, tmp.scale, threshold)); + CHECK(quat_near_equal(q_identity, tmp.rotation, threshold)); + CHECK(vector_all_near_equal3(zero, tmp.translation, threshold)); + CHECK(vector_all_near_equal3(one, tmp.scale, threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + Vector4Type test_scale = vector_set(FloatType(1.2)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + TransformType transform_a = qvv_set(rotation_around_z, x_axis, test_scale); + Vector4Type result = qvv_mul_point3(x_axis, transform_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.2), FloatType(0.0)), threshold)); + result = qvv_mul_point3(y_axis, transform_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-0.2), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + TransformType transform_b = qvv_set(rotation_around_x, y_axis, test_scale); + result = qvv_mul_point3(x_axis, transform_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.2), FloatType(1.0), FloatType(0.0)), threshold)); + result = qvv_mul_point3(y_axis, transform_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(-1.2)), threshold)); + + TransformType transform_ab = qvv_mul(transform_a, transform_b); + TransformType transform_ba = qvv_mul(transform_b, transform_a); + result = qvv_mul_point3(x_axis, transform_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.2), FloatType(1.0), FloatType(-1.44)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3(qvv_mul_point3(x_axis, transform_a), transform_b), threshold)); + result = qvv_mul_point3(y_axis, transform_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-0.24), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3(qvv_mul_point3(y_axis, transform_a), transform_b), threshold)); + result = qvv_mul_point3(x_axis, transform_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-0.2), FloatType(1.44), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3(qvv_mul_point3(x_axis, transform_b), transform_a), threshold)); + result = qvv_mul_point3(y_axis, transform_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(-0.2), FloatType(0.0), FloatType(-1.44)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3(qvv_mul_point3(y_axis, transform_b), transform_a), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type y_axis = vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + TransformType transform_a = qvv_set(rotation_around_z, x_axis, vector_set(FloatType(1.0))); + Vector4Type result = qvv_mul_point3_no_scale(x_axis, transform_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = qvv_mul_point3_no_scale(y_axis, transform_a); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0)), threshold)); + + QuatType rotation_around_x = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0))); + TransformType transform_b = qvv_set(rotation_around_x, y_axis, vector_set(FloatType(1.0))); + result = qvv_mul_point3_no_scale(x_axis, transform_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.0), FloatType(0.0)), threshold)); + result = qvv_mul_point3_no_scale(y_axis, transform_b); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(-1.0)), threshold)); + + TransformType transform_ab = qvv_mul_no_scale(transform_a, transform_b); + TransformType transform_ba = qvv_mul_no_scale(transform_b, transform_a); + result = qvv_mul_point3_no_scale(x_axis, transform_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(1.0), FloatType(1.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3_no_scale(qvv_mul_point3_no_scale(x_axis, transform_a), transform_b), threshold)); + result = qvv_mul_point3_no_scale(y_axis, transform_ab); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3_no_scale(qvv_mul_point3_no_scale(y_axis, transform_a), transform_b), threshold)); + result = qvv_mul_point3_no_scale(x_axis, transform_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3_no_scale(qvv_mul_point3_no_scale(x_axis, transform_b), transform_a), threshold)); + result = qvv_mul_point3_no_scale(y_axis, transform_ba); + CHECK(vector_all_near_equal3(result, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0)), threshold)); + CHECK(vector_all_near_equal3(result, qvv_mul_point3_no_scale(qvv_mul_point3_no_scale(y_axis, transform_b), transform_a), threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + Vector4Type test_scale1 = vector_set(FloatType(1.2)); + Vector4Type test_scale2 = vector_set(FloatType(-1.2)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + TransformType transform_a = qvv_set(rotation_around_z, x_axis, test_scale1); + TransformType transform_b = qvv_inverse(transform_a); + TransformType transform_ab = qvv_mul(transform_a, transform_b); + CHECK(quat_near_equal(identity.rotation, transform_ab.rotation, threshold)); + CHECK(vector_all_near_equal3(identity.translation, transform_ab.translation, threshold)); + CHECK(vector_all_near_equal3(identity.scale, transform_ab.scale, threshold)); + + transform_a = qvv_set(rotation_around_z, x_axis, test_scale2); + transform_b = qvv_inverse(transform_a); + transform_ab = qvv_mul(transform_a, transform_b); + CHECK(quat_near_equal(identity.rotation, transform_ab.rotation, threshold)); + CHECK(vector_all_near_equal3(identity.translation, transform_ab.translation, threshold)); + CHECK(vector_all_near_equal3(identity.scale, transform_ab.scale, threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + TransformType transform_a = qvv_set(rotation_around_z, x_axis, vector_set(FloatType(1.0))); + TransformType transform_b = qvv_inverse_no_scale(transform_a); + TransformType transform_ab = qvv_mul_no_scale(transform_a, transform_b); + CHECK(quat_near_equal(identity.rotation, transform_ab.rotation, threshold)); + CHECK(vector_all_near_equal3(identity.translation, transform_ab.translation, threshold)); + CHECK(vector_all_near_equal3(identity.scale, transform_ab.scale, threshold)); + } + + { + Vector4Type x_axis = vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0)); + QuatType rotation_around_z = quat_from_euler(scalar_deg_to_rad(FloatType(0.0)), scalar_deg_to_rad(FloatType(90.0)), scalar_deg_to_rad(FloatType(0.0))); + TransformType transform_a = qvv_set(rotation_around_z, x_axis, vector_set(FloatType(1.0))); + CHECK(quat_is_normalized(qvv_normalize(transform_a).rotation, threshold)); + + QuatType quat = quat_set(FloatType(-0.001138), FloatType(0.91623), FloatType(-1.624598), FloatType(0.715671)); + TransformType transform_b = qvv_set(quat, x_axis, vector_set(FloatType(1.0))); + CHECK(!quat_is_normalized(transform_b.rotation, threshold)); + CHECK(quat_is_normalized(qvv_normalize(transform_b).rotation, threshold)); + } + + { + FloatType alpha = FloatType(0.33); + ScalarType alpha_s = scalar_set(alpha); + QuatType quat0 = quat_normalize(quat_from_euler(scalar_deg_to_rad(FloatType(30.0)), scalar_deg_to_rad(FloatType(-45.0)), scalar_deg_to_rad(FloatType(90.0)))); + QuatType quat1 = quat_normalize(quat_from_euler(scalar_deg_to_rad(FloatType(45.0)), scalar_deg_to_rad(FloatType(60.0)), scalar_deg_to_rad(FloatType(120.0)))); + + QuatType quat_ref_lerp = quat_lerp(quat0, quat1, alpha); + QuatType quat_ref_lerp_s = quat_lerp(quat0, quat1, alpha_s); + QuatType quat_ref_slerp = quat_slerp(quat0, quat1, alpha); + QuatType quat_ref_slerp_s = quat_slerp(quat0, quat1, alpha_s); + + Vector4Type trans0 = vector_set(FloatType(-0.001138), FloatType(0.91623), FloatType(-1.624598)); + Vector4Type trans1 = vector_set(FloatType(-0.001138), FloatType(0.91623), FloatType(-1.624598)); + + Vector4Type trans_ref = vector_lerp(trans0, trans1, alpha); + Vector4Type trans_ref_s = vector_lerp(trans0, trans1, alpha_s); + + Vector4Type scale0 = vector_set(FloatType(-1.915), FloatType(0.23656), FloatType(-3.7811)); + Vector4Type scale1 = vector_set(FloatType(-0.2113), FloatType(12.22335), FloatType(-1.7261)); + + Vector4Type scale_ref = vector_lerp(scale0, scale1, alpha); + Vector4Type scale_ref_s = vector_lerp(scale0, scale1, alpha_s); + + TransformType transform0 = qvv_set(quat0, trans0, scale0); + TransformType transform1 = qvv_set(quat1, trans1, scale1); + + TransformType transform_ref_lerp = qvv_set(quat_ref_lerp, trans_ref, scale_ref); + TransformType transform_ref_slerp = qvv_set(quat_ref_slerp, trans_ref, scale_ref); + + TransformType transform_ref_lerp_s = qvv_set(quat_ref_lerp_s, trans_ref_s, scale_ref_s); + TransformType transform_ref_slerp_s = qvv_set(quat_ref_slerp_s, trans_ref_s, scale_ref_s); + + TransformType transform_lerp = qvv_lerp(transform0, transform1, alpha); + TransformType transform_lerp_s = qvv_lerp(transform0, transform1, alpha_s); + + TransformType transform_slerp = qvv_slerp(transform0, transform1, alpha); + TransformType transform_slerp_s = qvv_slerp(transform0, transform1, alpha_s); + + TransformType transform_lerp_no_scale = qvv_lerp_no_scale(transform0, transform1, alpha); + TransformType transform_lerp_no_scale_s = qvv_lerp_no_scale(transform0, transform1, alpha_s); + + TransformType transform_slerp_no_scale = qvv_slerp_no_scale(transform0, transform1, alpha); + TransformType transform_slerp_no_scale_s = qvv_slerp_no_scale(transform0, transform1, alpha_s); + + CHECK(quat_near_equal(transform_lerp.rotation, transform_ref_lerp.rotation, threshold)); + CHECK(quat_near_equal(transform_lerp_s.rotation, transform_ref_lerp_s.rotation, threshold)); + CHECK(vector_all_near_equal3(transform_lerp.translation, transform_ref_lerp.translation, threshold)); + CHECK(vector_all_near_equal3(transform_lerp_s.translation, transform_ref_lerp_s.translation, threshold)); + CHECK(vector_all_near_equal3(transform_lerp.scale, transform_ref_lerp.scale, threshold)); + CHECK(vector_all_near_equal3(transform_lerp_s.scale, transform_ref_lerp_s.scale, threshold)); + + CHECK(quat_near_equal(transform_slerp.rotation, transform_ref_slerp.rotation, threshold)); + CHECK(quat_near_equal(transform_slerp_s.rotation, transform_ref_slerp_s.rotation, threshold)); + CHECK(vector_all_near_equal3(transform_slerp.translation, transform_ref_slerp.translation, threshold)); + CHECK(vector_all_near_equal3(transform_slerp_s.translation, transform_ref_slerp_s.translation, threshold)); + CHECK(vector_all_near_equal3(transform_slerp.scale, transform_ref_slerp.scale, threshold)); + CHECK(vector_all_near_equal3(transform_slerp_s.scale, transform_ref_slerp_s.scale, threshold)); + + CHECK(quat_near_equal(transform_lerp_no_scale.rotation, transform_ref_lerp.rotation, threshold)); + CHECK(quat_near_equal(transform_lerp_no_scale_s.rotation, transform_ref_lerp_s.rotation, threshold)); + CHECK(vector_all_near_equal3(transform_lerp_no_scale.translation, transform_ref_lerp.translation, threshold)); + CHECK(vector_all_near_equal3(transform_lerp_no_scale_s.translation, transform_ref_lerp_s.translation, threshold)); + CHECK(vector_all_near_equal3(transform_lerp_no_scale.scale, transform0.scale, threshold)); + CHECK(vector_all_near_equal3(transform_lerp_no_scale_s.scale, transform0.scale, threshold)); + + CHECK(quat_near_equal(transform_slerp_no_scale.rotation, transform_ref_slerp.rotation, threshold)); + CHECK(quat_near_equal(transform_slerp_no_scale_s.rotation, transform_ref_slerp_s.rotation, threshold)); + CHECK(vector_all_near_equal3(transform_slerp_no_scale.translation, transform_ref_slerp.translation, threshold)); + CHECK(vector_all_near_equal3(transform_slerp_no_scale_s.translation, transform_ref_slerp_s.translation, threshold)); + CHECK(vector_all_near_equal3(transform_slerp_no_scale.scale, transform0.scale, threshold)); + CHECK(vector_all_near_equal3(transform_slerp_no_scale_s.scale, transform0.scale, threshold)); + } +} + +TEST_CASE("qvvf math", "[math][qvv]") +{ + test_qvv_impl(qvv_identity(), 1.0E-4F); + + const quatf src_rotation = quat_set(0.39564531008956383F, 0.044254239301713752F, 0.22768840967675355F, 0.88863059760894492F); + const vector4f src_translation = vector_set(-2.65F, 2.996113F, 0.68123521F); + const vector4f src_scale = vector_set(1.2F, 0.8F, 2.1F); + const qvvf src = qvv_set(src_rotation, src_translation, src_scale); + const qvvd dst = qvv_cast(src); + CHECK(quat_near_equal(src.rotation, quat_cast(dst.rotation), 1.0E-6F)); + CHECK(vector_all_near_equal3(src.translation, vector_cast(dst.translation), 1.0E-6F)); + CHECK(vector_all_near_equal3(src.scale, vector_cast(dst.scale), 1.0E-6F)); +} + +TEST_CASE("qvvd math", "[math][qvv]") +{ + test_qvv_impl(qvv_identity(), 1.0E-6); + + const quatd src_rotation = quat_set(0.39564531008956383, 0.044254239301713752, 0.22768840967675355, 0.88863059760894492); + const vector4d src_translation = vector_set(-2.65, 2.996113, 0.68123521); + const vector4d src_scale = vector_set(1.2, 0.8, 2.1); + const qvvd src = qvv_set(src_rotation, src_translation, src_scale); + const qvvf dst = qvv_cast(src); + CHECK(quat_near_equal(src.rotation, quat_cast(dst.rotation), 1.0E-6)); + CHECK(vector_all_near_equal3(src.translation, vector_cast(dst.translation), 1.0E-6)); + CHECK(vector_all_near_equal3(src.scale, vector_cast(dst.scale), 1.0E-6)); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_scalar.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_scalar.cpp new file mode 100644 index 00000000..c772fec9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_scalar.cpp @@ -0,0 +1,511 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace rtm; + +template +static void test_scalar_impl(const FloatType threshold, const FloatType trig_threshold) +{ + const FloatType infinity = std::numeric_limits::infinity(); + const FloatType nan = std::numeric_limits::quiet_NaN(); + + CHECK(scalar_cast(scalar_set(FloatType(0.2345))) == FloatType(0.2345)); + + const FloatType value = FloatType(0.2345); + CHECK(scalar_equal(scalar_load(&value), scalar_set(value))); + CHECK(scalar_load(&value) == FloatType(0.2345)); + + FloatType tmp = FloatType(0.0); + scalar_store(FloatType(16.5), &tmp); + CHECK(tmp == FloatType(16.5)); + scalar_store(scalar_set(FloatType(4.5)), &tmp); + CHECK(tmp == FloatType(4.5)); + + CHECK(scalar_floor(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_floor(FloatType(-0.0)) == FloatType(0.0)); + CHECK(scalar_floor(FloatType(0.5)) == FloatType(0.0)); + CHECK(scalar_floor(FloatType(2.5)) == FloatType(2.0)); + CHECK(scalar_floor(FloatType(3.0)) == FloatType(3.0)); + CHECK(scalar_floor(FloatType(-0.5)) == FloatType(-1.0)); + CHECK(scalar_floor(FloatType(-2.5)) == FloatType(-3.0)); + CHECK(scalar_floor(FloatType(-3.0)) == FloatType(-3.0)); + CHECK(scalar_floor(FloatType(infinity)) == FloatType(infinity)); + CHECK(scalar_floor(FloatType(-infinity)) == FloatType(-infinity)); + CHECK(std::isnan(scalar_floor(FloatType(nan)))); + + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(-0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(0.5)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(2.5)))) == FloatType(2.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(3.0)))) == FloatType(3.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(-0.5)))) == FloatType(-1.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(-2.5)))) == FloatType(-3.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(-3.0)))) == FloatType(-3.0)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(infinity)))) == FloatType(infinity)); + CHECK(scalar_cast(scalar_floor(scalar_set(FloatType(-infinity)))) == FloatType(-infinity)); + CHECK(std::isnan(scalar_cast(scalar_floor(scalar_set(FloatType(nan)))))); + + CHECK(scalar_ceil(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_ceil(FloatType(-0.0)) == FloatType(0.0)); + CHECK(scalar_ceil(FloatType(0.5)) == FloatType(1.0)); + CHECK(scalar_ceil(FloatType(2.5)) == FloatType(3.0)); + CHECK(scalar_ceil(FloatType(3.0)) == FloatType(3.0)); + CHECK(scalar_ceil(FloatType(-0.5)) == FloatType(0.0)); + CHECK(scalar_ceil(FloatType(-2.5)) == FloatType(-2.0)); + CHECK(scalar_ceil(FloatType(-3.0)) == FloatType(-3.0)); + CHECK(scalar_ceil(FloatType(infinity)) == FloatType(infinity)); + CHECK(scalar_ceil(FloatType(-infinity)) == FloatType(-infinity)); + CHECK(std::isnan(scalar_ceil(FloatType(nan)))); + + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(-0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(0.5)))) == FloatType(1.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(2.5)))) == FloatType(3.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(3.0)))) == FloatType(3.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(-0.5)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(-2.5)))) == FloatType(-2.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(-3.0)))) == FloatType(-3.0)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(infinity)))) == FloatType(infinity)); + CHECK(scalar_cast(scalar_ceil(scalar_set(FloatType(-infinity)))) == FloatType(-infinity)); + CHECK(std::isnan(scalar_cast(scalar_ceil(scalar_set(FloatType(nan)))))); + + CHECK(scalar_clamp(FloatType(0.5), FloatType(0.0), FloatType(1.0)) == FloatType(0.5)); + CHECK(scalar_clamp(FloatType(-0.5), FloatType(0.0), FloatType(1.0)) == FloatType(0.0)); + CHECK(scalar_clamp(FloatType(1.5), FloatType(0.0), FloatType(1.0)) == FloatType(1.0)); + + CHECK(scalar_cast(scalar_clamp(scalar_set(FloatType(0.5)), scalar_set(FloatType(0.0)), scalar_set(FloatType(1.0)))) == FloatType(0.5)); + CHECK(scalar_cast(scalar_clamp(scalar_set(FloatType(-0.5)), scalar_set(FloatType(0.0)), scalar_set(FloatType(1.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_clamp(scalar_set(FloatType(1.5)), scalar_set(FloatType(0.0)), scalar_set(FloatType(1.0)))) == FloatType(1.0)); + + CHECK(scalar_abs(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_abs(FloatType(2.0)) == FloatType(2.0)); + CHECK(scalar_abs(FloatType(-2.0)) == FloatType(2.0)); + + CHECK(scalar_equal(scalar_abs(scalar_set(FloatType(0.0))), scalar_set(FloatType(0.0)))); + CHECK(scalar_equal(scalar_abs(scalar_set(FloatType(2.0))), scalar_set(FloatType(2.0)))); + CHECK(scalar_equal(scalar_abs(scalar_set(FloatType(-2.0))), scalar_set(FloatType(2.0)))); + + CHECK(scalar_equal(FloatType(1.123), FloatType(1.123)) == true); + CHECK(scalar_equal(FloatType(1.123), FloatType(1.124)) == false); + CHECK(scalar_equal(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.123))) == true); + CHECK(scalar_equal(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.124))) == false); + + CHECK(scalar_near_equal(FloatType(1.0), FloatType(1.0), FloatType(0.00001)) == true); + CHECK(scalar_near_equal(FloatType(1.0), FloatType(1.000001), FloatType(0.00001)) == true); + CHECK(scalar_near_equal(FloatType(1.0), FloatType(0.999999), FloatType(0.00001)) == true); + CHECK(scalar_near_equal(FloatType(1.0), FloatType(1.001), FloatType(0.00001)) == false); + CHECK(scalar_near_equal(FloatType(1.0), FloatType(0.999), FloatType(0.00001)) == false); + + CHECK(scalar_sqrt(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_near_equal(scalar_sqrt(FloatType(0.5)), std::sqrt(FloatType(0.5)), threshold)); + CHECK(scalar_near_equal(scalar_sqrt(FloatType(32.5)), std::sqrt(FloatType(32.5)), threshold)); + CHECK(scalar_equal(scalar_sqrt(FloatType(32.5)), scalar_cast(scalar_sqrt(scalar_set(FloatType(32.5)))))); + + CHECK(scalar_lower_than(FloatType(1.123), FloatType(1.123)) == false); + CHECK(scalar_lower_than(FloatType(1.123), FloatType(1.124)) == true); + CHECK(scalar_lower_than(FloatType(1.124), FloatType(1.123)) == false); + CHECK(scalar_lower_than(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.123))) == false); + CHECK(scalar_lower_than(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.124))) == true); + CHECK(scalar_lower_than(scalar_set(FloatType(1.124)), scalar_set(FloatType(1.123))) == false); + + CHECK(scalar_lower_equal(FloatType(1.123), FloatType(1.123)) == true); + CHECK(scalar_lower_equal(FloatType(1.123), FloatType(1.124)) == true); + CHECK(scalar_lower_equal(FloatType(1.124), FloatType(1.123)) == false); + CHECK(scalar_lower_equal(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.123))) == true); + CHECK(scalar_lower_equal(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.124))) == true); + CHECK(scalar_lower_equal(scalar_set(FloatType(1.124)), scalar_set(FloatType(1.123))) == false); + + CHECK(scalar_greater_than(FloatType(1.123), FloatType(1.123)) == false); + CHECK(scalar_greater_than(FloatType(1.123), FloatType(1.124)) == false); + CHECK(scalar_greater_than(FloatType(1.124), FloatType(1.123)) == true); + CHECK(scalar_greater_than(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.123))) == false); + CHECK(scalar_greater_than(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.124))) == false); + CHECK(scalar_greater_than(scalar_set(FloatType(1.124)), scalar_set(FloatType(1.123))) == true); + + CHECK(scalar_greater_equal(FloatType(1.123), FloatType(1.123)) == true); + CHECK(scalar_greater_equal(FloatType(1.123), FloatType(1.124)) == false); + CHECK(scalar_greater_equal(FloatType(1.124), FloatType(1.123)) == true); + CHECK(scalar_greater_equal(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.123))) == true); + CHECK(scalar_greater_equal(scalar_set(FloatType(1.123)), scalar_set(FloatType(1.124))) == false); + CHECK(scalar_greater_equal(scalar_set(FloatType(1.124)), scalar_set(FloatType(1.123))) == true); + + CHECK(scalar_near_equal(scalar_sqrt_reciprocal(FloatType(0.5)), FloatType(1.0) / std::sqrt(FloatType(0.5)), threshold)); + CHECK(scalar_near_equal(scalar_sqrt_reciprocal(FloatType(32.5)), FloatType(1.0) / std::sqrt(FloatType(32.5)), threshold)); + + CHECK(scalar_near_equal(scalar_cast(scalar_sqrt_reciprocal(scalar_set(FloatType(0.5)))), FloatType(1.0) / std::sqrt(FloatType(0.5)), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_sqrt_reciprocal(scalar_set(FloatType(32.5)))), FloatType(1.0) / std::sqrt(FloatType(32.5)), threshold)); + + CHECK(scalar_near_equal(scalar_reciprocal(FloatType(0.5)), FloatType(1.0 / 0.5), threshold)); + CHECK(scalar_near_equal(scalar_reciprocal(FloatType(32.5)), FloatType(1.0 / 32.5), threshold)); + CHECK(scalar_near_equal(scalar_reciprocal(FloatType(-0.5)), FloatType(1.0 / -0.5), threshold)); + CHECK(scalar_near_equal(scalar_reciprocal(FloatType(-32.5)), FloatType(1.0 / -32.5), threshold)); + + CHECK(scalar_near_equal(scalar_cast(scalar_reciprocal(scalar_set(FloatType(0.5)))), FloatType(1.0 / 0.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_reciprocal(scalar_set(FloatType(32.5)))), FloatType(1.0 / 32.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_reciprocal(scalar_set(FloatType(-0.5)))), FloatType(1.0 / -0.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_reciprocal(scalar_set(FloatType(-32.5)))), FloatType(1.0 / -32.5), threshold)); + + CHECK(scalar_near_equal(scalar_add(FloatType(-0.5), FloatType(1.0)), FloatType(-0.5) + FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_add(FloatType(1.0), FloatType(-0.5)), FloatType(1.0) + FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_add(FloatType(1.0), FloatType(1.0)), FloatType(1.0) + FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_cast(scalar_add(scalar_set(FloatType(-0.5)), scalar_set(FloatType(1.0)))), FloatType(-0.5) + FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_add(scalar_set(FloatType(1.0)), scalar_set(FloatType(-0.5)))), FloatType(1.0) + FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_add(scalar_set(FloatType(1.0)), scalar_set(FloatType(1.0)))), FloatType(1.0) + FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_sub(FloatType(-0.5), FloatType(1.0)), FloatType(-0.5) - FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_sub(FloatType(1.0), FloatType(-0.5)), FloatType(1.0) - FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_sub(FloatType(1.0), FloatType(1.0)), FloatType(1.0) - FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_cast(scalar_sub(scalar_set(FloatType(-0.5)), scalar_set(FloatType(1.0)))), FloatType(-0.5) - FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_sub(scalar_set(FloatType(1.0)), scalar_set(FloatType(-0.5)))), FloatType(1.0) - FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_sub(scalar_set(FloatType(1.0)), scalar_set(FloatType(1.0)))), FloatType(1.0) - FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_mul(FloatType(-0.5), FloatType(1.0)), FloatType(-0.5) * FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_mul(FloatType(1.0), FloatType(-0.5)), FloatType(1.0) * FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_mul(FloatType(1.0), FloatType(1.0)), FloatType(1.0) * FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_cast(scalar_mul(scalar_set(FloatType(-0.5)), scalar_set(FloatType(1.0)))), FloatType(-0.5) * FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_mul(scalar_set(FloatType(1.0)), scalar_set(FloatType(-0.5)))), FloatType(1.0) * FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_mul(scalar_set(FloatType(1.0)), scalar_set(FloatType(1.0)))), FloatType(1.0) * FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_div(FloatType(-0.5), FloatType(1.0)), FloatType(-0.5) / FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_div(FloatType(1.0), FloatType(-0.5)), FloatType(1.0) / FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_div(FloatType(1.0), FloatType(1.0)), FloatType(1.0) / FloatType(1.0), threshold)); + + CHECK(scalar_near_equal(scalar_cast(scalar_div(scalar_set(FloatType(-0.5)), scalar_set(FloatType(1.0)))), FloatType(-0.5) / FloatType(1.0), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_div(scalar_set(FloatType(1.0)), scalar_set(FloatType(-0.5)))), FloatType(1.0) / FloatType(-0.5), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_div(scalar_set(FloatType(1.0)), scalar_set(FloatType(1.0)))), FloatType(1.0) / FloatType(1.0), threshold)); + + const FloatType values[] = { FloatType(-1.0 / 3.0), FloatType(0.0341), FloatType(-0.54132) }; + CHECK(scalar_near_equal(scalar_mul_add(values[0], values[1], values[2]), (values[0] * values[1]) + values[2], threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_mul_add(scalar_set(values[0]), scalar_set(values[1]), scalar_set(values[2]))), (values[0] * values[1]) + values[2], threshold)); + + CHECK(scalar_near_equal(scalar_neg_mul_sub(values[0], values[1], values[2]), values[2] - (values[0] * values[1]), threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_neg_mul_sub(scalar_set(values[0]), scalar_set(values[1]), scalar_set(values[2]))), values[2] - (values[0] * values[1]), threshold)); + + CHECK(scalar_near_equal(scalar_lerp(values[0], values[1], values[2]), ((values[1] - values[0]) * values[2]) + values[0], threshold)); + CHECK(scalar_near_equal(scalar_lerp(scalar_set(values[0]), scalar_set(values[1]), scalar_set(values[2])), scalar_set(((values[1] - values[0]) * values[2]) + values[0]), scalar_set(threshold))); + + // Lerp must be stable and return exactly the start when the interpolation alpha is 0.0 and exactly the end when 1.0 + CHECK(scalar_near_equal(scalar_lerp(values[0], values[1], FloatType(0.0)), values[0], FloatType(0.0))); + CHECK(scalar_near_equal(scalar_lerp(values[0], values[1], FloatType(1.0)), values[1], FloatType(0.0))); + CHECK(scalar_near_equal(scalar_lerp(scalar_set(values[0]), scalar_set(values[1]), scalar_set(FloatType(0.0))), scalar_set(values[0]), scalar_set(FloatType(0.0)))); + CHECK(scalar_near_equal(scalar_lerp(scalar_set(values[0]), scalar_set(values[1]), scalar_set(FloatType(1.0))), scalar_set(values[1]), scalar_set(FloatType(0.0)))); + + using Vector4Type = typename float_traits::vector4; + + const FloatType half_pi = FloatType(rtm::constants::half_pi()); + const FloatType pi = FloatType(rtm::constants::pi()); + + const FloatType angles[] = + { + FloatType(0.0), FloatType(-0.0), + pi, -pi, + half_pi, -half_pi, + half_pi * FloatType(0.5), -half_pi * FloatType(0.5), + half_pi * FloatType(0.25), -half_pi * FloatType(0.25), + FloatType(0.5), FloatType(-0.5), + FloatType(32.5), FloatType(-32.5), + }; + + for (const FloatType angle : angles) + { + INFO("The angle is " << angle); + + { + const FloatType ref_sin = std::sin(angle); + const FloatType ref_cos = std::cos(angle); + + INFO("The reference sin(angle) is " << ref_sin); + INFO("The reference cos(angle) is " << ref_cos); + + CHECK(scalar_near_equal(scalar_sin(angle), ref_sin, trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_sin(scalar_set(angle))), ref_sin, trig_threshold)); + CHECK(scalar_near_equal(scalar_cos(angle), ref_cos, trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_cos(scalar_set(angle))), ref_cos, trig_threshold)); + + FloatType sin_result; + FloatType cos_result; + scalar_sincos(angle, sin_result, cos_result); + CHECK(scalar_near_equal(sin_result, ref_sin, trig_threshold)); + CHECK(scalar_near_equal(cos_result, ref_cos, trig_threshold)); + + Vector4Type sincos0 = scalar_sincos(angle); + CHECK(scalar_near_equal(vector_get_x(sincos0), ref_sin, trig_threshold)); + CHECK(scalar_near_equal(vector_get_y(sincos0), ref_cos, trig_threshold)); + + Vector4Type sincos1 = scalar_sincos(scalar_set(angle)); + CHECK(scalar_near_equal(vector_get_x(sincos1), ref_sin, trig_threshold)); + CHECK(scalar_near_equal(vector_get_y(sincos1), ref_cos, trig_threshold)); + + CHECK(scalar_near_equal(scalar_asin(ref_sin), std::asin(ref_sin), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_asin(scalar_set(ref_sin))), std::asin(ref_sin), trig_threshold)); + CHECK(scalar_near_equal(scalar_acos(ref_cos), std::acos(ref_cos), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_acos(scalar_set(ref_cos))), std::acos(ref_cos), trig_threshold)); + } + + { + const FloatType ref_tan = std::tan(angle); + const FloatType rtm_tan = scalar_tan(angle); + + INFO("The reference tan(angle) is " << ref_tan); + INFO("The RTM tan(angle) is " << rtm_tan); + + // For +-PI/2, we only test that the value is really large or really small + if (scalar_abs(angle) == FloatType(half_pi)) + { + CHECK(scalar_greater_than(scalar_abs(rtm_tan), FloatType(1.0e6))); + CHECK(scalar_greater_than(scalar_cast(scalar_abs(scalar_tan(scalar_set(angle)))), FloatType(1.0e6))); + } + else + { + CHECK(scalar_near_equal(rtm_tan, ref_tan, trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_tan(scalar_set(angle))), ref_tan, trig_threshold)); + } + } + } + + const FloatType angles_acos[] = { FloatType(-1.0), FloatType(-0.75), FloatType(-0.5), FloatType(-0.25), FloatType(0.0), FloatType(0.25), FloatType(0.5), FloatType(0.75), FloatType(1.0) }; + for (const FloatType angle : angles_acos) + { + CHECK(scalar_near_equal(scalar_acos(angle), std::acos(angle), trig_threshold)); + } + + const FloatType angles_atan[] = { FloatType(-10.0), FloatType(-5.0), FloatType(-0.5), FloatType(-0.25), FloatType(0.0), FloatType(0.25), FloatType(0.5), FloatType(0.75), FloatType(81.0) }; + for (const FloatType angle : angles_atan) + { + CHECK(scalar_near_equal(scalar_atan(angle), std::atan(angle), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan(scalar_set(angle))), std::atan(angle), trig_threshold)); + } + + CHECK(scalar_near_equal(scalar_atan2(FloatType(-2.0), FloatType(-2.0)), std::atan2(FloatType(-2.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(-1.0), FloatType(-2.0)), std::atan2(FloatType(-1.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(-2.0), FloatType(-1.0)), std::atan2(FloatType(-2.0), FloatType(-1.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(2.0), FloatType(2.0)), std::atan2(FloatType(2.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(1.0), FloatType(2.0)), std::atan2(FloatType(1.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(2.0), FloatType(1.0)), std::atan2(FloatType(2.0), FloatType(1.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(2.0), FloatType(0.0)), std::atan2(FloatType(2.0), FloatType(0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(-2.0), FloatType(0.0)), std::atan2(FloatType(-2.0), FloatType(0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(2.0), FloatType(-0.0)), std::atan2(FloatType(2.0), FloatType(-0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(-2.0), FloatType(-0.0)), std::atan2(FloatType(-2.0), FloatType(-0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(0.0), FloatType(2.0)), std::atan2(FloatType(0.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(0.0), FloatType(-2.0)), std::atan2(FloatType(0.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(-0.0), FloatType(2.0)), std::atan2(FloatType(-0.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_atan2(FloatType(-0.0), FloatType(-2.0)), std::atan2(FloatType(-0.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_atan2(FloatType(0.0), FloatType(0.0)) == FloatType(0.0)); + + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-2.0)), scalar_set(FloatType(-2.0)))), std::atan2(FloatType(-2.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-1.0)), scalar_set(FloatType(-2.0)))), std::atan2(FloatType(-1.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-2.0)), scalar_set(FloatType(-1.0)))), std::atan2(FloatType(-2.0), FloatType(-1.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(2.0)), scalar_set(FloatType(2.0)))), std::atan2(FloatType(2.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(1.0)), scalar_set(FloatType(2.0)))), std::atan2(FloatType(1.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(2.0)), scalar_set(FloatType(1.0)))), std::atan2(FloatType(2.0), FloatType(1.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(2.0)), scalar_set(FloatType(0.0)))), std::atan2(FloatType(2.0), FloatType(0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-2.0)), scalar_set(FloatType(0.0)))), std::atan2(FloatType(-2.0), FloatType(0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(2.0)), scalar_set(FloatType(-0.0)))), std::atan2(FloatType(2.0), FloatType(-0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-2.0)), scalar_set(FloatType(-0.0)))), std::atan2(FloatType(-2.0), FloatType(-0.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.0)))), std::atan2(FloatType(0.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(0.0)), scalar_set(FloatType(-2.0)))), std::atan2(FloatType(0.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-0.0)), scalar_set(FloatType(2.0)))), std::atan2(FloatType(-0.0), FloatType(2.0)), trig_threshold)); + CHECK(scalar_near_equal(scalar_cast(scalar_atan2(scalar_set(FloatType(-0.0)), scalar_set(FloatType(-2.0)))), std::atan2(FloatType(-0.0), FloatType(-2.0)), trig_threshold)); + CHECK(scalar_cast(scalar_atan2(scalar_set(FloatType(0.0)), scalar_set(FloatType(0.0)))) == FloatType(0.0)); + + CHECK(scalar_min(FloatType(-0.5), FloatType(1.0)) == FloatType(-0.5)); + CHECK(scalar_min(FloatType(1.0), FloatType(-0.5)) == FloatType(-0.5)); + CHECK(scalar_min(FloatType(1.0), FloatType(1.0)) == FloatType(1.0)); + + CHECK(scalar_equal(scalar_min(scalar_set(FloatType(-0.5)), scalar_set(FloatType(1.0))), scalar_set(FloatType(-0.5)))); + CHECK(scalar_equal(scalar_min(scalar_set(FloatType(1.0)), scalar_set(FloatType(-0.5))), scalar_set(FloatType(-0.5)))); + CHECK(scalar_equal(scalar_min(scalar_set(FloatType(1.0)), scalar_set(FloatType(1.0))), scalar_set(FloatType(1.0)))); + + CHECK(scalar_max(FloatType(-0.5), FloatType(1.0)) == FloatType(1.0)); + CHECK(scalar_max(FloatType(1.0), FloatType(-0.5)) == FloatType(1.0)); + CHECK(scalar_max(FloatType(1.0), FloatType(1.0)) == FloatType(1.0)); + + CHECK(scalar_equal(scalar_max(scalar_set(FloatType(-0.5)), scalar_set(FloatType(1.0))), scalar_set(FloatType(1.0)))); + CHECK(scalar_equal(scalar_max(scalar_set(FloatType(1.0)), scalar_set(FloatType(-0.5))), scalar_set(FloatType(1.0)))); + CHECK(scalar_equal(scalar_max(scalar_set(FloatType(1.0)), scalar_set(FloatType(1.0))), scalar_set(FloatType(1.0)))); + + CHECK(scalar_is_finite(FloatType(0.0)) == true); + CHECK(scalar_is_finite(FloatType(32.0)) == true); + CHECK(scalar_is_finite(FloatType(-32.0)) == true); + CHECK(scalar_is_finite(std::numeric_limits::infinity()) == false); + CHECK(scalar_is_finite(std::numeric_limits::quiet_NaN()) == false); + CHECK(scalar_is_finite(std::numeric_limits::signaling_NaN()) == false); + + CHECK(scalar_is_finite(scalar_set(FloatType(0.0))) == true); + CHECK(scalar_is_finite(scalar_set(FloatType(32.0))) == true); + CHECK(scalar_is_finite(scalar_set(FloatType(-32.0))) == true); + CHECK(scalar_is_finite(scalar_set(std::numeric_limits::infinity())) == false); + CHECK(scalar_is_finite(scalar_set(std::numeric_limits::quiet_NaN())) == false); + CHECK(scalar_is_finite(scalar_set(std::numeric_limits::signaling_NaN())) == false); + + CHECK(scalar_round_symmetric(FloatType(-1.75)) == FloatType(-2.0)); + CHECK(scalar_round_symmetric(FloatType(-1.5)) == FloatType(-2.0)); + CHECK(scalar_round_symmetric(FloatType(-1.4999)) == FloatType(-1.0)); + CHECK(scalar_round_symmetric(FloatType(-0.5)) == FloatType(-1.0)); + CHECK(scalar_round_symmetric(FloatType(-0.4999)) == FloatType(0.0)); + CHECK(scalar_round_symmetric(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_round_symmetric(FloatType(-0.0)) == FloatType(0.0)); + CHECK(scalar_round_symmetric(FloatType(0.4999)) == FloatType(0.0)); + CHECK(scalar_round_symmetric(FloatType(0.5)) == FloatType(1.0)); + CHECK(scalar_round_symmetric(FloatType(1.4999)) == FloatType(1.0)); + CHECK(scalar_round_symmetric(FloatType(1.5)) == FloatType(2.0)); + CHECK(scalar_round_symmetric(FloatType(1.75)) == FloatType(2.0)); + CHECK(scalar_round_symmetric(infinity) == FloatType(infinity)); + CHECK(scalar_round_symmetric(-infinity) == FloatType(-infinity)); + CHECK(std::isnan(scalar_round_symmetric(nan))); + + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(-1.75)))) == FloatType(-2.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(-1.5)))) == FloatType(-2.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(-1.4999)))) == FloatType(-1.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(-0.5)))) == FloatType(-1.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(-0.4999)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(-0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(0.4999)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(0.5)))) == FloatType(1.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(1.4999)))) == FloatType(1.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(1.5)))) == FloatType(2.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(FloatType(1.75)))) == FloatType(2.0)); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(infinity))) == infinity); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(-infinity))) == -infinity); + CHECK(std::isnan(scalar_cast(scalar_round_symmetric(scalar_set(nan))))); + + CHECK(scalar_round_bankers(FloatType(-2.5)) == FloatType(-2.0)); + CHECK(scalar_round_bankers(FloatType(-1.75)) == FloatType(-2.0)); + CHECK(scalar_round_bankers(FloatType(-1.5)) == FloatType(-2.0)); + CHECK(scalar_round_bankers(FloatType(-1.4999)) == FloatType(-1.0)); + CHECK(scalar_round_bankers(FloatType(-0.5)) == FloatType(0.0)); + CHECK(scalar_round_bankers(FloatType(-0.4999)) == FloatType(0.0)); + CHECK(scalar_round_bankers(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_round_bankers(FloatType(-0.0)) == FloatType(0.0)); + CHECK(scalar_round_bankers(FloatType(0.4999)) == FloatType(0.0)); + CHECK(scalar_round_bankers(FloatType(0.5)) == FloatType(0.0)); + CHECK(scalar_round_bankers(FloatType(1.4999)) == FloatType(1.0)); + CHECK(scalar_round_bankers(FloatType(1.5)) == FloatType(2.0)); + CHECK(scalar_round_bankers(FloatType(1.75)) == FloatType(2.0)); + CHECK(scalar_round_bankers(FloatType(2.5)) == FloatType(2.0)); + + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-2.5)))) == FloatType(-2.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-1.75)))) == FloatType(-2.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-1.5)))) == FloatType(-2.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-1.4999)))) == FloatType(-1.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-0.5)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-0.4999)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(-0.0)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(0.4999)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(0.5)))) == FloatType(0.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(1.4999)))) == FloatType(1.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(1.5)))) == FloatType(2.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(1.75)))) == FloatType(2.0)); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(FloatType(2.5)))) == FloatType(2.0)); + + CHECK(scalar_fraction(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_fraction(FloatType(-0.0)) == FloatType(0.0)); + CHECK(scalar_fraction(FloatType(1.0)) == FloatType(0.0)); + CHECK(scalar_fraction(FloatType(-1.0)) == FloatType(0.0)); + CHECK(scalar_near_equal(scalar_fraction(FloatType(0.25)), FloatType(0.25), threshold)); + CHECK(scalar_near_equal(scalar_fraction(FloatType(0.5)), FloatType(0.5), threshold)); + CHECK(scalar_near_equal(scalar_fraction(FloatType(0.75)), FloatType(0.75), threshold)); + + CHECK(scalar_deg_to_rad(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_near_equal(scalar_deg_to_rad(FloatType(90.0)), FloatType(rtm::constants::half_pi()), threshold)); + CHECK(scalar_near_equal(scalar_deg_to_rad(FloatType(-90.0)), FloatType(-rtm::constants::half_pi()), threshold)); + CHECK(scalar_near_equal(scalar_deg_to_rad(FloatType(180.0)), FloatType(rtm::constants::pi()), threshold)); + CHECK(scalar_near_equal(scalar_deg_to_rad(FloatType(-180.0)), FloatType(-rtm::constants::pi()), threshold)); + CHECK(scalar_near_equal(scalar_deg_to_rad(FloatType(360.0)), FloatType(rtm::constants::two_pi()), threshold)); + CHECK(scalar_near_equal(scalar_deg_to_rad(FloatType(-360.0)), FloatType(-rtm::constants::two_pi()), threshold)); + + CHECK(scalar_rad_to_deg(FloatType(0.0)) == FloatType(0.0)); + CHECK(scalar_near_equal(scalar_rad_to_deg(FloatType(rtm::constants::half_pi())), FloatType(90.0), threshold)); + CHECK(scalar_near_equal(scalar_rad_to_deg(FloatType(-rtm::constants::half_pi())), FloatType(-90.0), threshold)); + CHECK(scalar_near_equal(scalar_rad_to_deg(FloatType(rtm::constants::pi())), FloatType(180.0), threshold)); + CHECK(scalar_near_equal(scalar_rad_to_deg(FloatType(-rtm::constants::pi())), FloatType(-180.0), threshold)); + CHECK(scalar_near_equal(scalar_rad_to_deg(FloatType(rtm::constants::two_pi())), FloatType(360.0), threshold)); + CHECK(scalar_near_equal(scalar_rad_to_deg(FloatType(-rtm::constants::two_pi())), FloatType(-360.0), threshold)); +} + +TEST_CASE("scalarf math", "[math][scalar]") +{ + test_scalar_impl(1.0E-6F, 1.0E-5F); + + CHECK(scalar_floor(1073741824.5F) == 1073741824.0F); + CHECK(scalar_floor(-1073741824.5F) == -1073741824.0F); + CHECK(scalar_cast(scalar_floor(scalar_set(1073741824.5F))) == 1073741824.0F); + CHECK(scalar_cast(scalar_floor(scalar_set(-1073741824.5F))) == -1073741824.0F); + + CHECK(scalar_ceil(1073741824.5F) == 1073741824.0F); + CHECK(scalar_ceil(-1073741824.5F) == -1073741824.0F); + CHECK(scalar_cast(scalar_ceil(scalar_set(1073741824.5F))) == 1073741824.0F); + CHECK(scalar_cast(scalar_ceil(scalar_set(-1073741824.5F))) == -1073741824.0F); + + CHECK(scalar_round_symmetric(1073741824.5F) == 1073741824.0F); + CHECK(scalar_round_symmetric(-1073741824.5F) == -1073741824.0F); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(1073741824.5F))) == 1073741824.0F); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(-1073741824.5F))) == -1073741824.0F); + + CHECK(scalar_round_bankers(1073741824.5F) == 1073741824.0F); + CHECK(scalar_round_bankers(-1073741824.5F) == -1073741824.0F); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(1073741824.5F))) == 1073741824.0F); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(-1073741824.5F))) == -1073741824.0F); +} + +TEST_CASE("scalard math", "[math][scalar]") +{ + test_scalar_impl(1.0E-9, 1.0E-9); + + CHECK(scalar_floor(36028797018963968.5) == 36028797018963968.5); + CHECK(scalar_floor(-36028797018963968.5) == -36028797018963968.5); + CHECK(scalar_cast(scalar_floor(scalar_set(36028797018963968.5))) == 36028797018963968.5); + CHECK(scalar_cast(scalar_floor(scalar_set(-36028797018963968.5))) == -36028797018963968.5); + + CHECK(scalar_ceil(36028797018963968.5) == 36028797018963968.5); + CHECK(scalar_ceil(-36028797018963968.5) == -36028797018963968.5); + CHECK(scalar_cast(scalar_ceil(scalar_set(36028797018963968.5))) == 36028797018963968.5); + CHECK(scalar_cast(scalar_ceil(scalar_set(-36028797018963968.5))) == -36028797018963968.5); + + CHECK(scalar_round_symmetric(36028797018963968.5) == 36028797018963968.5); + CHECK(scalar_round_symmetric(-36028797018963968.5) == -36028797018963968.5); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(36028797018963968.5))) == 36028797018963968.5); + CHECK(scalar_cast(scalar_round_symmetric(scalar_set(-36028797018963968.5))) == -36028797018963968.5); + + CHECK(scalar_round_bankers(36028797018963968.5) == 36028797018963968.5); + CHECK(scalar_round_bankers(-36028797018963968.5) == -36028797018963968.5); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(36028797018963968.5))) == 36028797018963968.5); + CHECK(scalar_cast(scalar_round_bankers(scalar_set(-36028797018963968.5))) == -36028797018963968.5); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4_impl.h new file mode 100644 index 00000000..c28d3620 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4_impl.h @@ -0,0 +1,1336 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace rtm; + +template +inline const FloatType* vector_as_float_ptr_raw(const Vector4Type& input); + +template<> +inline const float* vector_as_float_ptr_raw(const vector4f& input) +{ + return vector_to_pointer(input); +} + +template<> +inline const double* vector_as_float_ptr_raw(const vector4d& input) +{ + return vector_to_pointer(input); +} + +template +inline Vector4Type scalar_cross3(const Vector4Type& lhs, const Vector4Type& rhs) +{ + const FloatType lhs_x = vector_get_x(lhs); + const FloatType lhs_y = vector_get_y(lhs); + const FloatType lhs_z = vector_get_z(lhs); + const FloatType rhs_x = vector_get_x(rhs); + const FloatType rhs_y = vector_get_y(rhs); + const FloatType rhs_z = vector_get_z(rhs); + return vector_set((lhs_y * rhs_z) - (lhs_z * rhs_y), (lhs_z * rhs_x) - (lhs_x * rhs_z), (lhs_x * rhs_y) - (lhs_y * rhs_x)); +} + +template +inline FloatType scalar_dot(const Vector4Type& lhs, const Vector4Type& rhs) +{ + const FloatType lhs_x = vector_get_x(lhs); + const FloatType lhs_y = vector_get_y(lhs); + const FloatType lhs_z = vector_get_z(lhs); + const FloatType lhs_w = vector_get_w(lhs); + const FloatType rhs_x = vector_get_x(rhs); + const FloatType rhs_y = vector_get_y(rhs); + const FloatType rhs_z = vector_get_z(rhs); + const FloatType rhs_w = vector_get_w(rhs); + return (lhs_x * rhs_x) + (lhs_y * rhs_y) + (lhs_z * rhs_z) + (lhs_w * rhs_w); +} + +template +inline FloatType scalar_dot3(const Vector4Type& lhs, const Vector4Type& rhs) +{ + const FloatType lhs_x = vector_get_x(lhs); + const FloatType lhs_y = vector_get_y(lhs); + const FloatType lhs_z = vector_get_z(lhs); + const FloatType rhs_x = vector_get_x(rhs); + const FloatType rhs_y = vector_get_y(rhs); + const FloatType rhs_z = vector_get_z(rhs); + return (lhs_x * rhs_x) + (lhs_y * rhs_y) + (lhs_z * rhs_z); +} + +template +inline Vector4Type scalar_normalize3(const Vector4Type& input, const Vector4Type& fallback, FloatType threshold) +{ + FloatType len_sq = scalar_dot3(input, input); + if (len_sq >= threshold) + { + FloatType inv_len = rtm::scalar_sqrt_reciprocal(len_sq); + return vector_set(vector_get_x(input) * inv_len, vector_get_y(input) * inv_len, vector_get_z(input) * inv_len); + } + else + return fallback; +} + +template +void test_vector4_getset_impl() +{ + using QuatType = typename float_traits::quat; + using Vector4Type = typename float_traits::vector4; + using ScalarType = typename float_traits::scalar; + using Float2Type = typename float_traits::float2; + using Float3Type = typename float_traits::float3; + using Float4Type = typename float_traits::float4; + + const Vector4Type zero = vector_zero(); + const QuatType identity = quat_identity(); + + struct alignas(16) Tmp + { + uint8_t padding0[8]; // 8 | 8 + FloatType values[4]; // 24 | 40 + uint8_t padding1[8]; // 32 | 48 + }; + + Tmp tmp = { { 0 }, { FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0) }, {} }; + alignas(16) uint8_t buffer[64]; + + const FloatType test_value0_flt[4] = { FloatType(2.0), FloatType(9.34), FloatType(-54.12), FloatType(6000.0) }; + const FloatType test_value1_flt[4] = { FloatType(0.75), FloatType(-4.52), FloatType(44.68), FloatType(-54225.0) }; + const FloatType test_value3_flt[4] = { FloatType(2.0), FloatType(-9.34), FloatType(54.12), FloatType(6000.1) }; + const Vector4Type test_value0 = vector_set(test_value0_flt[0], test_value0_flt[1], test_value0_flt[2], test_value0_flt[3]); + const Vector4Type test_value1 = vector_set(test_value1_flt[0], test_value1_flt[1], test_value1_flt[2], test_value1_flt[3]); + const Vector4Type test_value3 = vector_set(test_value3_flt[0], test_value3_flt[1], test_value3_flt[2], test_value3_flt[3]); + + ////////////////////////////////////////////////////////////////////////// + // Setters, getters, and casts + + CHECK(FloatType(vector_get_x(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(FloatType(vector_get_y(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(FloatType(vector_get_z(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_w(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(scalar_cast(vector_get_x(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(scalar_cast(vector_get_y(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(scalar_cast(vector_get_z(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(scalar_cast(vector_get_w(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(FloatType(vector_get_x(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12)), scalar_set(FloatType(10000.0))))) == FloatType(0.0)); + CHECK(FloatType(vector_get_y(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12)), scalar_set(FloatType(10000.0))))) == FloatType(2.34)); + CHECK(FloatType(vector_get_z(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12)), scalar_set(FloatType(10000.0))))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_w(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12)), scalar_set(FloatType(10000.0))))) == FloatType(10000.0)); + + CHECK(FloatType(vector_get_x(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12)))) == FloatType(0.0)); + CHECK(FloatType(vector_get_y(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12)))) == FloatType(2.34)); + CHECK(FloatType(vector_get_z(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_w(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12)))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12))))) == FloatType(0.0)); + CHECK(FloatType(vector_get_y(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12))))) == FloatType(2.34)); + CHECK(FloatType(vector_get_z(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12))))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_w(vector_set(scalar_set(FloatType(0.0)), scalar_set(FloatType(2.34)), scalar_set(FloatType(-3.12))))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x(vector_set(FloatType(-3.12)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_y(vector_set(FloatType(-3.12)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_z(vector_set(FloatType(-3.12)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_w(vector_set(FloatType(-3.12)))) == FloatType(-3.12)); + + CHECK(FloatType(vector_get_x(vector_set(scalar_set(FloatType(-3.12))))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_y(vector_set(scalar_set(FloatType(-3.12))))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_z(vector_set(scalar_set(FloatType(-3.12))))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_w(vector_set(scalar_set(FloatType(-3.12))))) == FloatType(-3.12)); + + CHECK(FloatType(vector_get_x(zero)) == FloatType(0.0)); + CHECK(FloatType(vector_get_y(zero)) == FloatType(0.0)); + CHECK(FloatType(vector_get_z(zero)) == FloatType(0.0)); + CHECK(FloatType(vector_get_w(zero)) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load(&tmp.values[0]))) == tmp.values[0]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load(&tmp.values[0]))) == tmp.values[1]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load(&tmp.values[0]))) == tmp.values[2]); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load(&tmp.values[0]))) == tmp.values[3]); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load1(&tmp.values[1]))) == tmp.values[1]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load1(&tmp.values[1]))) == FloatType(0.0)); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load1(&tmp.values[1]))) == FloatType(0.0)); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load1(&tmp.values[1]))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load2(&tmp.values[1]))) == tmp.values[1]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load2(&tmp.values[1]))) == tmp.values[2]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load2(&tmp.values[1]))) == FloatType(0.0)); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load2(&tmp.values[1]))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load3(&tmp.values[1]))) == tmp.values[1]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load3(&tmp.values[1]))) == tmp.values[2]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load3(&tmp.values[1]))) == tmp.values[3]); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load3(&tmp.values[1]))) == FloatType(0.0)); + + Float2Type tmpf2 = { tmp.values[0], tmp.values[1] }; + Float3Type tmpf3 = { tmp.values[0], tmp.values[1], tmp.values[2] }; + Float4Type tmpf4 = { tmp.values[0], tmp.values[1], tmp.values[2], tmp.values[3] }; + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load2(&tmpf2))) == tmpf2.x); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load2(&tmpf2))) == tmpf2.y); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load2(&tmpf2))) == FloatType(0.0)); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load2(&tmpf2))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load3(&tmpf3))) == tmpf3.x); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load3(&tmpf3))) == tmpf3.y); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load3(&tmpf3))) == tmpf3.z); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load3(&tmpf3))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load(&tmpf4))) == tmpf4.x); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load(&tmpf4))) == tmpf4.y); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load(&tmpf4))) == tmpf4.z); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load(&tmpf4))) == tmpf4.w); + + std::memcpy(&buffer[1], &tmp.values[0], sizeof(tmp.values)); + CHECK(FloatType(vector_get_x((Vector4Type)vector_load(&buffer[1]))) == tmp.values[0]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load(&buffer[1]))) == tmp.values[1]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load(&buffer[1]))) == tmp.values[2]); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load(&buffer[1]))) == tmp.values[3]); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load1(&buffer[1 + sizeof(FloatType)]))) == tmp.values[1]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load1(&buffer[1 + sizeof(FloatType)]))) == FloatType(0.0)); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load1(&buffer[1 + sizeof(FloatType)]))) == FloatType(0.0)); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load1(&buffer[1 + sizeof(FloatType)]))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load2(&buffer[1 + sizeof(FloatType)]))) == tmp.values[1]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load2(&buffer[1 + sizeof(FloatType)]))) == tmp.values[2]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load2(&buffer[1 + sizeof(FloatType)]))) == FloatType(0.0)); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load2(&buffer[1 + sizeof(FloatType)]))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_load3(&buffer[1 + sizeof(FloatType)]))) == tmp.values[1]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_load3(&buffer[1 + sizeof(FloatType)]))) == tmp.values[2]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_load3(&buffer[1 + sizeof(FloatType)]))) == tmp.values[3]); + CHECK(FloatType(vector_get_w((Vector4Type)vector_load3(&buffer[1 + sizeof(FloatType)]))) == FloatType(0.0)); + + CHECK(FloatType(vector_get_x((Vector4Type)vector_broadcast(&tmp.values[0]))) == tmp.values[0]); + CHECK(FloatType(vector_get_y((Vector4Type)vector_broadcast(&tmp.values[0]))) == tmp.values[0]); + CHECK(FloatType(vector_get_z((Vector4Type)vector_broadcast(&tmp.values[0]))) == tmp.values[0]); + CHECK(FloatType(vector_get_w((Vector4Type)vector_broadcast(&tmp.values[0]))) == tmp.values[0]); + + CHECK(FloatType(vector_get_x(quat_to_vector(identity))) == FloatType(quat_get_x(identity))); + CHECK(FloatType(vector_get_y(quat_to_vector(identity))) == FloatType(quat_get_y(identity))); + CHECK(FloatType(vector_get_z(quat_to_vector(identity))) == FloatType(quat_get_z(identity))); + CHECK(FloatType(vector_get_w(quat_to_vector(identity))) == FloatType(quat_get_w(identity))); + + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(0.0)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(2.34)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(-3.12)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)))) == FloatType(10000.0)); + + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::x)) == FloatType(0.0)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::y)) == FloatType(2.34)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::z)) == FloatType(-3.12)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::w)) == FloatType(10000.0)); + + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::a)) == FloatType(0.0)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::b)) == FloatType(2.34)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::c)) == FloatType(-3.12)); + CHECK(FloatType(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::d)) == FloatType(10000.0)); + + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::x)) == FloatType(0.0)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::y)) == FloatType(2.34)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::z)) == FloatType(-3.12)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::w)) == FloatType(10000.0)); + + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::a)) == FloatType(0.0)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::b)) == FloatType(2.34)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::c)) == FloatType(-3.12)); + CHECK(scalar_cast(vector_get_component(vector_set(FloatType(0.0), FloatType(2.34), FloatType(-3.12), FloatType(10000.0)), mix4::d)) == FloatType(10000.0)); + + CHECK((vector_as_float_ptr_raw(vector_load(&tmp.values[0]))[0] == tmp.values[0])); + CHECK((vector_as_float_ptr_raw(vector_load(&tmp.values[0]))[1] == tmp.values[1])); + CHECK((vector_as_float_ptr_raw(vector_load(&tmp.values[0]))[2] == tmp.values[2])); + CHECK((vector_as_float_ptr_raw(vector_load(&tmp.values[0]))[3] == tmp.values[3])); + + vector_store(test_value0, &tmp.values[0]); + CHECK(FloatType(vector_get_x(test_value0)) == tmp.values[0]); + CHECK(FloatType(vector_get_y(test_value0)) == tmp.values[1]); + CHECK(FloatType(vector_get_z(test_value0)) == tmp.values[2]); + CHECK(FloatType(vector_get_w(test_value0)) == tmp.values[3]); + + vector_store1(test_value0, &tmp.values[0]); + CHECK(FloatType(vector_get_x(test_value0)) == tmp.values[0]); + + vector_store2(test_value0, &tmp.values[0]); + CHECK(FloatType(vector_get_x(test_value0)) == tmp.values[0]); + CHECK(FloatType(vector_get_y(test_value0)) == tmp.values[1]); + + vector_store3(test_value1, &tmp.values[0]); + CHECK(FloatType(vector_get_x(test_value1)) == tmp.values[0]); + CHECK(FloatType(vector_get_y(test_value1)) == tmp.values[1]); + CHECK(FloatType(vector_get_z(test_value1)) == tmp.values[2]); + CHECK(FloatType(vector_get_w(test_value0)) == tmp.values[3]); + + vector_store(test_value1, &buffer[1]); + CHECK(FloatType(vector_get_x(test_value1)) == FloatType(vector_get_x((Vector4Type)vector_load(&buffer[1])))); + CHECK(FloatType(vector_get_y(test_value1)) == FloatType(vector_get_y((Vector4Type)vector_load(&buffer[1])))); + CHECK(FloatType(vector_get_z(test_value1)) == FloatType(vector_get_z((Vector4Type)vector_load(&buffer[1])))); + CHECK(FloatType(vector_get_w(test_value1)) == FloatType(vector_get_w((Vector4Type)vector_load(&buffer[1])))); + + vector_store1(test_value1, &buffer[1]); + CHECK(FloatType(vector_get_x(test_value1)) == FloatType(vector_get_x((Vector4Type)vector_load1(&buffer[1])))); + + vector_store2(test_value1, &buffer[1]); + CHECK(FloatType(vector_get_x(test_value1)) == FloatType(vector_get_x((Vector4Type)vector_load2(&buffer[1])))); + + vector_store3(test_value1, &buffer[1]); + CHECK(FloatType(vector_get_x(test_value1)) == FloatType(vector_get_x((Vector4Type)vector_load3(&buffer[1])))); + CHECK(FloatType(vector_get_y(test_value1)) == FloatType(vector_get_y((Vector4Type)vector_load3(&buffer[1])))); + CHECK(FloatType(vector_get_z(test_value1)) == FloatType(vector_get_z((Vector4Type)vector_load3(&buffer[1])))); + + vector_store(test_value1, &tmpf4); + CHECK(FloatType(vector_get_x(test_value1)) == tmpf4.x); + CHECK(FloatType(vector_get_y(test_value1)) == tmpf4.y); + CHECK(FloatType(vector_get_z(test_value1)) == tmpf4.z); + CHECK(FloatType(vector_get_w(test_value1)) == tmpf4.w); + + vector_store2(test_value1, &tmpf2); + CHECK(FloatType(vector_get_x(test_value1)) == tmpf2.x); + CHECK(FloatType(vector_get_y(test_value1)) == tmpf2.y); + + vector_store3(test_value1, &tmpf3); + CHECK(FloatType(vector_get_x(test_value1)) == tmpf3.x); + CHECK(FloatType(vector_get_y(test_value1)) == tmpf3.y); + CHECK(FloatType(vector_get_z(test_value1)) == tmpf3.z); + + CHECK((FloatType)vector_get_min_component(test_value0) == FloatType(vector_get_z(test_value0))); + CHECK((FloatType)vector_get_min_component(test_value3) == FloatType(vector_get_y(test_value3))); + CHECK((FloatType)vector_get_max_component(test_value0) == FloatType(vector_get_w(test_value0))); + CHECK(scalar_equal(vector_get_min_component(test_value0), (ScalarType)vector_as_scalar(vector_dup_z(test_value0)))); + CHECK(scalar_equal(vector_get_min_component(test_value3), (ScalarType)vector_as_scalar(vector_dup_y(test_value3)))); + CHECK(scalar_equal(vector_get_max_component(test_value0), (ScalarType)vector_as_scalar(vector_dup_w(test_value0)))); + + CHECK(vector_all_near_equal(vector_set_x(zero, FloatType(4.0)), vector_set(FloatType(4.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_set_y(zero, FloatType(4.0)), vector_set(FloatType(0.0), FloatType(4.0), FloatType(0.0), FloatType(0.0)), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_set_z(zero, FloatType(4.0)), vector_set(FloatType(0.0), FloatType(0.0), FloatType(4.0), FloatType(0.0)), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_set_w(zero, FloatType(4.0)), vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(4.0)), FloatType(0.0))); + + CHECK(vector_all_near_equal(vector_set_x(zero, scalar_set(FloatType(4.0))), vector_set(FloatType(4.0), FloatType(0.0), FloatType(0.0), FloatType(0.0)), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_set_y(zero, scalar_set(FloatType(4.0))), vector_set(FloatType(0.0), FloatType(4.0), FloatType(0.0), FloatType(0.0)), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_set_z(zero, scalar_set(FloatType(4.0))), vector_set(FloatType(0.0), FloatType(0.0), FloatType(4.0), FloatType(0.0)), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_set_w(zero, scalar_set(FloatType(4.0))), vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(4.0)), FloatType(0.0))); + + CHECK((FloatType)vector_as_scalar(test_value1) == FloatType(vector_get_x(test_value1))); + CHECK(scalar_equal(vector_as_scalar(test_value1), scalar_set(vector_get_x(test_value1)))); +} + +template +void test_vector4_arithmetic_impl(const FloatType threshold) +{ + using Vector4Type = typename float_traits::vector4; + using ScalarType = typename float_traits::scalar; + + const Vector4Type zero = vector_zero(); + const Vector4Type infinity = vector_set(std::numeric_limits::infinity()); + const Vector4Type nan = vector_set(std::numeric_limits::quiet_NaN()); + + const FloatType test_value0_flt[4] = { FloatType(2.0), FloatType(9.34), FloatType(-54.12), FloatType(6000.0) }; + const FloatType test_value1_flt[4] = { FloatType(0.75), FloatType(-4.52), FloatType(44.68), FloatType(-54225.0) }; + const FloatType test_value2_flt[4] = { FloatType(-2.65), FloatType(2.996113), FloatType(0.68123521), FloatType(-5.9182) }; + const Vector4Type test_value0 = vector_set(test_value0_flt[0], test_value0_flt[1], test_value0_flt[2], test_value0_flt[3]); + const Vector4Type test_value1 = vector_set(test_value1_flt[0], test_value1_flt[1], test_value1_flt[2], test_value1_flt[3]); + const Vector4Type test_value2 = vector_set(test_value2_flt[0], test_value2_flt[1], test_value2_flt[2], test_value2_flt[3]); + + ////////////////////////////////////////////////////////////////////////// + // Arithmetic + + CHECK(scalar_near_equal(vector_get_x(vector_add(test_value0, test_value1)), test_value0_flt[0] + test_value1_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_add(test_value0, test_value1)), test_value0_flt[1] + test_value1_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_add(test_value0, test_value1)), test_value0_flt[2] + test_value1_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_add(test_value0, test_value1)), test_value0_flt[3] + test_value1_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_sub(test_value0, test_value1)), test_value0_flt[0] - test_value1_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_sub(test_value0, test_value1)), test_value0_flt[1] - test_value1_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_sub(test_value0, test_value1)), test_value0_flt[2] - test_value1_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_sub(test_value0, test_value1)), test_value0_flt[3] - test_value1_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_mul(test_value0, test_value1)), test_value0_flt[0] * test_value1_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_mul(test_value0, test_value1)), test_value0_flt[1] * test_value1_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_mul(test_value0, test_value1)), test_value0_flt[2] * test_value1_flt[2], threshold)); + // We have a strange codegen bug with gcc5, use the Catch near equal impl instead + CHECK(scalar_cast(vector_get_w(vector_mul(test_value0, test_value1))) == Approx(test_value0_flt[3] * test_value1_flt[3]).margin(threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_mul(test_value0, FloatType(2.34))), test_value0_flt[0] * FloatType(2.34), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_mul(test_value0, FloatType(2.34))), test_value0_flt[1] * FloatType(2.34), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_mul(test_value0, FloatType(2.34))), test_value0_flt[2] * FloatType(2.34), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_mul(test_value0, FloatType(2.34))), test_value0_flt[3] * FloatType(2.34), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_mul(test_value0, scalar_set(FloatType(2.34)))), test_value0_flt[0] * FloatType(2.34), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_mul(test_value0, scalar_set(FloatType(2.34)))), test_value0_flt[1] * FloatType(2.34), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_mul(test_value0, scalar_set(FloatType(2.34)))), test_value0_flt[2] * FloatType(2.34), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_mul(test_value0, scalar_set(FloatType(2.34)))), test_value0_flt[3] * FloatType(2.34), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_div(test_value0, test_value1)), test_value0_flt[0] / test_value1_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_div(test_value0, test_value1)), test_value0_flt[1] / test_value1_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_div(test_value0, test_value1)), test_value0_flt[2] / test_value1_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_div(test_value0, test_value1)), test_value0_flt[3] / test_value1_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_max(test_value0, test_value1)), scalar_max(test_value0_flt[0], test_value1_flt[0]), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_max(test_value0, test_value1)), scalar_max(test_value0_flt[1], test_value1_flt[1]), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_max(test_value0, test_value1)), scalar_max(test_value0_flt[2], test_value1_flt[2]), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_max(test_value0, test_value1)), scalar_max(test_value0_flt[3], test_value1_flt[3]), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_min(test_value0, test_value1)), scalar_min(test_value0_flt[0], test_value1_flt[0]), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_min(test_value0, test_value1)), scalar_min(test_value0_flt[1], test_value1_flt[1]), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_min(test_value0, test_value1)), scalar_min(test_value0_flt[2], test_value1_flt[2]), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_min(test_value0, test_value1)), scalar_min(test_value0_flt[3], test_value1_flt[3]), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_clamp(test_value0, test_value1, test_value2)), scalar_clamp(test_value0_flt[0], test_value1_flt[0], test_value2_flt[0]), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_clamp(test_value0, test_value1, test_value2)), scalar_clamp(test_value0_flt[1], test_value1_flt[1], test_value2_flt[1]), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_clamp(test_value0, test_value1, test_value2)), scalar_clamp(test_value0_flt[2], test_value1_flt[2], test_value2_flt[2]), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_clamp(test_value0, test_value1, test_value2)), scalar_clamp(test_value0_flt[3], test_value1_flt[3], test_value2_flt[3]), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_abs(test_value0)), scalar_abs(test_value0_flt[0]), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_abs(test_value0)), scalar_abs(test_value0_flt[1]), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_abs(test_value0)), scalar_abs(test_value0_flt[2]), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_abs(test_value0)), scalar_abs(test_value0_flt[3]), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_neg(test_value0)), -test_value0_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_neg(test_value0)), -test_value0_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_neg(test_value0)), -test_value0_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_neg(test_value0)), -test_value0_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_reciprocal(test_value0)), scalar_reciprocal(test_value0_flt[0]), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_reciprocal(test_value0)), scalar_reciprocal(test_value0_flt[1]), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_reciprocal(test_value0)), scalar_reciprocal(test_value0_flt[2]), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_reciprocal(test_value0)), scalar_reciprocal(test_value0_flt[3]), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_sqrt(vector_abs(test_value0))), scalar_sqrt(scalar_abs(test_value0_flt[0])), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_sqrt(vector_abs(test_value0))), scalar_sqrt(scalar_abs(test_value0_flt[1])), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_sqrt(vector_abs(test_value0))), scalar_sqrt(scalar_abs(test_value0_flt[2])), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_sqrt(vector_abs(test_value0))), scalar_sqrt(scalar_abs(test_value0_flt[3])), threshold)); + + const Vector4Type neg_zero = vector_set(FloatType(-0.0)); + CHECK(FloatType(vector_get_x(vector_floor(neg_zero))) == scalar_floor(FloatType(-0.0))); + CHECK(FloatType(vector_get_x(vector_floor(test_value0))) == scalar_floor(test_value0_flt[0])); + CHECK(FloatType(vector_get_y(vector_floor(test_value0))) == scalar_floor(test_value0_flt[1])); + CHECK(FloatType(vector_get_z(vector_floor(test_value0))) == scalar_floor(test_value0_flt[2])); + CHECK(FloatType(vector_get_w(vector_floor(test_value0))) == scalar_floor(test_value0_flt[3])); + CHECK(FloatType(vector_get_x(vector_floor(infinity))) == scalar_floor(FloatType(vector_get_x(infinity)))); + CHECK(FloatType(vector_get_y(vector_floor(infinity))) == scalar_floor(FloatType(vector_get_y(infinity)))); + CHECK(FloatType(vector_get_z(vector_floor(infinity))) == scalar_floor(FloatType(vector_get_z(infinity)))); + CHECK(FloatType(vector_get_w(vector_floor(infinity))) == scalar_floor(FloatType(vector_get_w(infinity)))); + CHECK(FloatType(vector_get_x(vector_floor(vector_neg(infinity)))) == scalar_floor(-FloatType(vector_get_x(infinity)))); + CHECK(FloatType(vector_get_y(vector_floor(vector_neg(infinity)))) == scalar_floor(-FloatType(vector_get_y(infinity)))); + CHECK(FloatType(vector_get_z(vector_floor(vector_neg(infinity)))) == scalar_floor(-FloatType(vector_get_z(infinity)))); + CHECK(FloatType(vector_get_w(vector_floor(vector_neg(infinity)))) == scalar_floor(-FloatType(vector_get_w(infinity)))); + CHECK(std::isnan(FloatType(vector_get_x(vector_floor(nan))))); + CHECK(std::isnan(FloatType(vector_get_y(vector_floor(nan))))); + CHECK(std::isnan(FloatType(vector_get_z(vector_floor(nan))))); + CHECK(std::isnan(FloatType(vector_get_w(vector_floor(nan))))); + + CHECK(FloatType(vector_get_x(vector_ceil(neg_zero))) == scalar_ceil(FloatType(-0.0))); + CHECK(FloatType(vector_get_x(vector_ceil(test_value0)) == scalar_ceil(test_value0_flt[0]))); + CHECK(FloatType(vector_get_y(vector_ceil(test_value0)) == scalar_ceil(test_value0_flt[1]))); + CHECK(FloatType(vector_get_z(vector_ceil(test_value0)) == scalar_ceil(test_value0_flt[2]))); + CHECK(FloatType(vector_get_w(vector_ceil(test_value0)) == scalar_ceil(test_value0_flt[3]))); + CHECK(FloatType(vector_get_x(vector_ceil(infinity))) == scalar_ceil(FloatType(vector_get_x(infinity)))); + CHECK(FloatType(vector_get_y(vector_ceil(infinity))) == scalar_ceil(FloatType(vector_get_y(infinity)))); + CHECK(FloatType(vector_get_z(vector_ceil(infinity))) == scalar_ceil(FloatType(vector_get_z(infinity)))); + CHECK(FloatType(vector_get_w(vector_ceil(infinity))) == scalar_ceil(FloatType(vector_get_w(infinity)))); + CHECK(FloatType(vector_get_x(vector_ceil(vector_neg(infinity)))) == scalar_ceil(-FloatType(vector_get_x(infinity)))); + CHECK(FloatType(vector_get_y(vector_ceil(vector_neg(infinity)))) == scalar_ceil(-FloatType(vector_get_y(infinity)))); + CHECK(FloatType(vector_get_z(vector_ceil(vector_neg(infinity)))) == scalar_ceil(-FloatType(vector_get_z(infinity)))); + CHECK(FloatType(vector_get_w(vector_ceil(vector_neg(infinity)))) == scalar_ceil(-FloatType(vector_get_w(infinity)))); + CHECK(std::isnan(FloatType(vector_get_x(vector_ceil(nan))))); + CHECK(std::isnan(FloatType(vector_get_y(vector_ceil(nan))))); + CHECK(std::isnan(FloatType(vector_get_z(vector_ceil(nan))))); + CHECK(std::isnan(FloatType(vector_get_w(vector_ceil(nan))))); + + const Vector4Type scalar_cross3_result = scalar_cross3(test_value0, test_value1); + const Vector4Type vector_cross3_result = vector_cross3(test_value0, test_value1); + // We have a strange codegen bug with gcc, use the Catch near equal impl instead + CHECK(FloatType(vector_get_x(vector_cross3_result)) == Approx(FloatType(vector_get_x(scalar_cross3_result))).margin(threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(vector_cross3_result)), FloatType(vector_get_y(scalar_cross3_result)), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(vector_cross3_result)), FloatType(vector_get_z(scalar_cross3_result)), threshold)); + + const FloatType test_value10_flt[4] = { FloatType(-0.001138), FloatType(0.91623), FloatType(-1.624598), FloatType(0.715671) }; + const FloatType test_value11_flt[4] = { FloatType(0.1138), FloatType(-0.623), FloatType(1.4598), FloatType(-0.5671) }; + const Vector4Type test_value10 = vector_set(test_value10_flt[0], test_value10_flt[1], test_value10_flt[2], test_value10_flt[3]); + const Vector4Type test_value11 = vector_set(test_value11_flt[0], test_value11_flt[1], test_value11_flt[2], test_value11_flt[3]); + const FloatType scalar_dot_result = scalar_dot(test_value10, test_value11); + const FloatType vector_dot_result = vector_dot(test_value10, test_value11); + CHECK(scalar_near_equal(vector_dot_result, scalar_dot_result, threshold)); + + const FloatType scalar_dot3_result = scalar_dot3(test_value10, test_value11); + const FloatType vector_dot3_result = vector_dot3(test_value10, test_value11); + CHECK(scalar_near_equal(vector_dot3_result, scalar_dot3_result, threshold)); + const ScalarType vector_dot3_result_scalar = vector_dot3(test_value10, test_value11); + CHECK(scalar_equal(vector_dot3_result, scalar_cast(vector_dot3_result_scalar))); + const Vector4Type vector_dot3_result_vec = vector_dot3(test_value10, test_value11); + CHECK(scalar_equal(vector_dot3_result, (FloatType)vector_get_x(vector_dot3_result_vec))); + CHECK(scalar_equal(vector_dot3_result, (FloatType)vector_get_y(vector_dot3_result_vec))); + CHECK(scalar_equal(vector_dot3_result, (FloatType)vector_get_z(vector_dot3_result_vec))); + CHECK(scalar_equal(vector_dot3_result, (FloatType)vector_get_w(vector_dot3_result_vec))); + + const ScalarType vector_sdot_result = vector_dot(test_value10, test_value11); + CHECK(scalar_near_equal(scalar_cast(vector_sdot_result), scalar_dot_result, threshold)); + + const Vector4Type vector_vdot_result = vector_dot(test_value10, test_value11); + CHECK(scalar_near_equal(vector_get_x(vector_vdot_result), scalar_dot_result, threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_vdot_result), scalar_dot_result, threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_vdot_result), scalar_dot_result, threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_vdot_result), scalar_dot_result, threshold)); + + CHECK(scalar_near_equal(scalar_dot(test_value0, test_value0), vector_length_squared(test_value0), threshold)); + CHECK(scalar_near_equal(scalar_dot(test_value0, test_value0), scalar_cast(vector_length_squared(test_value0)), threshold)); + const FloatType vector_length_squared3_ref = scalar_dot3(test_value0, test_value0); + const FloatType vector_length_squared3_result = vector_length_squared3(test_value0); + CHECK(scalar_near_equal(vector_length_squared3_ref, vector_length_squared3_result, threshold)); + const ScalarType vector_length_squared3_result_scalar = vector_length_squared3(test_value0); + CHECK(scalar_equal(vector_length_squared3_result, scalar_cast(vector_length_squared3_result_scalar))); + + CHECK(scalar_near_equal(rtm::scalar_sqrt(scalar_dot(test_value0, test_value0)), vector_length(test_value0), threshold)); + CHECK(scalar_near_equal(rtm::scalar_sqrt(scalar_dot(test_value0, test_value0)), scalar_cast(vector_length(test_value0)), threshold)); + const FloatType vector_length3_result = vector_length3(test_value0); + CHECK(scalar_near_equal(rtm::scalar_sqrt(scalar_dot3(test_value0, test_value0)), vector_length3_result, threshold)); + const ScalarType vector_length3_result_scalar = vector_length3(test_value0); + CHECK(scalar_equal(vector_length3_result, scalar_cast(vector_length3_result_scalar))); + + CHECK(scalar_near_equal(rtm::scalar_sqrt_reciprocal(scalar_dot(test_value0, test_value0)), vector_length_reciprocal(test_value0), threshold)); + CHECK(scalar_near_equal(rtm::scalar_sqrt_reciprocal(scalar_dot(test_value0, test_value0)), scalar_cast(vector_length_reciprocal(test_value0)), threshold)); + CHECK(scalar_near_equal(rtm::scalar_sqrt_reciprocal(scalar_dot3(test_value0, test_value0)), vector_length_reciprocal3(test_value0), threshold)); + CHECK(scalar_near_equal(rtm::scalar_sqrt_reciprocal(scalar_dot3(test_value0, test_value0)), scalar_cast(vector_length_reciprocal3(test_value0)), threshold)); + + const Vector4Type test_value_diff = vector_sub(test_value0, test_value1); + const FloatType vector_distance3_result = vector_distance3(test_value0, test_value1); + CHECK(scalar_near_equal(rtm::scalar_sqrt(scalar_dot3(test_value_diff, test_value_diff)), vector_distance3_result, threshold)); + const ScalarType vector_distance3_result_scalar = vector_distance3(test_value0, test_value1); + CHECK(scalar_equal(vector_distance3_result, scalar_cast(vector_distance3_result_scalar))); + + const Vector4Type scalar_normalize3_result = scalar_normalize3(test_value0, zero, threshold); + const Vector4Type vector_normalize3_result = vector_normalize3(test_value0); + CHECK(scalar_near_equal(vector_get_x(vector_normalize3_result), vector_get_x(scalar_normalize3_result), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_normalize3_result), vector_get_y(scalar_normalize3_result), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_normalize3_result), vector_get_z(scalar_normalize3_result), threshold)); + + const Vector4Type vector_normalize3_result_safe = vector_normalize3(test_value0, zero, threshold); + CHECK(scalar_near_equal(vector_get_x(vector_normalize3_result_safe), vector_get_x(scalar_normalize3_result), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_normalize3_result_safe), vector_get_y(scalar_normalize3_result), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_normalize3_result_safe), vector_get_z(scalar_normalize3_result), threshold)); + + const Vector4Type scalar_normalize3_result0 = scalar_normalize3(zero, zero, threshold); + const Vector4Type vector_normalize3_result0 = vector_normalize3(zero, zero, threshold); + CHECK(scalar_near_equal(vector_get_x(vector_normalize3_result0), vector_get_x(scalar_normalize3_result0), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_normalize3_result0), vector_get_y(scalar_normalize3_result0), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_normalize3_result0), vector_get_z(scalar_normalize3_result0), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_lerp(test_value10, test_value11, FloatType(0.33))), ((test_value11_flt[0] - test_value10_flt[0]) * FloatType(0.33)) + test_value10_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_lerp(test_value10, test_value11, FloatType(0.33))), ((test_value11_flt[1] - test_value10_flt[1]) * FloatType(0.33)) + test_value10_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_lerp(test_value10, test_value11, FloatType(0.33))), ((test_value11_flt[2] - test_value10_flt[2]) * FloatType(0.33)) + test_value10_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_lerp(test_value10, test_value11, FloatType(0.33))), ((test_value11_flt[3] - test_value10_flt[3]) * FloatType(0.33)) + test_value10_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_lerp(test_value10, test_value11, scalar_set(FloatType(0.33)))), ((test_value11_flt[0] - test_value10_flt[0]) * FloatType(0.33)) + test_value10_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_lerp(test_value10, test_value11, scalar_set(FloatType(0.33)))), ((test_value11_flt[1] - test_value10_flt[1]) * FloatType(0.33)) + test_value10_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_lerp(test_value10, test_value11, scalar_set(FloatType(0.33)))), ((test_value11_flt[2] - test_value10_flt[2]) * FloatType(0.33)) + test_value10_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_lerp(test_value10, test_value11, scalar_set(FloatType(0.33)))), ((test_value11_flt[3] - test_value10_flt[3]) * FloatType(0.33)) + test_value10_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_lerp(test_value10, test_value11, vector_set(FloatType(0.33)))), ((test_value11_flt[0] - test_value10_flt[0]) * FloatType(0.33)) + test_value10_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_lerp(test_value10, test_value11, vector_set(FloatType(0.33)))), ((test_value11_flt[1] - test_value10_flt[1]) * FloatType(0.33)) + test_value10_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_lerp(test_value10, test_value11, vector_set(FloatType(0.33)))), ((test_value11_flt[2] - test_value10_flt[2]) * FloatType(0.33)) + test_value10_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_lerp(test_value10, test_value11, vector_set(FloatType(0.33)))), ((test_value11_flt[3] - test_value10_flt[3]) * FloatType(0.33)) + test_value10_flt[3], threshold)); + + // Lerp must be stable and return exactly the start when the interpolation alpha is 0.0 and exactly the end when 1.0 + CHECK(vector_all_near_equal(vector_lerp(test_value10, test_value11, FloatType(0.0)), test_value10, FloatType(0.0))); + CHECK(vector_all_near_equal(vector_lerp(test_value10, test_value11, FloatType(1.0)), test_value11, FloatType(0.0))); + CHECK(vector_all_near_equal(vector_lerp(test_value10, test_value11, scalar_set(FloatType(0.0))), test_value10, FloatType(0.0))); + CHECK(vector_all_near_equal(vector_lerp(test_value10, test_value11, scalar_set(FloatType(1.0))), test_value11, FloatType(0.0))); + CHECK(vector_all_near_equal(vector_lerp(test_value10, test_value11, vector_set(FloatType(0.0))), test_value10, FloatType(0.0))); + CHECK(vector_all_near_equal(vector_lerp(test_value10, test_value11, vector_set(FloatType(1.0))), test_value11, FloatType(0.0))); + + CHECK(FloatType(vector_get_x(vector_fraction(neg_zero))) == scalar_fraction(FloatType(-0.0))); + CHECK(scalar_near_equal(vector_get_x(vector_fraction(test_value0)), scalar_fraction(test_value0_flt[0]), threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_fraction(test_value0)), scalar_fraction(test_value0_flt[1]), threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_fraction(test_value0)), scalar_fraction(test_value0_flt[2]), threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_fraction(test_value0)), scalar_fraction(test_value0_flt[3]), threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_mul_add(test_value10, test_value11, test_value2)), (test_value10_flt[0] * test_value11_flt[0]) + test_value2_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_mul_add(test_value10, test_value11, test_value2)), (test_value10_flt[1] * test_value11_flt[1]) + test_value2_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_mul_add(test_value10, test_value11, test_value2)), (test_value10_flt[2] * test_value11_flt[2]) + test_value2_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_mul_add(test_value10, test_value11, test_value2)), (test_value10_flt[3] * test_value11_flt[3]) + test_value2_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_mul_add(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[0] * test_value11_flt[0]) + test_value2_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_mul_add(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[1] * test_value11_flt[0]) + test_value2_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_mul_add(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[2] * test_value11_flt[0]) + test_value2_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_mul_add(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[3] * test_value11_flt[0]) + test_value2_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_mul_add(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[0] * test_value11_flt[0]) + test_value2_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_mul_add(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[1] * test_value11_flt[0]) + test_value2_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_mul_add(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[2] * test_value11_flt[0]) + test_value2_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_mul_add(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[3] * test_value11_flt[0]) + test_value2_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_neg_mul_sub(test_value10, test_value11, test_value2)), (test_value10_flt[0] * -test_value11_flt[0]) + test_value2_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_neg_mul_sub(test_value10, test_value11, test_value2)), (test_value10_flt[1] * -test_value11_flt[1]) + test_value2_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_neg_mul_sub(test_value10, test_value11, test_value2)), (test_value10_flt[2] * -test_value11_flt[2]) + test_value2_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_neg_mul_sub(test_value10, test_value11, test_value2)), (test_value10_flt[3] * -test_value11_flt[3]) + test_value2_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_neg_mul_sub(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[0] * -test_value11_flt[0]) + test_value2_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_neg_mul_sub(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[1] * -test_value11_flt[0]) + test_value2_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_neg_mul_sub(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[2] * -test_value11_flt[0]) + test_value2_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_neg_mul_sub(test_value10, test_value11_flt[0], test_value2)), (test_value10_flt[3] * -test_value11_flt[0]) + test_value2_flt[3], threshold)); + + CHECK(scalar_near_equal(vector_get_x(vector_neg_mul_sub(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[0] * -test_value11_flt[0]) + test_value2_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_neg_mul_sub(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[1] * -test_value11_flt[0]) + test_value2_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_neg_mul_sub(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[2] * -test_value11_flt[0]) + test_value2_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_neg_mul_sub(test_value10, scalar_set(test_value11_flt[0]), test_value2)), (test_value10_flt[3] * -test_value11_flt[0]) + test_value2_flt[3], threshold)); +} + +template +void test_vector4_relational_impl(const FloatType threshold) +{ + using Vector4Type = typename float_traits::vector4; + + const Vector4Type zero = vector_zero(); + + const FloatType test_value0_flt[4] = { FloatType(2.0), FloatType(9.34), FloatType(-54.12), FloatType(6000.0) }; + const FloatType test_value1_flt[4] = { FloatType(0.75), FloatType(-4.52), FloatType(44.68), FloatType(-54225.0) }; + const FloatType test_value3_flt[4] = { FloatType(2.0), FloatType(-9.34), FloatType(54.12), FloatType(6000.1) }; + const Vector4Type test_value0 = vector_set(test_value0_flt[0], test_value0_flt[1], test_value0_flt[2], test_value0_flt[3]); + const Vector4Type test_value1 = vector_set(test_value1_flt[0], test_value1_flt[1], test_value1_flt[2], test_value1_flt[3]); + const Vector4Type test_value3 = vector_set(test_value3_flt[0], test_value3_flt[1], test_value3_flt[2], test_value3_flt[3]); + + ////////////////////////////////////////////////////////////////////////// + // Comparisons and masking + + CHECK((mask_get_x(vector_equal(test_value0, test_value1)) != 0) == (test_value0_flt[0] == test_value1_flt[0])); + CHECK((mask_get_y(vector_equal(test_value0, test_value1)) != 0) == (test_value0_flt[1] == test_value1_flt[1])); + CHECK((mask_get_z(vector_equal(test_value0, test_value1)) != 0) == (test_value0_flt[2] == test_value1_flt[2])); + CHECK((mask_get_w(vector_equal(test_value0, test_value1)) != 0) == (test_value0_flt[3] == test_value1_flt[3])); + + CHECK((mask_get_x(vector_equal(test_value0, test_value0)) != 0) == (test_value0_flt[0] == test_value0_flt[0])); + CHECK((mask_get_y(vector_equal(test_value0, test_value0)) != 0) == (test_value0_flt[1] == test_value0_flt[1])); + CHECK((mask_get_z(vector_equal(test_value0, test_value0)) != 0) == (test_value0_flt[2] == test_value0_flt[2])); + CHECK((mask_get_w(vector_equal(test_value0, test_value0)) != 0) == (test_value0_flt[3] == test_value0_flt[3])); + + CHECK((mask_get_x(vector_less_than(test_value0, test_value1)) != 0) == (test_value0_flt[0] < test_value1_flt[0])); + CHECK((mask_get_y(vector_less_than(test_value0, test_value1)) != 0) == (test_value0_flt[1] < test_value1_flt[1])); + CHECK((mask_get_z(vector_less_than(test_value0, test_value1)) != 0) == (test_value0_flt[2] < test_value1_flt[2])); + CHECK((mask_get_w(vector_less_than(test_value0, test_value1)) != 0) == (test_value0_flt[3] < test_value1_flt[3])); + + CHECK((mask_get_x(vector_less_equal(test_value0, test_value3)) != 0) == (test_value0_flt[0] <= test_value3_flt[0])); + CHECK((mask_get_y(vector_less_equal(test_value0, test_value3)) != 0) == (test_value0_flt[1] <= test_value3_flt[1])); + CHECK((mask_get_z(vector_less_equal(test_value0, test_value3)) != 0) == (test_value0_flt[2] <= test_value3_flt[2])); + CHECK((mask_get_w(vector_less_equal(test_value0, test_value3)) != 0) == (test_value0_flt[3] <= test_value3_flt[3])); + + CHECK((mask_get_x(vector_greater_than(test_value0, test_value1)) != 0) == (test_value0_flt[0] > test_value1_flt[0])); + CHECK((mask_get_y(vector_greater_than(test_value0, test_value1)) != 0) == (test_value0_flt[1] > test_value1_flt[1])); + CHECK((mask_get_z(vector_greater_than(test_value0, test_value1)) != 0) == (test_value0_flt[2] > test_value1_flt[2])); + CHECK((mask_get_w(vector_greater_than(test_value0, test_value1)) != 0) == (test_value0_flt[3] > test_value1_flt[3])); + + CHECK((mask_get_x(vector_greater_equal(test_value0, test_value1)) != 0) == (test_value0_flt[0] >= test_value1_flt[0])); + CHECK((mask_get_y(vector_greater_equal(test_value0, test_value1)) != 0) == (test_value0_flt[1] >= test_value1_flt[1])); + CHECK((mask_get_z(vector_greater_equal(test_value0, test_value1)) != 0) == (test_value0_flt[2] >= test_value1_flt[2])); + CHECK((mask_get_w(vector_greater_equal(test_value0, test_value1)) != 0) == (test_value0_flt[3] >= test_value1_flt[3])); + + CHECK(vector_all_less_than(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0))) == true); + CHECK(vector_all_less_than(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0))) == false); + CHECK(vector_all_less_than(zero, zero) == false); + + CHECK(vector_all_less_than2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_than2(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than2(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than2(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than2(zero, zero) == false); + + CHECK(vector_all_less_than3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_than3(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than3(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than3(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == false); + CHECK(vector_all_less_than3(zero, zero) == false); + + CHECK(vector_any_less_than(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0))) == true); + CHECK(vector_any_less_than(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0))) == true); + CHECK(vector_any_less_than(zero, zero) == false); + + CHECK(vector_any_less_than2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than2(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than2(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than2(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == false); + CHECK(vector_any_less_than2(zero, zero) == false); + + CHECK(vector_any_less_than3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than3(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than3(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than3(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_than3(zero, zero) == false); + + CHECK(vector_all_less_equal(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0))) == true); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(1.0))) == true); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(0.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(0.0), FloatType(-1.0))) == false); + CHECK(vector_all_less_equal(zero, zero) == true); + + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(0.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal2(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal2(zero, zero) == true); + + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(0.0), FloatType(1.0), FloatType(0.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(-1.0), FloatType(0.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(0.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal3(zero, vector_set(FloatType(0.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0))) == false); + CHECK(vector_all_less_equal3(zero, zero) == true); + + CHECK(vector_any_less_equal(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0))) == true); + CHECK(vector_any_less_equal(zero, vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0))) == true); + CHECK(vector_any_less_equal(zero, vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(-1.0))) == true); + CHECK(vector_any_less_equal(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(-1.0))) == true); + CHECK(vector_any_less_equal(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(1.0))) == true); + CHECK(vector_any_less_equal(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0))) == false); + CHECK(vector_any_less_equal(zero, zero) == true); + + CHECK(vector_any_less_equal2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal2(zero, vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal2(zero, vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal2(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0))) == false); + CHECK(vector_any_less_equal2(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0))) == false); + CHECK(vector_any_less_equal2(zero, zero) == true); + + CHECK(vector_any_less_equal3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal3(zero, vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal3(zero, vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal3(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0))) == true); + CHECK(vector_any_less_equal3(zero, vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0))) == false); + CHECK(vector_any_less_equal3(zero, zero) == true); + + CHECK(vector_all_greater_than(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), zero) == true); + CHECK(vector_all_greater_than(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_than(zero, zero) == false); + + CHECK(vector_all_greater_than2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_all_greater_than2(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than2(zero, zero) == false); + + CHECK(vector_all_greater_than3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_all_greater_than3(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_than3(zero, zero) == false); + + CHECK(vector_any_greater_than(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), zero) == true); + CHECK(vector_any_greater_than(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(1.0)), zero) == true); + CHECK(vector_any_greater_than(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(-1.0)), zero) == false); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_any_greater_than(zero, zero) == false); + + CHECK(vector_any_greater_than2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than2(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than2(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than2(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than2(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than2(zero, zero) == false); + + CHECK(vector_any_greater_than3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than3(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than3(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_than3(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_than3(zero, zero) == false); + + CHECK(vector_all_greater_equal(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), zero) == true); + CHECK(vector_all_greater_equal(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_all_greater_equal(zero, zero) == true); + + CHECK(vector_all_greater_equal2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_all_greater_equal2(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal2(zero, zero) == true); + + CHECK(vector_all_greater_equal3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_all_greater_equal3(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_all_greater_equal3(zero, zero) == true); + + CHECK(vector_any_greater_equal(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(-1.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(-1.0)), zero) == false); + CHECK(vector_any_greater_equal(zero, zero) == true); + + CHECK(vector_any_greater_equal2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal2(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal2(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_equal2(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal2(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_equal2(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_equal2(zero, zero) == true); + + CHECK(vector_any_greater_equal3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(-1.0), FloatType(1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(0.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(-1.0), FloatType(0.0), FloatType(-1.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(0.0), FloatType(0.0)), zero) == true); + CHECK(vector_any_greater_equal3(vector_set(FloatType(-1.0), FloatType(-1.0), FloatType(-1.0), FloatType(0.0)), zero) == false); + CHECK(vector_any_greater_equal3(zero, zero) == true); + + CHECK(vector_all_near_equal(zero, zero, threshold) == true); + CHECK(vector_all_near_equal(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), FloatType(1.0001)) == true); + CHECK(vector_all_near_equal(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), FloatType(1.0)) == true); + CHECK(vector_all_near_equal(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), FloatType(0.9999)) == false); + + CHECK(vector_all_near_equal2(zero, zero, threshold) == true); + CHECK(vector_all_near_equal2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_all_near_equal2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_all_near_equal2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(0.9999)) == false); + + CHECK(vector_all_near_equal3(zero, zero, threshold) == true); + CHECK(vector_all_near_equal3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_all_near_equal3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_all_near_equal3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(0.9999)) == false); + + CHECK(vector_any_near_equal(zero, zero, threshold) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(1.0), FloatType(2.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(2.0), FloatType(1.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(2.0), FloatType(1.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(1.0), FloatType(2.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(2.0), FloatType(1.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(2.0), FloatType(1.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(1.0)), FloatType(0.9999)) == false); + + CHECK(vector_any_near_equal2(zero, zero, threshold) == true); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(1.0), FloatType(2.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(2.0), FloatType(1.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0001)) == false); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(1.0), FloatType(2.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(2.0), FloatType(1.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0)) == false); + CHECK(vector_any_near_equal2(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(0.9999)) == false); + + CHECK(vector_any_near_equal3(zero, zero, threshold) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(1.0), FloatType(2.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(2.0), FloatType(1.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0001)) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(1.0), FloatType(2.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(2.0), FloatType(1.0), FloatType(2.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(2.0), FloatType(2.0), FloatType(1.0), FloatType(2.0)), FloatType(1.0)) == true); + CHECK(vector_any_near_equal3(zero, vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(2.0)), FloatType(0.9999)) == false); +} + +template +inline Vector4Type reference_vector_and(const Vector4Type& input0, const Vector4Type& input1) +{ + using Int1Type = typename float_traits::int1; + + Int1Type input0_[4]; + Int1Type input1_[4]; + + static_assert(sizeof(Vector4Type) == sizeof(input0_), "Unexpected size"); + std::memcpy(&input0_[0], &input0, sizeof(Vector4Type)); + std::memcpy(&input1_[0], &input1, sizeof(Vector4Type)); + + Int1Type result_[4]; + result_[0] = input0_[0] & input1_[0]; + result_[1] = input0_[1] & input1_[1]; + result_[2] = input0_[2] & input1_[2]; + result_[3] = input0_[3] & input1_[3]; + + Vector4Type result; + std::memcpy(&result, &result_[0], sizeof(Vector4Type)); + + return result; +} + +template +inline Vector4Type reference_vector_or(const Vector4Type& input0, const Vector4Type& input1) +{ + using Int1Type = typename float_traits::int1; + + Int1Type input0_[4]; + Int1Type input1_[4]; + + static_assert(sizeof(Vector4Type) == sizeof(input0_), "Unexpected size"); + std::memcpy(&input0_[0], &input0, sizeof(Vector4Type)); + std::memcpy(&input1_[0], &input1, sizeof(Vector4Type)); + + Int1Type result_[4]; + result_[0] = input0_[0] | input1_[0]; + result_[1] = input0_[1] | input1_[1]; + result_[2] = input0_[2] | input1_[2]; + result_[3] = input0_[3] | input1_[3]; + + Vector4Type result; + std::memcpy(&result, &result_[0], sizeof(Vector4Type)); + + return result; +} + +template +inline Vector4Type reference_vector_xor(const Vector4Type& input0, const Vector4Type& input1) +{ + using Int1Type = typename float_traits::int1; + + Int1Type input0_[4]; + Int1Type input1_[4]; + + static_assert(sizeof(Vector4Type) == sizeof(input0_), "Unexpected size"); + std::memcpy(&input0_[0], &input0, sizeof(Vector4Type)); + std::memcpy(&input1_[0], &input1, sizeof(Vector4Type)); + + Int1Type result_[4]; + result_[0] = input0_[0] ^ input1_[0]; + result_[1] = input0_[1] ^ input1_[1]; + result_[2] = input0_[2] ^ input1_[2]; + result_[3] = input0_[3] ^ input1_[3]; + + Vector4Type result; + std::memcpy(&result, &result_[0], sizeof(Vector4Type)); + + return result; +} + +template +void test_vector4_logical_impl() +{ + using Vector4Type = typename float_traits::vector4; + + const FloatType test_value0_flt[4] = { FloatType(2.0), FloatType(9.34), FloatType(-54.12), FloatType(6000.0) }; + const FloatType test_value1_flt[4] = { FloatType(0.75), FloatType(-4.52), FloatType(44.68), FloatType(-54225.0) }; + const FloatType test_value2_flt[4] = { FloatType(2.0), FloatType(-9.34), FloatType(54.12), FloatType(6000.1) }; + const Vector4Type test_value0 = vector_set(test_value0_flt[0], test_value0_flt[1], test_value0_flt[2], test_value0_flt[3]); + const Vector4Type test_value1 = vector_set(test_value1_flt[0], test_value1_flt[1], test_value1_flt[2], test_value1_flt[3]); + const Vector4Type test_value2 = vector_set(test_value2_flt[0], test_value2_flt[1], test_value2_flt[2], test_value2_flt[3]); + + CHECK(vector_all_near_equal(vector_and(test_value0, test_value1), reference_vector_and(test_value0, test_value1), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_and(test_value1, test_value2), reference_vector_and(test_value1, test_value2), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_and(test_value0, test_value2), reference_vector_and(test_value0, test_value2), FloatType(0.0))); + + CHECK(vector_all_near_equal(vector_or(test_value0, test_value1), reference_vector_or(test_value0, test_value1), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_or(test_value1, test_value2), reference_vector_or(test_value1, test_value2), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_or(test_value0, test_value2), reference_vector_or(test_value0, test_value2), FloatType(0.0))); + + CHECK(vector_all_near_equal(vector_xor(test_value0, test_value1), reference_vector_xor(test_value0, test_value1), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_xor(test_value1, test_value2), reference_vector_xor(test_value1, test_value2), FloatType(0.0))); + CHECK(vector_all_near_equal(vector_xor(test_value0, test_value2), reference_vector_xor(test_value0, test_value2), FloatType(0.0))); +} + +template +void test_vector4_impl(const FloatType threshold) +{ + using Vector4Type = typename float_traits::vector4; + + const Vector4Type zero = vector_zero(); + + const FloatType test_value0_flt[4] = { FloatType(2.0), FloatType(9.34), FloatType(-54.12), FloatType(6000.0) }; + const FloatType test_value1_flt[4] = { FloatType(0.75), FloatType(-4.52), FloatType(44.68), FloatType(-54225.0) }; + const Vector4Type test_value0 = vector_set(test_value0_flt[0], test_value0_flt[1], test_value0_flt[2], test_value0_flt[3]); + const Vector4Type test_value1 = vector_set(test_value1_flt[0], test_value1_flt[1], test_value1_flt[2], test_value1_flt[3]); + + const FloatType inf = std::numeric_limits::infinity(); + const FloatType nan = std::numeric_limits::quiet_NaN(); + CHECK(vector_is_finite(zero) == true); + CHECK(vector_is_finite(vector_set(inf, inf, inf, inf)) == false); + CHECK(vector_is_finite(vector_set(inf, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite(vector_set(FloatType(1.0), FloatType(inf), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite(vector_set(FloatType(1.0), FloatType(1.0), FloatType(inf), FloatType(1.0))) == false); + CHECK(vector_is_finite(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(inf))) == false); + CHECK(vector_is_finite(vector_set(nan, nan, nan, nan)) == false); + CHECK(vector_is_finite(vector_set(nan, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite(vector_set(FloatType(1.0), FloatType(nan), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite(vector_set(FloatType(1.0), FloatType(1.0), FloatType(nan), FloatType(1.0))) == false); + CHECK(vector_is_finite(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(nan))) == false); + + CHECK(vector_is_finite2(zero) == true); + CHECK(vector_is_finite2(vector_set(inf, inf, inf, inf)) == false); + CHECK(vector_is_finite2(vector_set(inf, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite2(vector_set(FloatType(1.0), FloatType(inf), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(inf), FloatType(1.0))) == true); + CHECK(vector_is_finite2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(inf))) == true); + CHECK(vector_is_finite2(vector_set(nan, nan, nan, nan)) == false); + CHECK(vector_is_finite2(vector_set(nan, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite2(vector_set(FloatType(1.0), FloatType(nan), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(nan), FloatType(1.0))) == true); + CHECK(vector_is_finite2(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(nan))) == true); + + CHECK(vector_is_finite3(zero) == true); + CHECK(vector_is_finite3(vector_set(inf, inf, inf, inf)) == false); + CHECK(vector_is_finite3(vector_set(inf, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite3(vector_set(FloatType(1.0), FloatType(inf), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(inf), FloatType(1.0))) == false); + CHECK(vector_is_finite3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(inf))) == true); + CHECK(vector_is_finite3(vector_set(nan, nan, nan, nan)) == false); + CHECK(vector_is_finite3(vector_set(nan, FloatType(1.0), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite3(vector_set(FloatType(1.0), FloatType(nan), FloatType(1.0), FloatType(1.0))) == false); + CHECK(vector_is_finite3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(nan), FloatType(1.0))) == false); + CHECK(vector_is_finite3(vector_set(FloatType(1.0), FloatType(1.0), FloatType(1.0), FloatType(nan))) == true); + + ////////////////////////////////////////////////////////////////////////// + // Swizzling, permutations, and mixing + + CHECK(scalar_near_equal(vector_get_x(vector_select(vector_less_than(zero, vector_set(FloatType(1.0))), test_value0, test_value1)), test_value0_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_select(vector_less_than(zero, vector_set(FloatType(1.0))), test_value0, test_value1)), test_value0_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_select(vector_less_than(zero, vector_set(FloatType(1.0))), test_value0, test_value1)), test_value0_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_select(vector_less_than(zero, vector_set(FloatType(1.0))), test_value0, test_value1)), test_value0_flt[3], threshold)); + CHECK(scalar_near_equal(vector_get_x(vector_select(vector_less_than(vector_set(FloatType(1.0)), zero), test_value0, test_value1)), test_value1_flt[0], threshold)); + CHECK(scalar_near_equal(vector_get_y(vector_select(vector_less_than(vector_set(FloatType(1.0)), zero), test_value0, test_value1)), test_value1_flt[1], threshold)); + CHECK(scalar_near_equal(vector_get_z(vector_select(vector_less_than(vector_set(FloatType(1.0)), zero), test_value0, test_value1)), test_value1_flt[2], threshold)); + CHECK(scalar_near_equal(vector_get_w(vector_select(vector_less_than(vector_set(FloatType(1.0)), zero), test_value0, test_value1)), test_value1_flt[3], threshold)); + + ////////////////////////////////////////////////////////////////////////// + // Misc + + auto scalar_sign = [](FloatType value) { return value >= FloatType(0.0) ? FloatType(1.0) : FloatType(-1.0); }; + CHECK(FloatType(vector_get_x(vector_sign(test_value0))) == scalar_sign(test_value0_flt[0])); + CHECK(FloatType(vector_get_y(vector_sign(test_value0))) == scalar_sign(test_value0_flt[1])); + CHECK(FloatType(vector_get_z(vector_sign(test_value0))) == scalar_sign(test_value0_flt[2])); + CHECK(FloatType(vector_get_w(vector_sign(test_value0))) == scalar_sign(test_value0_flt[3])); + + CHECK(FloatType(vector_get_x(vector_copy_sign(test_value0, test_value1))) == std::copysign(test_value0_flt[0], test_value1_flt[0])); + CHECK(FloatType(vector_get_y(vector_copy_sign(test_value0, test_value1))) == std::copysign(test_value0_flt[1], test_value1_flt[1])); + CHECK(FloatType(vector_get_z(vector_copy_sign(test_value0, test_value1))) == std::copysign(test_value0_flt[2], test_value1_flt[2])); + CHECK(FloatType(vector_get_w(vector_copy_sign(test_value0, test_value1))) == std::copysign(test_value0_flt[3], test_value1_flt[3])); + + { + const Vector4Type infinity_v = vector_set(std::numeric_limits::infinity()); + const Vector4Type nan_v = vector_set(std::numeric_limits::quiet_NaN()); + + const Vector4Type input0 = vector_set(FloatType(-1.75), FloatType(-1.5), FloatType(-1.4999), FloatType(-0.5)); + const Vector4Type input1 = vector_set(FloatType(-0.4999), FloatType(0.0), FloatType(0.4999), FloatType(0.5)); + const Vector4Type input2 = vector_set(FloatType(1.4999), FloatType(1.5), FloatType(1.75), FloatType(0.0)); + const Vector4Type input3 = infinity_v; + const Vector4Type input4 = vector_neg(infinity_v); + const Vector4Type input5 = nan_v; + + const Vector4Type result0 = vector_round_symmetric(input0); + const Vector4Type result1 = vector_round_symmetric(input1); + const Vector4Type result2 = vector_round_symmetric(input2); + const Vector4Type result3 = vector_round_symmetric(input3); + const Vector4Type result4 = vector_round_symmetric(input4); + const Vector4Type result5 = vector_round_symmetric(input5); + const Vector4Type result6 = vector_round_symmetric(vector_set(FloatType(-0.0))); + + CHECK(FloatType(vector_get_x(result0)) == scalar_round_symmetric(FloatType(vector_get_x(input0)))); + CHECK(FloatType(vector_get_y(result0)) == scalar_round_symmetric(FloatType(vector_get_y(input0)))); + CHECK(FloatType(vector_get_z(result0)) == scalar_round_symmetric(FloatType(vector_get_z(input0)))); + CHECK(FloatType(vector_get_w(result0)) == scalar_round_symmetric(FloatType(vector_get_w(input0)))); + + CHECK(FloatType(vector_get_x(result1)) == scalar_round_symmetric(FloatType(vector_get_x(input1)))); + CHECK(FloatType(vector_get_y(result1)) == scalar_round_symmetric(FloatType(vector_get_y(input1)))); + CHECK(FloatType(vector_get_z(result1)) == scalar_round_symmetric(FloatType(vector_get_z(input1)))); + CHECK(FloatType(vector_get_w(result1)) == scalar_round_symmetric(FloatType(vector_get_w(input1)))); + + CHECK(FloatType(vector_get_x(result2)) == scalar_round_symmetric(FloatType(vector_get_x(input2)))); + CHECK(FloatType(vector_get_y(result2)) == scalar_round_symmetric(FloatType(vector_get_y(input2)))); + CHECK(FloatType(vector_get_z(result2)) == scalar_round_symmetric(FloatType(vector_get_z(input2)))); + CHECK(FloatType(vector_get_w(result2)) == scalar_round_symmetric(FloatType(vector_get_w(input2)))); + + CHECK(FloatType(vector_get_x(result3)) == scalar_round_symmetric(FloatType(vector_get_x(input3)))); + CHECK(FloatType(vector_get_y(result3)) == scalar_round_symmetric(FloatType(vector_get_y(input3)))); + CHECK(FloatType(vector_get_z(result3)) == scalar_round_symmetric(FloatType(vector_get_z(input3)))); + CHECK(FloatType(vector_get_w(result3)) == scalar_round_symmetric(FloatType(vector_get_w(input3)))); + + CHECK(FloatType(vector_get_x(result4)) == scalar_round_symmetric(FloatType(vector_get_x(input4)))); + CHECK(FloatType(vector_get_y(result4)) == scalar_round_symmetric(FloatType(vector_get_y(input4)))); + CHECK(FloatType(vector_get_z(result4)) == scalar_round_symmetric(FloatType(vector_get_z(input4)))); + CHECK(FloatType(vector_get_w(result4)) == scalar_round_symmetric(FloatType(vector_get_w(input4)))); + + CHECK(FloatType(vector_get_x(result6)) == scalar_round_symmetric(FloatType(-0.0))); + + CHECK(std::isnan(FloatType(vector_get_x(result5)))); + CHECK(std::isnan(FloatType(vector_get_y(result5)))); + CHECK(std::isnan(FloatType(vector_get_z(result5)))); + CHECK(std::isnan(FloatType(vector_get_w(result5)))); + } + + { + const Vector4Type infinity_v = vector_set(std::numeric_limits::infinity()); + const Vector4Type nan_v = vector_set(std::numeric_limits::quiet_NaN()); + + const Vector4Type input0 = vector_set(FloatType(-1.75), FloatType(-1.5), FloatType(-1.4999), FloatType(-0.5)); + const Vector4Type input1 = vector_set(FloatType(-0.4999), FloatType(0.0), FloatType(0.4999), FloatType(0.5)); + const Vector4Type input2 = vector_set(FloatType(1.4999), FloatType(1.5), FloatType(1.75), FloatType(0.0)); + const Vector4Type input3 = infinity_v; + const Vector4Type input4 = vector_neg(infinity_v); + const Vector4Type input5 = nan_v; + const Vector4Type input6 = vector_set(FloatType(2.5), FloatType(-2.5), FloatType(2.75), FloatType(-2.75)); + + const Vector4Type result0 = vector_round_bankers(input0); + const Vector4Type result1 = vector_round_bankers(input1); + const Vector4Type result2 = vector_round_bankers(input2); + const Vector4Type result3 = vector_round_bankers(input3); + const Vector4Type result4 = vector_round_bankers(input4); + const Vector4Type result5 = vector_round_bankers(input5); + const Vector4Type result6 = vector_round_bankers(input6); + const Vector4Type result7 = vector_round_symmetric(vector_set(FloatType(-0.0))); + + CHECK(FloatType(vector_get_x(result0)) == scalar_round_bankers(FloatType(vector_get_x(input0)))); + CHECK(FloatType(vector_get_y(result0)) == scalar_round_bankers(FloatType(vector_get_y(input0)))); + CHECK(FloatType(vector_get_z(result0)) == scalar_round_bankers(FloatType(vector_get_z(input0)))); + CHECK(FloatType(vector_get_w(result0)) == scalar_round_bankers(FloatType(vector_get_w(input0)))); + + CHECK(FloatType(vector_get_x(result1)) == scalar_round_bankers(FloatType(vector_get_x(input1)))); + CHECK(FloatType(vector_get_y(result1)) == scalar_round_bankers(FloatType(vector_get_y(input1)))); + CHECK(FloatType(vector_get_z(result1)) == scalar_round_bankers(FloatType(vector_get_z(input1)))); + CHECK(FloatType(vector_get_w(result1)) == scalar_round_bankers(FloatType(vector_get_w(input1)))); + + CHECK(FloatType(vector_get_x(result2)) == scalar_round_bankers(FloatType(vector_get_x(input2)))); + CHECK(FloatType(vector_get_y(result2)) == scalar_round_bankers(FloatType(vector_get_y(input2)))); + CHECK(FloatType(vector_get_z(result2)) == scalar_round_bankers(FloatType(vector_get_z(input2)))); + CHECK(FloatType(vector_get_w(result2)) == scalar_round_bankers(FloatType(vector_get_w(input2)))); + + CHECK(FloatType(vector_get_x(result3)) == scalar_round_bankers(FloatType(vector_get_x(input3)))); + CHECK(FloatType(vector_get_y(result3)) == scalar_round_bankers(FloatType(vector_get_y(input3)))); + CHECK(FloatType(vector_get_z(result3)) == scalar_round_bankers(FloatType(vector_get_z(input3)))); + CHECK(FloatType(vector_get_w(result3)) == scalar_round_bankers(FloatType(vector_get_w(input3)))); + + CHECK(FloatType(vector_get_x(result4)) == scalar_round_bankers(FloatType(vector_get_x(input4)))); + CHECK(FloatType(vector_get_y(result4)) == scalar_round_bankers(FloatType(vector_get_y(input4)))); + CHECK(FloatType(vector_get_z(result4)) == scalar_round_bankers(FloatType(vector_get_z(input4)))); + CHECK(FloatType(vector_get_w(result4)) == scalar_round_bankers(FloatType(vector_get_w(input4)))); + + CHECK(std::isnan(FloatType(vector_get_x(result5)))); + CHECK(std::isnan(FloatType(vector_get_y(result5)))); + CHECK(std::isnan(FloatType(vector_get_z(result5)))); + CHECK(std::isnan(FloatType(vector_get_w(result5)))); + + CHECK(FloatType(vector_get_x(result6)) == scalar_round_bankers(FloatType(vector_get_x(input6)))); + CHECK(FloatType(vector_get_y(result6)) == scalar_round_bankers(FloatType(vector_get_y(input6)))); + CHECK(FloatType(vector_get_z(result6)) == scalar_round_bankers(FloatType(vector_get_z(input6)))); + CHECK(FloatType(vector_get_w(result6)) == scalar_round_bankers(FloatType(vector_get_w(input6)))); + + CHECK(FloatType(vector_get_x(result7)) == scalar_round_symmetric(FloatType(-0.0))); + } + + { + const FloatType half_pi = FloatType(rtm::constants::half_pi()); + const FloatType pi = FloatType(rtm::constants::pi()); + + const FloatType angles[] = { FloatType(0.0), pi, -pi, half_pi, -half_pi, FloatType(0.5), FloatType(32.5), FloatType(-0.5), FloatType(-32.5) }; + + for (const FloatType angle : angles) + { + INFO("angle: " << angle); + + const Vector4Type angle_v = vector_set(angle); + + const FloatType ref_sin = scalar_sin(angle); + const FloatType ref_cos = scalar_cos(angle); + const FloatType ref_tan = scalar_tan(angle); + const Vector4Type ref_sincos = scalar_sincos(angle); + const FloatType ref_asin = scalar_asin(ref_sin); + const FloatType ref_acos = scalar_acos(ref_cos); + + const Vector4Type rtm_sin = vector_sin(angle_v); + const Vector4Type rtm_cos = vector_cos(angle_v); + Vector4Type rtm_sin2; + Vector4Type rtm_cos2; + vector_sincos(angle_v, rtm_sin2, rtm_cos2); + const Vector4Type rtm_tan = vector_tan(angle_v); + const Vector4Type rtm_asin = vector_asin(vector_set(ref_sin)); + const Vector4Type rtm_acos = vector_acos(vector_set(ref_cos)); + + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_sin)), ref_sin, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_sin)), ref_sin, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_sin)), ref_sin, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_sin)), ref_sin, threshold)); + + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_asin)), ref_asin, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_asin)), ref_asin, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_asin)), ref_asin, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_asin)), ref_asin, threshold)); + + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_cos)), ref_cos, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_cos)), ref_cos, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_cos)), ref_cos, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_cos)), ref_cos, threshold)); + + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_acos)), ref_acos, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_acos)), ref_acos, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_acos)), ref_acos, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_acos)), ref_acos, threshold)); + + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_sin2)), (FloatType)vector_get_x(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_sin2)), (FloatType)vector_get_x(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_sin2)), (FloatType)vector_get_x(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_sin2)), (FloatType)vector_get_x(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_cos2)), (FloatType)vector_get_y(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_cos2)), (FloatType)vector_get_y(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_cos2)), (FloatType)vector_get_y(ref_sincos), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_cos2)), (FloatType)vector_get_y(ref_sincos), threshold)); + + // For +-PI/2, we only test that the value is really large or really small + if (scalar_abs(angle) == half_pi) + { + CHECK(vector_all_greater_equal(vector_abs(rtm_tan), vector_set(FloatType(1.0e6)))); + } + else + { + CHECK(scalar_near_equal(FloatType(vector_get_x(rtm_tan)), ref_tan, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(rtm_tan)), ref_tan, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(rtm_tan)), ref_tan, threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(rtm_tan)), ref_tan, threshold)); + } + } + + const FloatType angles_atan[] = { FloatType(-10.0), FloatType(-5.0), FloatType(-0.5), FloatType(-0.25), FloatType(0.0), FloatType(0.25), FloatType(0.5), FloatType(0.75), FloatType(81.0) }; + for (const FloatType angle : angles_atan) + { + const Vector4Type angle_v = vector_set(angle); + + CHECK(scalar_near_equal(FloatType(vector_get_x(vector_atan(angle_v))), scalar_atan(angle), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(vector_atan(angle_v))), scalar_atan(angle), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(vector_atan(angle_v))), scalar_atan(angle), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(vector_atan(angle_v))), scalar_atan(angle), threshold)); + } + + const std::pair angles_atan2[] = + { + { FloatType(-2.0), FloatType(-2.0) }, + { FloatType(-1.0), FloatType(-2.0) }, + { FloatType(-2.0), FloatType(-1.0) }, + { FloatType(2.0), FloatType(2.0) }, + { FloatType(1.0), FloatType(2.0) }, + { FloatType(2.0), FloatType(1.0) }, + { FloatType(2.0), FloatType(0.0) }, + { FloatType(-2.0), FloatType(0.0) }, + { FloatType(2.0), FloatType(-0.0) }, + { FloatType(-2.0), FloatType(-0.0) }, + { FloatType(0.0), FloatType(2.0) }, + { FloatType(0.0), FloatType(-2.0) }, + { FloatType(-0.0), FloatType(2.0) }, + { FloatType(-0.0), FloatType(-2.0) }, + }; + for (const std::pair& angles_ : angles_atan2) + { + const Vector4Type angle_y = vector_set(angles_.first); + const Vector4Type angle_x = vector_set(angles_.second); + + CHECK(scalar_near_equal(FloatType(vector_get_x(vector_atan2(angle_y, angle_x))), scalar_atan2(angles_.first, angles_.second), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_y(vector_atan2(angle_y, angle_x))), scalar_atan2(angles_.first, angles_.second), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_z(vector_atan2(angle_y, angle_x))), scalar_atan2(angles_.first, angles_.second), threshold)); + CHECK(scalar_near_equal(FloatType(vector_get_w(vector_atan2(angle_y, angle_x))), scalar_atan2(angles_.first, angles_.second), threshold)); + } + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4d.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4d.cpp new file mode 100644 index 00000000..829e83ee --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4d.cpp @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_impl.h" + +TEST_CASE("vector4d math get/set", "[math][vector4]") +{ + test_vector4_getset_impl(); +} + +TEST_CASE("vector4d math arithmetic", "[math][vector4]") +{ + test_vector4_arithmetic_impl(1.0E-9); +} + +TEST_CASE("vector4d math relational", "[math][vector4]") +{ + test_vector4_relational_impl(1.0E-9); +} + +TEST_CASE("vector4d math logical", "[math][vector4]") +{ + test_vector4_logical_impl(); +} + +TEST_CASE("vector4d math misc", "[math][vector4]") +{ + test_vector4_impl(1.0E-9); + + const vector4d src = vector_set(-2.65, 2.996113, 0.68123521, -5.9182); + const vector4f dst = vector_cast(src); + CHECK(scalar_near_equal(vector_get_x(dst), -2.65F, 1.0E-6F)); + CHECK(scalar_near_equal(vector_get_y(dst), 2.996113F, 1.0E-6F)); + CHECK(scalar_near_equal(vector_get_z(dst), 0.68123521F, 1.0E-6F)); + CHECK(scalar_near_equal(vector_get_w(dst), -5.9182F, 1.0E-6F)); + + const vector4d large_values = vector_set(36028797018963968.5, 36028797018963968.5, -36028797018963968.5, -36028797018963968.5); + CHECK(double(vector_get_x(vector_floor(large_values))) == scalar_floor(double(vector_get_x(large_values)))); + CHECK(double(vector_get_y(vector_floor(large_values))) == scalar_floor(double(vector_get_y(large_values)))); + CHECK(double(vector_get_z(vector_floor(large_values))) == scalar_floor(double(vector_get_z(large_values)))); + CHECK(double(vector_get_w(vector_floor(large_values))) == scalar_floor(double(vector_get_w(large_values)))); + + CHECK(double(vector_get_x(vector_ceil(large_values))) == scalar_ceil(double(vector_get_x(large_values)))); + CHECK(double(vector_get_y(vector_ceil(large_values))) == scalar_ceil(double(vector_get_y(large_values)))); + CHECK(double(vector_get_z(vector_ceil(large_values))) == scalar_ceil(double(vector_get_z(large_values)))); + CHECK(double(vector_get_w(vector_ceil(large_values))) == scalar_ceil(double(vector_get_w(large_values)))); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4f.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4f.cpp new file mode 100644 index 00000000..8206b702 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/test_vector4f.cpp @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// Copyright (c) 2018 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_impl.h" + +TEST_CASE("vector4f math get/set", "[math][vector4]") +{ + test_vector4_getset_impl(); +} + +TEST_CASE("vector4f math arithmetic", "[math][vector4]") +{ +#if defined(RTM_NO_INTRINSICS) + const float threshold = 1.0E-4F; +#else + const float threshold = 1.0E-5F; +#endif + + test_vector4_arithmetic_impl(threshold); +} + +TEST_CASE("vector4f math relational", "[math][vector4]") +{ +#if defined(RTM_NO_INTRINSICS) + const float threshold = 1.0E-4F; +#else + const float threshold = 1.0E-5F; +#endif + + test_vector4_relational_impl(threshold); +} + +TEST_CASE("vector4f math logical", "[math][vector4]") +{ + test_vector4_logical_impl(); +} + +TEST_CASE("vector4f math misc", "[math][vector4]") +{ +#if defined(RTM_NO_INTRINSICS) + const float threshold = 1.0E-4F; +#else + const float threshold = 1.0E-5F; +#endif + + test_vector4_impl(threshold); + + const vector4f src = vector_set(-2.65F, 2.996113F, 0.68123521F, -5.9182F); + const vector4d dst = vector_cast(src); + CHECK(scalar_near_equal(vector_get_x(dst), -2.65, 1.0E-6)); + CHECK(scalar_near_equal(vector_get_y(dst), 2.996113, 1.0E-6)); + CHECK(scalar_near_equal(vector_get_z(dst), 0.68123521, 1.0E-6)); + CHECK(scalar_near_equal(vector_get_w(dst), -5.9182, 1.0E-6)); + + const vector4f large_values = vector_set(1073741824.5F, 1073741824.5F, -1073741824.5F, -1073741824.5F); + CHECK(float(vector_get_x(vector_floor(large_values))) == scalar_floor(float(vector_get_x(large_values)))); + CHECK(float(vector_get_y(vector_floor(large_values))) == scalar_floor(float(vector_get_y(large_values)))); + CHECK(float(vector_get_z(vector_floor(large_values))) == scalar_floor(float(vector_get_z(large_values)))); + CHECK(float(vector_get_w(vector_floor(large_values))) == scalar_floor(float(vector_get_w(large_values)))); + + CHECK(float(vector_get_x(vector_ceil(large_values))) == scalar_ceil(float(vector_get_x(large_values)))); + CHECK(float(vector_get_y(vector_ceil(large_values))) == scalar_ceil(float(vector_get_y(large_values)))); + CHECK(float(vector_get_z(vector_ceil(large_values))) == scalar_ceil(float(vector_get_z(large_values)))); + CHECK(float(vector_get_w(vector_ceil(large_values))) == scalar_ceil(float(vector_get_w(large_values)))); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4_mix_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4_mix_impl.h new file mode 100644 index 00000000..f79c40cc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4_mix_impl.h @@ -0,0 +1,97 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +using namespace rtm; + +template +void test_vector_mix_impl(const FloatType threshold) +{ + (void)threshold; + +#if defined(RTM_IMPL_WITH_VECTOR_MIX_TESTS) + const FloatType test_value0_flt[4] = { FloatType(2.0), FloatType(9.34), FloatType(-54.12), FloatType(6000.0) }; + const FloatType test_value1_flt[4] = { FloatType(0.75), FloatType(-4.52), FloatType(44.68), FloatType(-54225.0) }; + + const Vector4Type test_value0 = vector_set(test_value0_flt[0], test_value0_flt[1], test_value0_flt[2], test_value0_flt[3]); + const Vector4Type test_value1 = vector_set(test_value1_flt[0], test_value1_flt[1], test_value1_flt[2], test_value1_flt[3]); + + Vector4Type results[8 * 8]; + uint32_t index = 0; + +#define RTM_TEST_MIX_XYZ(comp0, comp1, comp2) \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1); \ + results[index++] = vector_mix(test_value0, test_value1) + +#define RTM_TEST_MIX_XY(comp0, comp1) \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::x); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::y); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::z); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::w); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::a); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::b); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::c); \ + RTM_TEST_MIX_XYZ(comp0, comp1, mix4::d) + + // This generates 8*8 = 64 unit tests... it takes a while to compile and uses a lot of stack space + RTM_TEST_MIX_XY(XArg, YArg); + + index = 0; + + const int comp0 = (int)XArg; + const int comp1 = (int)YArg; + for (int comp2 = 0; comp2 < 8; ++comp2) + { + for (int comp3 = 0; comp3 < 8; ++comp3) + { + INFO("vector_mix<" << comp0 << ", " << comp1 << ", " << comp2 << ", " << comp3 << ">"); + + const Vector4Type expected = vector_set( + rtm_impl::is_mix_xyzw((mix4)comp0) ? test_value0_flt[comp0 - (int)mix4::x] : test_value1_flt[comp0 - (int)mix4::a], + rtm_impl::is_mix_xyzw((mix4)comp1) ? test_value0_flt[comp1 - (int)mix4::x] : test_value1_flt[comp1 - (int)mix4::a], + rtm_impl::is_mix_xyzw((mix4)comp2) ? test_value0_flt[comp2 - (int)mix4::x] : test_value1_flt[comp2 - (int)mix4::a], + rtm_impl::is_mix_xyzw((mix4)comp3) ? test_value0_flt[comp3 - (int)mix4::x] : test_value1_flt[comp3 - (int)mix4::a]); + + CHECK(vector_all_near_equal(expected, results[index], threshold)); + + ++index; + } + } + +#undef RTM_TEST_MIX_XY +#undef RTM_TEST_MIX_XYZ +#endif // defined(RTM_IMPL_WITH_VECTOR_MIX_TESTS) +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_aa.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_aa.cpp new file mode 100644 index 00000000..f7d2654c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_aa.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ab.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ab.cpp new file mode 100644 index 00000000..99ea3165 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ab.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ac.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ac.cpp new file mode 100644 index 00000000..1a863a77 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ac.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ad.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ad.cpp new file mode 100644 index 00000000..0a6109a1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ad.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_aw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_aw.cpp new file mode 100644 index 00000000..22acbf69 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_aw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ax.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ax.cpp new file mode 100644 index 00000000..5568c350 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ax.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ay.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ay.cpp new file mode 100644 index 00000000..4c1b79f8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ay.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_az.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_az.cpp new file mode 100644 index 00000000..99d466b4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_az.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ba.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ba.cpp new file mode 100644 index 00000000..05f5a6de --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ba.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bb.cpp new file mode 100644 index 00000000..b1210931 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bc.cpp new file mode 100644 index 00000000..baba7ea0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bd.cpp new file mode 100644 index 00000000..0bbb30ed --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bw.cpp new file mode 100644 index 00000000..dfb7af31 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bx.cpp new file mode 100644 index 00000000..bd64cc41 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_by.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_by.cpp new file mode 100644 index 00000000..38ddeaff --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_by.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bz.cpp new file mode 100644 index 00000000..7c7fae58 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_bz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ca.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ca.cpp new file mode 100644 index 00000000..9c9448b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ca.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cb.cpp new file mode 100644 index 00000000..3542ce66 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cc.cpp new file mode 100644 index 00000000..d79732ee --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cd.cpp new file mode 100644 index 00000000..ed5a1409 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cw.cpp new file mode 100644 index 00000000..3cf64b6f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cx.cpp new file mode 100644 index 00000000..317ec25f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cy.cpp new file mode 100644 index 00000000..fdf9c3b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cz.cpp new file mode 100644 index 00000000..f16074a5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_cz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_da.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_da.cpp new file mode 100644 index 00000000..3db529e6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_da.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_db.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_db.cpp new file mode 100644 index 00000000..7ddbc635 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_db.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dc.cpp new file mode 100644 index 00000000..d5355b7d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dd.cpp new file mode 100644 index 00000000..10316de9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dw.cpp new file mode 100644 index 00000000..0f78a2da --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dx.cpp new file mode 100644 index 00000000..b06dedaa --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dy.cpp new file mode 100644 index 00000000..b69b92dd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dz.cpp new file mode 100644 index 00000000..d5968bf2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_dz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wa.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wa.cpp new file mode 100644 index 00000000..9ef15cbb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wa.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wb.cpp new file mode 100644 index 00000000..84f1a3e6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wc.cpp new file mode 100644 index 00000000..208d929a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wd.cpp new file mode 100644 index 00000000..d67f1935 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ww.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ww.cpp new file mode 100644 index 00000000..83d7bbaf --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ww.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wx.cpp new file mode 100644 index 00000000..eb913c69 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wy.cpp new file mode 100644 index 00000000..3012dde3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wz.cpp new file mode 100644 index 00000000..2dd27e94 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_wz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xa.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xa.cpp new file mode 100644 index 00000000..6672ee6f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xa.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xb.cpp new file mode 100644 index 00000000..bde9a4f4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xc.cpp new file mode 100644 index 00000000..6cdab307 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xd.cpp new file mode 100644 index 00000000..ae41c9cb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xw.cpp new file mode 100644 index 00000000..ba844153 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xx.cpp new file mode 100644 index 00000000..30ab9d74 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xy.cpp new file mode 100644 index 00000000..dc4fab22 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xz.cpp new file mode 100644 index 00000000..75da3525 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_xz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ya.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ya.cpp new file mode 100644 index 00000000..0ad0f437 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_ya.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yb.cpp new file mode 100644 index 00000000..d517d0bc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yc.cpp new file mode 100644 index 00000000..c1538d1d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yd.cpp new file mode 100644 index 00000000..cfaa9bba --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yw.cpp new file mode 100644 index 00000000..9a11c073 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yx.cpp new file mode 100644 index 00000000..dc5208a8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yy.cpp new file mode 100644 index 00000000..fb6efe21 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yz.cpp new file mode 100644 index 00000000..09636ef6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_yz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_za.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_za.cpp new file mode 100644 index 00000000..595d04c3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_za.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zb.cpp new file mode 100644 index 00000000..8e2e5dae --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zc.cpp new file mode 100644 index 00000000..a61a6882 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zd.cpp new file mode 100644 index 00000000..e612d102 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zw.cpp new file mode 100644 index 00000000..0edf28f8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zx.cpp new file mode 100644 index 00000000..a82d4e48 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zy.cpp new file mode 100644 index 00000000..6699b1e0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zz.cpp new file mode 100644 index 00000000..00d171c3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4d_mix_zz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4d vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_aa.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_aa.cpp new file mode 100644 index 00000000..f0b52da8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_aa.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ab.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ab.cpp new file mode 100644 index 00000000..84733eb9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ab.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ac.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ac.cpp new file mode 100644 index 00000000..71c4266e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ac.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ad.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ad.cpp new file mode 100644 index 00000000..0c86e6db --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ad.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_aw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_aw.cpp new file mode 100644 index 00000000..f3d8c216 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_aw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ax.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ax.cpp new file mode 100644 index 00000000..95b1e794 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ax.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ay.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ay.cpp new file mode 100644 index 00000000..a786f1fd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ay.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_az.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_az.cpp new file mode 100644 index 00000000..6ea6ee31 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_az.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ba.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ba.cpp new file mode 100644 index 00000000..05b84f37 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ba.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bb.cpp new file mode 100644 index 00000000..f0ee13c2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bc.cpp new file mode 100644 index 00000000..5f1cacf8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bd.cpp new file mode 100644 index 00000000..efd9ada9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bw.cpp new file mode 100644 index 00000000..98aab323 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bx.cpp new file mode 100644 index 00000000..c7220693 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_by.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_by.cpp new file mode 100644 index 00000000..2c8f849b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_by.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bz.cpp new file mode 100644 index 00000000..0584a409 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_bz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ca.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ca.cpp new file mode 100644 index 00000000..a27b6aa1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ca.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cb.cpp new file mode 100644 index 00000000..179fd51b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cc.cpp new file mode 100644 index 00000000..f72e8e67 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cd.cpp new file mode 100644 index 00000000..95d86d9f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cw.cpp new file mode 100644 index 00000000..d436eafd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cx.cpp new file mode 100644 index 00000000..fd57c416 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cy.cpp new file mode 100644 index 00000000..b9af70fb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cz.cpp new file mode 100644 index 00000000..f57d9eb5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_cz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_da.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_da.cpp new file mode 100644 index 00000000..02a7f8ff --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_da.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_db.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_db.cpp new file mode 100644 index 00000000..d3c651b2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_db.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dc.cpp new file mode 100644 index 00000000..c5f8ea27 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dd.cpp new file mode 100644 index 00000000..c4920de3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dw.cpp new file mode 100644 index 00000000..4b70bba7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dx.cpp new file mode 100644 index 00000000..d817bb96 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dy.cpp new file mode 100644 index 00000000..62187c56 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dz.cpp new file mode 100644 index 00000000..30161cb2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_dz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wa.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wa.cpp new file mode 100644 index 00000000..1ff21ead --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wa.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wb.cpp new file mode 100644 index 00000000..a01e651b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wc.cpp new file mode 100644 index 00000000..0d48896a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wd.cpp new file mode 100644 index 00000000..0287bb76 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ww.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ww.cpp new file mode 100644 index 00000000..c53e49a6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ww.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wx.cpp new file mode 100644 index 00000000..aa0d469c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wy.cpp new file mode 100644 index 00000000..aab94331 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wz.cpp new file mode 100644 index 00000000..91af8f78 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_wz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xa.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xa.cpp new file mode 100644 index 00000000..5e072b64 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xa.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xb.cpp new file mode 100644 index 00000000..e04f7af3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xc.cpp new file mode 100644 index 00000000..252897df --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xd.cpp new file mode 100644 index 00000000..568fabf2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xw.cpp new file mode 100644 index 00000000..9da0a382 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xx.cpp new file mode 100644 index 00000000..09955983 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xy.cpp new file mode 100644 index 00000000..6c77de1b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xz.cpp new file mode 100644 index 00000000..8ada3156 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_xz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ya.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ya.cpp new file mode 100644 index 00000000..e5ae5b09 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_ya.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yb.cpp new file mode 100644 index 00000000..c8dfb90e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yc.cpp new file mode 100644 index 00000000..b9382150 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yd.cpp new file mode 100644 index 00000000..9b93bbcc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yw.cpp new file mode 100644 index 00000000..57b3fb5e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yx.cpp new file mode 100644 index 00000000..4ca263f3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yy.cpp new file mode 100644 index 00000000..b9bfc134 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yz.cpp new file mode 100644 index 00000000..e3915ede --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_yz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_za.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_za.cpp new file mode 100644 index 00000000..91a71fc1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_za.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zb.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zb.cpp new file mode 100644 index 00000000..d5e7de9d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zb.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zc.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zc.cpp new file mode 100644 index 00000000..3ce71aa4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zc.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zd.cpp new file mode 100644 index 00000000..446520fc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zd.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zw.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zw.cpp new file mode 100644 index 00000000..41bf5d28 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zw.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zx.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zx.cpp new file mode 100644 index 00000000..7f47b704 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zx.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zy.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zy.cpp new file mode 100644 index 00000000..ce337bde --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zz.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zz.cpp new file mode 100644 index 00000000..968e3c58 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tests/sources/vector_mix/test_vector4f_mix_zz.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "test_vector4_mix_impl.h" + +TEST_CASE("vector4f vector_mix", "[math][vector4]") +{ + test_vector_mix_impl(1.0E-6F); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/README.md new file mode 100644 index 00000000..bb279bb5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/README.md @@ -0,0 +1,3 @@ +# Android Misc + +This directory contains various Android related files that are shared between the various projects. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/build.gradle.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/build.gradle.in new file mode 100644 index 00000000..1858cd5d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/build.gradle.in @@ -0,0 +1,57 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "@RTM_ANDROID_PROJECT_PACKAGE_NAME@" + minSdkVersion 24 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + arguments '-DUSE_SIMD_INSTRUCTIONS=@USE_SIMD_INSTRUCTIONS@', + '-DWITH_VECTOR_MIX_TESTS=@WITH_VECTOR_MIX_TESTS@' + } + } + ndk { + abiFilters '@CPU_INSTRUCTION_SET@' + } + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + debuggable = true + signingConfig signingConfigs.debug + externalNativeBuild { + cmake { + // Because we want to debug 'release' builds, the 'debuggable' property above + // forces the build configuration to 'Debug' which disables optimizations. + // Force a release configutation anyway. + arguments '-DCMAKE_BUILD_TYPE=Release' + } + } + } + } + externalNativeBuild { + cmake { + path '@RTM_PROJECT_ROOT@/src/main/cpp/CMakeLists.txt' + } + } + sourceSets { + main { + manifest.srcFile './src/main/AndroidManifest.xml' + java.srcDirs = ['@RTM_PROJECT_ROOT@/src/main/java'] + res.srcDirs = ['./src/main/res'] + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/proguard-rules.pro b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/src/main/AndroidManifest.xml.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/src/main/AndroidManifest.xml.in new file mode 100644 index 00000000..7a87a5c6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/src/main/AndroidManifest.xml.in @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/src/main/res/values/strings.xml.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/src/main/res/values/strings.xml.in new file mode 100644 index 00000000..5585fad8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/app/src/main/res/values/strings.xml.in @@ -0,0 +1,4 @@ + + + @RTM_ANDROID_PROJECT_NAME@ + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/build.gradle b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/build.gradle new file mode 100644 index 00000000..1606295a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/build.gradle @@ -0,0 +1,25 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle.properties new file mode 100644 index 00000000..fe2852f9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + +# Run tasks in parallel +org.gradle.parallel=true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle/wrapper/gradle-wrapper.jar b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle/wrapper/gradle-wrapper.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..656c0945 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Mar 27 21:02:15 EDT 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradlew b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradlew.bat b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/settings.gradle.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/settings.gradle.in new file mode 100644 index 00000000..d4f1299d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/android_misc/settings.gradle.in @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='@RTM_ANDROID_PROJECT_NAME@' diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/appveyor_ci.bat b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/appveyor_ci.bat new file mode 100644 index 00000000..218ec126 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/appveyor_ci.bat @@ -0,0 +1,60 @@ +@echo off + +REM Unpack arguments +SET WORKER_IMAGE=%1 +SET PLATFORM=%2 +SET CONFIG=%3 +SET TOOLCHAIN=%4 +SET PYTHON_PATH=%5 + +echo Worker image: %WORKER_IMAGE% +echo Platform: %PLATFORM% +echo Config: %CONFIG% +echo Toolchain: %TOOLCHAIN% +echo Python path: %PYTHON_PATH% + +REM Convert the build image and toolchain into our compiler string +IF /i %TOOLCHAIN%==msvc GOTO :msvc +IF /i %TOOLCHAIN%==clang GOTO :clang + +echo Unknown toolchain: %TOOLCHAIN% +exit /B 1 + +:msvc +IF /i %WORKER_IMAGE%=="Visual Studio 2015" SET COMPILER=vs2015 +IF /i %WORKER_IMAGE%=="Visual Studio 2017" SET COMPILER=vs2017 +IF /i %WORKER_IMAGE%=="Visual Studio 2019" SET COMPILER=vs2019 +IF /i %WORKER_IMAGE%=="Previous Visual Studio 2019" SET COMPILER=vs2019 +GOTO :next + +:clang +IF /i %WORKER_IMAGE%=="Visual Studio 2019" SET COMPILER=vs2019-clang +IF /i %WORKER_IMAGE%=="Previous Visual Studio 2019" SET COMPILER=vs2019-clang + +REM HACK!!! Disable clang build for now with appveyor since vcpkg breaks the compiler detection of cmake +REM Fake build success +exit /B 0 + +GOTO :next + +:next +REM Set our switch if we need to run unit tests +SET UNIT_TEST_FLAG=-unit_test +IF /i %PLATFORM%==arm64 SET UNIT_TEST_FLAG= + +REM If PYTHON_PATH isn't set, assume it is in PATH +IF NOT DEFINED PYTHON_PATH SET PYTHON_PATH=python.exe + +REM Build and run unit tests +%PYTHON_PATH% make.py -build %UNIT_TEST_FLAG% -compiler %COMPILER% -config %CONFIG% -cpu %PLATFORM% -clean +IF NOT %ERRORLEVEL% EQU 0 GOTO :build_failure + +%PYTHON_PATH% make.py -build %UNIT_TEST_FLAG% -compiler %COMPILER% -config %CONFIG% -cpu %PLATFORM% -nosimd +IF NOT %ERRORLEVEL% EQU 0 GOTO :build_failure + +REM Done! +exit /B 0 + +:build_failure +echo Build failed! +exit /B 1 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/CMakeLists.txt new file mode 100644 index 00000000..c23bb924 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.2) +project(rtm_bench_root NONE) + +if(PLATFORM_ANDROID) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_android") +elseif(PLATFORM_IOS) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_ios") +else() + add_subdirectory("${PROJECT_SOURCE_DIR}/main_generic") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/CMakeLists.txt new file mode 100644 index 00000000..b9fa451f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.2) +project(rtm_bench_gradle_shim NONE) + +# Set our project root since our gradle files used to build live in the binary output directory +# but the actual source files live in the source/current directory. +set(RTM_PROJECT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/app) +file(RELATIVE_PATH RTM_PROJECT_ROOT ${RTM_PROJECT_ROOT} ${PROJECT_SOURCE_DIR}/app) + +# Setup our state +set(RTM_ANDROID_PROJECT_NAME "RTM Benchmark") +set(RTM_ANDROID_PROJECT_PACKAGE_NAME "com.rtm.benchmark") + +# Configure gradle for our build configuration +configure_file(${RTM_ANDROID_MISC_DIR}/app/build.gradle.in app/build.gradle @ONLY) +configure_file(${RTM_ANDROID_MISC_DIR}/app/src/main/AndroidManifest.xml.in app/src/main/AndroidManifest.xml @ONLY) +configure_file(${RTM_ANDROID_MISC_DIR}/app/src/main/res/values/strings.xml.in app/src/main/res/values/strings.xml @ONLY) +configure_file(${RTM_ANDROID_MISC_DIR}/settings.gradle.in settings.gradle @ONLY) + +# Copy gradle related files +file(COPY + ${RTM_ANDROID_MISC_DIR}/build.gradle + ${RTM_ANDROID_MISC_DIR}/gradle.properties + ${RTM_ANDROID_MISC_DIR}/gradle + ${RTM_ANDROID_MISC_DIR}/gradlew + ${RTM_ANDROID_MISC_DIR}/gradlew.bat + DESTINATION .) + +file(COPY + ${RTM_ANDROID_MISC_DIR}/app/proguard-rules.pro + DESTINATION app) + +add_custom_target(${PROJECT_NAME} ALL + COMMAND gradlew.bat + # Decide whether we should build Debug or Release + $<$:assembleDebug> + $<$:assembleRelease>) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..101bdb54 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.2) +project(rtm_bench CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Project root is \tools\bench\main_android +set(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") + +# Google Benchmark +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "No need to run benchmark's tests" FORCE) +add_subdirectory("${PROJECT_ROOT_DIR}/../../../external/benchmark" google_benchmark) + +include_directories("${PROJECT_ROOT_DIR}/../../../includes") +include_directories("${PROJECT_ROOT_DIR}/../../../external/benchmark/include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_BENCH_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_ROOT_DIR}/../sources/*.h + ${PROJECT_ROOT_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_library(${PROJECT_NAME} SHARED ${ALL_BENCH_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Enable debug symbols +target_compile_options(${PROJECT_NAME} PRIVATE -g) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +target_include_directories(${PROJECT_NAME} PUBLIC jni) + +target_link_libraries(${PROJECT_NAME} m log) +target_link_libraries(${PROJECT_NAME} benchmark) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/cpp/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/cpp/main.cpp new file mode 100644 index 00000000..88b1019f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/cpp/main.cpp @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include + +// Inspired from https://stackoverflow.com/questions/8870174/is-stdcout-usable-in-android-ndk +class androidbuf final : public std::streambuf +{ +public: + enum { bufsize = 4096 }; + androidbuf() + { + this->setp(buffer, buffer + bufsize - 1); + } + +private: + int overflow(int c) + { + if (c == traits_type::eof()) + { + *this->pptr() = traits_type::to_char_type(c); + this->sbumpc(); + } + return this->sync() ? traits_type::eof() : traits_type::not_eof(c); + } + + int sync() + { + int rc = 0; + if (this->pbase() != this->pptr()) + { + char writebuf[bufsize + 1]; + memcpy(writebuf, this->pbase(), this->pptr() - this->pbase()); + writebuf[this->pptr() - this->pbase()] = '\0'; + + rc = __android_log_write(ANDROID_LOG_INFO, "rtm", writebuf) > 0; + this->setp(buffer, buffer + bufsize - 1); + } + return rc; + } + + char buffer[bufsize]; +}; + +extern "C" jint Java_com_rtm_benchmark_MainActivity_runBenchmark(JNIEnv* env, jobject caller) +{ + std::cout.rdbuf(new androidbuf()); + + int argc = 0; + benchmark::Initialize(&argc, nullptr); + + benchmark::RunSpecifiedBenchmarks(); + + return 0; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/java/com/rtm/benchmark/MainActivity.java b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/java/com/rtm/benchmark/MainActivity.java new file mode 100644 index 00000000..9ce18b5f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_android/app/src/main/java/com/rtm/benchmark/MainActivity.java @@ -0,0 +1,51 @@ +package com.rtm.benchmark; + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class MainActivity extends Activity { + static { + System.loadLibrary("rtm_bench"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView resultTextView = new TextView(this); + + int result = runBenchmark(); + + if (result == 0) + resultTextView.setText("All benchmarks ran successfully!"); + + setContentView(resultTextView); + } + + public native int runBenchmark(); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_generic/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_generic/CMakeLists.txt new file mode 100644 index 00000000..d38a57aa --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_generic/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required (VERSION 3.2) +project(rtm_bench CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Google Benchmark +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "No need to run benchmark's tests" FORCE) +add_subdirectory("${PROJECT_SOURCE_DIR}/../../../external/benchmark" google_benchmark) + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/benchmark/include") + +# Grab all of our benchmark source files +file(GLOB_RECURSE ALL_BENCH_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_BENCH_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} ${ALL_BENCH_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +setup_default_compiler_flags(${PROJECT_NAME}) +target_link_libraries(${PROJECT_NAME} PRIVATE benchmark) # Link Google Benchmark + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_generic/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_generic/main.cpp new file mode 100644 index 00000000..bd37ef5a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_generic/main.cpp @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#ifdef _WIN32 +#include + +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); +#endif + +int main(int argc, char* argv[]) +{ + benchmark::Initialize(&argc, argv); + + benchmark::RunSpecifiedBenchmarks(); + +#ifdef _WIN32 + if (IsDebuggerPresent()) + { + printf("Press any key to continue...\n"); + while (_kbhit() == 0); + } +#endif + + return 0; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_ios/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_ios/CMakeLists.txt new file mode 100644 index 00000000..27bb792e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_ios/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required (VERSION 3.2) +project(rtm_bench) + +# Google Benchmark +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "No need to run benchmark's tests" FORCE) +add_subdirectory("${PROJECT_SOURCE_DIR}/../../../external/benchmark" google_benchmark) + +# iOS cmake toolchain does not support CMAKE_CXX_STANDARD +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Force enable debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + +# Enable optimizations in Release +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +set(MACOSX_BUNDLE_EXECUTABLE_NAME ${PROJECT_NAME}) +set(MACOSX_BUNDLE_INFO_STRING "com.rtm.rtm-bench") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.rtm.rtm-bench") +set(MACOSX_BUNDLE_BUNDLE_NAME "rtm-bench") + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/benchmark/include") + +# Grab all of our benchmark source files +file(GLOB_RECURSE ALL_BENCH_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_BENCH_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ALL_BENCH_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} PRIVATE benchmark) # Link Google Benchmark + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_ios/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_ios/main.cpp new file mode 100644 index 00000000..c1be95f5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/main_ios/main.cpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +int main(int argc, char* argv[]) +{ + benchmark::Initialize(&argc, argv); + + benchmark::RunSpecifiedBenchmarks(); + + return 0; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_conjugate.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_conjugate.cpp new file mode 100644 index 00000000..c8083bbd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_conjugate.cpp @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Very odd but consistent result. mul/xor have about the same performance in this profile +// but xor should be faster. +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_conjugate_scalar(quatf_arg0 input) RTM_NO_EXCEPT +{ + return quat_set(-quat_get_x(input), -quat_get_y(input), -quat_get_z(input), quat_get_w(input)); +} + +// Wins on Haswell laptop x64 AVX +// It seems that on haswell, xor incurs a domain switch penalty and is slower. +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_conjugate_mul(quatf_arg0 input) RTM_NO_EXCEPT +{ +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -1.0f, -1.0f, -1.0f, 1.0f }; + return _mm_mul_ps(input, signs); +#elif defined(RTM_NEON_INTRINSICS) + alignas(16) constexpr float signs_f[4] = { -1.0f, -1.0f, -1.0f, 1.0f }; + const float32x4_t signs = *reinterpret_cast(&signs_f[0]); + return vmulq_f32(input, signs); +#else + return quat_set(quat_get_x(input) * -1.0f, quat_get_y(input) * -1.0f, quat_get_z(input) * -1.0f, quat_get_w(input)); +#endif +} + +#if defined(RTM_SSE2_INTRINSICS) || defined(RTM_NEON_INTRINSICS) +// Wins on iPad Pro ARM64 +// Wins on Pixel 3 ARM64 +// mul/XOR/neg all end up taking 3 instructions, all dependent but XOR is fastest. +// Wins on Ryzen 2990X desktop clang9 x64 AVX +// Performance is about the same as mul. +// Wins on Samsung S8 ARM64 +// Same performance as mul/neg +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_conjugate_xor(quatf_arg0 input) RTM_NO_EXCEPT +{ +#if defined(RTM_SSE2_INTRINSICS) + constexpr __m128 signs = { -0.0f, -0.0f, -0.0f, 0.0f }; + return _mm_xor_ps(input, signs); +#elif defined(RTM_NEON_INTRINSICS) + alignas(16) constexpr uint32_t signs_u[4] = { 0x80000000U, 0x80000000U, 0x80000000U, 0 }; + const uint32x4_t signs = *reinterpret_cast(&signs_u[0]); + return vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(input), signs)); +#else + #error not implemented +#endif +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +// Wins on Pixel 3 ARMv7 +// mul/XOR/neg all end up taking 3 instructions, all dependent but neg is fastest. +// Wins on Samsung S8 ARMv7 +// XOR/neg/scalar all have the same performance +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_conjugate_neg(quatf_arg0 input) RTM_NO_EXCEPT +{ + const float32x4_t neg_input = vnegq_f32(input); + return vsetq_lane_f32(vgetq_lane_f32(input, 3), neg_input, 3); +} +#endif + +static void bm_quat_conjugate_scalar(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_conjugate_scalar(q0); + q1 = quat_conjugate_scalar(q1); + q2 = quat_conjugate_scalar(q2); + q3 = quat_conjugate_scalar(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_conjugate_scalar); + +static void bm_quat_conjugate_mul(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_conjugate_mul(q0); + q1 = quat_conjugate_mul(q1); + q2 = quat_conjugate_mul(q2); + q3 = quat_conjugate_mul(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_conjugate_mul); + +#if defined(RTM_SSE2_INTRINSICS) || defined(RTM_NEON_INTRINSICS) +static void bm_quat_conjugate_xor(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_conjugate_xor(q0); + q1 = quat_conjugate_xor(q1); + q2 = quat_conjugate_xor(q2); + q3 = quat_conjugate_xor(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_conjugate_xor); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_quat_conjugate_neg(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_conjugate_neg(q0); + q1 = quat_conjugate_neg(q1); + q2 = quat_conjugate_neg(q2); + q3 = quat_conjugate_neg(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_conjugate_neg); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_from_positive_w.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_from_positive_w.cpp new file mode 100644 index 00000000..ce4ef465 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_from_positive_w.cpp @@ -0,0 +1,261 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +// Wins on Samsung S8 ARMv7 +// Just a bit faster than neon +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_from_positive_w_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Operation order is important here, due to rounding, ((1.0 - (X*X)) - Y*Y) - Z*Z is more accurate than 1.0 - dot3(xyz, xyz) + float w_squared = ((1.0F - vector_get_x(input) * vector_get_x(input)) - vector_get_y(input) * vector_get_y(input)) - vector_get_z(input) * vector_get_z(input); + // w_squared can be negative either due to rounding or due to quantization imprecision, we take the absolute value + // to ensure the resulting quaternion is always normalized with a positive W component + float w = scalar_sqrt(scalar_abs(w_squared)); + return quat_set_w(vector_to_quat(input), w); +} + +#if defined(RTM_SSE4_INTRINSICS) +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_from_positive_w_sse4_andnot(vector4f_arg0 input) RTM_NO_EXCEPT +{ + __m128 x2y2z2 = _mm_mul_ps(input, input); + __m128 one = _mm_set_ss(1.0F); + __m128 w_squared = _mm_sub_ss(_mm_sub_ss(_mm_sub_ss(one, x2y2z2), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(1, 1, 1, 1))), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(2, 2, 2, 2))); + w_squared = _mm_andnot_ps(_mm_set_ss(-0.0F), w_squared); + __m128 w = _mm_sqrt_ss(w_squared); + return _mm_insert_ps(input, w, 0x30); +} + +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_from_positive_w_sse4_and(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + + __m128 x2y2z2 = _mm_mul_ps(input, input); + __m128 one = _mm_set_ss(1.0F); + __m128 w_squared = _mm_sub_ss(_mm_sub_ss(_mm_sub_ss(one, x2y2z2), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(1, 1, 1, 1))), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(2, 2, 2, 2))); + w_squared = _mm_and_ps(w_squared, _mm_castsi128_ps(abs_mask)); + __m128 w = _mm_sqrt_ss(w_squared); + return _mm_insert_ps(input, w, 0x30); +} +#endif + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Haswell laptop x64 AVX +// The various variants are almost all the same. +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// By all accounts, quat_from_positive_w_sse4_and should be faster since it uses nearly the same code +// except that it needs 1 instruction instead of 2 to set the W component. It ends up being dramatically +// slower because VS2017 decides to use XMM6 and spills it on the stack instead of using the volatile XMM5 +// register which is unused and doesn't need spilling... +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_from_positive_w_sse2_and(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + + __m128 x2y2z2 = _mm_mul_ps(input, input); + __m128 one = _mm_set_ss(1.0F); + __m128 w_squared = _mm_sub_ss(_mm_sub_ss(_mm_sub_ss(one, x2y2z2), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(1, 1, 1, 1))), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(2, 2, 2, 2))); + w_squared = _mm_and_ps(w_squared, _mm_castsi128_ps(abs_mask)); + __m128 w = _mm_sqrt_ss(w_squared); + __m128 input_wyzx = _mm_shuffle_ps(input, input, _MM_SHUFFLE(0, 2, 1, 3)); + __m128 result_wyzx = _mm_move_ss(input_wyzx, w); + return _mm_shuffle_ps(result_wyzx, result_wyzx, _MM_SHUFFLE(0, 2, 1, 3)); +} + +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_from_positive_w_sse2_and2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + + __m128 x2y2z2 = _mm_mul_ps(input, input); + __m128 one = _mm_set_ss(1.0F); + __m128 w_squared = _mm_sub_ss(_mm_sub_ss(_mm_sub_ss(one, x2y2z2), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(1, 1, 1, 1))), _mm_shuffle_ps(x2y2z2, x2y2z2, _MM_SHUFFLE(2, 2, 2, 2))); + w_squared = _mm_and_ps(w_squared, _mm_castsi128_ps(abs_mask)); + __m128 w = _mm_sqrt_ss(w_squared); + __m128 zzww = _mm_shuffle_ps(input, w, _MM_SHUFFLE(0, 0, 2, 2)); + return _mm_shuffle_ps(input, zzww, _MM_SHUFFLE(2, 0, 1, 0)); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +// Wins on iPad Pro ARM64 +// Same perf as ref +// Wins on Pixel 3 ARM64 +// Wins on Pixel 3 ARMv7 +// Wins on Samsung S8 ARM64 +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_from_positive_w_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + float32x4_t x2y2z2 = vmulq_f32(input, input); + float w_squared = ((1.0F - vgetq_lane_f32(x2y2z2, 0)) - vgetq_lane_f32(x2y2z2, 1)) - vgetq_lane_f32(x2y2z2, 2); + float w = rtm::scalar_sqrt(rtm::scalar_abs(w_squared)); + return vsetq_lane_f32(w, input, 3); +} +#endif + +static void bm_quat_from_positive_w_scalar(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_from_positive_w_scalar(quat_to_vector(q0)); + q1 = quat_from_positive_w_scalar(quat_to_vector(q1)); + q2 = quat_from_positive_w_scalar(quat_to_vector(q2)); + q3 = quat_from_positive_w_scalar(quat_to_vector(q3)); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_from_positive_w_scalar); + +#if defined(RTM_SSE4_INTRINSICS) +static void bm_quat_from_positive_w_sse4_andnot(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_from_positive_w_sse4_andnot(q0); + q1 = quat_from_positive_w_sse4_andnot(q1); + q2 = quat_from_positive_w_sse4_andnot(q2); + q3 = quat_from_positive_w_sse4_andnot(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_from_positive_w_sse4_andnot); + +static void bm_quat_from_positive_w_sse4_and(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_from_positive_w_sse4_and(q0); + q1 = quat_from_positive_w_sse4_and(q1); + q2 = quat_from_positive_w_sse4_and(q2); + q3 = quat_from_positive_w_sse4_and(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_from_positive_w_sse4_and); +#endif + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_quat_from_positive_w_sse2_and(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_from_positive_w_sse2_and(q0); + q1 = quat_from_positive_w_sse2_and(q1); + q2 = quat_from_positive_w_sse2_and(q2); + q3 = quat_from_positive_w_sse2_and(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_from_positive_w_sse2_and); + +static void bm_quat_from_positive_w_sse2_and2(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_from_positive_w_sse2_and2(q0); + q1 = quat_from_positive_w_sse2_and2(q1); + q2 = quat_from_positive_w_sse2_and2(q2); + q3 = quat_from_positive_w_sse2_and2(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_from_positive_w_sse2_and2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_quat_from_positive_w_neon(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_from_positive_w_neon(q0); + q1 = quat_from_positive_w_neon(q1); + q2 = quat_from_positive_w_neon(q2); + q3 = quat_from_positive_w_neon(q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_from_positive_w_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_mul.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_mul.cpp new file mode 100644 index 00000000..d949ebc0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_mul.cpp @@ -0,0 +1,486 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_scalar(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + const float lhs_x = quat_get_x(lhs); + const float lhs_y = quat_get_y(lhs); + const float lhs_z = quat_get_z(lhs); + const float lhs_w = quat_get_w(lhs); + + const float rhs_x = quat_get_x(rhs); + const float rhs_y = quat_get_y(rhs); + const float rhs_z = quat_get_z(rhs); + const float rhs_w = quat_get_w(rhs); + + const float x = (rhs_w * lhs_x) + (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + const float y = (rhs_w * lhs_y) - (rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + const float z = (rhs_w * lhs_z) + (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + const float w = (rhs_w * lhs_w) - (rhs_x * lhs_x) - (rhs_y * lhs_y) - (rhs_z * lhs_z); + + return quat_set(x, y, z, w); +} + +#if defined(RTM_FMA_INTRINSICS) +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_fma_mul(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + constexpr __m128 control_wzyx = { 1.0f,-1.0f, 1.0f,-1.0f }; + constexpr __m128 control_zwxy = { 1.0f, 1.0f,-1.0f,-1.0f }; + constexpr __m128 control_yxwz = { -1.0f, 1.0f, 1.0f,-1.0f }; + + const __m128 r_xxxx = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 r_yyyy = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 r_zzzz = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(2, 2, 2, 2)); + const __m128 r_wwww = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 3, 3, 3)); + + const __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, lhs); + const __m128 l_wzyx = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, l_wzyx); + const __m128 l_zwxy = _mm_shuffle_ps(l_wzyx, l_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, l_zwxy); + const __m128 l_yxwz = _mm_shuffle_ps(l_zwxy, l_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, l_yxwz); + const __m128 result0 = _mm_fmadd_ps(lwrx_lzrx_lyrx_lxrx, control_wzyx, lxrw_lyrw_lzrw_lwrw); + const __m128 result1 = _mm_fmadd_ps(lzry_lwry_lxry_lyry, control_zwxy, result0); + return _mm_fmadd_ps(lyrz_lxrz_lwrz_lzrz, control_yxwz, result1); +} + +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_fma_xor(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + constexpr __m128 control_wzyx = { 0.0f,-0.0f, 0.0f,-0.0f }; + constexpr __m128 control_zwxy = { 0.0f, 0.0f,-0.0f,-0.0f }; + constexpr __m128 control_yxwz = { -0.0f, 0.0f, 0.0f,-0.0f }; + + const __m128 r_xxxx = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 r_yyyy = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 r_zzzz = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(2, 2, 2, 2)); + const __m128 r_wwww = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 3, 3, 3)); + + const __m128 l_wzyx = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, l_wzyx); + const __m128 l_zwxy = _mm_shuffle_ps(l_wzyx, l_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + + const __m128 lwrx_nlzrx_lyrx_nlxrx = _mm_xor_ps(lwrx_lzrx_lyrx_lxrx, control_wzyx); + + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, l_zwxy); + const __m128 l_yxwz = _mm_shuffle_ps(l_zwxy, l_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lzry_lwry_nlxry_nlyry = _mm_xor_ps(lzry_lwry_lxry_lyry, control_zwxy); + + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, l_yxwz); + const __m128 result0 = _mm_fmadd_ps(r_wwww, lhs, lwrx_nlzrx_lyrx_nlxrx); + + const __m128 nlyrz_lxrz_lwrz_wlzrz = _mm_xor_ps(lyrz_lxrz_lwrz_lzrz, control_yxwz); + const __m128 result1 = _mm_add_ps(lzry_lwry_nlxry_nlyry, nlyrz_lxrz_lwrz_wlzrz); + return _mm_add_ps(result0, result1); +} +#endif + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Haswell laptop x64 AVX +// It seems that on haswell, xor incurs a domain switch penalty and is slower. +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_sse_mul(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + constexpr __m128 control_wzyx = { 1.0f,-1.0f, 1.0f,-1.0f }; + constexpr __m128 control_zwxy = { 1.0f, 1.0f,-1.0f,-1.0f }; + constexpr __m128 control_yxwz = { -1.0f, 1.0f, 1.0f,-1.0f }; + + const __m128 r_xxxx = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 r_yyyy = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 r_zzzz = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(2, 2, 2, 2)); + const __m128 r_wwww = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 3, 3, 3)); + + const __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, lhs); + const __m128 l_wzyx = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, l_wzyx); + const __m128 l_zwxy = _mm_shuffle_ps(l_wzyx, l_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + + const __m128 lwrx_nlzrx_lyrx_nlxrx = _mm_mul_ps(lwrx_lzrx_lyrx_lxrx, control_wzyx); + + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, l_zwxy); + const __m128 l_yxwz = _mm_shuffle_ps(l_zwxy, l_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lzry_lwry_nlxry_nlyry = _mm_mul_ps(lzry_lwry_lxry_lyry, control_zwxy); + + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, l_yxwz); + const __m128 result0 = _mm_add_ps(lxrw_lyrw_lzrw_lwrw, lwrx_nlzrx_lyrx_nlxrx); + + const __m128 nlyrz_lxrz_lwrz_wlzrz = _mm_mul_ps(lyrz_lxrz_lwrz_lzrz, control_yxwz); + const __m128 result1 = _mm_add_ps(lzry_lwry_nlxry_nlyry, nlyrz_lxrz_lwrz_wlzrz); + return _mm_add_ps(result0, result1); +} + +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_sse_xor(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + constexpr __m128 control_wzyx = { 0.0f,-0.0f, 0.0f,-0.0f }; + constexpr __m128 control_zwxy = { 0.0f, 0.0f,-0.0f,-0.0f }; + constexpr __m128 control_yxwz = { -0.0f, 0.0f, 0.0f,-0.0f }; + + const __m128 r_xxxx = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 r_yyyy = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 r_zzzz = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(2, 2, 2, 2)); + const __m128 r_wwww = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 3, 3, 3)); + + const __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, lhs); + const __m128 l_wzyx = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, l_wzyx); + const __m128 l_zwxy = _mm_shuffle_ps(l_wzyx, l_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + + const __m128 lwrx_nlzrx_lyrx_nlxrx = _mm_xor_ps(lwrx_lzrx_lyrx_lxrx, control_wzyx); + + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, l_zwxy); + const __m128 l_yxwz = _mm_shuffle_ps(l_zwxy, l_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + + const __m128 lzry_lwry_nlxry_nlyry = _mm_xor_ps(lzry_lwry_lxry_lyry, control_zwxy); + + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, l_yxwz); + const __m128 result0 = _mm_add_ps(lxrw_lyrw_lzrw_lwrw, lwrx_nlzrx_lyrx_nlxrx); + + const __m128 nlyrz_lxrz_lwrz_wlzrz = _mm_xor_ps(lyrz_lxrz_lwrz_lzrz, control_yxwz); + const __m128 result1 = _mm_add_ps(lzry_lwry_nlxry_nlyry, nlyrz_lxrz_lwrz_wlzrz); + return _mm_add_ps(result0, result1); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +// Wins on iPad Pro ARM64 +// Wins on Pixel 3 ARM64 +// XOR is slower a bit likely due to pipeline stalls and with ARMV7 the zipping variant doesn't +// reduce the number of instructions by as much as ARM64. +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_neon_mul(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + alignas(16) constexpr float control_wzyx_f[4] = { 1.0f, -1.0f, 1.0f, -1.0f }; + alignas(16) constexpr float control_zwxy_f[4] = { 1.0f, 1.0f, -1.0f, -1.0f }; + alignas(16) constexpr float control_yxwz_f[4] = { -1.0f, 1.0f, 1.0f, -1.0f }; + + const float32x4_t control_wzyx = *reinterpret_cast(&control_wzyx_f[0]); + const float32x4_t control_zwxy = *reinterpret_cast(&control_zwxy_f[0]); + const float32x4_t control_yxwz = *reinterpret_cast(&control_yxwz_f[0]); + + const float32x2_t r_xy = vget_low_f32(rhs); + const float32x2_t r_zw = vget_high_f32(rhs); + + const float32x4_t lxrw_lyrw_lzrw_lwrw = vmulq_lane_f32(lhs, r_zw, 1); + + const float32x4_t l_yxwz = vrev64q_f32(lhs); + const float32x4_t l_wzyx = vcombine_f32(vget_high_f32(l_yxwz), vget_low_f32(l_yxwz)); + const float32x4_t l_zwxy = vrev64q_f32(l_wzyx); + + const float32x4_t lwrx_lzrx_lyrx_lxrx = vmulq_lane_f32(l_wzyx, r_xy, 0); + const float32x4_t lzry_lwry_lxry_lyry = vmulq_lane_f32(l_zwxy, r_xy, 1); + const float32x4_t lyrz_lxrz_lwrz_lzrz = vmulq_lane_f32(l_yxwz, r_zw, 0); + +#if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result0 = vfmaq_f32(lxrw_lyrw_lzrw_lwrw, lwrx_lzrx_lyrx_lxrx, control_wzyx); + const float32x4_t result1 = vfmaq_f32(result0, lzry_lwry_lxry_lyry, control_zwxy); + return vfmaq_f32(result1, lyrz_lxrz_lwrz_lzrz, control_yxwz); +#else + const float32x4_t result0 = vmlaq_f32(lxrw_lyrw_lzrw_lwrw, lwrx_lzrx_lyrx_lxrx, control_wzyx); + const float32x4_t result1 = vmlaq_f32(result0, lzry_lwry_lxry_lyry, control_zwxy); + return vmlaq_f32(result1, lyrz_lxrz_lwrz_lzrz, control_yxwz); +#endif +} + +// Wins on Samsung S8 ARMv7 +// XOR is a bit faster than scalar +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_neon_xor(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + alignas(16) constexpr uint32x4_t control_wzyx_f[4] = { 0, 0x80000000U, 0, 0x80000000U }; + alignas(16) constexpr uint32x4_t control_zwxy_f[4] = { 0, 0, 0x80000000U, 0x80000000U }; + alignas(16) constexpr uint32x4_t control_yxwz_f[4] = { 0x80000000U, 0, 0, 0x80000000U }; + + const uint32x4_t control_wzyx = *reinterpret_cast(&control_wzyx_f[0]); + const uint32x4_t control_zwxy = *reinterpret_cast(&control_zwxy_f[0]); + const uint32x4_t control_yxwz = *reinterpret_cast(&control_yxwz_f[0]); + + float32x2_t r_xy = vget_low_f32(rhs); + float32x2_t r_zw = vget_high_f32(rhs); + + float32x4_t lxrw_lyrw_lzrw_lwrw = vmulq_lane_f32(lhs, r_zw, 1); + + float32x4_t l_yxwz = vrev64q_f32(lhs); + float32x4_t l_wzyx = vcombine_f32(vget_high_f32(l_yxwz), vget_low_f32(l_yxwz)); + float32x4_t lwrx_lzrx_lyrx_lxrx = vmulq_lane_f32(l_wzyx, r_xy, 0); + + float32x4_t result0 = vaddq_f32(vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(lwrx_lzrx_lyrx_lxrx), control_wzyx)), lxrw_lyrw_lzrw_lwrw); + + float32x4_t l_zwxy = vrev64q_f32(l_wzyx); + float32x4_t lzry_lwry_lxry_lyry = vmulq_lane_f32(l_zwxy, r_xy, 1); + + float32x4_t result1 = vaddq_f32(vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(lzry_lwry_lxry_lyry), control_zwxy)), result0); + + float32x4_t lyrz_lxrz_lwrz_lzrz = vmulq_lane_f32(l_yxwz, r_zw, 0); + + return vaddq_f32(vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(lyrz_lxrz_lwrz_lzrz), control_yxwz)), result1); +} + +// Wins on iPad Pro ARM64 +RTM_FORCE_NOINLINE quatf RTM_SIMD_CALL quat_mul_neon_neg(quatf_arg0 lhs, quatf_arg1 rhs) RTM_NO_EXCEPT +{ + // Use shuffles and negation instead of loading constants and doing mul/xor. + // On ARM64, neg and shuffles are usually 2 cycles while xor is still 3 cycles. + // We have to shuffle things anyway, might as well leverage everything we can. + + // Dispatch rev first, if we can't dual dispatch with neg below, we won't stall it + // [l.y, l.x, l.w, l.z] + const float32x4_t y_x_w_z = vrev64q_f32(lhs); + + // [-l.x, -l.y, -l.z, -l.w] + const float32x4_t neg_lhs = vnegq_f32(lhs); + + // trn([l.y, l.x, l.w, l.z], [-l.x, -l.y, -l.z, -l.w]) = [l.y, -l.x, l.w, -l.z], [l.x, -l.y, l.z, -l.w] + float32x4x2_t y_nx_w_nz__x_ny_z_nw = vtrnq_f32(y_x_w_z, neg_lhs); + + // [l.w, -l.z, l.y, -l.x] + float32x4_t l_wzyx = vcombine_f32(vget_high_f32(y_nx_w_nz__x_ny_z_nw.val[0]), vget_low_f32(y_nx_w_nz__x_ny_z_nw.val[0])); + + // [l.z, l.w, -l.x, -l.y] + float32x4_t l_zwxy = vcombine_f32(vget_high_f32(lhs), vget_low_f32(neg_lhs)); + + // neg([l.w, -l.z, l.y, -l.x]) = [-l.w, l.z, -l.y, l.x] + float32x4_t nw_z_ny_x = vnegq_f32(l_wzyx); + + // [-l.y, l.x, l.w, -l.z] + float32x4_t l_yxwz = vcombine_f32(vget_high_f32(nw_z_ny_x), vget_low_f32(l_wzyx)); + + const float32x2_t r_xy = vget_low_f32(rhs); + const float32x2_t r_zw = vget_high_f32(rhs); + + const float32x4_t lxrw_lyrw_lzrw_lwrw = vmulq_lane_f32(lhs, r_zw, 1); + +#if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result0 = vfmaq_lane_f32(lxrw_lyrw_lzrw_lwrw, l_wzyx, r_xy, 0); + const float32x4_t result1 = vfmaq_lane_f32(result0, l_zwxy, r_xy, 1); + return vfmaq_lane_f32(result1, l_yxwz, r_zw, 0); +#else + const float32x4_t result0 = vmlaq_lane_f32(lxrw_lyrw_lzrw_lwrw, l_wzyx, r_xy, 0); + const float32x4_t result1 = vmlaq_lane_f32(result0, l_zwxy, r_xy, 1); + return vmlaq_lane_f32(result1, l_yxwz, r_zw, 0); +#endif +} +#endif + +static void bm_quat_mul_scalar(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_scalar(q0, q0); + q1 = quat_mul_scalar(q1, q1); + q2 = quat_mul_scalar(q2, q2); + q3 = quat_mul_scalar(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_scalar); + +#if defined(RTM_FMA_INTRINSICS) +static void bm_quat_mul_fma_mul(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_fma_mul(q0, q0); + q1 = quat_mul_fma_mul(q1, q1); + q2 = quat_mul_fma_mul(q2, q2); + q3 = quat_mul_fma_mul(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_fma_mul); + +static void bm_quat_mul_fma_xor(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_fma_xor(q0, q0); + q1 = quat_mul_fma_xor(q1, q1); + q2 = quat_mul_fma_xor(q2, q2); + q3 = quat_mul_fma_xor(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_fma_xor); +#endif + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_quat_mul_sse_mul(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_sse_mul(q0, q0); + q1 = quat_mul_sse_mul(q1, q1); + q2 = quat_mul_sse_mul(q2, q2); + q3 = quat_mul_sse_mul(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_sse_mul); + +static void bm_quat_mul_sse_xor(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_sse_xor(q0, q0); + q1 = quat_mul_sse_xor(q1, q1); + q2 = quat_mul_sse_xor(q2, q2); + q3 = quat_mul_sse_xor(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_sse_xor); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_quat_mul_neon_mul(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_neon_mul(q0, q0); + q1 = quat_mul_neon_mul(q1, q1); + q2 = quat_mul_neon_mul(q2, q2); + q3 = quat_mul_neon_mul(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_neon_mul); + +static void bm_quat_mul_neon_xor(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_neon_xor(q0, q0); + q1 = quat_mul_neon_xor(q1, q1); + q2 = quat_mul_neon_xor(q2, q2); + q3 = quat_mul_neon_xor(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_neon_xor); + +static void bm_quat_mul_neon_neg(benchmark::State& state) +{ + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + q0 = quat_mul_neon_neg(q0, q0); + q1 = quat_mul_neon_neg(q1, q1); + q2 = quat_mul_neon_neg(q2, q2); + q3 = quat_mul_neon_neg(q3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); +} + +BENCHMARK(bm_quat_mul_neon_neg); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_mul_vector3.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_mul_vector3.cpp new file mode 100644 index 00000000..f3449cf8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_quat_mul_vector3.cpp @@ -0,0 +1,567 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL quat_mul_vector3_ref(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT +{ + quatf vector_quat = quat_set_w(vector_to_quat(vector), 0.0f); + quatf inv_rotation = quat_conjugate(rotation); + return quat_to_vector(quat_mul(quat_mul(inv_rotation, vector_quat), rotation)); +} + +#if defined(RTM_FMA_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL quat_mul_vector3_fma(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT +{ + const __m128 inv_rotation = quat_conjugate(rotation); + + // Normally when we multiply our inverse rotation quaternion with the input vector as a quaternion with W = 0.0. + // As a result, we can strip the whole part that uses W saving a few instructions. + // Because we have the rotation and its inverse, we also can use them to avoid flipping the signs + // when lining up our SIMD additions. For the first quaternion multiplication, we can avoid 3 XORs by + // doing 2 shuffles instead. The same trick can also be used with the second quaternion multiplication. + // We also don't care about the result W lane but it comes for free. + + // temp = quat_mul(inv_rotation, vector_quat) + __m128 temp; + { + const __m128 rotation_tmp0 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(3, 0, 2, 1)); // r.y, r.z, -r.x, -r.w + const __m128 rotation_tmp1 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(3, 1, 2, 0)); // r.x, r.z, -r.y, -r.w + + const __m128 v_xxxx = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 v_yyyy = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 v_zzzz = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(2, 2, 2, 2)); + + const __m128 rotation_tmp2 = _mm_shuffle_ps(rotation_tmp0, rotation_tmp1, _MM_SHUFFLE(0, 2, 1, 3)); // -r.w, r.z, -r.y, r.x + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(v_xxxx, rotation_tmp2); + + const __m128 rotation_tmp3 = _mm_shuffle_ps(inv_rotation, rotation, _MM_SHUFFLE(1, 0, 3, 2)); // -r.z, -r.w, r.x, r.y + const __m128 rotation_tmp4 = _mm_shuffle_ps(rotation_tmp0, rotation_tmp1, _MM_SHUFFLE(1, 3, 2, 0)); // r.y, -r.x, -r.w, r.z + + temp = _mm_fmadd_ps(v_zzzz, rotation_tmp4, _mm_fmadd_ps(v_yyyy, rotation_tmp3, lwrx_lzrx_lyrx_lxrx)); + } + + // result = quat_mul(temp, rotation) + { + const __m128 rotation_tmp0 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(2, 0, 2, 0)); // r.x, r.z, -r.x, -r.z + + __m128 r_xxxx = _mm_shuffle_ps(rotation_tmp0, rotation_tmp0, _MM_SHUFFLE(2, 0, 2, 0)); // r.x, -r.x, r.x, -r.x + __m128 r_yyyy = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(1, 1, 1, 1)); // r.y, r.y, -r.y, -r.y + __m128 r_zzzz = _mm_shuffle_ps(rotation_tmp0, rotation_tmp0, _MM_SHUFFLE(3, 1, 1, 3)); // -r.z, r.z, r.z, -r.z + __m128 r_wwww = _mm_shuffle_ps(rotation, rotation, _MM_SHUFFLE(3, 3, 3, 3)); // r.w, r.w, r.w, r.w + + __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, temp); + + __m128 t_wzyx = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0, 1, 2, 3)); + __m128 t_zwxy = _mm_shuffle_ps(t_wzyx, t_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 t_yxwz = _mm_shuffle_ps(t_zwxy, t_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + + __m128 result0 = _mm_fmadd_ps(r_xxxx, t_wzyx, lxrw_lyrw_lzrw_lwrw); + __m128 result1 = _mm_fmadd_ps(r_yyyy, t_zwxy, result0); + return _mm_fmadd_ps(r_zzzz, t_yxwz, result1); + } +} +#endif + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Haswell laptop x64 AVX +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL quat_mul_vector3_sse2(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT +{ + const __m128 inv_rotation = quat_conjugate(rotation); + + // Normally when we multiply our inverse rotation quaternion with the input vector as a quaternion with W = 0.0. + // As a result, we can strip the whole part that uses W saving a few instructions. + // Because we have the rotation and its inverse, we also can use them to avoid flipping the signs + // when lining up our SIMD additions. For the first quaternion multiplication, we can avoid 3 XORs by + // doing 2 shuffles instead. The same trick can also be used with the second quaternion multiplication. + // We also don't care about the result W lane but it comes for free. + + // temp = quat_mul(inv_rotation, vector_quat) + __m128 temp; + { + const __m128 rotation_tmp0 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(3, 0, 2, 1)); // r.y, r.z, -r.x, -r.w + const __m128 rotation_tmp1 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(3, 1, 2, 0)); // r.x, r.z, -r.y, -r.w + + const __m128 v_xxxx = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(0, 0, 0, 0)); + const __m128 v_yyyy = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(1, 1, 1, 1)); + const __m128 v_zzzz = _mm_shuffle_ps(vector, vector, _MM_SHUFFLE(2, 2, 2, 2)); + + const __m128 rotation_tmp2 = _mm_shuffle_ps(rotation_tmp0, rotation_tmp1, _MM_SHUFFLE(0, 2, 1, 3)); // -r.w, r.z, -r.y, r.x + const __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(v_xxxx, rotation_tmp2); + + const __m128 rotation_tmp3 = _mm_shuffle_ps(inv_rotation, rotation, _MM_SHUFFLE(1, 0, 3, 2)); // -r.z, -r.w, r.x, r.y + const __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(v_yyyy, rotation_tmp3); + + const __m128 rotation_tmp4 = _mm_shuffle_ps(rotation_tmp0, rotation_tmp1, _MM_SHUFFLE(1, 3, 2, 0)); // r.y, -r.x, -r.w, r.z + const __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(v_zzzz, rotation_tmp4); + + temp = _mm_add_ps(_mm_add_ps(lwrx_lzrx_lyrx_lxrx, lzry_lwry_lxry_lyry), lyrz_lxrz_lwrz_lzrz); + } + + // result = quat_mul(temp, rotation) + { + const __m128 rotation_tmp0 = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(2, 0, 2, 0)); // r.x, r.z, -r.x, -r.z + + __m128 r_xxxx = _mm_shuffle_ps(rotation_tmp0, rotation_tmp0, _MM_SHUFFLE(2, 0, 2, 0)); // r.x, -r.x, r.x, -r.x + __m128 r_yyyy = _mm_shuffle_ps(rotation, inv_rotation, _MM_SHUFFLE(1, 1, 1, 1)); // r.y, r.y, -r.y, -r.y + __m128 r_zzzz = _mm_shuffle_ps(rotation_tmp0, rotation_tmp0, _MM_SHUFFLE(3, 1, 1, 3)); // -r.z, r.z, r.z, -r.z + __m128 r_wwww = _mm_shuffle_ps(rotation, rotation, _MM_SHUFFLE(3, 3, 3, 3)); // r.w, r.w, r.w, r.w + + __m128 lxrw_lyrw_lzrw_lwrw = _mm_mul_ps(r_wwww, temp); + + __m128 t_wzyx = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0, 1, 2, 3)); + __m128 lwrx_lzrx_lyrx_lxrx = _mm_mul_ps(r_xxxx, t_wzyx); + + __m128 t_zwxy = _mm_shuffle_ps(t_wzyx, t_wzyx, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 lzry_lwry_lxry_lyry = _mm_mul_ps(r_yyyy, t_zwxy); + + __m128 t_yxwz = _mm_shuffle_ps(t_zwxy, t_zwxy, _MM_SHUFFLE(0, 1, 2, 3)); + __m128 lyrz_lxrz_lwrz_lzrz = _mm_mul_ps(r_zzzz, t_yxwz); + + __m128 result0 = _mm_add_ps(lxrw_lyrw_lzrw_lwrw, lwrx_lzrx_lyrx_lxrx); + __m128 result1 = _mm_add_ps(lzry_lwry_lxry_lyry, lyrz_lxrz_lwrz_lzrz); + return _mm_add_ps(result0, result1); + } +} +#endif + +// Wins on Pixel 3 ARMv7 +// Wins on Pixel 3 ARM64 +// Scalar is much faster. The zipping impl isn't faster here unlike quat_mul for ARM64, it doesn't reduce the instruction +// count by as much. +// Wins on Samsung S8 ARMv7 +// Dramatically faster +// Wins on Samsung S8 ARM64 +// Much faster +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL quat_mul_vector3_scalar(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT +{ +#if defined(RTM_NEON_INTRINSICS) + const float32x4_t n_rotation = vnegq_f32(rotation); +#else + const quatf n_rotation = quat_conjugate(rotation); +#endif + + // temp = quat_mul(inv_rotation, vector_quat) + float temp_x; + float temp_y; + float temp_z; + float temp_w; + { + const float lhs_x = quat_get_x(n_rotation); + const float lhs_y = quat_get_y(n_rotation); + const float lhs_z = quat_get_z(n_rotation); + const float lhs_w = quat_get_w(rotation); + + const float rhs_x = vector_get_x(vector); + const float rhs_y = vector_get_y(vector); + const float rhs_z = vector_get_z(vector); + + temp_x = (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + temp_y = -(rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + temp_z = (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + temp_w = -(rhs_x * lhs_x) - (rhs_y * lhs_y) - (rhs_z * lhs_z); + } + + // result = quat_mul(temp, rotation) + { + const float lhs_x = temp_x; + const float lhs_y = temp_y; + const float lhs_z = temp_z; + const float lhs_w = temp_w; + + const float rhs_x = quat_get_x(rotation); + const float rhs_y = quat_get_y(rotation); + const float rhs_z = quat_get_z(rotation); + const float rhs_w = quat_get_w(rotation); + + const float x = (rhs_w * lhs_x) + (rhs_x * lhs_w) + (rhs_y * lhs_z) - (rhs_z * lhs_y); + const float y = (rhs_w * lhs_y) - (rhs_x * lhs_z) + (rhs_y * lhs_w) + (rhs_z * lhs_x); + const float z = (rhs_w * lhs_z) + (rhs_x * lhs_y) - (rhs_y * lhs_x) + (rhs_z * lhs_w); + + return vector_set(x, y, z, z); + } +} + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL quat_mul_vector3_neon_neg(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT +{ + // Normally when we multiply our inverse rotation quaternion with the input vector as a quaternion with W = 0.0. + // As a result, we can strip the whole part that uses W saving a few instructions. + // Use the same tricks as quat_mul + + // temp = quat_mul(inv_rotation, vector_quat) + float32x4_t temp; + { + float32x4_t lhs = quat_conjugate(rotation); + float32x4_t rhs = vector; + + // Dispatch rev first, if we can't dual dispatch with neg below, we won't stall it + // [l.y, l.x, l.w, l.z] + const float32x4_t y_x_w_z = vrev64q_f32(lhs); + + // [-l.x, -l.y, -l.z, -l.w] + const float32x4_t neg_lhs = vnegq_f32(lhs); + + // trn([l.y, l.x, l.w, l.z], [-l.x, -l.y, -l.z, -l.w]) = [l.y, -l.x, l.w, -l.z], [l.x, -l.y, l.z, -l.w] + float32x4x2_t y_nx_w_nz__x_ny_z_nw = vtrnq_f32(y_x_w_z, neg_lhs); + + // [l.w, -l.z, l.y, -l.x] + float32x4_t l_wzyx = vcombine_f32(vget_high_f32(y_nx_w_nz__x_ny_z_nw.val[0]), vget_low_f32(y_nx_w_nz__x_ny_z_nw.val[0])); + + // [l.z, l.w, -l.x, -l.y] + float32x4_t l_zwxy = vcombine_f32(vget_high_f32(lhs), vget_low_f32(neg_lhs)); + + // neg([l.w, -l.z, l.y, -l.x]) = [-l.w, l.z, -l.y, l.x] + float32x4_t nw_z_ny_x = vnegq_f32(l_wzyx); + + // [-l.y, l.x, l.w, -l.z] + float32x4_t l_yxwz = vcombine_f32(vget_high_f32(nw_z_ny_x), vget_low_f32(l_wzyx)); + + const float32x2_t r_xy = vget_low_f32(rhs); + const float32x2_t r_zw = vget_high_f32(rhs); + + const float32x4_t result0 = vmulq_lane_f32(l_wzyx, r_xy, 0); + + #if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result1 = vfmaq_lane_f32(result0, l_zwxy, r_xy, 1); + temp = vfmaq_lane_f32(result1, l_yxwz, r_zw, 0); + #else + const float32x4_t result1 = vmlaq_lane_f32(result0, l_zwxy, r_xy, 1); + temp = vmlaq_lane_f32(result1, l_yxwz, r_zw, 0); + #endif + } + + // result = quat_mul(temp, rotation) + { + float32x4_t lhs = temp; + float32x4_t rhs = rotation; + + // Dispatch rev first, if we can't dual dispatch with neg below, we won't stall it + // [l.y, l.x, l.w, l.z] + const float32x4_t y_x_w_z = vrev64q_f32(lhs); + + // [-l.x, -l.y, -l.z, -l.w] + const float32x4_t neg_lhs = vnegq_f32(lhs); + + // trn([l.y, l.x, l.w, l.z], [-l.x, -l.y, -l.z, -l.w]) = [l.y, -l.x, l.w, -l.z], [l.x, -l.y, l.z, -l.w] + float32x4x2_t y_nx_w_nz__x_ny_z_nw = vtrnq_f32(y_x_w_z, neg_lhs); + + // [l.w, -l.z, l.y, -l.x] + float32x4_t l_wzyx = vcombine_f32(vget_high_f32(y_nx_w_nz__x_ny_z_nw.val[0]), vget_low_f32(y_nx_w_nz__x_ny_z_nw.val[0])); + + // [l.z, l.w, -l.x, -l.y] + float32x4_t l_zwxy = vcombine_f32(vget_high_f32(lhs), vget_low_f32(neg_lhs)); + + // neg([l.w, -l.z, l.y, -l.x]) = [-l.w, l.z, -l.y, l.x] + float32x4_t nw_z_ny_x = vnegq_f32(l_wzyx); + + // [-l.y, l.x, l.w, -l.z] + float32x4_t l_yxwz = vcombine_f32(vget_high_f32(nw_z_ny_x), vget_low_f32(l_wzyx)); + + const float32x2_t r_xy = vget_low_f32(rhs); + const float32x2_t r_zw = vget_high_f32(rhs); + + const float32x4_t lxrw_lyrw_lzrw_lwrw = vmulq_lane_f32(temp, r_zw, 1); + + #if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result0 = vfmaq_lane_f32(lxrw_lyrw_lzrw_lwrw, l_wzyx, r_xy, 0); + const float32x4_t result1 = vfmaq_lane_f32(result0, l_zwxy, r_xy, 1); + return vfmaq_lane_f32(result1, l_yxwz, r_zw, 0); + #else + const float32x4_t result0 = vmlaq_lane_f32(lxrw_lyrw_lzrw_lwrw, l_wzyx, r_xy, 0); + const float32x4_t result1 = vmlaq_lane_f32(result0, l_zwxy, r_xy, 1); + return vmlaq_lane_f32(result1, l_yxwz, r_zw, 0); + #endif + } +} + +// Wins on iPad Pro ARM64 +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL quat_mul_vector3_neon_mul(vector4f_arg0 vector, quatf_arg1 rotation) RTM_NO_EXCEPT +{ + alignas(16) constexpr float control_wzyx_f[4] = { 1.0f, -1.0f, 1.0f, -1.0f }; + alignas(16) constexpr float control_zwxy_f[4] = { 1.0f, 1.0f, -1.0f, -1.0f }; + alignas(16) constexpr float control_yxwz_f[4] = { -1.0f, 1.0f, 1.0f, -1.0f }; + + const float32x4_t control_wzyx = *reinterpret_cast(&control_wzyx_f[0]); + const float32x4_t control_zwxy = *reinterpret_cast(&control_zwxy_f[0]); + const float32x4_t control_yxwz = *reinterpret_cast(&control_yxwz_f[0]); + + const float32x4_t inv_rotation = quat_conjugate(rotation); + + // Normally when we multiply our inverse rotation quaternion with the input vector as a quaternion with W = 0.0. + // As a result, we can strip the whole part that uses W saving a few instructions. + // We also don't care about the result W lane but it comes for free. + // With ARMv7, it seems that floating point instructions aren't the bottleneck and using shuffles to move things + // around to take advantage of sign flipping like the other implementations does not benefit. + + // temp = quat_mul(inv_rotation, vector_quat) + float32x4_t temp; + { + const float32x2_t r_xy = vget_low_f32(vector); + const float32x2_t r_zw = vget_high_f32(vector); + + const float32x4_t l_yxwz = vrev64q_f32(inv_rotation); + const float32x4_t l_wzyx = vcombine_f32(vget_high_f32(l_yxwz), vget_low_f32(l_yxwz)); + const float32x4_t l_zwxy = vrev64q_f32(l_wzyx); + + const float32x4_t lwrx_lzrx_lyrx_lxrx = vmulq_lane_f32(l_wzyx, r_xy, 0); + const float32x4_t lzry_lwry_lxry_lyry = vmulq_lane_f32(l_zwxy, r_xy, 1); + const float32x4_t lyrz_lxrz_lwrz_lzrz = vmulq_lane_f32(l_yxwz, r_zw, 0); + + const float32x4_t result0 = vmulq_f32(lwrx_lzrx_lyrx_lxrx, control_wzyx); + +#if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result1 = vfmaq_f32(result0, lzry_lwry_lxry_lyry, control_zwxy); + temp = vfmaq_f32(result1, lyrz_lxrz_lwrz_lzrz, control_yxwz); +#else + const float32x4_t result1 = vmlaq_f32(result0, lzry_lwry_lxry_lyry, control_zwxy); + temp = vmlaq_f32(result1, lyrz_lxrz_lwrz_lzrz, control_yxwz); +#endif + } + + // result = quat_mul(temp, rotation) + { + const float32x2_t r_xy = vget_low_f32(rotation); + const float32x2_t r_zw = vget_high_f32(rotation); + + const float32x4_t lxrw_lyrw_lzrw_lwrw = vmulq_lane_f32(temp, r_zw, 1); + + const float32x4_t l_yxwz = vrev64q_f32(temp); + const float32x4_t l_wzyx = vcombine_f32(vget_high_f32(l_yxwz), vget_low_f32(l_yxwz)); + const float32x4_t l_zwxy = vrev64q_f32(l_wzyx); + + const float32x4_t lwrx_lzrx_lyrx_lxrx = vmulq_lane_f32(l_wzyx, r_xy, 0); + const float32x4_t lzry_lwry_lxry_lyry = vmulq_lane_f32(l_zwxy, r_xy, 1); + const float32x4_t lyrz_lxrz_lwrz_lzrz = vmulq_lane_f32(l_yxwz, r_zw, 0); + +#if defined(RTM_NEON64_INTRINSICS) + const float32x4_t result0 = vfmaq_f32(lxrw_lyrw_lzrw_lwrw, lwrx_lzrx_lyrx_lxrx, control_wzyx); + const float32x4_t result1 = vfmaq_f32(result0, lzry_lwry_lxry_lyry, control_zwxy); + return vfmaq_f32(result1, lyrz_lxrz_lwrz_lzrz, control_yxwz); +#else + const float32x4_t result0 = vmlaq_f32(lxrw_lyrw_lzrw_lwrw, lwrx_lzrx_lyrx_lxrx, control_wzyx); + const float32x4_t result1 = vmlaq_f32(result0, lzry_lwry_lxry_lyry, control_zwxy); + return vmlaq_f32(result1, lyrz_lxrz_lwrz_lzrz, control_yxwz); +#endif + } +} +#endif + +static void bm_quat_mul_vector3_ref(benchmark::State& state) +{ + vector4f v0 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v1 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v2 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v3 = vector_set(12.0f, 32.0f, -2.0f); + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + v0 = quat_mul_vector3_ref(v0, q0); + v1 = quat_mul_vector3_ref(v1, q1); + v2 = quat_mul_vector3_ref(v2, q2); + v3 = quat_mul_vector3_ref(v3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); +} + +BENCHMARK(bm_quat_mul_vector3_ref); + +static void bm_quat_mul_vector3_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v1 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v2 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v3 = vector_set(12.0f, 32.0f, -2.0f); + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + v0 = quat_mul_vector3_scalar(v0, q0); + v1 = quat_mul_vector3_scalar(v1, q1); + v2 = quat_mul_vector3_scalar(v2, q2); + v3 = quat_mul_vector3_scalar(v3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); +} + +BENCHMARK(bm_quat_mul_vector3_scalar); + +#if defined(RTM_FMA_INTRINSICS) +static void bm_quat_mul_vector3_fma(benchmark::State& state) +{ + vector4f v0 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v1 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v2 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v3 = vector_set(12.0f, 32.0f, -2.0f); + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + v0 = quat_mul_vector3_fma(v0, q0); + v1 = quat_mul_vector3_fma(v1, q1); + v2 = quat_mul_vector3_fma(v2, q2); + v3 = quat_mul_vector3_fma(v3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); +} + +BENCHMARK(bm_quat_mul_vector3_fma); +#endif + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_quat_mul_vector3_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v1 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v2 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v3 = vector_set(12.0f, 32.0f, -2.0f); + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + v0 = quat_mul_vector3_sse2(v0, q0); + v1 = quat_mul_vector3_sse2(v1, q1); + v2 = quat_mul_vector3_sse2(v2, q2); + v3 = quat_mul_vector3_sse2(v3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); +} + +BENCHMARK(bm_quat_mul_vector3_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_quat_mul_vector3_neon_neg(benchmark::State& state) +{ + vector4f v0 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v1 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v2 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v3 = vector_set(12.0f, 32.0f, -2.0f); + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + v0 = quat_mul_vector3_neon_neg(v0, q0); + v1 = quat_mul_vector3_neon_neg(v1, q1); + v2 = quat_mul_vector3_neon_neg(v2, q2); + v3 = quat_mul_vector3_neon_neg(v3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); +} + +BENCHMARK(bm_quat_mul_vector3_neon_neg); + +static void bm_quat_mul_vector3_neon_mul(benchmark::State& state) +{ + vector4f v0 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v1 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v2 = vector_set(12.0f, 32.0f, -2.0f); + vector4f v3 = vector_set(12.0f, 32.0f, -2.0f); + quatf q0 = quat_identity(); + quatf q1 = quat_identity(); + quatf q2 = quat_identity(); + quatf q3 = quat_identity(); + + for (auto _ : state) + { + v0 = quat_mul_vector3_neon_mul(v0, q0); + v1 = quat_mul_vector3_neon_mul(v1, q1); + v2 = quat_mul_vector3_neon_mul(v2, q2); + v3 = quat_mul_vector3_neon_mul(v3, q3); + } + + benchmark::DoNotOptimize(q0); + benchmark::DoNotOptimize(q1); + benchmark::DoNotOptimize(q2); + benchmark::DoNotOptimize(q3); + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); +} + +BENCHMARK(bm_quat_mul_vector3_neon_mul); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_qvv_mul.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_qvv_mul.cpp new file mode 100644 index 00000000..ede04c93 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_qvv_mul.cpp @@ -0,0 +1,140 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE qvvf RTM_SIMD_CALL qvv_mul_ref(qvvf_arg0 lhs, qvvf_arg1 rhs) RTM_NO_EXCEPT +{ + const vector4f min_scale = vector_min(lhs.scale, rhs.scale); + const vector4f scale = vector_mul(lhs.scale, rhs.scale); + + if (vector_any_less_than3(min_scale, vector_zero())) + { + // If we have negative scale, we go through a matrix + const matrix3x4f lhs_mtx = matrix_from_qvv(lhs); + const matrix3x4f rhs_mtx = matrix_from_qvv(rhs); + matrix3x4f result_mtx = matrix_mul(lhs_mtx, rhs_mtx); + result_mtx = matrix_remove_scale(result_mtx); + + const vector4f sign = vector_sign(scale); + result_mtx.x_axis = vector_mul(result_mtx.x_axis, vector_dup_x(sign)); + result_mtx.y_axis = vector_mul(result_mtx.y_axis, vector_dup_y(sign)); + result_mtx.z_axis = vector_mul(result_mtx.z_axis, vector_dup_z(sign)); + + const quatf rotation = quat_from_matrix(result_mtx); + const vector4f translation = result_mtx.w_axis; + return qvv_set(rotation, translation, scale); + } + else + { + const quatf rotation = quat_mul(lhs.rotation, rhs.rotation); + const vector4f translation = vector_add(quat_mul_vector3(vector_mul(lhs.translation, rhs.scale), rhs.rotation), rhs.translation); + return qvv_set(rotation, translation, scale); + } +} + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Haswell laptop x64 AVX +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE qvvf RTM_SIMD_CALL qvv_mul_sse2(qvvf_arg0 lhs, qvvf_arg1 rhs) RTM_NO_EXCEPT +{ + const vector4f min_scale = vector_min(lhs.scale, rhs.scale); + const vector4f scale = vector_mul(lhs.scale, rhs.scale); + + if (vector_any_less_than3(min_scale, vector_zero())) + { + // If we have negative scale, we go through a matrix + const matrix3x4f lhs_mtx = matrix_from_qvv(lhs); + const matrix3x4f rhs_mtx = matrix_from_qvv(rhs); + matrix3x4f result_mtx = matrix_mul(lhs_mtx, rhs_mtx); + result_mtx = matrix_remove_scale(result_mtx); + + constexpr __m128 signs = { -0.0f, -0.0f, -0.0f, -0.0f }; + const __m128 sign_bits = _mm_and_ps(scale, signs); // Mask out the sign bit + + result_mtx.x_axis = _mm_xor_ps(result_mtx.x_axis, _mm_shuffle_ps(sign_bits, sign_bits, _MM_SHUFFLE(0, 0, 0, 0))); + result_mtx.y_axis = _mm_xor_ps(result_mtx.y_axis, _mm_shuffle_ps(sign_bits, sign_bits, _MM_SHUFFLE(1, 1, 1, 1))); + result_mtx.z_axis = _mm_xor_ps(result_mtx.z_axis, _mm_shuffle_ps(sign_bits, sign_bits, _MM_SHUFFLE(2, 2, 2, 2))); + + const quatf rotation = quat_from_matrix(result_mtx); + const vector4f translation = result_mtx.w_axis; + return qvv_set(rotation, translation, scale); + } + else + { + const quatf rotation = quat_mul(lhs.rotation, rhs.rotation); + const vector4f translation = vector_add(quat_mul_vector3(vector_mul(lhs.translation, rhs.scale), rhs.rotation), rhs.translation); + return qvv_set(rotation, translation, scale); + } +} +#endif + +static void bm_qvv_mul_ref(benchmark::State& state) +{ + qvvf t0 = qvv_identity(); + qvvf t1 = qvv_set(quat_identity(), vector_zero(), vector_set(-1.0f)); + qvvf t2 = qvv_identity(); + qvvf t3 = qvv_set(quat_identity(), vector_zero(), vector_set(-1.0f)); + + for (auto _ : state) + { + t0 = qvv_mul_ref(t0, t1); + t2 = qvv_mul_ref(t2, t3); + } + + benchmark::DoNotOptimize(t0); + benchmark::DoNotOptimize(t1); + benchmark::DoNotOptimize(t2); + benchmark::DoNotOptimize(t3); +} + +BENCHMARK(bm_qvv_mul_ref); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_qvv_mul_sse2(benchmark::State& state) +{ + qvvf t0 = qvv_identity(); + qvvf t1 = qvv_set(quat_identity(), vector_zero(), vector_set(-1.0f)); + qvvf t2 = qvv_identity(); + qvvf t3 = qvv_set(quat_identity(), vector_zero(), vector_set(-1.0f)); + + for (auto _ : state) + { + t0 = qvv_mul_sse2(t0, t1); + t2 = qvv_mul_sse2(t2, t3); + } + + benchmark::DoNotOptimize(t0); + benchmark::DoNotOptimize(t1); + benchmark::DoNotOptimize(t2); + benchmark::DoNotOptimize(t3); +} + +BENCHMARK(bm_qvv_mul_sse2); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_abs.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_abs.cpp new file mode 100644 index 00000000..5e8fc13e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_abs.cpp @@ -0,0 +1,121 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Oddly consistently faster despite the fact that the scalar impl is 3 instructions +// and converts to/from double while sse2_and is 2 instructions (a needless shuffle + and). +// It seems the functions are so short that the timing is dominated by something else. +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_abs_scalar(float input) RTM_NO_EXCEPT +{ + return std::fabs(input); +} + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Haswell laptop x64 AVX +// Both impl are about the same. +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_abs_sse2_and(float input) RTM_NO_EXCEPT +{ + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + return _mm_cvtss_f32(_mm_and_ps(_mm_set_ps1(input), _mm_castsi128_ps(abs_mask))); +} +#endif + +static void bm_scalar_abs_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = -123.134f; + float f2 = -123.134f; + float f3 = -123.134f; + float f4 = -123.134f; + float f5 = -123.134f; + float f6 = -123.134f; + float f7 = -123.134f; + + for (auto _ : state) + { + f0 = scalar_abs_scalar(f0); + f1 = scalar_abs_scalar(f1); + f2 = scalar_abs_scalar(f2); + f3 = scalar_abs_scalar(f3); + f4 = scalar_abs_scalar(f4); + f5 = scalar_abs_scalar(f5); + f6 = scalar_abs_scalar(f6); + f7 = scalar_abs_scalar(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_abs_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_abs_sse2_and(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = -123.134f; + float f2 = -123.134f; + float f3 = -123.134f; + float f4 = -123.134f; + float f5 = -123.134f; + float f6 = -123.134f; + float f7 = -123.134f; + + for (auto _ : state) + { + f0 = scalar_abs_sse2_and(f0); + f1 = scalar_abs_sse2_and(f1); + f2 = scalar_abs_sse2_and(f2); + f3 = scalar_abs_sse2_and(f3); + f4 = scalar_abs_sse2_and(f4); + f5 = scalar_abs_sse2_and(f5); + f6 = scalar_abs_sse2_and(f6); + f7 = scalar_abs_sse2_and(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_abs_sse2_and); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_ceil.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_ceil.cpp new file mode 100644 index 00000000..dc67e0b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_ceil.cpp @@ -0,0 +1,217 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_ceil_scalar(float input) RTM_NO_EXCEPT +{ + return std::ceil(input); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_ceil_sse2(float input) RTM_NO_EXCEPT +{ + __m128 input_s = _mm_set_ps1(input); + + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input_s, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ss(input_s, input_s); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input_s)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + __m128 is_positive = _mm_cmplt_ss(integer_part, input_s); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Negative values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_positive)); + + // Subtract our bias to properly handle positive values + integer_part = _mm_sub_ss(integer_part, bias); + + __m128 result = _mm_or_ps(_mm_and_ps(use_original_input, input_s), _mm_andnot_ps(use_original_input, integer_part)); + return _mm_cvtss_f32(result); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_ceil_neon(float input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const float fractional_limit = 8388608.0F; // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + float abs_input = std::abs(input); + bool is_input_large = abs_input >= fractional_limit; + + // Test if our input is NaN with (value != value), it is only true for NaN + bool is_nan = input != input; + + // Combine our masks to determine if we should return the original value + bool use_original_input = is_input_large | is_nan; + + // Convert to an integer and back. This does banker's rounding by default + float integer_part = static_cast(static_cast(input)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + bool is_positive = integer_part < input; + + // Add our bias to properly handle positive values + integer_part = is_positive ? (integer_part + 1.0F) : integer_part; + + return use_original_input ? input : integer_part; +} +#endif + +static void bm_scalar_ceil_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_ceil_scalar(f0); + f1 = scalar_ceil_scalar(f1); + f2 = scalar_ceil_scalar(f2); + f3 = scalar_ceil_scalar(f3); + f4 = scalar_ceil_scalar(f4); + f5 = scalar_ceil_scalar(f5); + f6 = scalar_ceil_scalar(f6); + f7 = scalar_ceil_scalar(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_ceil_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_ceil_sse2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_ceil_sse2(f0); + f1 = scalar_ceil_sse2(f1); + f2 = scalar_ceil_sse2(f2); + f3 = scalar_ceil_sse2(f3); + f4 = scalar_ceil_sse2(f4); + f5 = scalar_ceil_sse2(f5); + f6 = scalar_ceil_sse2(f6); + f7 = scalar_ceil_sse2(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_ceil_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_scalar_ceil_neon(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_ceil_neon(f0); + f1 = scalar_ceil_neon(f1); + f2 = scalar_ceil_neon(f2); + f3 = scalar_ceil_neon(f3); + f4 = scalar_ceil_neon(f4); + f5 = scalar_ceil_neon(f5); + f6 = scalar_ceil_neon(f6); + f7 = scalar_ceil_neon(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_ceil_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_floor.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_floor.cpp new file mode 100644 index 00000000..37c16ec7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_floor.cpp @@ -0,0 +1,217 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_floor_scalar(float input) RTM_NO_EXCEPT +{ + return std::floor(input); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_floor_sse2(float input) RTM_NO_EXCEPT +{ + __m128 input_s = _mm_set_ps1(input); + + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input_s, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ss(input_s, input_s); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input_s)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + __m128 is_negative = _mm_cmpgt_ss(integer_part, input_s); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Positive values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_negative)); + + // Add our bias to properly handle negative values + integer_part = _mm_add_ss(integer_part, bias); + + __m128 result = _mm_or_ps(_mm_and_ps(use_original_input, input_s), _mm_andnot_ps(use_original_input, integer_part)); + return _mm_cvtss_f32(result); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_floor_neon(float input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const float fractional_limit = 8388608.0F; // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + float abs_input = std::abs(input); + bool is_input_large = abs_input >= fractional_limit; + + // Test if our input is NaN with (value != value), it is only true for NaN + bool is_nan = input != input; + + // Combine our masks to determine if we should return the original value + bool use_original_input = is_input_large | is_nan; + + // Convert to an integer and back. This does banker's rounding by default + float integer_part = static_cast(static_cast(input)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + bool is_negative = integer_part > input; + + // Subtract our bias to properly handle negative values + integer_part = is_negative ? (integer_part - 1.0F) : integer_part; + + return use_original_input ? input : integer_part; +} +#endif + +static void bm_scalar_floor_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_floor_scalar(f0); + f1 = scalar_floor_scalar(f1); + f2 = scalar_floor_scalar(f2); + f3 = scalar_floor_scalar(f3); + f4 = scalar_floor_scalar(f4); + f5 = scalar_floor_scalar(f5); + f6 = scalar_floor_scalar(f6); + f7 = scalar_floor_scalar(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_floor_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_floor_sse2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_floor_sse2(f0); + f1 = scalar_floor_sse2(f1); + f2 = scalar_floor_sse2(f2); + f3 = scalar_floor_sse2(f3); + f4 = scalar_floor_sse2(f4); + f5 = scalar_floor_sse2(f5); + f6 = scalar_floor_sse2(f6); + f7 = scalar_floor_sse2(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_floor_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_scalar_floor_neon(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_floor_neon(f0); + f1 = scalar_floor_neon(f1); + f2 = scalar_floor_neon(f2); + f3 = scalar_floor_neon(f3); + f4 = scalar_floor_neon(f4); + f5 = scalar_floor_neon(f5); + f6 = scalar_floor_neon(f6); + f7 = scalar_floor_neon(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_floor_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_reciprocal.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_reciprocal.cpp new file mode 100644 index 00000000..48ea215f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_reciprocal.cpp @@ -0,0 +1,229 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_reciprocal_scalar(float input) RTM_NO_EXCEPT +{ + return 1.0f / input; +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_reciprocal_sse2(float input) RTM_NO_EXCEPT +{ + __m128 input_s = _mm_set_ps1(input); + + // Perform two passes of Newton-Raphson iteration on the hardware estimate + __m128 x0 = _mm_rcp_ss(input_s); + + // First iteration + __m128 x1 = _mm_sub_ss(_mm_add_ss(x0, x0), _mm_mul_ss(input_s, _mm_mul_ss(x0, x0))); + + // Second iteration + __m128 x2 = _mm_sub_ss(_mm_add_ss(x1, x1), _mm_mul_ss(input_s, _mm_mul_ss(x1, x1))); + return _mm_cvtss_f32(x2); +} +#endif + +#if defined(RTM_NEON64_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_reciprocal_neon64(float input) RTM_NO_EXCEPT +{ + // Perform two passes of Newton-Raphson iteration on the hardware estimate + float x0 = vrecpes_f32(input); + + // First iteration + float x1 = x0 * vrecpss_f32(x0, input); + + // Second iteration + float x2 = x1 * vrecpss_f32(x1, input); + return x2; +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_reciprocal_neon(float input) RTM_NO_EXCEPT +{ + float32x2_t input_v = vdup_n_f32(input); + + // Perform two passes of Newton-Raphson iteration on the hardware estimate + float32x2_t x0 = vrecpe_f32(input_v); + + // First iteration + float32x2_t x1 = vmul_f32(x0, vrecps_f32(x0, input_v)); + + // Second iteration + float32x2_t x2 = vmul_f32(x1, vrecps_f32(x1, input_v)); + return vget_lane_f32(x2, 0); +} +#endif + +static void bm_scalar_reciprocal_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_reciprocal_scalar(f0); + f1 = scalar_reciprocal_scalar(f1); + f2 = scalar_reciprocal_scalar(f2); + f3 = scalar_reciprocal_scalar(f3); + f4 = scalar_reciprocal_scalar(f4); + f5 = scalar_reciprocal_scalar(f5); + f6 = scalar_reciprocal_scalar(f6); + f7 = scalar_reciprocal_scalar(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_reciprocal_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_reciprocal_sse2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_reciprocal_sse2(f0); + f1 = scalar_reciprocal_sse2(f1); + f2 = scalar_reciprocal_sse2(f2); + f3 = scalar_reciprocal_sse2(f3); + f4 = scalar_reciprocal_sse2(f4); + f5 = scalar_reciprocal_sse2(f5); + f6 = scalar_reciprocal_sse2(f6); + f7 = scalar_reciprocal_sse2(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_reciprocal_sse2); +#endif + +#if defined(RTM_NEON64_INTRINSICS) +static void bm_scalar_reciprocal_neon64(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_reciprocal_neon64(f0); + f1 = scalar_reciprocal_neon64(f1); + f2 = scalar_reciprocal_neon64(f2); + f3 = scalar_reciprocal_neon64(f3); + f4 = scalar_reciprocal_neon64(f4); + f5 = scalar_reciprocal_neon64(f5); + f6 = scalar_reciprocal_neon64(f6); + f7 = scalar_reciprocal_neon64(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_reciprocal_neon64); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_scalar_reciprocal_neon(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_reciprocal_neon(f0); + f1 = scalar_reciprocal_neon(f1); + f2 = scalar_reciprocal_neon(f2); + f3 = scalar_reciprocal_neon(f3); + f4 = scalar_reciprocal_neon(f4); + f5 = scalar_reciprocal_neon(f5); + f6 = scalar_reciprocal_neon(f6); + f7 = scalar_reciprocal_neon(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_reciprocal_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_round_bankers.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_round_bankers.cpp new file mode 100644 index 00000000..05ba4557 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_round_bankers.cpp @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_round_bankers_scalar(float input) RTM_NO_EXCEPT +{ + if (!scalar_is_finite(input)) + return input; + + int32_t whole = static_cast(input); + float whole_f = static_cast(whole); + float remainder = scalar_abs(input - whole_f); + if (remainder < 0.5F) + return whole_f; + if (remainder > 0.5F) + return input >= 0.0F ? (whole_f + 1.0F) : (whole_f - 1.0F); + + if ((whole % 2) == 0) + return whole_f; + else + return input >= 0.0F ? (whole_f + 1.0F) : (whole_f - 1.0F); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_round_bankers_sse2(float input) RTM_NO_EXCEPT +{ + __m128 input_s = _mm_set_ps1(input); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input_s, sign_mask); + + // We add the largest integer that a 32 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + __m128 truncating_offset = _mm_or_ps(sign, fractional_limit); + __m128 integer_part = _mm_sub_ss(_mm_add_ss(input_s, truncating_offset), truncating_offset); + + // If our input was so large that it had no fractional part, return it unchanged + // Otherwise return our integer part + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input_s, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + __m128 result = _mm_or_ps(_mm_and_ps(is_input_large, input_s), _mm_andnot_ps(is_input_large, integer_part)); + return _mm_cvtss_f32(result); +} +#endif + +#if defined(RTM_NEON64_INTRINSICS) && defined(__ARM_FEATURE_DIRECTED_ROUNDING) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_round_bankers_neon64(float input) RTM_NO_EXCEPT +{ + return vrndns_f32(input); +} +#endif + +static void bm_scalar_round_bankers_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_round_bankers_scalar(f0); + f1 = scalar_round_bankers_scalar(f1); + f2 = scalar_round_bankers_scalar(f2); + f3 = scalar_round_bankers_scalar(f3); + f4 = scalar_round_bankers_scalar(f4); + f5 = scalar_round_bankers_scalar(f5); + f6 = scalar_round_bankers_scalar(f6); + f7 = scalar_round_bankers_scalar(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_round_bankers_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_round_bankers_sse2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_round_bankers_sse2(f0); + f1 = scalar_round_bankers_sse2(f1); + f2 = scalar_round_bankers_sse2(f2); + f3 = scalar_round_bankers_sse2(f3); + f4 = scalar_round_bankers_sse2(f4); + f5 = scalar_round_bankers_sse2(f5); + f6 = scalar_round_bankers_sse2(f6); + f7 = scalar_round_bankers_sse2(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_round_bankers_sse2); +#endif + +#if defined(RTM_NEON64_INTRINSICS) && defined(__ARM_FEATURE_DIRECTED_ROUNDING) +static void bm_scalar_round_bankers_neon64(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_round_bankers_neon64(f0); + f1 = scalar_round_bankers_neon64(f1); + f2 = scalar_round_bankers_neon64(f2); + f3 = scalar_round_bankers_neon64(f3); + f4 = scalar_round_bankers_neon64(f4); + f5 = scalar_round_bankers_neon64(f5); + f6 = scalar_round_bankers_neon64(f6); + f7 = scalar_round_bankers_neon64(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_round_bankers_neon64); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_round_symmetric.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_round_symmetric.cpp new file mode 100644 index 00000000..bdf1bda0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_round_symmetric.cpp @@ -0,0 +1,205 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_round_symmetric_scalar(float input) RTM_NO_EXCEPT +{ + return input >= 0.0F ? scalar_floor(input + 0.5F) : scalar_ceil(input - 0.5F); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_round_symmetric_sse2(float input) RTM_NO_EXCEPT +{ + __m128 input_s = _mm_set_ps1(input); + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input_s, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ss(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ss(input_s, input_s); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input_s, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128 bias = _mm_or_ps(sign, _mm_set_ps1(0.5F)); + __m128 biased_input = _mm_add_ss(input_s, bias); + + // Convert to an integer with truncation and back, this rounds towards zero. + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvttps_epi32(biased_input)); + + __m128 result = _mm_or_ps(_mm_and_ps(use_original_input, input_s), _mm_andnot_ps(use_original_input, integer_part)); + return _mm_cvtss_f32(result); +} +#endif + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_round_symmetric_2(float input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const float fractional_limit = 8388608.0F; // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + float abs_input = std::abs(input); + bool is_input_large = abs_input >= fractional_limit; + + // Test if our input is NaN with (value != value), it is only true for NaN + bool is_nan = input != input; + + // Combine our masks to determine if we should return the original value + bool use_original_input = is_input_large | is_nan; + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + float bias = input >= 0.0F ? 0.5F : -0.5F; + float biased_input = input + bias; + + // Convert to an integer with truncation and back, this rounds towards zero. + float integer_part = static_cast(static_cast(biased_input)); + + return use_original_input ? input : integer_part; +} + +static void bm_scalar_round_symmetric_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_round_symmetric_scalar(f0); + f1 = scalar_round_symmetric_scalar(f1); + f2 = scalar_round_symmetric_scalar(f2); + f3 = scalar_round_symmetric_scalar(f3); + f4 = scalar_round_symmetric_scalar(f4); + f5 = scalar_round_symmetric_scalar(f5); + f6 = scalar_round_symmetric_scalar(f6); + f7 = scalar_round_symmetric_scalar(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_round_symmetric_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_round_symmetric_sse2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_round_symmetric_sse2(f0); + f1 = scalar_round_symmetric_sse2(f1); + f2 = scalar_round_symmetric_sse2(f2); + f3 = scalar_round_symmetric_sse2(f3); + f4 = scalar_round_symmetric_sse2(f4); + f5 = scalar_round_symmetric_sse2(f5); + f6 = scalar_round_symmetric_sse2(f6); + f7 = scalar_round_symmetric_sse2(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_round_symmetric_sse2); +#endif + +static void bm_scalar_round_symmetric_2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_round_symmetric_2(f0); + f1 = scalar_round_symmetric_2(f1); + f2 = scalar_round_symmetric_2(f2); + f3 = scalar_round_symmetric_2(f3); + f4 = scalar_round_symmetric_2(f4); + f5 = scalar_round_symmetric_2(f5); + f6 = scalar_round_symmetric_2(f6); + f7 = scalar_round_symmetric_2(f7); + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_round_symmetric_2); diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_sin.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_sin.cpp new file mode 100644 index 00000000..ed9cb9a9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_scalar_sin.cpp @@ -0,0 +1,307 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_sin_scalar(float input) RTM_NO_EXCEPT +{ + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float quotient = input * rtm::constants::one_div_two_pi(); + quotient = scalar_round_bankers(quotient); + quotient = quotient * rtm::constants::two_pi(); + float x = input - quotient; + + // Remap our input in the [-pi/2, pi/2] range + const float reference = std::copysign(rtm::constants::pi(), x); + const float reflection = reference - x; + const float x_abs = scalar_abs(x); + x = x_abs <= rtm::constants::half_pi() ? x : reflection; + + // Calculate our value + const float x2 = x * x; + float result = (x2 * -2.3828544692960918e-8F) + 2.7521557770526783e-6F; + result = (result * x2) - 1.9840782426250314e-4F; + result = (result * x2) + 8.3333303183525942e-3F; + result = (result * x2) - 1.6666666601721269e-1F; + result = (result * x2) + 1.0F; + result = result * x; + return result; +} + +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_sin_std(float input) RTM_NO_EXCEPT +{ + return std::sin(input); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_sin_sse2(float input) RTM_NO_EXCEPT +{ + __m128 input_s = _mm_set_ps1(input); + + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ss(input_s, _mm_set_ps1(rtm::constants::one_div_two_pi())); + quotient = scalar_round_bankers(scalarf{ quotient }).value; + quotient = _mm_mul_ss(quotient, _mm_set_ps1(rtm::constants::two_pi())); + __m128 x = _mm_sub_ss(input_s, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(sign, _mm_set_ps1(rtm::constants::pi())); + + const __m128 reflection = _mm_sub_ss(reference, x); + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + + __m128 is_less_equal_than_half_pi = _mm_cmple_ss(x_abs, _mm_set_ps1(rtm::constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const float x2 = _mm_cvtss_f32(_mm_mul_ss(x, x)); + float result = (x2 * -2.3828544692960918e-8F) + 2.7521557770526783e-6F; + result = (result * x2) - 1.9840782426250314e-4F; + result = (result * x2) + 8.3333303183525942e-3F; + result = (result * x2) - 1.6666666601721269e-1F; + result = (result * x2) + 1.0F; + result = result * _mm_cvtss_f32(x); + return result; +} +#endif + +alignas(64) static constexpr float constants3_[12] = +{ + 2.7521557770526783e-6F, rtm::constants::one_div_two_pi(), + -2.3828544692960918e-8F, rtm::constants::two_pi(), + -1.9840782426250314e-4F, rtm::constants::pi(), + 8.3333303183525942e-3F, -rtm::constants::pi(), + + -1.6666666601721269e-1F, rtm::constants::half_pi(), + 1.0F, 1.0F, +}; + +#if defined(RTM_NEON64_INTRINSICS) +RTM_FORCE_NOINLINE float RTM_SIMD_CALL scalar_sin_neon64(float input) RTM_NO_EXCEPT +{ + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + const float32x2x4_t constants0 = vld4_f32(&constants3_[0]); + + float32x2_t input_v = vdup_n_f32(input); + const float32x2x2_t constants1 = vld2_f32(&constants3_[8]); + + // Remap our input in the [-pi, pi] range + float32x2_t quotient = vmul_n_f32(constants0.val[0], input); // [y] SIMD lane + bool is_x_positive = input >= 0.0F; + quotient = vrndn_f32(quotient); + float32x2_t reference_v = is_x_positive ? constants0.val[2] : constants0.val[3]; // [y] SIMD lane + +#if defined(RTM_NEON64_INTRINSICS) + float32x2_t x_v = vfms_lane_f32(input_v, quotient, constants0.val[1], 1); // [y] SIMD lane +#else + float32x2_t x_v = vmls_lane_f32(input_v, quotient, constants0.val[1], 1); // [y] SIMD lane +#endif + + float32x2_t reflection_v = vsub_f32(reference_v, x_v); + + // Remap our input in the [-pi/2, pi/2] range + uint32x2_t is_less_equal_than_half_pi = vcale_f32(x_v, constants1.val[0]); + x_v = vbsl_f32(is_less_equal_than_half_pi, x_v, reflection_v); // [y] SIMD lane + + // Calculate our value, we only care about the [x] SIMD lane + float32x2_t x2 = vmul_lane_f32(x_v, x_v, 1); // [y] SIMD lane + +#if defined(RTM_NEON64_INTRINSICS) + float32x2_t result = vfma_lane_f32(constants0.val[0], constants0.val[1], x2, 1); + result = vfma_lane_f32(constants0.val[2], result, x2, 1); + result = vfma_lane_f32(constants0.val[3], result, x2, 1); + result = vfma_lane_f32(constants1.val[0], result, x2, 1); + result = vfma_lane_f32(constants1.val[1], result, x2, 1); +#else + float32x2_t result = vmla_lane_f32(constants0.val[0], constants0.val[1], x2, 1); + result = vmla_lane_f32(constants0.val[2], result, x2, 1); + result = vmla_lane_f32(constants0.val[3], result, x2, 1); + result = vmla_lane_f32(constants1.val[0], result, x2, 1); + result = vmla_lane_f32(constants1.val[1], result, x2, 1); +#endif + + result = vmul_lane_f32(result, x_v, 1); + return vget_lane_f32(result, 0); +} +#endif + +static void bm_scalar_sin_scalar(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_sin_scalar(f0) * 100.0F; + f1 = scalar_sin_scalar(f1) * 100.0F; + f2 = scalar_sin_scalar(f2) * 100.0F; + f3 = scalar_sin_scalar(f3) * 100.0F; + f4 = scalar_sin_scalar(f4) * 100.0F; + f5 = scalar_sin_scalar(f5) * 100.0F; + f6 = scalar_sin_scalar(f6) * 100.0F; + f7 = scalar_sin_scalar(f7) * 100.0F; + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_sin_scalar); + +static void bm_scalar_sin_std(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_sin_std(f0) * 100.0F; + f1 = scalar_sin_std(f1) * 100.0F; + f2 = scalar_sin_std(f2) * 100.0F; + f3 = scalar_sin_std(f3) * 100.0F; + f4 = scalar_sin_std(f4) * 100.0F; + f5 = scalar_sin_std(f5) * 100.0F; + f6 = scalar_sin_std(f6) * 100.0F; + f7 = scalar_sin_std(f7) * 100.0F; + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_sin_std); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_scalar_sin_sse2(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_sin_sse2(f0) * 100.0F; + f1 = scalar_sin_sse2(f1) * 100.0F; + f2 = scalar_sin_sse2(f2) * 100.0F; + f3 = scalar_sin_sse2(f3) * 100.0F; + f4 = scalar_sin_sse2(f4) * 100.0F; + f5 = scalar_sin_sse2(f5) * 100.0F; + f6 = scalar_sin_sse2(f6) * 100.0F; + f7 = scalar_sin_sse2(f7) * 100.0F; + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_sin_sse2); +#endif + +#if defined(RTM_NEON64_INTRINSICS) +static void bm_scalar_sin_neon64(benchmark::State& state) +{ + float f0 = -123.134f; + float f1 = 123.134f; + float f2 = -123.134f; + float f3 = 123.134f; + float f4 = -123.134f; + float f5 = 123.134f; + float f6 = -123.134f; + float f7 = 123.134f; + + for (auto _ : state) + { + f0 = scalar_sin_neon64(f0) * 100.0F; + f1 = scalar_sin_neon64(f1) * 100.0F; + f2 = scalar_sin_neon64(f2) * 100.0F; + f3 = scalar_sin_neon64(f3) * 100.0F; + f4 = scalar_sin_neon64(f4) * 100.0F; + f5 = scalar_sin_neon64(f5) * 100.0F; + f6 = scalar_sin_neon64(f6) * 100.0F; + f7 = scalar_sin_neon64(f7) * 100.0F; + } + + benchmark::DoNotOptimize(f0); + benchmark::DoNotOptimize(f1); + benchmark::DoNotOptimize(f2); + benchmark::DoNotOptimize(f3); + benchmark::DoNotOptimize(f4); + benchmark::DoNotOptimize(f5); + benchmark::DoNotOptimize(f6); + benchmark::DoNotOptimize(f7); +} + +BENCHMARK(bm_scalar_sin_neon64); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_abs.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_abs.cpp new file mode 100644 index 00000000..01d4af2c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_abs.cpp @@ -0,0 +1,162 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_abs_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = vector_get_x(input); + scalarf y = vector_get_y(input); + scalarf z = vector_get_z(input); + scalarf w = vector_get_w(input); + return vector_set(scalar_abs(x), scalar_abs(y), scalar_abs(z), scalar_abs(w)); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_abs_sse2_maxsub(vector4f_arg0 input) RTM_NO_EXCEPT +{ + return vector_max(vector_sub(_mm_setzero_ps(), input), input); +} + +// Wins on Haswell laptop x64 AVX +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_abs_sse2_and(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + return _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); +} +#endif + +static void bm_vector_abs_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v1 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v2 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v3 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v4 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v5 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v6 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v7 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + + for (auto _ : state) + { + v0 = vector_abs_scalar(v0); + v1 = vector_abs_scalar(v1); + v2 = vector_abs_scalar(v2); + v3 = vector_abs_scalar(v3); + v4 = vector_abs_scalar(v4); + v5 = vector_abs_scalar(v5); + v6 = vector_abs_scalar(v6); + v7 = vector_abs_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_abs_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +static void bm_vector_abs_sse2_maxsub(benchmark::State& state) +{ + vector4f v0 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v1 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v2 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v3 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v4 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v5 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v6 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v7 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + + for (auto _ : state) + { + v0 = vector_abs_sse2_maxsub(v0); + v1 = vector_abs_sse2_maxsub(v1); + v2 = vector_abs_sse2_maxsub(v2); + v3 = vector_abs_sse2_maxsub(v3); + v4 = vector_abs_sse2_maxsub(v4); + v5 = vector_abs_sse2_maxsub(v5); + v6 = vector_abs_sse2_maxsub(v6); + v7 = vector_abs_sse2_maxsub(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_abs_sse2_maxsub); + +static void bm_vector_abs_sse2_and(benchmark::State& state) +{ + vector4f v0 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v1 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v2 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v3 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v4 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v5 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v6 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v7 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + + for (auto _ : state) + { + v0 = vector_abs_sse2_and(v0); + v1 = vector_abs_sse2_and(v1); + v2 = vector_abs_sse2_and(v2); + v3 = vector_abs_sse2_and(v3); + v4 = vector_abs_sse2_and(v4); + v5 = vector_abs_sse2_and(v5); + v6 = vector_abs_sse2_and(v6); + v7 = vector_abs_sse2_and(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_abs_sse2_and); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_acos.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_acos.cpp new file mode 100644 index 00000000..7e45ec38 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_acos.cpp @@ -0,0 +1,234 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_acos_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = scalar_acos(scalarf(vector_get_x(input))); + scalarf y = scalar_acos(scalarf(vector_get_y(input))); + scalarf z = scalar_acos(scalarf(vector_get_z(input))); + scalarf w = scalar_acos(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_acos_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use the identity: acos(value) + asin(value) = PI/2 + // This ends up being: acos(value) = PI/2 - asin(value) + // Since asin(value) = PI/2 - sqrt(1.0 - polynomial(value)) + // Our end result is acos(value) = sqrt(1.0 - polynomial(value)) + // This means we can re-use the same polynomial as asin() + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + // Use the sign bit to generate our absolute value since we'll re-use that constant + const __m128 sign_bit = _mm_set_ps1(-0.0F); + __m128 abs_value = _mm_andnot_ps(sign_bit, input); + + // Calculate our value + __m128 result = _mm_add_ps(_mm_mul_ps(abs_value, _mm_set_ps1(-1.2690614339589956e-3F)), _mm_set_ps1(6.7072304676685235e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-1.7162031184398074e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(3.0961594977611639e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-5.0207843052845647e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(8.8986946573346160e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-2.1459960076929829e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(1.5707963267948966F)); + + // Scale our result + __m128 scale = _mm_sqrt_ps(_mm_sub_ps(_mm_set_ps1(1.0F), abs_value)); + result = _mm_mul_ps(result, scale); + + // Normally the math is as follow: + // If input is positive: result + // If input is negative: PI - result = -result + PI + + // As such, the offset is 0.0 when the input is positive and PI when negative + __m128 is_input_negative = _mm_cmplt_ps(input, _mm_setzero_ps()); + __m128 offset = _mm_and_ps(is_input_negative, _mm_set_ps1(rtm::constants::pi())); + + // And our result has the same sign of the input + __m128 input_sign = _mm_and_ps(input, sign_bit); + result = _mm_or_ps(result, input_sign); + return _mm_add_ps(result, offset); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_acos_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use the identity: acos(value) + asin(value) = PI/2 + // This ends up being: acos(value) = PI/2 - asin(value) + // Since asin(value) = PI/2 - sqrt(1.0 - polynomial(value)) + // Our end result is acos(value) = sqrt(1.0 - polynomial(value)) + // This means we can re-use the same polynomial as asin() + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + // Use the sign bit to generate our absolute value since we'll re-use that constant + float32x4_t abs_value = vabsq_f32(input); + + // Calculate our value + float32x4_t result = vmlaq_n_f32(vdupq_n_f32(6.7072304676685235e-3F), abs_value, -1.2690614339589956e-3F); + result = vmlaq_f32(vdupq_n_f32(-1.7162031184398074e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(3.0961594977611639e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(-5.0207843052845647e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(8.8986946573346160e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(-2.1459960076929829e-1F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(1.5707963267948966F), result, abs_value); + + // Scale our result + float32x4_t scale = vector_sqrt(vsubq_f32(vdupq_n_f32(1.0F), abs_value)); + result = vmulq_f32(result, scale); + + // Normally the math is as follow: + // If input is positive: result + // If input is negative: PI - result = -result + PI + + // As such, the offset is 0.0 when the input is positive and PI when negative + uint32x4_t is_input_negative = vcltq_f32(input, vdupq_n_f32(0.0F)); + float32x4_t offset = vreinterpretq_f32_u32(vandq_u32(is_input_negative, vreinterpretq_u32_f32(vdupq_n_f32(rtm::constants::pi())))); + + // And our result has the same sign of the input + uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + uint32x4_t input_sign = vandq_u32(vreinterpretq_u32_f32(input), sign_mask); + + result = vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(result), input_sign)); + return vaddq_f32(result, offset); +} +#endif + +static void bm_vector_acos_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_acos_scalar(v0); + v1 = vector_acos_scalar(v1); + v2 = vector_acos_scalar(v2); + v3 = vector_acos_scalar(v3); + v4 = vector_acos_scalar(v4); + v5 = vector_acos_scalar(v5); + v6 = vector_acos_scalar(v6); + v7 = vector_acos_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_acos_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_acos_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_acos_sse2(v0); + v1 = vector_acos_sse2(v1); + v2 = vector_acos_sse2(v2); + v3 = vector_acos_sse2(v3); + v4 = vector_acos_sse2(v4); + v5 = vector_acos_sse2(v5); + v6 = vector_acos_sse2(v6); + v7 = vector_acos_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_acos_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_acos_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_acos_neon(v0); + v1 = vector_acos_neon(v1); + v2 = vector_acos_neon(v2); + v3 = vector_acos_neon(v3); + v4 = vector_acos_neon(v4); + v5 = vector_acos_neon(v5); + v6 = vector_acos_neon(v6); + v7 = vector_acos_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_acos_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_asin.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_asin.cpp new file mode 100644 index 00000000..62ae19e1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_asin.cpp @@ -0,0 +1,225 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_asin_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = scalar_asin(scalarf(vector_get_x(input))); + scalarf y = scalar_asin(scalarf(vector_get_y(input))); + scalarf z = scalar_asin(scalarf(vector_get_z(input))); + scalarf w = scalar_asin(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_asin_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 7 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + // Use the sign bit to generate our absolute value since we'll re-use that constant + const __m128 sign_bit = _mm_set_ps1(-0.0F); + __m128 abs_value = _mm_andnot_ps(sign_bit, input); + + // Calculate our value + __m128 result = _mm_add_ps(_mm_mul_ps(abs_value, _mm_set_ps1(-1.2690614339589956e-3F)), _mm_set_ps1(6.7072304676685235e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-1.7162031184398074e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(3.0961594977611639e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-5.0207843052845647e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(8.8986946573346160e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(-2.1459960076929829e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, abs_value), _mm_set_ps1(1.5707963267948966F)); + + // Scale our result + __m128 scale = _mm_sqrt_ps(_mm_sub_ps(_mm_set_ps1(1.0F), abs_value)); + result = _mm_mul_ps(result, scale); + + // Normally the math is as follow: + // If input is positive: PI/2 - result + // If input is negative: PI/2 - (PI - result) = PI/2 - PI + result = -PI/2 + result + + // As such, the offset is PI/2 and it takes the sign of the input + // This allows us to load a single constant from memory directly + __m128 input_sign = _mm_and_ps(input, sign_bit); + __m128 offset = _mm_or_ps(input_sign, _mm_set_ps1(rtm::constants::half_pi())); + + // And our result has the opposite sign of the input + result = _mm_xor_ps(result, _mm_xor_ps(input_sign, sign_bit)); + return _mm_add_ps(result, offset); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_asin_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 7 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // We first calculate our scale: sqrt(1.0 - abs(value)) + // Use the sign bit to generate our absolute value since we'll re-use that constant + float32x4_t abs_value = vabsq_f32(input); + + // Calculate our value + float32x4_t result = vmlaq_n_f32(vdupq_n_f32(6.7072304676685235e-3F), abs_value, -1.2690614339589956e-3F); + result = vmlaq_f32(vdupq_n_f32(-1.7162031184398074e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(3.0961594977611639e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(-5.0207843052845647e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(8.8986946573346160e-2F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(-2.1459960076929829e-1F), result, abs_value); + result = vmlaq_f32(vdupq_n_f32(1.5707963267948966F), result, abs_value); + + // Scale our result + float32x4_t scale = vector_sqrt(vsubq_f32(vdupq_n_f32(1.0F), abs_value)); + result = vmulq_f32(result, scale); + + // Normally the math is as follow: + // If input is positive: PI/2 - result + // If input is negative: PI/2 - (PI - result) = PI/2 - PI + result = -PI/2 + result + + // As such, the offset is PI/2 and it takes the sign of the input + // This allows us to load a single constant from memory directly + uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + uint32x4_t input_sign = vandq_u32(vreinterpretq_u32_f32(input), sign_mask); + float32x4_t offset = vreinterpretq_f32_u32(vorrq_u32(input_sign, vreinterpretq_u32_f32(vdupq_n_f32(rtm::constants::half_pi())))); + + // And our result has the opposite sign of the input + result = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(result), veorq_u32(input_sign, sign_mask))); + return vaddq_f32(result, offset); +} +#endif + +static void bm_vector_asin_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_asin_scalar(v0); + v1 = vector_asin_scalar(v1); + v2 = vector_asin_scalar(v2); + v3 = vector_asin_scalar(v3); + v4 = vector_asin_scalar(v4); + v5 = vector_asin_scalar(v5); + v6 = vector_asin_scalar(v6); + v7 = vector_asin_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_asin_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_asin_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_asin_sse2(v0); + v1 = vector_asin_sse2(v1); + v2 = vector_asin_sse2(v2); + v3 = vector_asin_sse2(v3); + v4 = vector_asin_sse2(v4); + v5 = vector_asin_sse2(v5); + v6 = vector_asin_sse2(v6); + v7 = vector_asin_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_asin_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_asin_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_asin_neon(v0); + v1 = vector_asin_neon(v1); + v2 = vector_asin_neon(v2); + v3 = vector_asin_neon(v3); + v4 = vector_asin_neon(v4); + v5 = vector_asin_neon(v5); + v6 = vector_asin_neon(v6); + v7 = vector_asin_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_asin_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_atan.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_atan.cpp new file mode 100644 index 00000000..7d88d8a6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_atan.cpp @@ -0,0 +1,228 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_atan_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = scalar_atan(scalarf(vector_get_x(input))); + scalarf y = scalar_atan(scalarf(vector_get_y(input))); + scalarf z = scalar_atan(scalarf(vector_get_z(input))); + scalarf w = scalar_atan(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_atan_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 13 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Discard our sign, we'll restore it later + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_value = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + + // Compute our value + __m128 is_larger_than_one = _mm_cmpgt_ps(abs_value, _mm_set_ps1(1.0F)); + __m128 reciprocal = vector_reciprocal(abs_value); + + __m128 x = vector_select(is_larger_than_one, reciprocal, abs_value); + + __m128 x2 = _mm_mul_ps(x, x); + + __m128 result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(7.2128853633444123e-3F)), _mm_set_ps1(-3.5059680836411644e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(8.1675882859940430e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.3374657325451267e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.9856563505717162e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-3.3324998579202170e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.0F)); + result = _mm_mul_ps(result, x); + + __m128 remapped = _mm_sub_ps(_mm_set_ps1(rtm::constants::half_pi()), result); + + // pi/2 - result + result = vector_select(is_larger_than_one, remapped, result); + + // Keep the original sign + return _mm_or_ps(result, _mm_and_ps(input, _mm_set_ps1(-0.0F))); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_atan_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 13 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Discard our sign, we'll restore it later + float32x4_t abs_value = vabsq_f32(input); + + // Compute our value + uint32x4_t is_larger_than_one = vcagtq_f32(input, vdupq_n_f32(1.0F)); + float32x4_t reciprocal = vector_reciprocal(abs_value); + + float32x4_t x = vector_select(is_larger_than_one, reciprocal, abs_value); + + float32x4_t x2 = vmulq_f32(x, x); + +#if defined(RTM_NEON64_INTRINSICS) + float32x4_t result = vfmaq_n_f32(vdupq_n_f32(-3.5059680836411644e-2F), x2, 7.2128853633444123e-3F); + result = vfmaq_f32(vdupq_n_f32(8.1675882859940430e-2F), result, x2); + result = vfmaq_f32(vdupq_n_f32(-1.3374657325451267e-1F), result, x2); + result = vfmaq_f32(vdupq_n_f32(1.9856563505717162e-1F), result, x2); + result = vfmaq_f32(vdupq_n_f32(-3.3324998579202170e-1F), result, x2); + result = vfmaq_f32(vdupq_n_f32(1.0F), result, x2); +#else + float32x4_t result = vmlaq_n_f32(vdupq_n_f32(-3.5059680836411644e-2F), x2, 7.2128853633444123e-3F); + result = vmlaq_f32(vdupq_n_f32(8.1675882859940430e-2F), result, x2); + result = vmlaq_f32(vdupq_n_f32(-1.3374657325451267e-1F), result, x2); + result = vmlaq_f32(vdupq_n_f32(1.9856563505717162e-1F), result, x2); + result = vmlaq_f32(vdupq_n_f32(-3.3324998579202170e-1F), result, x2); + result = vmlaq_f32(vdupq_n_f32(1.0F), result, x2); +#endif + + result = vmulq_f32(result, x); + + float32x4_t remapped = vsubq_f32(vdupq_n_f32(rtm::constants::half_pi()), result); + + // pi/2 - result + result = vector_select(is_larger_than_one, remapped, result); + + // Keep the original sign + return vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(result), vandq_u32(vreinterpretq_u32_f32(input), vreinterpretq_u32_f32(vdupq_n_f32(-0.0F))))); +} +#endif + +static void bm_vector_atan_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_atan_scalar(v0); + v1 = vector_atan_scalar(v1); + v2 = vector_atan_scalar(v2); + v3 = vector_atan_scalar(v3); + v4 = vector_atan_scalar(v4); + v5 = vector_atan_scalar(v5); + v6 = vector_atan_scalar(v6); + v7 = vector_atan_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_atan_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_atan_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_atan_sse2(v0); + v1 = vector_atan_sse2(v1); + v2 = vector_atan_sse2(v2); + v3 = vector_atan_sse2(v3); + v4 = vector_atan_sse2(v4); + v5 = vector_atan_sse2(v5); + v6 = vector_atan_sse2(v6); + v7 = vector_atan_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_atan_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_atan_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_atan_neon(v0); + v1 = vector_atan_neon(v1); + v2 = vector_atan_neon(v2); + v3 = vector_atan_neon(v3); + v4 = vector_atan_neon(v4); + v5 = vector_atan_neon(v5); + v6 = vector_atan_neon(v6); + v7 = vector_atan_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_atan_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_atan2.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_atan2.cpp new file mode 100644 index 00000000..8bb85237 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_atan2.cpp @@ -0,0 +1,242 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_atan2_scalar(vector4f_arg0 y, vector4f_arg1 x) RTM_NO_EXCEPT +{ + scalarf x_ = scalar_atan2(scalarf(vector_get_x(y)), scalarf(vector_get_x(x))); + scalarf y_ = scalar_atan2(scalarf(vector_get_y(y)), scalarf(vector_get_y(x))); + scalarf z = scalar_atan2(scalarf(vector_get_z(y)), scalarf(vector_get_z(x))); + scalarf w = scalar_atan2(scalarf(vector_get_w(y)), scalarf(vector_get_w(x))); + return vector_set(x_, y_, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_atan2_sse2(vector4f_arg0 y, vector4f_arg1 x) RTM_NO_EXCEPT +{ + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + + const __m128 zero = _mm_setzero_ps(); + __m128 is_x_zero = _mm_cmpeq_ps(x, zero); + __m128 is_y_zero = _mm_cmpeq_ps(y, zero); + __m128 inputs_are_zero = _mm_and_ps(is_x_zero, is_y_zero); + + __m128 is_x_positive = _mm_cmpgt_ps(x, zero); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 y_sign = _mm_and_ps(y, sign_mask); + + // If X == 0.0, our offset is PI/2 otherwise it is PI both with the sign of Y + __m128 half_pi = _mm_set_ps1(rtm::constants::half_pi()); + __m128 pi = _mm_set_ps1(rtm::constants::pi()); + __m128 offset = _mm_or_ps(_mm_and_ps(is_x_zero, half_pi), _mm_andnot_ps(is_x_zero, pi)); + offset = _mm_or_ps(offset, y_sign); + + // If X > 0.0, our offset is 0.0 + offset = _mm_andnot_ps(is_x_positive, offset); + + // If X == 0.0 and Y == 0.0, our offset is 0.0 + offset = _mm_andnot_ps(inputs_are_zero, offset); + + __m128 angle = _mm_div_ps(y, x); + __m128 value = vector_atan(angle); + + // If X == 0.0, our value is 0.0 otherwise it is atan(y/x) + value = _mm_andnot_ps(is_x_zero, value); + + // If X == 0.0 and Y == 0.0, our value is 0.0 + value = _mm_andnot_ps(inputs_are_zero, value); + + return _mm_add_ps(value, offset); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_atan2_neon(vector4f_arg0 y, vector4f_arg1 x) RTM_NO_EXCEPT +{ + // If X == 0.0 and Y != 0.0, we return PI/2 with the sign of Y + // If X == 0.0 and Y == 0.0, we return 0.0 + // If X > 0.0, we return atan(y/x) + // If X < 0.0, we return atan(y/x) + sign(Y) * PI + // See: https://en.wikipedia.org/wiki/Atan2#Definition_and_computation + +#if defined(RTM_NEON64_INTRINSICS) + uint32x4_t is_x_zero = vceqzq_f32(x); + uint32x4_t is_y_zero = vceqzq_f32(y); + uint32x4_t inputs_are_zero = vandq_u32(is_x_zero, is_y_zero); + + uint32x4_t is_x_positive = vcgtzq_f32(x); +#else + float32x4_t zero = vdupq_n_f32(0.0F); + uint32x4_t is_x_zero = vceqq_f32(x, zero); + uint32x4_t is_y_zero = vceqq_f32(y, zero); + uint32x4_t inputs_are_zero = vandq_u32(is_x_zero, is_y_zero); + + uint32x4_t is_x_positive = vcgtq_f32(x, zero); +#endif + + uint32x4_t y_sign = vandq_u32(vreinterpretq_u32_f32(y), vreinterpretq_u32_f32(vdupq_n_f32(-0.0F))); + + // If X == 0.0, our offset is PI/2 otherwise it is PI both with the sign of Y + float32x4_t half_pi = vdupq_n_f32(rtm::constants::half_pi()); + float32x4_t pi = vdupq_n_f32(rtm::constants::pi()); + float32x4_t offset = vreinterpretq_f32_u32(vorrq_u32(vandq_u32(is_x_zero, vreinterpretq_u32_f32(half_pi)), vandq_u32(vmvnq_u32(is_x_zero), vreinterpretq_u32_f32(pi)))); + offset = vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(offset), y_sign)); + + // If X > 0.0, our offset is 0.0 + offset = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(is_x_positive), vreinterpretq_u32_f32(offset))); + + // If X == 0.0 and Y == 0.0, our offset is 0.0 + offset = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(inputs_are_zero), vreinterpretq_u32_f32(offset))); + + float32x4_t angle = vector_div(y, x); + float32x4_t value = vector_atan(angle); + + // If X == 0.0, our value is 0.0 otherwise it is atan(y/x) + value = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(is_x_zero), vreinterpretq_u32_f32(value))); + + // If X == 0.0 and Y == 0.0, our value is 0.0 + value = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(inputs_are_zero), vreinterpretq_u32_f32(value))); + + return vaddq_f32(value, offset); +} +#endif + +static void bm_vector_atan2_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_atan2_scalar(v0, v1); + v1 = vector_atan2_scalar(v1, v2); + v2 = vector_atan2_scalar(v2, v3); + v3 = vector_atan2_scalar(v3, v4); + v4 = vector_atan2_scalar(v4, v5); + v5 = vector_atan2_scalar(v5, v6); + v6 = vector_atan2_scalar(v6, v7); + v7 = vector_atan2_scalar(v7, v0); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_atan2_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_atan2_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_atan2_sse2(v0, v1); + v1 = vector_atan2_sse2(v1, v2); + v2 = vector_atan2_sse2(v2, v3); + v3 = vector_atan2_sse2(v3, v4); + v4 = vector_atan2_sse2(v4, v5); + v5 = vector_atan2_sse2(v5, v6); + v6 = vector_atan2_sse2(v6, v7); + v7 = vector_atan2_sse2(v7, v0); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_atan2_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_atan2_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-0.134f); + vector4f v1 = vector_set(0.134f); + vector4f v2 = vector_set(-0.134f); + vector4f v3 = vector_set(0.134f); + vector4f v4 = vector_set(-0.134f); + vector4f v5 = vector_set(0.134f); + vector4f v6 = vector_set(-0.134f); + vector4f v7 = vector_set(0.134f); + + for (auto _ : state) + { + v0 = vector_atan2_neon(v0, v1); + v1 = vector_atan2_neon(v1, v2); + v2 = vector_atan2_neon(v2, v3); + v3 = vector_atan2_neon(v3, v4); + v4 = vector_atan2_neon(v4, v5); + v5 = vector_atan2_neon(v5, v6); + v6 = vector_atan2_neon(v6, v7); + v7 = vector_atan2_neon(v7, v0); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_atan2_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_ceil.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_ceil.cpp new file mode 100644 index 00000000..210cbba2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_ceil.cpp @@ -0,0 +1,219 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_ceil_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = vector_get_x(input); + scalarf y = vector_get_y(input); + scalarf z = vector_get_z(input); + scalarf w = vector_get_w(input); + return vector_set(scalar_ceil(x), scalar_ceil(y), scalar_ceil(z), scalar_ceil(w)); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_ceil_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ps(input, input); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + __m128 is_positive = _mm_cmplt_ps(integer_part, input); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Negative values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_positive)); + + // Subtract our bias to properly handle positive values + integer_part = _mm_sub_ps(integer_part, bias); + + return _mm_or_ps(_mm_and_ps(use_original_input, input), _mm_andnot_ps(use_original_input, integer_part)); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_ceil_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(input)); + + // Test if the returned value is smaller than the original. + // A positive input will round towards zero and be lower when we need it to be greater. + uint32x4_t is_positive = vcltq_f32(integer_part, input); + + float32x4_t bias = vcvtq_f32_s32(is_positive); + + // Subtract our bias to properly handle positive values + integer_part = vsubq_f32(integer_part, bias); + + return vbslq_f32(use_original_input, input, integer_part); +} +#endif + +static void bm_vector_ceil_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_ceil_scalar(v0); + v1 = vector_ceil_scalar(v1); + v2 = vector_ceil_scalar(v2); + v3 = vector_ceil_scalar(v3); + v4 = vector_ceil_scalar(v4); + v5 = vector_ceil_scalar(v5); + v6 = vector_ceil_scalar(v6); + v7 = vector_ceil_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_ceil_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_ceil_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_ceil_sse2(v0); + v1 = vector_ceil_sse2(v1); + v2 = vector_ceil_sse2(v2); + v3 = vector_ceil_sse2(v3); + v4 = vector_ceil_sse2(v4); + v5 = vector_ceil_sse2(v5); + v6 = vector_ceil_sse2(v6); + v7 = vector_ceil_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_ceil_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_ceil_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_ceil_neon(v0); + v1 = vector_ceil_neon(v1); + v2 = vector_ceil_neon(v2); + v3 = vector_ceil_neon(v3); + v4 = vector_ceil_neon(v4); + v5 = vector_ceil_neon(v5); + v6 = vector_ceil_neon(v6); + v7 = vector_ceil_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_ceil_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_cos.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_cos.cpp new file mode 100644 index 00000000..e69487f5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_cos.cpp @@ -0,0 +1,233 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_cos_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = scalar_cos(scalarf(vector_get_x(input))); + scalarf y = scalar_cos(scalarf(vector_get_y(input))); + scalarf z = scalar_cos(scalarf(vector_get_z(input))); + scalarf w = scalar_cos(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_cos_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ps(input, _mm_set_ps1(rtm::constants::one_div_two_pi())); + quotient = vector_round_bankers(quotient); + quotient = _mm_mul_ps(quotient, _mm_set_ps1(rtm::constants::two_pi())); + __m128 x = _mm_sub_ps(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 x_sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(x_sign, _mm_set_ps1(rtm::constants::pi())); + const __m128 reflection = _mm_sub_ps(reference, x); + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + __m128 is_less_equal_than_half_pi = _mm_cmple_ps(x_abs, _mm_set_ps1(rtm::constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const __m128 x2 = _mm_mul_ps(x, x); + __m128 result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(-2.6051615464872668e-7F)), _mm_set_ps1(2.4760495088926859e-5F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.3888377661039897e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(4.1666638865338612e-2F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-4.9999999508695869e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.0F)); + + // Remap into [-pi, pi] + return _mm_or_ps(result, _mm_andnot_ps(is_less_equal_than_half_pi, sign_mask)); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_cos_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 10 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float32x4_t quotient = vmulq_n_f32(input, rtm::constants::one_div_two_pi()); + quotient = vector_round_bankers(quotient); + quotient = vmulq_n_f32(quotient, rtm::constants::two_pi()); + float32x4_t x = vsubq_f32(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), sign_mask); + float32x4_t reference = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(rtm::constants::pi())))); + + float32x4_t reflection = vsubq_f32(reference, x); + float32x4_t is_less_equal_than_half_pi = vcaleq_f32(x, vdupq_n_f32(rtm::constants::half_pi())); + x = vbslq_f32(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + float32x4_t x2 = vmulq_f32(x, x); + +#if defined(RTM_NEON64_INTRINSICS) + float32x4_t result = vfmaq_n_f32(vdupq_n_f32(2.4760495088926859e-5F), x2, -2.6051615464872668e-7F); + result = vfmaq_f32(vdupq_n_f32(-1.3888377661039897e-3F), result, x2); + result = vfmaq_f32(vdupq_n_f32(4.1666638865338612e-2F), result, x2); + result = vfmaq_f32(vdupq_n_f32(-4.9999999508695869e-1F), result, x2); + result = vfmaq_f32(vdupq_n_f32(1.0F), result, x2); +#else + float32x4_t result = vmlaq_n_f32(vdupq_n_f32(2.4760495088926859e-5F), x2, -2.6051615464872668e-7F); + result = vmlaq_f32(vdupq_n_f32(-1.3888377661039897e-3F), result, x2); + result = vmlaq_f32(vdupq_n_f32(4.1666638865338612e-2F), result, x2); + result = vmlaq_f32(vdupq_n_f32(-4.9999999508695869e-1F), result, x2); + result = vmlaq_f32(vdupq_n_f32(1.0F), result, x2); +#endif + + // Remap into [-pi, pi] + return vbslq_f32(is_less_equal_than_half_pi, result, vnegq_f32(result)); +} +#endif + +static void bm_vector_cos_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + vector4f scale = vector_set(100.0F); + + for (auto _ : state) + { + v0 = vector_mul(vector_cos_scalar(v0), scale); + v1 = vector_mul(vector_cos_scalar(v1), scale); + v2 = vector_mul(vector_cos_scalar(v2), scale); + v3 = vector_mul(vector_cos_scalar(v3), scale); + v4 = vector_mul(vector_cos_scalar(v4), scale); + v5 = vector_mul(vector_cos_scalar(v5), scale); + v6 = vector_mul(vector_cos_scalar(v6), scale); + v7 = vector_mul(vector_cos_scalar(v7), scale); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_cos_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_cos_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + vector4f scale = vector_set(100.0F); + + for (auto _ : state) + { + v0 = vector_mul(vector_cos_sse2(v0), scale); + v1 = vector_mul(vector_cos_sse2(v1), scale); + v2 = vector_mul(vector_cos_sse2(v2), scale); + v3 = vector_mul(vector_cos_sse2(v3), scale); + v4 = vector_mul(vector_cos_sse2(v4), scale); + v5 = vector_mul(vector_cos_sse2(v5), scale); + v6 = vector_mul(vector_cos_sse2(v6), scale); + v7 = vector_mul(vector_cos_sse2(v7), scale); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_cos_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_cos_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + vector4f scale = vector_set(100.0F); + + for (auto _ : state) + { + v0 = vector_mul(vector_cos_neon(v0), scale); + v1 = vector_mul(vector_cos_neon(v1), scale); + v2 = vector_mul(vector_cos_neon(v2), scale); + v3 = vector_mul(vector_cos_neon(v3), scale); + v4 = vector_mul(vector_cos_neon(v4), scale); + v5 = vector_mul(vector_cos_neon(v5), scale); + v6 = vector_mul(vector_cos_neon(v6), scale); + v7 = vector_mul(vector_cos_neon(v7), scale); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_cos_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_floor.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_floor.cpp new file mode 100644 index 00000000..d75e1464 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_floor.cpp @@ -0,0 +1,287 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_floor_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = vector_get_x(input); + scalarf y = vector_get_y(input); + scalarf z = vector_get_z(input); + scalarf w = vector_get_w(input); + return vector_set(scalar_floor(x), scalar_floor(y), scalar_floor(z), scalar_floor(w)); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_floor_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ps(input, input); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvtps_epi32(input)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + __m128 is_negative = _mm_cmpgt_ps(integer_part, input); + + // Convert our mask to a float, ~0 yields -1.0 since it is a valid signed integer + // Positive values will yield a 0.0 bias + __m128 bias = _mm_cvtepi32_ps(_mm_castps_si128(is_negative)); + + // Add our bias to properly handle negative values + integer_part = _mm_add_ps(integer_part, bias); + + return _mm_or_ps(_mm_and_ps(use_original_input, input), _mm_andnot_ps(use_original_input, integer_part)); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_floor_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + float32x4_t abs_input = vabsq_f32(input); + uint32x4_t is_input_large = vcgeq_f32(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(input)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + uint32x4_t is_negative = vcgtq_f32(integer_part, input); + + float32x4_t bias = vcvtq_f32_s32(is_negative); + + // Add our bias to properly handle negative values + integer_part = vaddq_f32(integer_part, bias); + + return vbslq_f32(use_original_input, input, integer_part); +} + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_floor_neon_abs_cmp(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(input)); + + // Test if the returned value is greater than the original. + // A negative input will round towards zero and be greater when we need it to be smaller. + uint32x4_t is_negative = vcgtq_f32(integer_part, input); + + float32x4_t bias = vcvtq_f32_s32(is_negative); + + // Add our bias to properly handle negative values + integer_part = vaddq_f32(integer_part, bias); + + return vbslq_f32(use_original_input, input, integer_part); +} +#endif + +static void bm_vector_floor_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_floor_scalar(v0); + v1 = vector_floor_scalar(v1); + v2 = vector_floor_scalar(v2); + v3 = vector_floor_scalar(v3); + v4 = vector_floor_scalar(v4); + v5 = vector_floor_scalar(v5); + v6 = vector_floor_scalar(v6); + v7 = vector_floor_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_floor_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_floor_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_floor_sse2(v0); + v1 = vector_floor_sse2(v1); + v2 = vector_floor_sse2(v2); + v3 = vector_floor_sse2(v3); + v4 = vector_floor_sse2(v4); + v5 = vector_floor_sse2(v5); + v6 = vector_floor_sse2(v6); + v7 = vector_floor_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_floor_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_floor_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_floor_neon(v0); + v1 = vector_floor_neon(v1); + v2 = vector_floor_neon(v2); + v3 = vector_floor_neon(v3); + v4 = vector_floor_neon(v4); + v5 = vector_floor_neon(v5); + v6 = vector_floor_neon(v6); + v7 = vector_floor_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_floor_neon); + +static void bm_vector_floor_neon_abs_cmp(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_floor_neon_abs_cmp(v0); + v1 = vector_floor_neon_abs_cmp(v1); + v2 = vector_floor_neon_abs_cmp(v2); + v3 = vector_floor_neon_abs_cmp(v3); + v4 = vector_floor_neon_abs_cmp(v4); + v5 = vector_floor_neon_abs_cmp(v5); + v6 = vector_floor_neon_abs_cmp(v6); + v7 = vector_floor_neon_abs_cmp(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_floor_neon_abs_cmp); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_reciprocal.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_reciprocal.cpp new file mode 100644 index 00000000..ba0a2ed9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_reciprocal.cpp @@ -0,0 +1,173 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_reciprocal_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + return vector_div(vector_set(1.0F), input); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_reciprocal_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Perform two passes of Newton-Raphson iteration on the hardware estimate + __m128 x0 = _mm_rcp_ps(input); + + // First iteration + __m128 x1 = _mm_sub_ps(_mm_add_ps(x0, x0), _mm_mul_ps(input, _mm_mul_ps(x0, x0))); + + // Second iteration + __m128 x2 = _mm_sub_ps(_mm_add_ps(x1, x1), _mm_mul_ps(input, _mm_mul_ps(x1, x1))); + return x2; +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_reciprocal_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Perform two passes of Newton-Raphson iteration on the hardware estimate + float32x4_t x0 = vrecpeq_f32(input); + + // First iteration + float32x4_t x1 = vmulq_f32(x0, vrecpsq_f32(x0, input)); + + // Second iteration + float32x4_t x2 = vmulq_f32(x1, vrecpsq_f32(x1, input)); + return x2; +} +#endif + +static void bm_vector_reciprocal_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_reciprocal_scalar(v0); + v1 = vector_reciprocal_scalar(v1); + v2 = vector_reciprocal_scalar(v2); + v3 = vector_reciprocal_scalar(v3); + v4 = vector_reciprocal_scalar(v4); + v5 = vector_reciprocal_scalar(v5); + v6 = vector_reciprocal_scalar(v6); + v7 = vector_reciprocal_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_reciprocal_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_reciprocal_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_reciprocal_sse2(v0); + v1 = vector_reciprocal_sse2(v1); + v2 = vector_reciprocal_sse2(v2); + v3 = vector_reciprocal_sse2(v3); + v4 = vector_reciprocal_sse2(v4); + v5 = vector_reciprocal_sse2(v5); + v6 = vector_reciprocal_sse2(v6); + v7 = vector_reciprocal_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_reciprocal_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_reciprocal_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_reciprocal_neon(v0); + v1 = vector_reciprocal_neon(v1); + v2 = vector_reciprocal_neon(v2); + v3 = vector_reciprocal_neon(v3); + v4 = vector_reciprocal_neon(v4); + v5 = vector_reciprocal_neon(v5); + v6 = vector_reciprocal_neon(v6); + v7 = vector_reciprocal_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_reciprocal_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_round_bankers.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_round_bankers.cpp new file mode 100644 index 00000000..ed26d1f1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_round_bankers.cpp @@ -0,0 +1,236 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_bankers_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = scalar_round_bankers(scalarf(vector_get_x(input))); + scalarf y = scalar_round_bankers(scalarf(vector_get_y(input))); + scalarf z = scalar_round_bankers(scalarf(vector_get_z(input))); + scalarf w = scalar_round_bankers(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_bankers_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input, sign_mask); + + // We add the largest integer that a 32 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + __m128 truncating_offset = _mm_or_ps(sign, fractional_limit); + __m128 integer_part = _mm_sub_ps(_mm_add_ps(input, truncating_offset), truncating_offset); + + // If our input was so large that it had no fractional part, return it unchanged + // Otherwise return our integer part + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + return _mm_or_ps(_mm_and_ps(is_input_large, input), _mm_andnot_ps(is_input_large, integer_part)); +} +#endif + +#if defined(RTM_NEON64_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_bankers_neon64(vector4f_arg0 input) RTM_NO_EXCEPT +{ + return vrndnq_f32(input); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_bankers_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(input), vdupq_n_f32(-0.0F)); + + // We add the largest integer that a 32 bit floating point number can represent and subtract it afterwards. + // This relies on the fact that if we had a fractional part, the new value cannot be represented accurately + // and IEEE 754 will perform rounding for us. The default rounding mode is Banker's rounding. + // This has the effect of removing the fractional part while simultaneously rounding. + // Use the same sign as the input value to make sure we handle positive and negative values. + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + float32x4_t truncating_offset = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(fractional_limit))); + float32x4_t integer_part = vsubq_f32(vaddq_f32(input, truncating_offset), truncating_offset); + + // If our input was so large that it had no fractional part, return it unchanged + // Otherwise return our integer part + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + return vbslq_f32(is_input_large, input, integer_part); +} +#endif + +static void bm_vector_round_bankers_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_bankers_scalar(v0); + v1 = vector_round_bankers_scalar(v1); + v2 = vector_round_bankers_scalar(v2); + v3 = vector_round_bankers_scalar(v3); + v4 = vector_round_bankers_scalar(v4); + v5 = vector_round_bankers_scalar(v5); + v6 = vector_round_bankers_scalar(v6); + v7 = vector_round_bankers_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_bankers_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_round_bankers_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_bankers_sse2(v0); + v1 = vector_round_bankers_sse2(v1); + v2 = vector_round_bankers_sse2(v2); + v3 = vector_round_bankers_sse2(v3); + v4 = vector_round_bankers_sse2(v4); + v5 = vector_round_bankers_sse2(v5); + v6 = vector_round_bankers_sse2(v6); + v7 = vector_round_bankers_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_bankers_sse2); +#endif + +#if defined(RTM_NEON64_INTRINSICS) +static void bm_vector_round_bankers_neon64(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_bankers_neon64(v0); + v1 = vector_round_bankers_neon64(v1); + v2 = vector_round_bankers_neon64(v2); + v3 = vector_round_bankers_neon64(v3); + v4 = vector_round_bankers_neon64(v4); + v5 = vector_round_bankers_neon64(v5); + v6 = vector_round_bankers_neon64(v6); + v7 = vector_round_bankers_neon64(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_bankers_neon64); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_round_bankers_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_bankers_neon(v0); + v1 = vector_round_bankers_neon(v1); + v2 = vector_round_bankers_neon(v2); + v3 = vector_round_bankers_neon(v3); + v4 = vector_round_bankers_neon(v4); + v5 = vector_round_bankers_neon(v5); + v6 = vector_round_bankers_neon(v6); + v7 = vector_round_bankers_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_bankers_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_round_symmetric.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_round_symmetric.cpp new file mode 100644 index 00000000..8a3b01d9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_round_symmetric.cpp @@ -0,0 +1,211 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_symmetric_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const vector4f half = vector_set(0.5F); + const vector4f floored = vector_floor(vector_add(input, half)); + const vector4f ceiled = vector_ceil(vector_sub(input, half)); + const mask4f is_greater_equal = vector_greater_equal(input, vector_zero()); + return vector_select(is_greater_equal, floored, ceiled); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_symmetric_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 fractional_limit = _mm_set_ps1(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + __m128 abs_input = _mm_and_ps(input, _mm_castsi128_ps(abs_mask)); + __m128 is_input_large = _mm_cmpge_ps(abs_input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + __m128 is_nan = _mm_cmpneq_ps(input, input); + + // Combine our masks to determine if we should return the original value + __m128 use_original_input = _mm_or_ps(is_input_large, is_nan); + + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(input, sign_mask); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + __m128 bias = _mm_or_ps(sign, _mm_set_ps1(0.5F)); + __m128 biased_input = _mm_add_ps(input, bias); + + // Convert to an integer with truncation and back, this rounds towards zero. + __m128 integer_part = _mm_cvtepi32_ps(_mm_cvttps_epi32(biased_input)); + + return _mm_or_ps(_mm_and_ps(use_original_input, input), _mm_andnot_ps(use_original_input, integer_part)); +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_round_symmetric_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // NaN, +- Infinity, and numbers larger or equal to 2^23 remain unchanged + // since they have no fractional part. + + float32x4_t fractional_limit = vdupq_n_f32(8388608.0F); // 2^23 + + // Build our mask, larger values that have no fractional part, and infinities will be true + // Smaller values and NaN will be false + uint32x4_t is_input_large = vcageq_f32(input, fractional_limit); + + // Test if our input is NaN with (value != value), it is only true for NaN + uint32x4_t is_nan = vmvnq_u32(vceqq_f32(input, input)); + + // Combine our masks to determine if we should return the original value + uint32x4_t use_original_input = vorrq_u32(is_input_large, is_nan); + + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(input), vdupq_n_f32(-0.0F)); + + // For positive values, we add a bias of 0.5. + // For negative values, we add a bias of -0.5. + float32x4_t bias = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(0.5F)))); + float32x4_t biased_input = vaddq_f32(input, bias); + + // Convert to an integer and back. This does banker's rounding by default + float32x4_t integer_part = vcvtq_f32_s32(vcvtq_s32_f32(biased_input)); + + return vbslq_f32(use_original_input, input, integer_part); +} +#endif + +static void bm_vector_round_symmetric_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_symmetric_scalar(v0); + v1 = vector_round_symmetric_scalar(v1); + v2 = vector_round_symmetric_scalar(v2); + v3 = vector_round_symmetric_scalar(v3); + v4 = vector_round_symmetric_scalar(v4); + v5 = vector_round_symmetric_scalar(v5); + v6 = vector_round_symmetric_scalar(v6); + v7 = vector_round_symmetric_scalar(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_symmetric_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_round_symmetric_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_symmetric_sse2(v0); + v1 = vector_round_symmetric_sse2(v1); + v2 = vector_round_symmetric_sse2(v2); + v3 = vector_round_symmetric_sse2(v3); + v4 = vector_round_symmetric_sse2(v4); + v5 = vector_round_symmetric_sse2(v5); + v6 = vector_round_symmetric_sse2(v6); + v7 = vector_round_symmetric_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_symmetric_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_round_symmetric_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + for (auto _ : state) + { + v0 = vector_round_symmetric_neon(v0); + v1 = vector_round_symmetric_neon(v1); + v2 = vector_round_symmetric_neon(v2); + v3 = vector_round_symmetric_neon(v3); + v4 = vector_round_symmetric_neon(v4); + v5 = vector_round_symmetric_neon(v5); + v6 = vector_round_symmetric_neon(v6); + v7 = vector_round_symmetric_neon(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_round_symmetric_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_sign.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_sign.cpp new file mode 100644 index 00000000..9d1a51f8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_sign.cpp @@ -0,0 +1,122 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +// Wins on Ryzen 2990X desktop VS2017 x64 AVX +// Despite taking 5 instructions unlike sse2 which needs 2, this is consistently faster as well. +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_sign_ref(vector4f_arg0 input) RTM_NO_EXCEPT +{ + const mask4f mask = vector_greater_equal(input, vector_zero()); + return vector_select(mask, vector_set(1.0f), vector_set(-1.0f)); +} + +#if defined(RTM_SSE2_INTRINSICS) +// Wins on Haswell laptop x64 AVX +// Same performance as ref. +// Wins on Ryzen 2990X desktop clang9 x64 AVX +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_sign_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + constexpr __m128 signs = { -0.0f, -0.0f, -0.0f, -0.0f }; + constexpr __m128 one = { 1.0f, 1.0f, 1.0f, 1.0f }; + const __m128 sign_bits = _mm_and_ps(input, signs); // Mask out the sign bit + return _mm_or_ps(sign_bits, one); // Copy the sign bit onto +-1.0f +} +#endif + +static void bm_vector_sign_ref(benchmark::State& state) +{ + vector4f v0 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v1 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v2 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v3 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v4 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v5 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v6 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v7 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + + for (auto _ : state) + { + v0 = vector_sign_ref(v0); + v1 = vector_sign_ref(v1); + v2 = vector_sign_ref(v2); + v3 = vector_sign_ref(v3); + v4 = vector_sign_ref(v4); + v5 = vector_sign_ref(v5); + v6 = vector_sign_ref(v6); + v7 = vector_sign_ref(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_sign_ref); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_sign_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v1 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v2 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v3 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v4 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v5 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v6 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + vector4f v7 = vector_set(-1.0f, 1.0f, -2.0f, -123.134f); + + for (auto _ : state) + { + v0 = vector_sign_sse2(v0); + v1 = vector_sign_sse2(v1); + v2 = vector_sign_sse2(v2); + v3 = vector_sign_sse2(v3); + v4 = vector_sign_sse2(v4); + v5 = vector_sign_sse2(v5); + v6 = vector_sign_sse2(v6); + v7 = vector_sign_sse2(v7); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_sign_sse2); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_sin.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_sin.cpp new file mode 100644 index 00000000..7cb94cdb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/bench/sources/bench_vector_sin.cpp @@ -0,0 +1,233 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Realtime Math contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace rtm; + +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_sin_scalar(vector4f_arg0 input) RTM_NO_EXCEPT +{ + scalarf x = scalar_sin(scalarf(vector_get_x(input))); + scalarf y = scalar_sin(scalarf(vector_get_y(input))); + scalarf z = scalar_sin(scalarf(vector_get_z(input))); + scalarf w = scalar_sin(scalarf(vector_get_w(input))); + return vector_set(x, y, z, w); +} + +#if defined(RTM_SSE2_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_sin_sse2(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + __m128 quotient = _mm_mul_ps(input, _mm_set_ps1(rtm::constants::one_div_two_pi())); + quotient = vector_round_bankers(quotient); + quotient = _mm_mul_ps(quotient, _mm_set_ps1(rtm::constants::two_pi())); + __m128 x = _mm_sub_ps(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + const __m128 sign_mask = _mm_set_ps(-0.0F, -0.0F, -0.0F, -0.0F); + __m128 sign = _mm_and_ps(x, sign_mask); + __m128 reference = _mm_or_ps(sign, _mm_set_ps1(rtm::constants::pi())); + + const __m128 reflection = _mm_sub_ps(reference, x); + const __m128i abs_mask = _mm_set_epi32(0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL, 0x7FFFFFFFULL); + const __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(abs_mask)); + + __m128 is_less_equal_than_half_pi = _mm_cmple_ps(x_abs, _mm_set_ps1(rtm::constants::half_pi())); + + x = RTM_VECTOR4F_SELECT(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + const __m128 x2 = _mm_mul_ps(x, x); + __m128 result = _mm_add_ps(_mm_mul_ps(x2, _mm_set_ps1(-2.3828544692960918e-8F)), _mm_set_ps1(2.7521557770526783e-6F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.9840782426250314e-4F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(8.3333303183525942e-3F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(-1.6666666601721269e-1F)); + result = _mm_add_ps(_mm_mul_ps(result, x2), _mm_set_ps1(1.0F)); + result = _mm_mul_ps(result, x); + return result; +} +#endif + +#if defined(RTM_NEON_INTRINSICS) +RTM_FORCE_NOINLINE vector4f RTM_SIMD_CALL vector_sin_neon(vector4f_arg0 input) RTM_NO_EXCEPT +{ + // Use a degree 11 minimax approximation polynomial + // See: GPGPU Programming for Games and Science (David H. Eberly) + + // Remap our input in the [-pi, pi] range + float32x4_t quotient = vmulq_n_f32(input, rtm::constants::one_div_two_pi()); + quotient = vector_round_bankers(quotient); + quotient = vmulq_n_f32(quotient, rtm::constants::two_pi()); + float32x4_t x = vsubq_f32(input, quotient); + + // Remap our input in the [-pi/2, pi/2] range + uint32x4_t sign_mask = vreinterpretq_u32_f32(vdupq_n_f32(-0.0F)); + uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(x), sign_mask); + float32x4_t reference = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(rtm::constants::pi())))); + + float32x4_t reflection = vsubq_f32(reference, x); + float32x4_t is_less_equal_than_half_pi = vcaleq_f32(x, vdupq_n_f32(rtm::constants::half_pi())); + x = vbslq_f32(is_less_equal_than_half_pi, x, reflection); + + // Calculate our value + float32x4_t x2 = vmulq_f32(x, x); + +#if defined(RTM_NEON64_INTRINSICS) + float32x4_t result = vfmaq_n_f32(vdupq_n_f32(2.7521557770526783e-6F), x2, -2.3828544692960918e-8F); + result = vfmaq_f32(vdupq_n_f32(-1.9840782426250314e-4F), result, x2); + result = vfmaq_f32(vdupq_n_f32(8.3333303183525942e-3F), result, x2); + result = vfmaq_f32(vdupq_n_f32(-1.6666666601721269e-1F), result, x2); + result = vfmaq_f32(vdupq_n_f32(1.0F), result, x2); +#else + float32x4_t result = vmlaq_n_f32(vdupq_n_f32(2.7521557770526783e-6F), x2, -2.3828544692960918e-8F); + result = vmlaq_f32(vdupq_n_f32(-1.9840782426250314e-4F), result, x2); + result = vmlaq_f32(vdupq_n_f32(8.3333303183525942e-3F), result, x2); + result = vmlaq_f32(vdupq_n_f32(-1.6666666601721269e-1F), result, x2); + result = vmlaq_f32(vdupq_n_f32(1.0F), result, x2); +#endif + + result = vmulq_f32(result, x); + return result; +} +#endif + +static void bm_vector_sin_scalar(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + vector4f scale = vector_set(100.0F); + + for (auto _ : state) + { + v0 = vector_mul(vector_sin_scalar(v0), scale); + v1 = vector_mul(vector_sin_scalar(v1), scale); + v2 = vector_mul(vector_sin_scalar(v2), scale); + v3 = vector_mul(vector_sin_scalar(v3), scale); + v4 = vector_mul(vector_sin_scalar(v4), scale); + v5 = vector_mul(vector_sin_scalar(v5), scale); + v6 = vector_mul(vector_sin_scalar(v6), scale); + v7 = vector_mul(vector_sin_scalar(v7), scale); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_sin_scalar); + +#if defined(RTM_SSE2_INTRINSICS) +static void bm_vector_sin_sse2(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + vector4f scale = vector_set(100.0F); + + for (auto _ : state) + { + v0 = vector_mul(vector_sin_sse2(v0), scale); + v1 = vector_mul(vector_sin_sse2(v1), scale); + v2 = vector_mul(vector_sin_sse2(v2), scale); + v3 = vector_mul(vector_sin_sse2(v3), scale); + v4 = vector_mul(vector_sin_sse2(v4), scale); + v5 = vector_mul(vector_sin_sse2(v5), scale); + v6 = vector_mul(vector_sin_sse2(v6), scale); + v7 = vector_mul(vector_sin_sse2(v7), scale); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_sin_sse2); +#endif + +#if defined(RTM_NEON_INTRINSICS) +static void bm_vector_sin_neon(benchmark::State& state) +{ + vector4f v0 = vector_set(-123.134f); + vector4f v1 = vector_set(123.134f); + vector4f v2 = vector_set(-123.134f); + vector4f v3 = vector_set(123.134f); + vector4f v4 = vector_set(-123.134f); + vector4f v5 = vector_set(123.134f); + vector4f v6 = vector_set(-123.134f); + vector4f v7 = vector_set(123.134f); + + vector4f scale = vector_set(100.0F); + + for (auto _ : state) + { + v0 = vector_mul(vector_sin_neon(v0), scale); + v1 = vector_mul(vector_sin_neon(v1), scale); + v2 = vector_mul(vector_sin_neon(v2), scale); + v3 = vector_mul(vector_sin_neon(v3), scale); + v4 = vector_mul(vector_sin_neon(v4), scale); + v5 = vector_mul(vector_sin_neon(v5), scale); + v6 = vector_mul(vector_sin_neon(v6), scale); + v7 = vector_mul(vector_sin_neon(v7), scale); + } + + benchmark::DoNotOptimize(v0); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + benchmark::DoNotOptimize(v4); + benchmark::DoNotOptimize(v5); + benchmark::DoNotOptimize(v6); + benchmark::DoNotOptimize(v7); +} + +BENCHMARK(bm_vector_sin_neon); +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/release_scripts/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/release_scripts/README.md new file mode 100644 index 00000000..cc39b877 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/release_scripts/README.md @@ -0,0 +1,7 @@ +# Release scripts + +Validating a release requires a lot of work and to that end, some scripts were written to automate the process as much as possible. + +## test_everything.py + +This script runs the unit tests on every platform except iOS and Android. It will run every permutation possible. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/release_scripts/test_everything.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/release_scripts/test_everything.py new file mode 100644 index 00000000..f7be15cc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/release_scripts/test_everything.py @@ -0,0 +1,134 @@ +import os +import platform +import shutil +import subprocess +import sys + +def get_platform_compilers(): + if platform.system() == 'Windows': + return [ 'vs2015', 'vs2017', 'vs2019', 'vs2019-clang' ] + elif platform.system() == 'Linux': + compilers = [] + if shutil.which('g++-5'): + compilers.append('gcc5') + if shutil.which('g++-6'): + compilers.append('gcc6') + if shutil.which('g++-7'): + compilers.append('gcc7') + if shutil.which('g++-8'): + compilers.append('gcc8') + if shutil.which('g++-9'): + compilers.append('gcc9') + if shutil.which('g++-10'): + compilers.append('gcc10') + + if shutil.which('clang++-4.0'): + compilers.append('clang4') + if shutil.which('clang++-5.0'): + compilers.append('clang5') + if shutil.which('clang++-6.0'): + compilers.append('clang6') + if shutil.which('clang++-7'): + compilers.append('clang7') + if shutil.which('clang++-8'): + compilers.append('clang8') + if shutil.which('clang++-9'): + compilers.append('clang9') + if shutil.which('clang++-10'): + compilers.append('clang10') + if shutil.which('clang++-11'): + compilers.append('clang11') + + return compilers + elif platform.system() == 'Darwin': + return [ 'osx' ] + else: + print('Unknown platform!') + sys.exit(1) + +def get_python_exe_name(): + if platform.system() == 'Windows': + return 'python' + else: + return 'python3' + +if __name__ == "__main__": + os.environ['PYTHONIOENCODING'] = 'utf_8' + + configs = [ 'debug', 'release' ] + archs = [ 'x86', 'x64' ] + compilers = get_platform_compilers() + simd_opts = [ '', '-avx', '-avx2', '-nosimd' ] + python_exe = get_python_exe_name() + + if platform.system() == 'Darwin': + result = subprocess.check_output(['xcodebuild', '-version']).decode("utf-8") + if 'Xcode 11' in result: + archs.remove('x86') + + cmd_args = [] + for config in configs: + for arch in archs: + for compiler in compilers: + for simd in simd_opts: + if compiler == 'clang7' and simd == '-nosimd': + continue # Hack to avoid compiler issue + if compiler == 'clang8' and simd == '-nosimd': + continue # Hack to avoid compiler issue + args = [python_exe, 'make.py', '-compiler', compiler, '-cpu', arch, '-config', config, simd, '-build', '-unit_test', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + + if platform.system() == 'Windows': + for config in configs: + # Windows ARM + args = [python_exe, 'make.py', '-compiler', 'vs2017', '-cpu', 'arm64', '-config', config, '-build', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'vs2017', '-cpu', 'arm64', '-config', config, '-build', '-vector_mix_test', '-clean', '-nosimd'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'vs2019', '-cpu', 'arm64', '-config', config, '-build', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'vs2019', '-cpu', 'arm64', '-config', config, '-build', '-vector_mix_test', '-clean', '-nosimd'] + cmd_args.append([x for x in args if x]) + + # Android + args = [python_exe, 'make.py', '-compiler', 'android', '-cpu', 'armv7', '-config', config, '-build', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'android', '-cpu', 'armv7', '-config', config, '-build', '-vector_mix_test', '-clean', '-nosimd'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'android', '-cpu', 'arm64', '-config', config, '-build', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'android', '-cpu', 'arm64', '-config', config, '-build', '-vector_mix_test', '-clean', '-nosimd'] + cmd_args.append([x for x in args if x]) + elif platform.system() == 'Darwin': + for config in configs: + # iOS + args = [python_exe, 'make.py', '-compiler', 'ios', '-config', config, '-build', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'ios', '-config', config, '-build', '-vector_mix_test', '-clean', '-nosimd'] + cmd_args.append([x for x in args if x]) + + if platform.system() == 'Darwin' or platform.system() == 'Linux': + # Emscripten + args = [python_exe, 'make.py', '-compiler', 'emscripten', '-config', 'debug', '-build', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'emscripten', '-config', 'release', '-build', '-unit_test', '-vector_mix_test', '-clean'] + cmd_args.append([x for x in args if x]) + + root_dir = os.path.join(os.getcwd(), '../..') + os.chdir(root_dir) + + for args in cmd_args: + cmd = " ".join(args) + print('Running command: "{}" ...'.format(cmd)) + try: + if 'android' in args: + subprocess.check_call(args) + else: + subprocess.check_output(args) + except subprocess.CalledProcessError as e: + print('Failed command: {}'.format(cmd)) + print(e.output.decode(sys.stdout.encoding)) + sys.exit(1) + + print('Done!') + sys.exit(0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/setup_linux_compiler.sh b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/setup_linux_compiler.sh new file mode 100644 index 00000000..621bfda7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/setup_linux_compiler.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# Extract our command line arguments +COMPILER=$1 + +# Convert our GCC compiler into a list of packages it needs +if [[ $COMPILER == gcc5 ]]; then + PACKAGES="g++-5 g++-5-multilib g++-multilib" +elif [[ $COMPILER == gcc6 ]]; then + PACKAGES="g++-6 g++-6-multilib g++-multilib" +elif [[ $COMPILER == gcc7 ]]; then + PACKAGES="g++-7 g++-7-multilib g++-multilib" +elif [[ $COMPILER == gcc8 ]]; then + PACKAGES="g++-8 g++-8-multilib g++-multilib" +elif [[ $COMPILER == gcc9 ]]; then + PACKAGES="g++-9 g++-9-multilib g++-multilib" +elif [[ $COMPILER == gcc10 ]]; then + PACKAGES="g++-10 g++-10-multilib g++-multilib" +fi + +# If using clang, add our apt source key +if [[ $COMPILER == clang* ]]; then + curl -sSL "http://apt.llvm.org/llvm-snapshot.gpg.key" | sudo -E apt-key add - ; +fi + +# Convert our clang compiler into a list of packages it needs and its source +if [[ $COMPILER == clang4 ]]; then + # clang4 isn't available after xenial + PACKAGES="clang-4.0 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang5 ]]; then + PACKAGES="clang-5.0 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-5.0 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang6 ]]; then + PACKAGES="clang-6.0 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-6.0 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang7 ]]; then + PACKAGES="clang-7 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-7 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang8 ]]; then + PACKAGES="clang-8 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang9 ]]; then + PACKAGES="clang-9 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang10 ]]; then + PACKAGES="clang-10 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang11 ]]; then + PACKAGES="clang-11 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +fi + +# Install the packages we need +sudo -E apt-add-repository -y "ppa:ubuntu-toolchain-r/test"; +sudo -E apt-get -yq update; +sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install $PACKAGES; diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/setup_osx_compiler.sh b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/setup_osx_compiler.sh new file mode 100644 index 00000000..6120fc2f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/setup_osx_compiler.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Extract our command line arguments +COMPILER=$1 + +# Convert our compiler string into our XCode path +# Paths must match Github Action virtual images +if [[ $COMPILER == xcode10 ]]; then + XCODE_PATH="/Applications/Xcode_10.3.app" +elif [[ $COMPILER == xcode11 ]]; then + XCODE_PATH="/Applications/Xcode_11.7.app" +elif [[ $COMPILER == xcode12 ]]; then + XCODE_PATH="/Applications/Xcode_12.3.app" +fi + +# Select our XCode version +sudo xcode-select -s $XCODE_PATH/Contents/Developer; diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/vs_visualizers/rtm.natvis b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/vs_visualizers/rtm.natvis new file mode 100644 index 00000000..1c1aa215 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/rtm/tools/vs_visualizers/rtm.natvis @@ -0,0 +1,69 @@ + + + + + + ({x}, {y}, {z}, {w}) + + + + ({x}, {y}, {z}, {w}) + + + + ({m128_f32[0]}, {m128_f32[1]}, {m128_f32[2]}, {m128_f32[3]}) + + + + ({m128i_u32[0]}, {m128i_u32[1]}, {m128i_u32[2]}, {m128i_u32[3]}) + + + + ({m128d_f64[0]}, {m128d_f64[1]}) + + + + ({x}, {y}, {z}, {w}) + + + + ({xy.m128d_f64[0]}, {xy.m128d_f64[1]}, {zw.m128d_f64[0]}, {zw.m128d_f64[1]}) + + + + ({x}, {y}, {z}, {w}) + + + + ({xy.m128d_f64[0]}, {xy.m128d_f64[1]}, {zw.m128d_f64[0]}, {zw.m128d_f64[1]}) + + + + Degrees: {dbl * (180.0 / 3.14159265358979323846)} + + + + ({x}, {y}) + + + + ({x}, {y}, {z}) + + + + ({x}, {y}, {z}, {w}) + + + + ({x}, {y}) + + + + ({x}, {y}, {z}) + + + + ({x}, {y}, {z}, {w}) + + + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.all-contributorsrc b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.all-contributorsrc new file mode 100644 index 00000000..4f9c9252 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.all-contributorsrc @@ -0,0 +1,53 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "CodyDWJones", + "name": "CodyDWJones", + "avatar_url": "https://avatars.githubusercontent.com/u/28773740?v=4", + "profile": "https://github.com/CodyDWJones", + "contributions": [ + "code", + "maintenance" + ] + }, + { + "login": "janisozaur", + "name": "Michał Janiszewski", + "avatar_url": "https://avatars.githubusercontent.com/u/550290?v=4", + "profile": "https://github.com/janisozaur", + "contributions": [ + "code", + "maintenance" + ] + }, + { + "login": "tirpidz", + "name": "Martin Turcotte", + "avatar_url": "https://avatars.githubusercontent.com/u/9991876?v=4", + "profile": "https://github.com/tirpidz", + "contributions": [ + "maintenance" + ] + }, + { + "login": "Meradrin", + "name": "Meradrin", + "avatar_url": "https://avatars.githubusercontent.com/u/7066278?v=4", + "profile": "https://github.com/Meradrin", + "contributions": [ + "bug" + ] + } + ], + "contributorsPerLine": 7, + "projectName": "sjson-cpp", + "projectOwner": "nfrechette", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.editorconfig b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.editorconfig new file mode 100644 index 00000000..22452119 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +[*.{cpp,h,py,cmake,txt,java}] +charset = utf-8 +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{gradle,gradle.in}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/FUNDING.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/FUNDING.yml new file mode 100644 index 00000000..724e02b7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +#patreon: # Replace with a single Patreon username +#open_collective: # Replace with a single Open Collective username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +#liberapay: # Replace with a single Liberapay username +#issuehunt: # Replace with a single IssueHunt username +#otechie: # Replace with a single Otechie username +#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] + +github: [nfrechette] diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/build_pull_request.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/build_pull_request.yml new file mode 100644 index 00000000..8c1f4195 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/build_pull_request.yml @@ -0,0 +1,160 @@ +name: build pull request + +on: + pull_request: + branches: + - '**' + paths-ignore: + - '**/*.md' + +jobs: + linux: + runs-on: ubuntu-18.04 + strategy: + matrix: + compiler: [gcc5, gcc6, gcc7, gcc8, gcc9, gcc10, clang4, clang5, clang6, clang7, clang8, clang9, clang10, clang11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_linux_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x64 -unit_test + + osx-10: + runs-on: macos-10.15 + strategy: + matrix: + compiler: [xcode10, xcode11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_osx_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -ci -compiler osx -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -ci -compiler osx -config Debug -cpu x86 -unit_test + - name: Clean + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -ci -compiler osx -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -ci -compiler osx -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -ci -compiler osx -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -ci -compiler osx -config Release -cpu x86 -unit_test + - name: Clean + if: ${{ matrix.compiler == 'xcode10' }} + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -ci -compiler osx -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -ci -compiler osx -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (debug-arm64) + run: python3 make.py -ci -compiler ios -config Debug -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64) + run: python3 make.py -ci -compiler ios -config Release -build + + osx-11: + runs-on: macos-11 + strategy: + matrix: + compiler: [xcode12, xcode13] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_osx_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x64) + run: python3 make.py -ci -compiler osx -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -ci -compiler osx -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -ci -compiler osx -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -ci -compiler osx -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (debug-arm64) + run: python3 make.py -ci -compiler ios -config Debug -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64) + run: python3 make.py -ci -compiler ios -config Release -build + + emscripten: + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Cache emsdk + uses: actions/cache@v1 + id: 'emsdk' + with: + path: 'emsdk-cache' + key: ${{ runner.os }}-emsdk-1.39.11 + - name: Setup emsdk + uses: mymindstorm/setup-emsdk@v7 + with: + version: 1.39.11 + no-cache: true + actions-cache-folder: 'emsdk-cache' + - name: Cache emsdk build artifacts + uses: actions/cache@v1 + id: 'emsdk-build-artifacts' + with: + path: '~/.emscripten_cache' + key: ${{ runner.os }}-emsdk-build-artifacts-1.39.11 + - name: Building (debug) + run: python3 make.py -ci -compiler emscripten -config debug -build + - name: Running unit tests (debug) + run: python3 make.py -ci -compiler emscripten -config debug -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release) + run: python3 make.py -ci -compiler emscripten -config release -build + - name: Running unit tests (release) + run: python3 make.py -ci -compiler emscripten -config release -unit_test diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/build_push.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/build_push.yml new file mode 100644 index 00000000..b8bd47ce --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/build_push.yml @@ -0,0 +1,111 @@ +name: build + +on: + push: + branches: + - '**' + paths-ignore: + - '**/*.md' + +jobs: + linux: + runs-on: ubuntu-18.04 + strategy: + matrix: + compiler: [gcc10, clang11] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_linux_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -build + - name: Running unit tests (debug-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (debug-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x86 -build + - name: Running unit tests (release-x86) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x86 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -ci -compiler ${{ matrix.compiler }} -config Release -cpu x64 -unit_test + + osx-11: + runs-on: macos-11 + strategy: + matrix: + compiler: [xcode13] + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup ${{ matrix.compiler }} compiler + run: ./tools/setup_osx_compiler.sh ${{ matrix.compiler }} + - name: Building (debug-x64) + run: python3 make.py -ci -compiler osx -config Debug -cpu x64 -build + - name: Running unit tests (debug-x64) + run: python3 make.py -ci -compiler osx -config Debug -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release-x64) + run: python3 make.py -ci -compiler osx -config Release -cpu x64 -build + - name: Running unit tests (release-x64) + run: python3 make.py -ci -compiler osx -config Release -cpu x64 -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (debug-arm64) + run: python3 make.py -ci -compiler ios -config Debug -build + - name: Clean + run: python3 make.py -clean_only + - name: Building for iOS (release-arm64) + run: python3 make.py -ci -compiler ios -config Release -build + + emscripten: + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Cache emsdk + uses: actions/cache@v1 + id: 'emsdk' + with: + path: 'emsdk-cache' + key: ${{ runner.os }}-emsdk-1.39.11 + - name: Setup emsdk + uses: mymindstorm/setup-emsdk@v7 + with: + version: 1.39.11 + no-cache: true + actions-cache-folder: 'emsdk-cache' + - name: Cache emsdk build artifacts + uses: actions/cache@v1 + id: 'emsdk-build-artifacts' + with: + path: '~/.emscripten_cache' + key: ${{ runner.os }}-emsdk-build-artifacts-1.39.11 + - name: Building (debug) + run: python3 make.py -ci -compiler emscripten -config debug -build + - name: Running unit tests (debug) + run: python3 make.py -ci -compiler emscripten -config debug -unit_test + - name: Clean + run: python3 make.py -clean_only + - name: Building (release) + run: python3 make.py -ci -compiler emscripten -config release -build + - name: Running unit tests (release) + run: python3 make.py -ci -compiler emscripten -config release -unit_test diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/sonarcloud.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..fad8aa82 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.github/workflows/sonarcloud.yml @@ -0,0 +1,54 @@ +name: sonarcloud + +on: + push: + branches: + - '**' + paths-ignore: + - '**/*.md' + pull_request: + branches: + - '**' + paths-ignore: + - '**/*.md' + +jobs: + condition-check: + runs-on: ubuntu-18.04 + outputs: + ok: ${{ steps.check-secrets.outputs.ok }} + steps: + - name: Secret guard + id: check-secrets + run: | + if [ ! -z "${{ secrets.SONAR_TOKEN }}" ]; then + echo "::set-output name=ok::true" + fi + sonarcloud: + needs: + - condition-check + if: ${{ needs.condition-check.outputs.ok == 'true' }} + runs-on: ubuntu-18.04 + steps: + - name: Git checkout + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Setup build wrapper + run: | + wget -q https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip + unzip build-wrapper-linux-x86.zip + - name: Setup Sonar Scanner + run: | + wget -q https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.2.0.1873-linux.zip + unzip sonar-scanner-cli-4.2.0.1873-linux.zip + - name: Setup compiler + run: ./tools/setup_linux_compiler.sh clang5 + - name: Build with wrapper + run: ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw_output python3 make.py -compiler clang5 -config Release -cpu x64 -build + - name: Run Sonar Scanner and upload to Sonarcloud + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io + run: ./sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.gitignore b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.gitignore new file mode 100644 index 00000000..053145c1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.gitignore @@ -0,0 +1,5 @@ +# Temporary data rules +build/ +*.suo +**/__pycache__/ +.vscode/ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.gitmodules b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.gitmodules new file mode 100644 index 00000000..fe59bc71 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/catch2"] + path = external/catch2 + url = https://github.com/catchorg/Catch2 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CHANGELOG.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CHANGELOG.md new file mode 100644 index 00000000..ce25737b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CHANGELOG.md @@ -0,0 +1,66 @@ +# Significant changes per release + +## 0.8.2 + +* Add versioned namespace to allow multiple versions to coexist within a binary +* Add OS X/Linux ARM development support +* Other minor changes + +## 0.8.1 + +* Minor static analysis fixes + +## 0.8.0 + +* Added support for clang 8, 9, and 10 +* Added support for GCC 10 +* Added support for clang with VS 2019 +* Added support for emscripten +* Converted android to use gradle instead of NVIDIA CodeWorks +* Updated Catch2 to 2.11.0 +* Migrated from Travis to GitHub Actions +* Cleanup and minor misc changes + +## 0.7.0 + +* Added support for VS 2019, GCC 9, clang 7, and Xcode 11 +* Added custom support for NaN/Inf handling +* Cleanup and minor misc changes + +## 0.6.0 + +* Added support for Windows ARM64, GCC 8, clang 6, and Xcode 10 +* Integrated SonarCloud +* Cleanup and minor changes & fixes + +## 0.5.0 + +* Cleaned up error handling +* Enabled as many warnings as possible +* Other minor misc changes + +## 0.4.0 + +* Added iOS support +* Other misc additions + +## 0.3.0 + +* Added Android support through NVIDIA CodeWorks +* Added cmake utilities + +## 0.2.0 + +* New SJSON file writer added +* Added unit tests +* Added contributing guidelines, setup instructions, code of conduct +* Added CI builds with AppVeyor and Travis +* Added support for Linux and OS X +* Various other fixes and additions + +## 0.1.0 + +Initial release! + +* Reads SJSON files in ANSI or UTF-8 +* Nothing fancy, works as advertised diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CMakeLists.txt new file mode 100644 index 00000000..8d214e2b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required (VERSION 3.2) +project(sjson-cpp NONE) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") + +include(CMakeUtils) +include(CMakeCompiler) +include(CMakePlatforms) + +set(CPU_INSTRUCTION_SET false CACHE STRING "CPU instruction set") + +if(CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES Debug Release) + set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING "Reset the configurations to what we need" FORCE) +endif() + +# Grab all of our include files +file(GLOB_RECURSE SJSON_CPP_INCLUDE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/includes/*.h + ${PROJECT_SOURCE_DIR}/*.md + ${PROJECT_SOURCE_DIR}/cmake/*.cmake + ${PROJECT_SOURCE_DIR}/tools/release_scripts/*.py + ${PROJECT_SOURCE_DIR}/tools/vs_visualizers/*.natvis + ) + +create_source_groups("${SJSON_CPP_INCLUDE_FILES}" ${PROJECT_SOURCE_DIR}) + +file(GLOB SJSON_CPP_ROOT_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.md + ${PROJECT_SOURCE_DIR}/*.py) + +# Create a dummy target so they show up in the IDE +add_custom_target(${PROJECT_NAME} SOURCES ${SJSON_CPP_INCLUDE_FILES} ${SJSON_CPP_ROOT_FILES}) + +# Enable CTest +enable_testing() + +# Add other projects +add_subdirectory("${PROJECT_SOURCE_DIR}/tests") diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CODE_OF_CONDUCT.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..48fcffad --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at zeno490@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CONTRIBUTING.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CONTRIBUTING.md new file mode 100644 index 00000000..878ae947 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# How to contribute + +Thank you for your interest in the sjson-cpp! All contributions that follow our guidelines below and abide by our [code of conduct](CODE_OF_CONDUCT.md) are welcome. + +In this document you will find relevant reading material, what contributions we are looking for, and what we are *not* looking for. + +Project contact email: zeno490@gmail.com + +# Getting set up + +See the [readme](README.md) file for details on how to generate project solutions, build, and run the unit tests. Every pull request should run with continuous integration on every platform we support and the unit tests will also execute as well. + +The project uses [GitHub issues](https://github.com/nfrechette/sjson-cpp/issues) to track bugs and feature requests. + +Whether you create an issue or a pull request, I will do my best to comment or reply within 48 hours. + +# Contributions we are looking for + +Several issues already have a [help wanted](https://github.com/nfrechette/sjson-cpp/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label. Those tasks should be either reasonably simple or tasks that I do not think I will get the chance to get to very soon. + +Feature requests are welcome providing that they fit within the project scope. For smaller features, you are welcome to create an issue with as much relevant information as you can. If it makes sense, I will prioritize it or add it to the backlog, and if I feel it is beyond the scope of the project, I will tell you why and close the issue. For larger topics, the best way to move forward is to reach out by email. + +# Contributions we are *not* looking for + +A lot of older compilers do not properly support C++11 and there is no plan to support them. This also applies to platforms that are either not mainstream or that we cannot easily test with continuous integration. If you would like to support such an exotic environment, reach out by email first so we can discuss this. + +If you aren't sure, don't be afraid to reach out by email! diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/LICENSE b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/LICENSE new file mode 100644 index 00000000..4a4711f0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Nicholas Frechette, Cody Jones, and sjson-cpp contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/README.md new file mode 100644 index 00000000..e402aa63 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/README.md @@ -0,0 +1,126 @@ +[![CLA assistant](https://cla-assistant.io/readme/badge/nfrechette/sjson-cpp)](https://cla-assistant.io/nfrechette/sjson-cpp) +[![All Contributors](https://img.shields.io/github/all-contributors/nfrechette/sjson-cpp)](#contributors-) +[![Build status](https://ci.appveyor.com/api/projects/status/oynd3x3d9umjaruf/branch/develop?svg=true)](https://ci.appveyor.com/project/nfrechette/sjson-cpp) +[![Build status](https://github.com/nfrechette/sjson-cpp/workflows/build/badge.svg)](https://github.com/nfrechette/sjson-cpp/actions) +[![Sonar Status](https://sonarcloud.io/api/project_badges/measure?project=nfrechette_sjson-cpp&metric=alert_status)](https://sonarcloud.io/dashboard?id=nfrechette_sjson-cpp) +[![GitHub (pre-)release](https://img.shields.io/github/release/nfrechette/sjson-cpp/all.svg)](https://github.com/nfrechette/sjson-cpp/releases) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nfrechette/sjson-cpp/master/LICENSE) + +# sjson-cpp + +`sjson-cpp` is a C++ library to read and write [Simplified JSON](http://help.autodesk.com/view/Stingray/ENU/?guid=__stingray_help_managing_content_sjson_html) files. +It aims to be minimal, fast, and get out of the way of the programmer. + +By design, the library does no memory allocations. This is in contrast to the [nflibs C parser](https://github.com/niklasfrykholm/nflibs). + +Everything is **100% C++11** header based for easy and trivial integration. + +This parser is intended to accept only pure SJSON, and it will fail if given a JSON file, unlike the [Autodesk JS Stingray parser](https://github.com/Autodesk/sjson). + +## The SJSON format + +The data format is described [here](http://help.autodesk.com/view/Stingray/ENU/?guid=__stingray_help_managing_content_sjson_html) in the Stingray documentation. + +TODO: Add a reference sjson file showing the format as a form of loose specification + +## Unicode support + +UTF-8 support is as follow: + +* String values return a raw `StringView` into the SJSON buffer. It is the responsability of the caller to interpret it as ANSI or UTF-8. +* String values properly support escaped unicode sequences in that they are returned raw in the `StringView`. +* Keys do not support UTF-8, they must be ANSI. +* The BOM is properly skipped if present + +Unicode formats other than UTF-8 aren't supported. + +## Supported platforms + +* Windows VS2015 x86 and x64 +* Windows (VS2017, VS2019) x86, x64, and ARM64 +* Windows VS2019 with clang9 x86 and x64 +* Linux (gcc 5 to 10) x86 and x64 +* Linux (clang 4 to 11) x86 and x64 +* OS X (Xcode 10.3) x86 and x64 +* OS X (Xcode 11.7, 12.5, 13.2) x64 and ARM64 +* Android (NDK 21) ARMv7-A and ARM64 +* iOS (Xcode 10.3, 11.7, 12.5, 13.2) ARM64 +* Emscripten (1.39.11) WASM + +The above supported platform list is only what is tested every release but if it compiles, it should run just fine. + +## External dependencies + +There are none! You don't need anything else to get started: everything is self contained. +See [here](./external) for details. + +## Getting up and running + +This library is **100%** headers as such you just need to include them in your own project to start using it. However, if you wish to run the unit tests you will need a few things, see below. + +### Windows, Linux, and OS X for x86 and x64 + +1. Install *CMake 3.2* or higher (*3.14* for Visual Studio 2019, or *3.10* on OS X with *Xcode 10*), *Python 2.7 or 3*, and the proper compiler for your platform. +2. Execute `git submodule update --init` to get the files of external submodules (e.g. Catch2). +3. Generate the IDE solution with: `python make.py` + The solution is generated under `./build` +4. Build the IDE solution with: `python make.py -build` +5. Run the unit tests with: `python make.py -unit_test` + +### Windows ARM64 + +For *Windows on ARM64*, the steps are identical to *x86 and x64* but you will need *CMake 3.13 or higher* and you must provide the architecture on the command line: `python make.py -compiler vs2017 -cpu arm64` + +### Android + +For *Android*, the steps are identical to *Windows, Linux, and OS X* but you also need to install *Android NDK 21* (or higher). The build uses `gradle` and `-unit_test` will deploy and run on the device when executed (make sure that the `adb` executable is in your `PATH` for this to work). + +*Android Studio v3.5* can be used to launch and debug. After running *CMake* to build and generate everything, the *Android Studio* projects can be found under the `./build` directory. + +### iOS + +For *iOS*, the steps are identical to the other platforms but due to code signing, you will need to perform the builds from *Xcode* manually. Note that this is only an issue if you attempt to use the tools or run the unit tests locally. + +### Emscripten + +Emscripten support currently only has been tested on OS X and Linux. To use it, make sure to install a recent version of Emscripten SDK 1.39.11+. + +## Commit message format + +This library uses the [angular.js message format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) and it is enforced with commit linting through every pull request. + +## Authors + +* [Nicholas Frechette](https://github.com/nfrechette) +* [Cody Jones](https://github.com/CodyDWJones) + +## License, copyright, and code of conduct + +This project uses the [MIT license](LICENSE). + +Copyright (c) 2017 Nicholas Frechette, Cody Jones, and sjson-cpp contributors + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + +

CodyDWJones

💻 🚧

Michał Janiszewski

💻 🚧

Martin Turcotte

🚧

Meradrin

🐛
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/appveyor.yml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/appveyor.yml new file mode 100644 index 00000000..d4c0ff10 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/appveyor.yml @@ -0,0 +1,49 @@ +version: 0.8.2.{build} + +environment: + PYTHON: "C:\\Python36-x64\\python.exe" + matrix: + - toolchain: msvc + - toolchain: clang + +image: + - Visual Studio 2015 + - Visual Studio 2017 + - Visual Studio 2019 + +configuration: + - Debug + - Release + +platform: + - x86 + - x64 + - arm64 + +matrix: + exclude: + - image: Visual Studio 2015 + platform: arm64 + - image: Visual Studio 2015 + toolchain: clang + - image: Visual Studio 2017 + toolchain: clang + - image: Visual Studio 2019 + platform: arm64 + toolchain: clang + - image: Previous Visual Studio 2019 + platform: arm64 + toolchain: clang + + +init: +# Only run latest compiler on push, run everything on pull request +- ps: if (!$env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_WORKER_IMAGE -ne "Visual Studio 2019") {Exit-AppveyorBuild} + +install: +- cmd: >- + git submodule update --init --recursive + +build_script: +- cmd: >- + .\tools\appveyor_ci.bat "%APPVEYOR_BUILD_WORKER_IMAGE%" %platform% %configuration% %toolchain% "%PYTHON%" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakeCompiler.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakeCompiler.cmake new file mode 100644 index 00000000..fae4e939 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakeCompiler.cmake @@ -0,0 +1,36 @@ +cmake_minimum_required (VERSION 3.2) + +macro(setup_default_compiler_flags _project_name) + if(MSVC) + # Replace some default compiler switches and add new ones + STRING(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI + STRING(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Bump warnings to W4 + target_compile_options(${_project_name} PRIVATE /Zi) # Add debug info + target_compile_options(${_project_name} PRIVATE /Oi) # Generate intrinsic functions + target_compile_options(${_project_name} PRIVATE /WX) # Treat warnings as errors + target_compile_options(${_project_name} PRIVATE /MP) # Enable parallel compilation + + if(MSVC_VERSION GREATER 1900) + # VS2017 and above + target_compile_options(${_project_name} PRIVATE /permissive-) + endif() + + # Add linker flags + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") + else() + # TODO: Handle OS X properly: https://stackoverflow.com/questions/5334095/cmake-multiarchitecture-compilation + if(CPU_INSTRUCTION_SET MATCHES "x86") + target_compile_options(${_project_name} PRIVATE "-m32") + target_link_libraries(${_project_name} PRIVATE "-m32") + elseif(CPU_INSTRUCTION_SET MATCHES "x64") + target_compile_options(${_project_name} PRIVATE "-m64") + target_link_libraries(${_project_name} PRIVATE "-m64") + endif() + + target_compile_options(${_project_name} PRIVATE -Wall -Wextra) # Enable all warnings + target_compile_options(${_project_name} PRIVATE -Wshadow) # Enable shadowing warnings + target_compile_options(${_project_name} PRIVATE -Werror) # Treat warnings as errors + + target_compile_options(${_project_name} PRIVATE -g) # Enable debug symbols + endif() +endmacro() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakePlatforms.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakePlatforms.cmake new file mode 100644 index 00000000..8f88f160 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakePlatforms.cmake @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.2) + +if(PLATFORM_NAME) + return() # Already set +endif() + +# Detect which platform we have +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(PLATFORM_WINDOWS 1) + set(PLATFORM_NAME "Windows") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + if(PLATFORM_IOS) + set(PLATFORM_NAME "iOS") + else() + set(PLATFORM_OSX 1) + set(PLATFORM_NAME "OS X") + endif() +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(PLATFORM_LINUX 1) + set(PLATFORM_NAME "Linux") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") + set(PLATFORM_ANDROID 1) + set(PLATFORM_NAME "Android") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + set(PLATFORM_EMSCRIPTEN 1) + set(PLATFORM_NAME "Emscripten") +else() + message(FATAL_ERROR "Unknown platform ${CMAKE_SYSTEM_NAME}!") +endif() + +message(STATUS "Detected platform: ${PLATFORM_NAME}") diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakeUtils.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakeUtils.cmake new file mode 100644 index 00000000..c18543f8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/CMakeUtils.cmake @@ -0,0 +1,12 @@ +cmake_minimum_required (VERSION 3.2) + +# Create groups for the test source files, this creates the proper directory structure under the +# Visual Studio filters +macro(create_source_groups _source_files _relative_directory) + foreach(_file IN ITEMS ${_source_files}) + get_filename_component(_file_path "${_file}" PATH) + file(RELATIVE_PATH _file_path_rel "${_relative_directory}" "${_file_path}") + string(REPLACE "/" "\\" _group_path "${_file_path_rel}") + source_group("${_group_path}" FILES "${_file}") + endforeach() +endmacro() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/Toolchain-Android.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/Toolchain-Android.cmake new file mode 100644 index 00000000..c5570d4c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/Toolchain-Android.cmake @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.2) + +# For Android, we just set the platform name as we won't be using CMake to build anything. +# Instead Gradle is used through CMake. + +set(PLATFORM_ANDROID 1) +set(PLATFORM_NAME "Android") + +# Remap our CPU instruction set +if(CPU_INSTRUCTION_SET MATCHES "armv7") + set(CPU_INSTRUCTION_SET "armeabi-v7a") +elseif(CPU_INSTRUCTION_SET MATCHES "arm64") + set(CPU_INSTRUCTION_SET "arm64-v8a") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/Toolchain-iOS.cmake b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/Toolchain-iOS.cmake new file mode 100644 index 00000000..4bc4c44c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/cmake/Toolchain-iOS.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required (VERSION 3.2) + +set(CMAKE_SYSTEM_NAME Darwin) + +# Set here instead of CMakePlatforms.cmake since we can't distinguis otherwise +set(PLATFORM_IOS 1) + +# Find and set the C/C++ compiler paths, cmake doesn't seem to do this properly on its own +execute_process(COMMAND xcrun --sdk iphoneos --find clang OUTPUT_VARIABLE CMAKE_C_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND xcrun --sdk iphoneos --find clang++ OUTPUT_VARIABLE CMAKE_CXX_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE) + +set(CMAKE_MACOSX_BUNDLE YES) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "") +set(CMAKE_OSX_SYSROOT iphoneos CACHE STRING "") +set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "") +set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 11.0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/external/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/external/README.md new file mode 100644 index 00000000..e71e8c2d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/external/README.md @@ -0,0 +1,7 @@ +# External dependencies + +Good news! There are no external dependencies needed by this library at runtime! + +## Catch + +[Catch2 v2.13.7](https://github.com/catchorg/Catch2/releases/tag/v2.13.7) is used by our [unit tests](../tests). You will only need it if you run the unit tests and it is included as-is without modifications. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/error.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/error.h new file mode 100644 index 00000000..aae9211c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/error.h @@ -0,0 +1,161 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// This library uses a simple system to handle asserts. Asserts are fatal and must terminate +// otherwise the behavior is undefined if execution continues. +// +// A total of 4 behaviors are supported: +// - We can print to stderr and abort +// - We can throw and exception +// - We can call a custom function +// - Do nothing and strip the check at compile time (default behavior) +// +// Aborting: +// In order to enable the aborting behavior, simply define the macro SJSON_CPP_ON_ASSERT_ABORT: +// #define SJSON_CPP_ON_ASSERT_ABORT +// +// Throwing: +// In order to enable the throwing behavior, simply define the macro SJSON_CPP_ON_ASSERT_THROW: +// #define SJSON_CPP_ON_ASSERT_THROW +// Note that the type of the exception thrown is sjson::runtime_assert. +// +// Custom function: +// In order to enable the custom function calling behavior, define the macro SJSON_CPP_ON_ASSERT_CUSTOM +// with the name of the function to call: +// #define SJSON_CPP_ON_ASSERT_CUSTOM on_custom_assert_impl +// Note that the function signature is as follow: +// void on_custom_assert_impl(const char* expression, int line, const char* file, const char* format, ...) {} +// +// You can also define your own assert implementation by defining the SJSON_CPP_ASSERT macro as well: +// #define SJSON_CPP_ON_ASSERT_CUSTOM +// #define SJSON_CPP_ASSERT(expression, format, ...) checkf(expression, ANSI_TO_TCHAR(format), #__VA_ARGS__) +// +// No checks: +// By default if no macro mentioned above is defined, all asserts will be stripped +// at compile time. +////////////////////////////////////////////////////////////////////////// + +#if defined(SJSON_CPP_ON_ASSERT_ABORT) + + #include "sjson/version.h" + + #include + #include + #include + + namespace sjson + { + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + namespace error_impl + { + inline void on_assert_abort(const char* expression, int line, const char* file, const char* format, ...) + { + (void)expression; + (void)line; + (void)file; + + va_list args; + va_start(args, format); + + std::vfprintf(stderr, format, args); + std::fprintf(stderr, "\n"); + + va_end(args); + + std::abort(); + } + } + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END + } + + #define SJSON_CPP_ASSERT(expression, format, ...) do { if (!(expression)) SJSON_CPP_IMPL_NAMESPACE::error_impl::on_assert_abort(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while (false) + #define SJSON_CPP_HAS_ASSERT_CHECKS + +#elif defined(SJSON_CPP_ON_ASSERT_THROW) + + #include "sjson/version.h" + + #include + #include + #include + #include + + namespace sjson + { + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + class runtime_assert final : public std::runtime_error + { + using std::runtime_error::runtime_error; // Inherit constructors + }; + + namespace error_impl + { + inline void on_assert_throw(const char* expression, int line, const char* file, const char* format, ...) + { + (void)expression; + (void)line; + (void)file; + + constexpr int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + va_list args; + va_start(args, format); + + const int count = vsnprintf(buffer, buffer_size, format, args); + + va_end(args); + + if (count >= 0 && count < buffer_size) + throw runtime_assert(std::string(&buffer[0], count)); + else + throw runtime_assert("Failed to format assert message!\n"); + } + } + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END + } + + #define SJSON_CPP_ASSERT(expression, format, ...) do { if (!(expression)) SJSON_CPP_IMPL_NAMESPACE::error_impl::on_assert_throw(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(false) + #define SJSON_CPP_HAS_ASSERT_CHECKS + +#elif defined(SJSON_CPP_ON_ASSERT_CUSTOM) + + #if !defined(SJSON_CPP_ASSERT) + #define SJSON_CPP_ASSERT(expression, format, ...) do { if (!(expression)) SJSON_CPP_ON_ASSERT_CUSTOM(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(false) + #endif + + #define SJSON_CPP_HAS_ASSERT_CHECKS + +#else + + #define SJSON_CPP_ASSERT(expression, format, ...) ((void)0) + +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/fwd.h new file mode 100644 index 00000000..8a5a2d4d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/fwd.h @@ -0,0 +1,57 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "sjson/version.h" + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public sjson-cpp types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + // Core + class runtime_assert; + class StringView; + + // Parser + struct ParserError; + struct ParserState; + struct Parser; + + // Writer + class StreamWriter; + class FileStreamWriter; + class ArrayWriter; + class ObjectWriter; + class Writer; + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/impl/cstdlib.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/impl/cstdlib.impl.h new file mode 100644 index 00000000..79a02790 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/impl/cstdlib.impl.h @@ -0,0 +1,63 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "sjson/version.h" + +////////////////////////////////////////////////////////////////////////// +// The Android NDK r10 and possibly earlier used a STD lib that poorly supported C++11. +// As such, it is necessary to polyfill a few missing things. +////////////////////////////////////////////////////////////////////////// +#if defined(__GNUG__) && defined(__ANDROID__) && __GNUC__ < 5 + #define SJSON_CPP_IMPL_POLYFILL_STD +#endif + +#if defined(SJSON_CPP_IMPL_POLYFILL_STD) + #include +#else + #include +#endif + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + namespace sjson_impl + { +#if defined(SJSON_CPP_IMPL_POLYFILL_STD) + // We need to polyfill these, bring the C version into our namespace + using ::strtoull; + using ::strtoll; + using ::strtof; +#else + // These properly exist in the C++ version, use them + using std::strtoull; + using std::strtoll; + using std::strtof; +#endif + } + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser.h new file mode 100644 index 00000000..60cdca5a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser.h @@ -0,0 +1,1017 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// This define allows external libraries using sjson-cpp to detect if it has already be included as a dependency +#if !defined(SJSON_CPP_PARSER) + #define SJSON_CPP_PARSER +#endif + +#include "sjson/parser_error.h" +#include "sjson/parser_state.h" +#include "sjson/version.h" +#include "sjson/impl/cstdlib.impl.h" +#include "sjson/string_view.h" + +#include +#include +#include +#include +#include +#include + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + class Parser + { + public: + Parser(const char* input, size_t input_length) + : m_input(input) + , m_input_length(input_length) + , m_state(input, input_length) + { + skip_bom(); + } + ~Parser() = default; + + // Prevent copying to avoid potential mistakes + Parser(const Parser& other) = delete; + Parser& operator=(const Parser& other) = delete; + + // Allow moving + Parser(Parser&& other) = default; + Parser& operator=(Parser&& other) = default; + + bool object_begins() { return read_opening_brace(); } + bool object_begins(const char* having_name) { return read_key(having_name) && read_equal_sign() && object_begins(); } + bool object_ends() { return read_closing_brace(); } + + bool try_object_begins(const char* having_name) + { + ParserState s = save_state(); + + if (!object_begins(having_name)) + { + restore_state(s); + return false; + } + + return true; + } + + bool try_object_ends() + { + ParserState s = save_state(); + + if (!object_ends()) + { + restore_state(s); + return false; + } + + return true; + } + + bool array_begins() { return read_opening_bracket(); } + bool array_begins(const char* having_name) { return read_key(having_name) && read_equal_sign() && read_opening_bracket(); } + bool array_ends() { return read_closing_bracket(); } + + bool try_array_begins(const char* having_name) + { + ParserState s = save_state(); + + if (!array_begins(having_name)) + { + restore_state(s); + return false; + } + + return true; + } + + bool try_array_ends() + { + ParserState s = save_state(); + + if (!array_ends()) + { + restore_state(s); + return false; + } + + return true; + } + + // TODO: To support 'null' value entries (e.g. foo = null), these functions should take as an argument an Option + // e.g. bool read(const char* key, Option& value) + // The return value tells us whether or not we successfully parsed our key/value pair + // The option returned tells us whether we parsed an actual value or a null literal + // The caller is responsible for interpreting the meaning of this + // TODO: These should all be renamed to try_read since on failure they restore the state prior to the parsing attempt + // TODO: Return the actual parsing error instead of a bool, maybe introduce an ErrorType that coerces to bool, true == success? + // TODO: Rename this for a cleaner API, try_read_string, try_read_bool, try_read_double, try_read_integer, etc + + // The StringView returned is a raw view of the SJSON buffer. Anything escaped within is thus returned as-is. + // Only the start/end quote is stripped from the returned string. + // e.g.: some_key = "this is an \"escaped\" string within another string" + // StringView start ^ end ^ + bool read(const char* key, StringView& value) { return read_key(key) && read_equal_sign() && read_string(value); } + bool read(const char* key, bool& value) { return read_key(key) && read_equal_sign() && read_bool(value); } + bool read(const char* key, double& value) { return read_key(key) && read_equal_sign() && read_double(&value, nullptr); } + bool read(const char* key, float& value) { return read_key(key) && read_equal_sign() && read_double(nullptr, &value); } + bool read(const char* key, int8_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, uint8_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, int16_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, uint16_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, int32_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, uint32_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, int64_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + bool read(const char* key, uint64_t& value) { return read_key(key) && read_equal_sign() && read_integer(value); } + + bool read(const char* key, double* values, uint32_t num_elements) + { + return read_key(key) && read_equal_sign() && read_opening_bracket() && read(values, num_elements) && read_closing_bracket(); + } + + bool read(const char* key, StringView* values, uint32_t num_elements) + { + return read_key(key) && read_equal_sign() && read_opening_bracket() && read(values, num_elements) && read_closing_bracket(); + } + + bool try_read(const char* key, StringView& value, const char* default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + value = default_value; + return false; + } + + if (read_string(value)) + return true; + } + + restore_state(s); + value = default_value; + return false; + } + + bool try_read(const char* key, bool& value, bool default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + value = default_value; + return false; + } + + if (read_bool(value)) + return true; + } + + restore_state(s); + value = default_value; + return false; + } + + bool try_read(const char* key, double& value, double default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + value = default_value; + return false; + } + + if (read_double(&value, nullptr)) + return true; + } + + restore_state(s); + value = default_value; + return false; + } + + bool try_read(const char* key, float& value, float default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + value = default_value; + return false; + } + + if (read_double(nullptr, &value)) + return true; + } + + restore_state(s); + value = default_value; + return false; + } + + bool try_read(const char* key, int8_t& value, int8_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, uint8_t& value, uint8_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, int16_t& value, int16_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, uint16_t& value, uint16_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, int32_t& value, int32_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, uint32_t& value, uint32_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, int64_t& value, int64_t default_value) { return try_read_integer_impl(key, value, default_value); } + bool try_read(const char* key, uint64_t& value, uint64_t default_value) { return try_read_integer_impl(key, value, default_value); } + + bool try_read(const char* key, double* values, uint32_t num_elements, double default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + std::fill(values, values + num_elements, default_value); + return false; + } + + if (read_opening_bracket() && read(values, num_elements) && read_closing_bracket()) + return true; + } + + restore_state(s); + std::fill(values, values + num_elements, default_value); + return false; + } + + bool try_read(const char* key, StringView* values, uint32_t num_elements, const char* default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + std::fill(values, values + num_elements, default_value); + return false; + } + + if (read_opening_bracket() && read(values, num_elements) && read_closing_bracket()) + return true; + } + + restore_state(s); + std::fill(values, values + num_elements, default_value); + return false; + } + + bool read(double* values, uint32_t num_elements) + { + if (num_elements == 0) + return true; + + for (uint32_t i = 0; i < num_elements; ++i) + { + if (!read_double(&values[i], nullptr)) + return false; + + if (i < (num_elements - 1) && !read_comma()) + return false; + } + + return true; + } + + bool read(StringView* values, uint32_t num_elements) + { + if (num_elements == 0) + return true; + + for (uint32_t i = 0; i < num_elements; ++i) + { + if (!read_string(values[i])) + return false; + + if (i < (num_elements - 1) && !read_comma()) + return false; + } + + return true; + } + + bool read_comma() { return read_symbol(',', ParserError::CommaExpected); } + + bool remainder_is_comments_and_whitespace() + { + if (!skip_comments_and_whitespace()) + return false; + + if (!eof()) + { + set_error(ParserError::UnexpectedContentAtEnd); + return false; + } + + return true; + } + + bool skip_comments_and_whitespace() + { + while (true) + { + if (eof()) + return true; + + if (std::isspace(static_cast(m_state.symbol))) + { + advance(); + continue; + } + + if (m_state.symbol == '/') + { + advance(); + + if (!read_comment()) + return false; + + continue; + } + + return true; + } + } + + void get_position(uint32_t& line, uint32_t& column) const + { + line = m_state.line; + column = m_state.column; + } + + bool eof() const { return m_state.offset >= m_input_length; } + + ParserError get_error() const { return m_state.error; } + bool is_valid() const { return m_state.error.error == ParserError::None; } + + ParserState save_state() const { return m_state; } + void restore_state(const ParserState& s) { m_state = s; } + void reset_state() { m_state = ParserState(m_input, m_input_length); } + + private: + static constexpr size_t k_max_number_length = 64; + + const char* m_input; + size_t m_input_length; + ParserState m_state; + + bool read_equal_sign() { return read_symbol('=', ParserError::EqualSignExpected); } + bool read_opening_brace() { return read_symbol('{', ParserError::OpeningBraceExpected); } + bool read_closing_brace() { return read_symbol('}', ParserError::ClosingBraceExpected); } + bool read_opening_bracket() { return read_symbol('[', ParserError::OpeningBracketExpected); } + bool read_closing_bracket() { return read_symbol(']', ParserError::ClosingBracketExpected); } + + bool read_symbol(char expected, int32_t reason_if_other_found) + { + if (!skip_comments_and_whitespace_fail_if_eof()) + return false; + + if (m_state.symbol == expected) + { + advance(); + return true; + } + + set_error(reason_if_other_found); + return false; + } + + // This function assumes that the first '/' character has already been consumed + // e.g.: // or /* + // symbol: ^ ^ + // TODO: Fix this + bool read_comment() + { + if (eof()) + { + set_error(ParserError::InputTruncated); + return false; + } + + if (m_state.symbol == '/') + { + while (!eof() && m_state.symbol != '\n') + advance(); + + return true; + } + else if (m_state.symbol == '*') + { + advance(); + + bool wasAsterisk = false; + + while (true) + { + if (eof()) + { + set_error(ParserError::InputTruncated); + return false; + } + else if (m_state.symbol == '*') + { + advance(); + wasAsterisk = true; + } + else if (wasAsterisk && m_state.symbol == '/') + { + advance(); + return true; + } + else + { + advance(); + wasAsterisk = false; + } + } + } + else + { + set_error(ParserError::CommentBeginsIncorrectly); + return false; + } + } + + bool read_key(const char* having_name) + { + if (!skip_comments_and_whitespace_fail_if_eof()) + return false; + + ParserState start_of_key = save_state(); + StringView actual; + + if (m_state.symbol == '"') + { + if (!read_string(actual)) + return false; + } + else + { + if (!read_unquoted_key(actual)) + return false; + } + + if (actual != having_name) + { + restore_state(start_of_key); + set_error(ParserError::IncorrectKey); + return false; + } + + return true; + } + + // The StringView value returned is a raw view of the SJSON buffer. Nothing is unescaped: + // escaped quotation marks will remain, escaped unicode sequences will remain, etc. + // It is the responsibility of the caller to handle this in a meaningful way. + bool read_string(StringView& value) + { + if (!skip_comments_and_whitespace_fail_if_eof()) + return false; + + if (m_state.symbol != '"') + { + set_error(ParserError::QuotationMarkExpected); + return false; + } + + advance(); + + size_t start_offset = m_state.offset; + size_t end_offset; + + while (true) + { + if (eof()) + { + set_error(ParserError::InputTruncated); + return false; + } + + if (m_state.symbol == '"') + { + end_offset = m_state.offset - 1; + advance(); + break; + } + + if (m_state.symbol == '\\') + { + // Strings are returned as slices of the input, so escape sequences cannot be un-escaped. + // Assume the escape sequence is valid and skip over it. + advance(); + + if (m_state.symbol == 'u') + { + advance(); + + // This is an escaped unicode character, skip the 4 bytes that follow + advance(); + advance(); + advance(); + advance(); + } + else + { + advance(); + } + } + else + { + advance(); + } + } + + value = StringView(m_input + start_offset, end_offset - start_offset + 1); + return true; + } + + // Unquoted keys do not support escaped unicode literals or any form of escaping + // e.g. foo_\u0066_bar = "this is an invalid key" + bool read_unquoted_key(StringView& value) + { + if (eof()) + { + set_error(ParserError::InputTruncated); + return false; + } + + size_t start_offset = m_state.offset; + size_t end_offset; + + while (true) + { + if (eof()) + { + set_error(ParserError::InputTruncated); + return false; + } + + if (m_state.symbol == '"') + { + set_error(ParserError::CannotUseQuotationMarkInUnquotedString); + return false; + } + + if (m_state.symbol == '=') + { + if (m_state.offset == start_offset) + { + set_error(ParserError::KeyExpected); + return false; + } + + end_offset = m_state.offset - 1; + break; + } + + if (std::isspace(static_cast(m_state.symbol))) + { + end_offset = m_state.offset - 1; + advance(); + break; + } + + advance(); + } + + value = StringView(m_input + start_offset, end_offset - start_offset + 1); + return true; + } + + bool read_bool(bool& value) + { + if (!skip_comments_and_whitespace_fail_if_eof()) + return false; + + ParserState start_of_literal = save_state(); + + if (m_state.symbol == 't') + { + advance(); + + if (m_state.symbol == 'r' && advance() && + m_state.symbol == 'u' && advance() && + m_state.symbol == 'e' && advance()) + { + value = true; + return true; + } + } + else if (m_state.symbol == 'f') + { + advance(); + + if (m_state.symbol == 'a' && advance() && + m_state.symbol == 'l' && advance() && + m_state.symbol == 's' && advance() && + m_state.symbol == 'e' && advance()) + { + value = false; + return true; + } + } + + restore_state(start_of_literal); + set_error(ParserError::TrueOrFalseExpected); + return false; + } + + bool read_double(double* dbl_value, float* flt_value) + { + if (!skip_comments_and_whitespace_fail_if_eof()) + return false; + + size_t start_offset = m_state.offset; + size_t end_offset; + + if (m_state.symbol == '"') + { + advance(); + + if (m_state.symbol == 'n' && advance()) + { + if (m_state.symbol == 'a' && advance() && + m_state.symbol == 'n' && advance() && + m_state.symbol == '"' && advance()) + { + if (dbl_value != nullptr) + *dbl_value = std::nan(""); + else + *flt_value = std::nanf(""); + return true; + } + } + else if (m_state.symbol == '-' && advance()) + { + if (m_state.symbol == 'i' && advance() && + m_state.symbol == 'n' && advance() && + m_state.symbol == 'f' && advance() && + m_state.symbol == '"' && advance()) + { + if (dbl_value != nullptr) + *dbl_value = -std::numeric_limits::infinity(); + else + *flt_value = -std::numeric_limits::infinity(); + return true; + } + } + else if (m_state.symbol == 'i' && advance() && + m_state.symbol == 'n' && advance() && + m_state.symbol == 'f' && advance() && + m_state.symbol == '"' && advance()) + { + if (dbl_value != nullptr) + *dbl_value = std::numeric_limits::infinity(); + else + *flt_value = std::numeric_limits::infinity(); + return true; + } + + set_error(ParserError::NumberExpected); + return false; + } + + if (m_state.symbol == '-') + advance(); + + if (m_state.symbol == '0') + { + advance(); + } + else if (std::isdigit(static_cast(m_state.symbol))) + { + while (std::isdigit(static_cast(m_state.symbol))) + advance(); + } + else + { + set_error(ParserError::NumberExpected); + return false; + } + + if (m_state.symbol == '.') + { + advance(); + + while (std::isdigit(static_cast(m_state.symbol))) + advance(); + } + + if (m_state.symbol == 'e' || m_state.symbol == 'E') + { + advance(); + + if (m_state.symbol == '+' || m_state.symbol == '-') + { + advance(); + } + else if (!std::isdigit(static_cast(m_state.symbol))) + { + set_error(ParserError::InvalidNumber); + return false; + } + + while (std::isdigit(static_cast(m_state.symbol))) + advance(); + } + + end_offset = m_state.offset - 1; + size_t length = end_offset - start_offset + 1; + + char slice[k_max_number_length + 1]; + if (length >= k_max_number_length) + { + set_error(ParserError::NumberIsTooLong); + return false; + } + + std::memcpy(slice, m_input + start_offset, length); + slice[length] = '\0'; + + char* last_used_symbol = nullptr; + if (dbl_value != nullptr) + *dbl_value = std::strtod(slice, &last_used_symbol); + else + *flt_value = sjson_impl::strtof(slice, &last_used_symbol); + + if (last_used_symbol != slice + length) + { + set_error(ParserError::NumberCouldNotBeConverted); + return false; + } + + return true; + } + + template + bool read_integer(IntegralType& value) + { + if (!skip_comments_and_whitespace_fail_if_eof()) + return false; + + size_t start_offset = m_state.offset; + size_t end_offset; + int base = 10; + + if (m_state.symbol == '-') + advance(); + + if (m_state.symbol == '0') + { + advance(); + + if (m_state.symbol == 'x' || m_state.symbol == 'X') + { + advance(); + base = 16; + + while (is_hex_digit(m_state.symbol)) + advance(); + } + else + { + base = 8; + + while (std::isdigit(static_cast(m_state.symbol))) + advance(); + } + } + else if (std::isdigit(static_cast(m_state.symbol))) + { + while (std::isdigit(static_cast(m_state.symbol))) + advance(); + } + else + { + set_error(ParserError::NumberExpected); + return false; + } + + if (m_state.symbol == '.') + { + set_error(ParserError::NumberExpected); + return false; + } + + end_offset = m_state.offset - 1; + size_t length = end_offset - start_offset + 1; + + char slice[k_max_number_length + 1]; + if (length >= k_max_number_length) + { + set_error(ParserError::NumberIsTooLong); + return false; + } + + std::memcpy(slice, m_input + start_offset, length); + slice[length] = '\0'; + + char* last_used_symbol = nullptr; + if (std::is_unsigned::value) + { + const uint64_t raw_value = sjson_impl::strtoull(slice, &last_used_symbol, base); + value = static_cast(raw_value); + + if (static_cast(value) != raw_value) + { + set_error(ParserError::NumberCouldNotBeConverted); + return false; + } + } + else + { + const int64_t raw_value = sjson_impl::strtoll(slice, &last_used_symbol, base); + value = static_cast(raw_value); + + if (static_cast(value) != raw_value) + { + set_error(ParserError::NumberCouldNotBeConverted); + return false; + } + } + + if (last_used_symbol != slice + length) + { + set_error(ParserError::NumberCouldNotBeConverted); + return false; + } + + return true; + } + + template + bool try_read_integer_impl(const char* key, IntegerType& value, IntegerType default_value) + { + ParserState s = save_state(); + + if (read_key(key) && read_equal_sign()) + { + if (try_read_null()) + { + value = default_value; + return false; + } + + if (read_integer(value)) + return true; + } + + restore_state(s); + value = default_value; + return false; + } + + static bool is_hex_digit(char value) + { + return std::isdigit(static_cast(value)) + || value == 'a' || value == 'A' + || value == 'b' || value == 'B' + || value == 'c' || value == 'C' + || value == 'd' || value == 'D' + || value == 'e' || value == 'E' + || value == 'f' || value == 'F'; + } + + // Attempts to read a 'null' literal. + // Returns true on success and the state is advanced otherwise + // the state remains unchanged and the function returns false. + bool try_read_null() + { + ParserState old_state = save_state(); + + skip_comments_and_whitespace(); + + if (m_state.symbol == 'n') + { + advance(); + + if (m_state.symbol == 'u' && advance() && + m_state.symbol == 'l' && advance() && + m_state.symbol == 'l' && advance()) + { + return true; + } + } + + restore_state(old_state); + return false; + } + + bool skip_comments_and_whitespace_fail_if_eof() + { + if (!skip_comments_and_whitespace()) + return false; + + if (eof()) + { + set_error(ParserError::InputTruncated); + return false; + } + + return true; + } + + void skip_bom() + { + ParserState initial_state = save_state(); + bool skipped_bom = false; + + if (m_state.symbol == char(uint8_t(0xEF))) + { + advance(); + if (m_state.symbol == char(uint8_t(0xBB))) + { + advance(); + if (m_state.symbol == char(uint8_t(0xBF))) + { + advance(); + skipped_bom = true; + } + } + } + + if (!skipped_bom) + restore_state(initial_state); + } + + bool advance() + { + if (eof()) + return false; + + m_state.offset++; + + if (eof()) + { + m_state.symbol = '\0'; + } + else + { + m_state.symbol = m_input[m_state.offset]; + + if (m_state.symbol == '\n') + { + ++m_state.line; + m_state.column = 1; + } + else + { + m_state.column++; + } + } + + return true; + } + + void set_error(int32_t error) + { + m_state.error.error = error; + m_state.error.line = m_state.line; + m_state.error.column = m_state.column; + } + }; + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser_error.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser_error.h new file mode 100644 index 00000000..f0de3f10 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser_error.h @@ -0,0 +1,115 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "sjson/version.h" + +#include + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + struct ParserError + { + enum : uint32_t + { + None, + InputTruncated, + OpeningBraceExpected, + ClosingBraceExpected, + EqualSignExpected, + OpeningBracketExpected, + ClosingBracketExpected, + CommaExpected, + CommentBeginsIncorrectly, + CannotUseQuotationMarkInUnquotedString, + KeyExpected, + IncorrectKey, + TrueOrFalseExpected, + QuotationMarkExpected, + NumberExpected, + NumberIsTooLong, + InvalidNumber, + NumberCouldNotBeConverted, + UnexpectedContentAtEnd, + + Last + }; + + uint32_t error = None; + uint32_t line = 0; + uint32_t column = 0; + + virtual const char* get_description() const + { + switch (error) + { + case None: + return "None"; + case InputTruncated: + return "The file ended sooner than expected"; + case OpeningBraceExpected: + return "An opening { is expected here"; + case ClosingBraceExpected: + return "A closing } is expected here"; + case EqualSignExpected: + return "An equal sign is expected here"; + case OpeningBracketExpected: + return "An opening [ is expected here"; + case ClosingBracketExpected: + return "A closing ] is expected here"; + case CommaExpected: + return "A comma is expected here"; + case CommentBeginsIncorrectly: + return "Comments must start with either // or /*"; + case CannotUseQuotationMarkInUnquotedString: + return "Quotation marks cannot be used in unquoted strings"; + case KeyExpected: + return "The name of a key is expected here"; + case IncorrectKey: + return "A different key is expected here"; + case TrueOrFalseExpected: + return "Only the literals true or false are allowed here"; + case QuotationMarkExpected: + return "A quotation mark is expected here"; + case NumberExpected: + return "A number is expected here"; + case NumberIsTooLong: + return "The number is too long; increase Parser::MAX_NUMBER_LENGTH"; + case InvalidNumber: + return "This number has an invalid format"; + case NumberCouldNotBeConverted: + return "This number could not be converted"; + case UnexpectedContentAtEnd: + return "There should not be any more content in this file"; + default: + return "Unknown error"; + } + } + }; + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser_state.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser_state.h new file mode 100644 index 00000000..05611c1d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/parser_state.h @@ -0,0 +1,57 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "sjson/parser_error.h" +#include "sjson/version.h" + +#include +#include + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + struct ParserState + { + ParserState(const char* input, size_t input_length) + : offset() + , line(1) + , column(1) + , symbol(input_length > 0 ? input[0] : '\0') + , error() + { + } + + size_t offset; + uint32_t line; + uint32_t column; + char symbol; + + ParserError error; + }; + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/string_view.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/string_view.h new file mode 100644 index 00000000..d7d2f777 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/string_view.h @@ -0,0 +1,123 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "sjson/error.h" +#include "sjson/version.h" + +#include +#include + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // A StringView is just a pointer to a string and an associated length. + // It does NOT own the memory and no allocation or deallocation ever takes place. + // An empty StringView() is equal to a StringView("") of the empty string. + // It is not legal to create a StringView that contains multiple NULL terminators + // and if you do so, the behavior is undefined. + // + // Two different StringViews are equal if the strings pointed to are equal. + // They do not need to be pointing to the same physical string. + // StringView("this") == StringView("this is fun", 4) + // + // The string pointed to is immutable. + ////////////////////////////////////////////////////////////////////////// + class StringView + { + public: + constexpr StringView() + : m_c_str(nullptr) + , m_length(0) + {} + + StringView(const char* str, size_t length) + : m_c_str(str) + , m_length(length) + { +#if defined(SJSON_CPP_USE_ERROR_CHECKS) && !defined(NDEBUG) + for (size_t i = 0; i < length; ++i) + SJSON_CPP_ASSERT(str[i] != '\0', "StringView cannot contain NULL terminators"); +#endif + } + + StringView(const char* str) + : m_c_str(str) + , m_length(str == nullptr ? 0 : std::strlen(str)) + {} + + StringView& operator=(const char* str) + { + m_c_str = str; + m_length = str == nullptr ? 0 : std::strlen(str); + return *this; + } + + constexpr const char* c_str() const { return m_length == 0 ? "" : m_c_str; } + constexpr size_t size() const { return m_length; } + constexpr bool empty() const { return m_length == 0; } + + bool operator==(const char* str) const + { + const size_t length = str == nullptr ? 0 : std::strlen(str); + if (m_length != length) + return false; + + if (m_c_str == str || length == 0) + return true; + + return std::memcmp(m_c_str, str, length) == 0; + } + + bool operator!=(const char* str) const { return !(*this == str); } + + bool operator==(const StringView& other) const + { + if (m_length != other.m_length) + return false; + + if (m_c_str == other.m_c_str || m_length == 0) + return true; + + return std::memcmp(m_c_str, other.m_c_str, m_length) == 0; + } + + bool operator !=(const StringView& other) const { return !(*this == other); } + + char operator[](size_t offset) const + { + SJSON_CPP_ASSERT(offset < m_length, "Invalid offset"); + return m_c_str[offset]; + } + + private: + const char* m_c_str; + size_t m_length; + }; + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/version.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/version.h new file mode 100644 index 00000000..f60dadd0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/version.h @@ -0,0 +1,88 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Macros to detect the sjson-cpp version +//////////////////////////////////////////////////////////////////////////////// + +#define SJSON_CPP_VERSION_MAJOR 0 +#define SJSON_CPP_VERSION_MINOR 8 +#define SJSON_CPP_VERSION_PATCH 2 + +//////////////////////////////////////////////////////////////////////////////// +// In order to allow multiple versions of this library to coexist side by side +// within the same executable/library, the symbols have to be unique per version. +// We achieve this by using a versioned namespace that we optionally inline. +// To disable namespace inlining, define SJSON_CPP_NO_INLINE_NAMESPACE before including +// any sjson-cpp header. To disable the versioned namespace altogether, +// define SJSON_CPP_NO_VERSION_NAMESPACE before including any sjson-cpp header. +//////////////////////////////////////////////////////////////////////////////// + +#if !defined(SJSON_CPP_NO_VERSION_NAMESPACE) + #if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER == 1900 + // VS2015 struggles with type resolution when inline namespaces are used + // For that reason, we disable it explicitly + #define SJSON_CPP_NO_VERSION_NAMESPACE + #endif +#endif + +// Name of the namespace, e.g. v08 +#define SJSON_CPP_IMPL_VERSION_NAMESPACE_NAME v ## SJSON_CPP_VERSION_MAJOR ## SJSON_CPP_VERSION_MINOR + +// Because this is being introduced in a patch release, as caution, it is disabled +// by default. It does break ABI if host runtimes forward declare types but that +// is something they shouldn't do with a 3rd party library. Now, we offer forward +// declaration headers to help prepare the migration in the next minor release. +#if defined(SJSON_CPP_NO_VERSION_NAMESPACE) || !defined(SJSON_CPP_ENABLE_VERSION_NAMESPACE) + // Namespace is inlined, its usage does not need to be qualified with the + // full version everywhere + #define SJSON_CPP_IMPL_NAMESPACE sjson + + #define SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + #define SJSON_CPP_IMPL_VERSION_NAMESPACE_END +#elif defined(SJSON_CPP_NO_INLINE_NAMESPACE) + // Namespace won't be inlined, its usage will have to be qualified with the + // full version everywhere + #define SJSON_CPP_IMPL_NAMESPACE sjson::SJSON_CPP_IMPL_VERSION_NAMESPACE_NAME + + #define SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN \ + namespace SJSON_CPP_IMPL_VERSION_NAMESPACE_NAME \ + { + + #define SJSON_CPP_IMPL_VERSION_NAMESPACE_END \ + } +#else + // Namespace is inlined, its usage does not need to be qualified with the + // full version everywhere + #define SJSON_CPP_IMPL_NAMESPACE sjson + + #define SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN \ + inline namespace SJSON_CPP_IMPL_VERSION_NAMESPACE_NAME \ + { + + #define SJSON_CPP_IMPL_VERSION_NAMESPACE_END \ + } +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/writer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/writer.h new file mode 100644 index 00000000..bd1badc5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/includes/sjson/writer.h @@ -0,0 +1,769 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// This define allows external libraries using sjson-cpp to detect if it has already be included as a dependency +#if !defined(SJSON_CPP_WRITER) + #define SJSON_CPP_WRITER +#endif + +#include "sjson/error.h" +#include "sjson/version.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sjson +{ + SJSON_CPP_IMPL_VERSION_NAMESPACE_BEGIN + + // TODO: Cleanup the locking stuff, wrap it in #ifdef to strip when asserts are disabled + + class Writer; + class ArrayWriter; + class ObjectWriter; + + // TODO: Make this an argument to the writer. For now we assume that SJSON generated files + // can be shared between various OS and having the most conservative line ending is safer. + constexpr const char* k_line_terminator = "\r\n"; + + class StreamWriter + { + public: + virtual ~StreamWriter() = default; + + virtual void write(const void* buffer, size_t buffer_size) = 0; + + inline void write(const char* str) { write(str, std::strlen(str)); } + }; + + class FileStreamWriter final : public StreamWriter + { + public: + explicit FileStreamWriter(std::FILE* file) + : m_file(file) + {} + + virtual void write(const void* buffer, size_t buffer_size) override + { + fwrite(buffer, sizeof(char), buffer_size, m_file); + } + + private: + std::FILE* m_file; + }; + + // A lambda that does not capture anything is equivalent to a static function + // and calling a function with it as an argument is equivalent to passing a function pointer. + // Of course, a pointer can safely and automatically coerce to 'bool' and as such + // a clean signature like this does not work: void insert(const char*, std::function + struct HavingArgument : std::enable_if, F>::value> {}; + + class ArrayWriter + { + public: + void push(const char* value); + void push(bool value); + void push(double value); + void push(float value) { push(double(value)); } + void push(int8_t value) { push_signed_integer(int64_t(value)); } + void push(uint8_t value) { push_unsigned_integer(uint64_t(value)); } + void push(int16_t value) { push_signed_integer(int64_t(value)); } + void push(uint16_t value) { push_unsigned_integer(uint64_t(value)); } + void push(int32_t value) { push_signed_integer(int64_t(value)); } + void push(uint32_t value) { push_unsigned_integer(uint64_t(value)); } + void push(int64_t value) { push_signed_integer(value); } + void push(uint64_t value) { push_unsigned_integer(value); } + + template::type* requirement = nullptr> + void push(F writer_fun); + + template::type* requirement = nullptr> + void push(F writer_fun); + + // TODO: Introduce a newline type + void push_newline(); + + private: + ArrayWriter(StreamWriter& stream_writer, uint32_t indent_level); + + ArrayWriter(const ArrayWriter&) = delete; + ArrayWriter& operator=(const ArrayWriter&) = delete; + + void push_signed_integer(int64_t value); + void push_unsigned_integer(uint64_t value); + void write_indentation(); + + StreamWriter& m_stream_writer; + uint32_t m_indent_level; + bool m_is_empty; + bool m_is_locked; + bool m_is_newline; + + friend ObjectWriter; + }; + + class ObjectWriter + { + public: + void insert(const char* key, const char* value); + void insert(const char* key, bool value); + void insert(const char* key, double value); + void insert(const char* key, float value) { insert(key, double(value)); } + void insert(const char* key, int8_t value) { insert_signed_integer(key, int64_t(value)); } + void insert(const char* key, uint8_t value) { insert_unsigned_integer(key, uint64_t(value)); } + void insert(const char* key, int16_t value) { insert_signed_integer(key, int64_t(value)); } + void insert(const char* key, uint16_t value) { insert_unsigned_integer(key, uint64_t(value)); } + void insert(const char* key, int32_t value) { insert_signed_integer(key, int64_t(value)); } + void insert(const char* key, uint32_t value) { insert_unsigned_integer(key, uint64_t(value)); } + void insert(const char* key, int64_t value) { insert_signed_integer(key, int64_t(value)); } + void insert(const char* key, uint64_t value) { insert_unsigned_integer(key, uint64_t(value)); } + + template::type* requirement = nullptr> + void insert(const char* key, F writer_fun); + + template::type* requirement = nullptr> + void insert(const char* key, F writer_fun); + + void insert_newline(); + + // Implement operator[] for convenience + class ValueRef final + { + public: + ValueRef(ValueRef&& other) noexcept; + ~ValueRef(); + + void operator=(const char* value); + void operator=(bool value); + void operator=(double value); + void operator=(float value) { *this = double(value); } + void operator=(int8_t value) { assign_signed_integer(int64_t(value)); } + void operator=(uint8_t value) { assign_unsigned_integer(uint64_t(value)); } + void operator=(int16_t value) { assign_signed_integer(int64_t(value)); } + void operator=(uint16_t value) { assign_unsigned_integer(uint64_t(value)); } + void operator=(int32_t value) { assign_signed_integer(int64_t(value)); } + void operator=(uint32_t value) { assign_unsigned_integer(uint64_t(value)); } + void operator=(int64_t value) { assign_signed_integer(int64_t(value)); } + void operator=(uint64_t value) { assign_unsigned_integer(uint64_t(value)); } + + template::type* requirement = nullptr> + void operator=(F writer_fun); + + template::type* requirement = nullptr> + void operator=(F writer_fun); + + private: + ValueRef(ObjectWriter& object_writer, const char* key); + + ValueRef(const ValueRef&) = delete; + ValueRef& operator=(const ValueRef&) = delete; + + void assign_signed_integer(int64_t value); + void assign_unsigned_integer(uint64_t value); + + ObjectWriter* m_object_writer; + bool m_is_empty; + bool m_is_locked; + + friend ObjectWriter; + }; + + ValueRef operator[](const char* key) { return ValueRef(*this, key); } + + protected: + inline ObjectWriter(StreamWriter& stream_writer, uint32_t indent_level); + + ObjectWriter(const ObjectWriter&) = delete; + ObjectWriter& operator=(const ObjectWriter&) = delete; + + void insert_signed_integer(const char* key, int64_t value); + void insert_unsigned_integer(const char* key, uint64_t value); + void write_indentation(); + + private: + StreamWriter& m_stream_writer; + uint32_t m_indent_level; + bool m_is_locked; + bool m_has_live_value_ref; + + friend ArrayWriter; + }; + + class Writer : public ObjectWriter + { + public: + explicit Writer(StreamWriter& stream_writer); + + private: + Writer(const Writer&) = delete; + Writer& operator=(const Writer&) = delete; + }; + + ////////////////////////////////////////////////////////////////////////// + + inline ObjectWriter::ObjectWriter(StreamWriter& stream_writer, uint32_t indent_level) + : m_stream_writer(stream_writer) + , m_indent_level(indent_level) + , m_is_locked(false) + , m_has_live_value_ref(false) + {} + + inline void ObjectWriter::insert(const char* key, const char* value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON value in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON value in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = \""); + m_stream_writer.write(value); + m_stream_writer.write("\""); + m_stream_writer.write(k_line_terminator); + } + + inline void ObjectWriter::insert(const char* key, bool value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON value in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON value in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = "); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%s%s", value ? "true" : "false", k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to insert SJSON value: [%s = %s]", key, value); + m_stream_writer.write(buffer, length); + } + + inline void ObjectWriter::insert(const char* key, double value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON value in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON value in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = "); + + if (std::isnan(value)) + { + m_stream_writer.write("\"nan\""); + m_stream_writer.write(k_line_terminator); + } + else if (std::isinf(value)) + { + m_stream_writer.write("\""); + + if (value < 0.0) + m_stream_writer.write("-"); + + m_stream_writer.write("inf\""); + m_stream_writer.write(k_line_terminator); + } + else + { + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%.17g%s", value, k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to insert SJSON value: [%s = %.17g]", key, value); + m_stream_writer.write(buffer, length); + } + } + + inline void ObjectWriter::insert_signed_integer(const char* key, int64_t value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON value in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON value in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = "); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%" PRId64 "%s", value, k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to insert SJSON value: [%s = %lld]", key, value); + m_stream_writer.write(buffer, length); + } + + inline void ObjectWriter::insert_unsigned_integer(const char* key, uint64_t value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON value in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON value in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = "); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%" PRIu64 "%s", value, k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to insert SJSON value: [%s = %llu]", key, value); + m_stream_writer.write(buffer, length); + } + + template::type* requirement> + inline void ObjectWriter::insert(const char* key, F writer_fun) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON object in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON object in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = {"); + m_stream_writer.write(k_line_terminator); + m_is_locked = true; + + ObjectWriter object_writer(m_stream_writer, m_indent_level + 1); + writer_fun(object_writer); + + m_is_locked = false; + write_indentation(); + + m_stream_writer.write("}"); + m_stream_writer.write(k_line_terminator); + } + + template::type* requirement > + inline void ObjectWriter::insert(const char* key, F writer_fun) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert SJSON array in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert SJSON array in object when it has a live ValueRef"); + + write_indentation(); + + m_stream_writer.write(key); + m_stream_writer.write(" = [ "); + m_is_locked = true; + + ArrayWriter array_writer(m_stream_writer, m_indent_level + 1); + writer_fun(array_writer); + + if (array_writer.m_is_newline) + { + write_indentation(); + m_stream_writer.write("]"); + m_stream_writer.write(k_line_terminator); + } + else + { + m_stream_writer.write(" ]"); + m_stream_writer.write(k_line_terminator); + } + + m_is_locked = false; + } + + inline void ObjectWriter::write_indentation() + { + for (uint32_t level = 0; level < m_indent_level; ++level) + m_stream_writer.write("\t"); + } + + inline void ObjectWriter::insert_newline() + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert newline in locked object"); + SJSON_CPP_ASSERT(!m_has_live_value_ref, "Cannot insert newline in object when it has a live ValueRef"); + + m_stream_writer.write(k_line_terminator); + } + + inline ObjectWriter::ValueRef::ValueRef(ObjectWriter& object_writer, const char* key) + : m_object_writer(&object_writer) + , m_is_empty(true) + , m_is_locked(false) + { + SJSON_CPP_ASSERT(!object_writer.m_is_locked, "Cannot insert SJSON value in locked object"); + SJSON_CPP_ASSERT(!object_writer.m_has_live_value_ref, "Cannot insert SJSON value in object when it has a live ValueRef"); + + object_writer.write_indentation(); + object_writer.m_stream_writer.write(key); + object_writer.m_stream_writer.write(" = "); + object_writer.m_has_live_value_ref = true; + object_writer.m_is_locked = true; + } + + inline ObjectWriter::ValueRef::ValueRef(ValueRef&& other) noexcept + : m_object_writer(other.m_object_writer) + , m_is_empty(other.m_is_empty) + , m_is_locked(other.m_is_locked) + { + other.m_object_writer = nullptr; + } + + inline ObjectWriter::ValueRef::~ValueRef() + { + if (m_object_writer != nullptr) + { + SJSON_CPP_ASSERT(!m_is_empty, "ValueRef has no associated value"); + SJSON_CPP_ASSERT(!m_is_locked, "ValueRef is locked"); + SJSON_CPP_ASSERT(m_object_writer->m_has_live_value_ref, "Expected a live ValueRef to be present"); + SJSON_CPP_ASSERT(m_object_writer->m_is_locked, "Expected object writer to be locked"); + + m_object_writer->m_has_live_value_ref = false; + m_object_writer->m_is_locked = false; + } + } + + inline void ObjectWriter::ValueRef::operator=(const char* value) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + m_object_writer->m_stream_writer.write("\""); + m_object_writer->m_stream_writer.write(value); + m_object_writer->m_stream_writer.write("\""); + m_object_writer->m_stream_writer.write(k_line_terminator); + m_is_empty = false; + } + + inline void ObjectWriter::ValueRef::operator=(bool value) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%s%s", value ? "true" : "false", k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to assign SJSON value: %s", value); + m_object_writer->m_stream_writer.write(buffer, length); + m_is_empty = false; + } + + inline void ObjectWriter::ValueRef::operator=(double value) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + if (std::isnan(value)) + { + m_object_writer->m_stream_writer.write("\"nan\""); + m_object_writer->m_stream_writer.write(k_line_terminator); + } + else if (std::isinf(value)) + { + m_object_writer->m_stream_writer.write("\""); + + if (value < 0.0) + m_object_writer->m_stream_writer.write("-"); + + m_object_writer->m_stream_writer.write("inf\""); + m_object_writer->m_stream_writer.write(k_line_terminator); + } + else + { + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%.17g%s", value, k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to assign SJSON value: %.17g", value); + m_object_writer->m_stream_writer.write(buffer, length); + } + + m_is_empty = false; + } + + template::type* requirement> + inline void ObjectWriter::ValueRef::operator=(F writer_fun) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + m_object_writer->m_stream_writer.write("{"); + m_object_writer->m_stream_writer.write(k_line_terminator); + m_is_locked = true; + + ObjectWriter object_writer(m_object_writer->m_stream_writer, m_object_writer->m_indent_level + 1); + writer_fun(object_writer); + + m_is_locked = false; + m_object_writer->write_indentation(); + m_object_writer->m_stream_writer.write("}"); + m_object_writer->m_stream_writer.write(k_line_terminator); + m_is_empty = false; + } + + template::type* requirement> + inline void ObjectWriter::ValueRef::operator=(F writer_fun) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + m_object_writer->m_stream_writer.write("[ "); + m_is_locked = true; + + ArrayWriter array_writer(m_object_writer->m_stream_writer, m_object_writer->m_indent_level + 1); + writer_fun(array_writer); + + if (array_writer.m_is_newline) + { + m_object_writer->write_indentation(); + m_object_writer->m_stream_writer.write("]"); + m_object_writer->m_stream_writer.write(k_line_terminator); + } + else + { + m_object_writer->m_stream_writer.write(" ]"); + m_object_writer->m_stream_writer.write(k_line_terminator); + } + + m_is_locked = false; + m_is_empty = false; + } + + inline void ObjectWriter::ValueRef::assign_signed_integer(int64_t value) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%" PRId64 "%s", value, k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to assign SJSON value: %lld", value); + m_object_writer->m_stream_writer.write(buffer, length); + m_is_empty = false; + } + + inline void ObjectWriter::ValueRef::assign_unsigned_integer(uint64_t value) + { + SJSON_CPP_ASSERT(m_is_empty, "Cannot write multiple values within a ValueRef"); + SJSON_CPP_ASSERT(m_object_writer != nullptr, "ValueRef not initialized"); + SJSON_CPP_ASSERT(!m_is_locked, "Cannot assign a value when locked"); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%" PRIu64 "%s", value, k_line_terminator); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to assign SJSON value: %llu", value); + m_object_writer->m_stream_writer.write(buffer, length); + m_is_empty = false; + } + + ////////////////////////////////////////////////////////////////////////// + + inline ArrayWriter::ArrayWriter(StreamWriter& stream_writer, uint32_t indent_level) + : m_stream_writer(stream_writer) + , m_indent_level(indent_level) + , m_is_empty(true) + , m_is_locked(false) + , m_is_newline(false) + {} + + inline void ArrayWriter::push(const char* value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON value in locked array"); + + if (!m_is_empty && !m_is_newline) + m_stream_writer.write(", "); + + if (m_is_newline) + write_indentation(); + + m_stream_writer.write("\""); + m_stream_writer.write(value); + m_stream_writer.write("\""); + m_is_empty = false; + m_is_newline = false; + } + + inline void ArrayWriter::push(bool value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON value in locked array"); + + if (!m_is_empty && !m_is_newline) + m_stream_writer.write(", "); + + if (m_is_newline) + write_indentation(); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%s", value ? "true" : "false"); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to push SJSON value: %s", value); + m_stream_writer.write(buffer, length); + m_is_empty = false; + m_is_newline = false; + } + + inline void ArrayWriter::push(double value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON value in locked array"); + + if (!m_is_empty && !m_is_newline) + m_stream_writer.write(", "); + + if (m_is_newline) + write_indentation(); + + if (std::isnan(value)) + { + m_stream_writer.write("\"nan\""); + } + else if (std::isinf(value)) + { + m_stream_writer.write("\""); + + if (value < 0.0) + m_stream_writer.write("-"); + + m_stream_writer.write("inf\""); + } + else + { + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%.17g", value); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to push SJSON value: %.17g", value); + m_stream_writer.write(buffer, length); + } + + m_is_empty = false; + m_is_newline = false; + } + + inline void ArrayWriter::push_signed_integer(int64_t value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON value in locked array"); + + if (!m_is_empty && !m_is_newline) + m_stream_writer.write(", "); + + if (m_is_newline) + write_indentation(); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%" PRId64, value); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to push SJSON value: %lld", value); + m_stream_writer.write(buffer, length); + m_is_empty = false; + m_is_newline = false; + } + + inline void ArrayWriter::push_unsigned_integer(uint64_t value) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON value in locked array"); + + if (!m_is_empty && !m_is_newline) + m_stream_writer.write(", "); + + if (m_is_newline) + write_indentation(); + + char buffer[256]; + size_t length = snprintf(buffer, sizeof(buffer), "%" PRIu64, value); + SJSON_CPP_ASSERT(length > 0 && length < sizeof(buffer), "Failed to push SJSON value: %llu", value); + m_stream_writer.write(buffer, length); + m_is_empty = false; + m_is_newline = false; + } + + template::type* requirement> + inline void ArrayWriter::push(F writer_fun) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON object in locked array"); + + if (!m_is_empty && !m_is_newline) + { + m_stream_writer.write(","); + m_stream_writer.write(k_line_terminator); + } + else if (m_is_empty) + m_stream_writer.write(k_line_terminator); + + write_indentation(); + m_stream_writer.write("{"); + m_stream_writer.write(k_line_terminator); + m_is_locked = true; + + ObjectWriter object_writer(m_stream_writer, m_indent_level + 1); + writer_fun(object_writer); + + write_indentation(); + m_stream_writer.write("}"); + m_stream_writer.write(k_line_terminator); + + m_is_locked = false; + m_is_empty = false; + m_is_newline = true; + } + + template::type* requirement> + inline void ArrayWriter::push(F writer_fun) + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot push SJSON array in locked array"); + + if (!m_is_empty && !m_is_newline) + m_stream_writer.write(", "); + + if (m_is_newline) + write_indentation(); + + m_stream_writer.write("[ "); + m_is_locked = true; + + ArrayWriter array_writer(m_stream_writer, m_indent_level); + writer_fun(array_writer); + + m_is_locked = false; + m_stream_writer.write(" ]"); + m_is_empty = false; + m_is_newline = false; + } + + inline void ArrayWriter::push_newline() + { + SJSON_CPP_ASSERT(!m_is_locked, "Cannot insert newline in locked array"); + + m_stream_writer.write(k_line_terminator); + m_is_newline = true; + } + + inline void ArrayWriter::write_indentation() + { + for (uint32_t level = 0; level < m_indent_level; ++level) + m_stream_writer.write("\t"); + } + + ////////////////////////////////////////////////////////////////////////// + + inline Writer::Writer(StreamWriter& stream_writer) + : ObjectWriter(stream_writer, 0) + {} + + SJSON_CPP_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/make.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/make.py new file mode 100644 index 00000000..d2e4bf55 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/make.py @@ -0,0 +1,403 @@ +import argparse +import multiprocessing +import os +import platform +import shutil +import subprocess +import sys + +def parse_argv(): + parser = argparse.ArgumentParser(add_help=False) + + actions = parser.add_argument_group(title='Actions', description='If no action is specified, on Windows, OS X, and Linux the solution/make files are generated. Multiple actions can be used simultaneously.') + actions.add_argument('-build', action='store_true') + actions.add_argument('-clean', action='store_true') + actions.add_argument('-clean_only', action='store_true') + actions.add_argument('-unit_test', action='store_true') + + target = parser.add_argument_group(title='Target') + target.add_argument('-compiler', choices=['vs2015', 'vs2017', 'vs2019', 'vs2019-clang', 'android', 'clang4', 'clang5', 'clang6', 'clang7', 'clang8', 'clang9', 'clang10', 'clang11', 'gcc4.8', 'gcc4.9', 'gcc5', 'gcc6', 'gcc7', 'gcc8', 'gcc9', 'gcc10', 'osx', 'ios', 'emscripten'], help='Defaults to the host system\'s default compiler') + target.add_argument('-config', choices=['Debug', 'Release'], type=str.capitalize) + target.add_argument('-cpu', choices=['x86', 'x64', 'armv7', 'arm64', 'wasm'], help='Defaults to the host system\'s architecture') + + misc = parser.add_argument_group(title='Miscellaneous') + misc.add_argument('-num_threads', help='No. to use while compiling') + misc.add_argument('-tests_matching', help='Only run tests whose names match this regex') + misc.add_argument('-ci', action='store_true', help='Whether or not this is a Continuous Integration build') + misc.add_argument('-help', action='help', help='Display this usage information') + + num_threads = multiprocessing.cpu_count() + if platform.system() == 'Linux' and sys.version_info >= (3, 4): + num_threads = len(os.sched_getaffinity(0)) + if not num_threads or num_threads == 0: + num_threads = 4 + + parser.set_defaults(build=False, clean=False, clean_only=False, unit_test=False, compiler=None, config='Release', cpu=None, num_threads=num_threads, tests_matching='') + + args = parser.parse_args() + + is_arm64_cpu = False + if platform.machine() == 'arm64' or platform.machine() == 'aarch64': + is_arm64_cpu = True + + # Sanitize and validate our options + if args.compiler == 'android': + if not args.cpu: + args.cpu = 'arm64' + + if not platform.system() == 'Windows': + print('Android is only supported on Windows') + sys.exit(1) + + if not args.cpu in ['armv7', 'arm64']: + print('{} cpu architecture not in supported list [armv7, arm64] for Android'.format(args.cpu)) + sys.exit(1) + elif args.compiler == 'ios': + if not args.cpu: + args.cpu = 'arm64' + + if not platform.system() == 'Darwin': + print('iOS is only supported on OS X') + sys.exit(1) + + if args.unit_test: + print('Unit tests cannot run from the command line on iOS') + sys.exit(1) + + if not args.cpu in ['arm64']: + print('{} cpu architecture not in supported list [arm64] for iOS'.format(args.cpu)) + sys.exit(1) + elif args.compiler == 'emscripten': + if not args.cpu: + args.cpu = 'wasm' + + if not platform.system() == 'Darwin' and not platform.system() == 'Linux': + print('Emscripten is only supported on OS X and Linux') + sys.exit(1) + + if not args.cpu in ['wasm']: + print('{} cpu architecture not in supported list [wasm] for Emscripten'.format(args.cpu)) + sys.exit(1) + else: + if not args.cpu: + if is_arm64_cpu: + args.cpu = 'arm64' + else: + args.cpu = 'x64' + + if args.cpu == 'arm64': + is_arm_supported = False + + # Cross compilation + if args.compiler in ['vs2017', 'vs2019', 'ios', 'android']: + is_arm_supported = True + + # Native compilation + if platform.system() == 'Darwin' and is_arm64_cpu: + is_arm_supported = True + elif platform.system() == 'Linux' and is_arm64_cpu: + is_arm_supported = True + + if not is_arm_supported: + print('arm64 is only supported with VS2017, VS2019, OS X (M1 processors), Linux, Android, and iOS') + sys.exit(1) + elif args.cpu == 'armv7': + if not args.compiler == 'android': + print('armv7 is only supported with Android') + sys.exit(1) + elif args.cpu == 'wasm': + if not args.compiler == 'emscripten': + print('wasm is only supported with Emscripten') + sys.exit(1) + + if platform.system() == 'Darwin' and args.cpu == 'x86': + result = subprocess.check_output(['xcodebuild', '-version']).decode("utf-8") + if 'Xcode 11' in result: + print('Versions of Xcode 11 and up no longer support x86') + sys.exit(1) + + return args + +def get_generator(compiler, cpu): + if compiler == None: + return None + + if platform.system() == 'Windows': + if compiler == 'vs2015': + if cpu == 'x86': + return 'Visual Studio 14' + elif cpu == 'x64': + return 'Visual Studio 14 Win64' + elif compiler == 'vs2017': + if cpu == 'x86': + return 'Visual Studio 15' + elif cpu == 'x64': + return 'Visual Studio 15 Win64' + elif cpu == 'arm64': + # VS2017 ARM/ARM64 support only works with cmake 3.13 and up and the architecture must be specified with + # the -A cmake switch + return 'Visual Studio 15 2017' + elif compiler == 'vs2019' or compiler == 'vs2019-clang': + return 'Visual Studio 16 2019' + elif compiler == 'android': + # For Android, we use the default generator since we don't build with CMake + return None + elif platform.system() == 'Darwin': + if compiler == 'osx' or compiler == 'ios': + return 'Xcode' + elif compiler == 'emscripten': + # Emscripten uses the default generator + return None + elif platform.system() == 'Linux': + if compiler == 'emscripten': + # Emscripten uses the default generator + return None + + return 'Unix Makefiles' + + print('Unknown compiler: {}'.format(compiler)) + print('See help with: python make.py -help') + sys.exit(1) + +def get_architecture(compiler, cpu): + if compiler == None: + return None + + if platform.system() == 'Windows': + if compiler == 'vs2017': + if cpu == 'arm64': + return 'ARM64' + elif compiler == 'vs2019' or compiler == 'vs2019-clang': + if cpu == 'x86': + return 'Win32' + else: + return cpu + + # This compiler/cpu pair does not need the architecture switch + return None + +def get_toolchain(compiler, cmake_script_dir): + if platform.system() == 'Windows' and compiler == 'android': + return os.path.join(cmake_script_dir, 'Toolchain-Android.cmake') + elif platform.system() == 'Darwin' and compiler == 'ios': + return os.path.join(cmake_script_dir, 'Toolchain-iOS.cmake') + + # No toolchain + return None + +def set_compiler_env(compiler, args): + if platform.system() == 'Linux': + if compiler == 'clang4': + os.environ['CC'] = 'clang-4.0' + os.environ['CXX'] = 'clang++-4.0' + elif compiler == 'clang5': + os.environ['CC'] = 'clang-5.0' + os.environ['CXX'] = 'clang++-5.0' + elif compiler == 'clang6': + os.environ['CC'] = 'clang-6.0' + os.environ['CXX'] = 'clang++-6.0' + elif compiler == 'clang7': + os.environ['CC'] = 'clang-7' + os.environ['CXX'] = 'clang++-7' + elif compiler == 'clang8': + os.environ['CC'] = 'clang-8' + os.environ['CXX'] = 'clang++-8' + elif compiler == 'clang9': + os.environ['CC'] = 'clang-9' + os.environ['CXX'] = 'clang++-9' + elif compiler == 'clang10': + os.environ['CC'] = 'clang-10' + os.environ['CXX'] = 'clang++-10' + elif compiler == 'clang11': + os.environ['CC'] = 'clang-11' + os.environ['CXX'] = 'clang++-11' + elif compiler == 'gcc4.8': + os.environ['CC'] = 'gcc-4.8' + os.environ['CXX'] = 'g++-4.8' + elif compiler == 'gcc4.9': + os.environ['CC'] = 'gcc-4.9' + os.environ['CXX'] = 'g++-4.9' + elif compiler == 'gcc5': + os.environ['CC'] = 'gcc-5' + os.environ['CXX'] = 'g++-5' + elif compiler == 'gcc6': + os.environ['CC'] = 'gcc-6' + os.environ['CXX'] = 'g++-6' + elif compiler == 'gcc7': + os.environ['CC'] = 'gcc-7' + os.environ['CXX'] = 'g++-7' + elif compiler == 'gcc8': + os.environ['CC'] = 'gcc-8' + os.environ['CXX'] = 'g++-8' + elif compiler == 'gcc9': + os.environ['CC'] = 'gcc-9' + os.environ['CXX'] = 'g++-9' + elif compiler == 'gcc10': + os.environ['CC'] = 'gcc-10' + os.environ['CXX'] = 'g++-10' + elif compiler == 'emscripten': + # Nothing to do for Emscripten + return + else: + print('Unknown compiler: {}'.format(compiler)) + print('See help with: python make.py -help') + sys.exit(1) + +def do_generate_solution(build_dir, cmake_script_dir, args): + compiler = args.compiler + cpu = args.cpu + config = args.config + + if compiler: + set_compiler_env(compiler, args) + + extra_switches = ['--no-warn-unused-cli'] + extra_switches.append('-DCPU_INSTRUCTION_SET:STRING={}'.format(cpu)) + + if not platform.system() == 'Windows': + extra_switches.append('-DCMAKE_BUILD_TYPE={}'.format(config.upper())) + + if platform.system() == 'Darwin' and compiler == 'ios' and args.ci: + # Disable code signing for CI iOS builds since we just test compilation + extra_switches.append('-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO') + extra_switches.append('-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO') + + toolchain = get_toolchain(compiler, cmake_script_dir) + if toolchain: + extra_switches.append('-DCMAKE_TOOLCHAIN_FILE={}'.format(toolchain)) + + # Generate IDE solution + print('Generating build files ...') + if compiler == 'emscripten': + cmake_cmd = 'emcmake cmake .. -DCMAKE_INSTALL_PREFIX="{}" {}'.format(build_dir, ' '.join(extra_switches)) + else: + cmake_generator = get_generator(compiler, cpu) + if not cmake_generator: + print('Using default generator') + else: + generator_suffix = '' + if compiler == 'vs2019-clang': + extra_switches.append('-T ClangCL') + generator_suffix = 'Clang CL' + + print('Using generator: {} {}'.format(cmake_generator, generator_suffix)) + extra_switches.append('-G "{}"'.format(cmake_generator)) + + cmake_arch = get_architecture(compiler, cpu) + if cmake_arch: + print('Using architecture: {}'.format(cmake_arch)) + extra_switches.append('-A {}'.format(cmake_arch)) + + cmake_cmd = 'cmake .. -DCMAKE_INSTALL_PREFIX="{}" {}'.format(build_dir, ' '.join(extra_switches)) + + result = subprocess.call(cmake_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_build(args): + config = args.config + + print('Building ...') + cmake_cmd = 'cmake --build .' + if platform.system() == 'Windows': + if args.compiler == 'android': + cmake_cmd += ' --config {}'.format(config) + else: + cmake_cmd += ' --config {} --target INSTALL'.format(config) + elif platform.system() == 'Darwin': + if args.compiler == 'ios': + cmake_cmd += ' --config {}'.format(config) + else: + cmake_cmd += ' --config {} --target install'.format(config) + else: + cmake_cmd += ' --target install' + + result = subprocess.call(cmake_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_tests_android(build_dir, args): + # Switch our working directory to where we built everything + working_dir = os.path.join(build_dir, 'tests', 'main_android') + os.chdir(working_dir) + + gradlew_exe = os.path.join(build_dir, '..', 'tests', 'main_android', 'gradlew.bat') + + # We uninstall first and then install + if args.config == 'Debug': + install_cmd = 'uninstallAll installDebug' + elif args.config == 'Release': + install_cmd = 'uninstallAll installRelease' + + # Install our app + test_cmd = '"{}" {}'.format(gradlew_exe, install_cmd) + result = subprocess.call(test_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Execute through ADB + run_cmd = 'adb shell am start -n "com.sjson.unit_tests/com.sjson.unit_tests.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER' + result = subprocess.call(run_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Restore working directory + os.chdir(build_dir) + +def do_tests_cmake(args): + ctest_cmd = 'ctest --output-on-failure --parallel {}'.format(args.num_threads) + + if platform.system() == 'Windows' or platform.system() == 'Darwin': + ctest_cmd += ' -C {}'.format(args.config) + if args.tests_matching: + ctest_cmd += ' --tests-regex {}'.format(args.tests_matching) + + result = subprocess.call(ctest_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_tests(build_dir, args): + print('Running unit tests ...') + + if args.compiler == 'android': + do_tests_android(build_dir, args) + else: + do_tests_cmake(args) + +if __name__ == "__main__": + args = parse_argv() + + build_dir = os.path.join(os.getcwd(), 'build') + cmake_script_dir = os.path.join(os.getcwd(), 'cmake') + + is_clean_requested = args.clean or args.clean_only + if is_clean_requested and os.path.exists(build_dir): + print('Cleaning previous build ...') + shutil.rmtree(build_dir) + + if args.clean_only: + sys.exit(0) + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + + os.chdir(build_dir) + + print('Using config: {}'.format(args.config)) + print('Using cpu: {}'.format(args.cpu)) + if args.compiler: + print('Using compiler: {}'.format(args.compiler)) + print('Using {} threads'.format(args.num_threads)) + + # Make sure 'make' runs with all available cores + os.environ['MAKEFLAGS'] = '-j{}'.format(args.num_threads) + + do_generate_solution(build_dir, cmake_script_dir, args) + + if args.build: + do_build(args) + + if args.unit_test: + do_tests(build_dir, args) + + sys.exit(0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/sonar-project.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/sonar-project.properties new file mode 100644 index 00000000..fdd36b40 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.organization=nfrechette-github + +sonar.projectKey=nfrechette_sjson-cpp +sonar.projectName=sjson-cpp +sonar.projectVersion=0.8.2 + +sonar.sources=includes,tests/sources,tests/main_generic + +sonar.cfamily.build-wrapper-output=bw_output +sonar.cfamily.threads=2 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/CMakeLists.txt new file mode 100644 index 00000000..6ffef2c9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required (VERSION 3.2) +project(sjson-cpp_unit_tests_root NONE) + +if(PLATFORM_ANDROID) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_android") +elseif(PLATFORM_IOS) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_ios") +elseif(PLATFORM_EMSCRIPTEN) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_emscripten") +else() + add_subdirectory("${PROJECT_SOURCE_DIR}/main_generic") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/CMakeLists.txt new file mode 100644 index 00000000..79e24ab3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.2) +project(sjson-cpp_unit_tests_gradle_shim NONE) + +# Set our project root since our gradle files used to build live in the binary output directory +# but the actual source files live in the source/current directory. +set(SJSON_PROJECT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/app) +file(RELATIVE_PATH SJSON_PROJECT_ROOT ${SJSON_PROJECT_ROOT} ${PROJECT_SOURCE_DIR}/app) + +# Configure gradle for our build configuration +configure_file(app/build.gradle.in app/build.gradle @ONLY) + +# Copy gradle related files +file(COPY build.gradle gradle.properties settings.gradle gradle DESTINATION .) + +add_custom_target(${PROJECT_NAME} ALL + COMMAND "${PROJECT_SOURCE_DIR}/gradlew.bat" + # Decide whether we should build Debug or Release + $<$:assembleDebug> + $<$:assembleRelease>) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/build.gradle.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/build.gradle.in new file mode 100644 index 00000000..ca59f4f5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/build.gradle.in @@ -0,0 +1,55 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.sjson.unit_tests" + minSdkVersion 24 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + } + } + ndk { + abiFilters '@CPU_INSTRUCTION_SET@' + } + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + debuggable = true + signingConfig signingConfigs.debug + externalNativeBuild { + cmake { + // Because we want to debug 'release' builds, the 'debuggable' property above + // forces the build configuration to 'Debug' which disables optimizations. + // Force a release configutation anyway. + arguments '-DCMAKE_BUILD_TYPE=Release' + } + } + } + } + externalNativeBuild { + cmake { + path '@SJSON_PROJECT_ROOT@/src/main/cpp/CMakeLists.txt' + } + } + sourceSets { + main { + manifest.srcFile '@SJSON_PROJECT_ROOT@/src/main/AndroidManifest.xml' + java.srcDirs = ['@SJSON_PROJECT_ROOT@/src/main/java'] + res.srcDirs = ['@SJSON_PROJECT_ROOT@/src/main/res'] + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/proguard-rules.pro b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/AndroidManifest.xml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..6f8e2015 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..552d9d34 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.2) +project(sjson-cpp_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Project root is \tests\main_android +set(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") + +include_directories("${PROJECT_ROOT_DIR}/../../includes") +include_directories("${PROJECT_ROOT_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_ROOT_DIR}/../sources/*.h + ${PROJECT_ROOT_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_library(${PROJECT_NAME} SHARED ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Enable exceptions +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) + +# Enable debug symbols +target_compile_options(${PROJECT_NAME} PRIVATE -g) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) + +target_include_directories(${PROJECT_NAME} PUBLIC jni) + +target_link_libraries(${PROJECT_NAME} m log) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/cpp/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/cpp/main.cpp new file mode 100644 index 00000000..d19f914f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/cpp/main.cpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +#include + +extern "C" jint Java_com_sjson_unit_1tests_MainActivity_getNumUnitTestCases(JNIEnv* env, jobject caller) +{ + return Catch::getRegistryHub().getTestCaseRegistry().getAllTests().size(); +} + +extern "C" jint Java_com_sjson_unit_1tests_MainActivity_runUnitTests(JNIEnv* env, jobject caller) +{ + int result = Catch::Session().run(); + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/java/com/sjson/unit_tests/MainActivity.java b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/java/com/sjson/unit_tests/MainActivity.java new file mode 100644 index 00000000..b39b390a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/java/com/sjson/unit_tests/MainActivity.java @@ -0,0 +1,31 @@ +package com.sjson.unit_tests; + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class MainActivity extends Activity { + static { + System.loadLibrary("sjson-cpp_unit_tests"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView resultTextView = new TextView(this); + + int numUnitTestCases = getNumUnitTestCases(); + int numFailed = runUnitTests(); + + if (numFailed == 0) + resultTextView.setText("All " + numUnitTestCases + " test cases ran successfully!"); + else + resultTextView.setText(numFailed + " test cases failed!"); + + setContentView(resultTextView); + } + + public native int getNumUnitTestCases(); + public native int runUnitTests(); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/res/values/strings.xml b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..a2382b3f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + sjson-cpp Unit Tests + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/build.gradle b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/build.gradle new file mode 100644 index 00000000..1606295a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/build.gradle @@ -0,0 +1,25 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle.properties new file mode 100644 index 00000000..fe2852f9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + +# Run tasks in parallel +org.gradle.parallel=true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle/wrapper/gradle-wrapper.jar b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle/wrapper/gradle-wrapper.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..656c0945 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Mar 27 21:02:15 EDT 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradlew b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradlew.bat b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/settings.gradle b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/settings.gradle new file mode 100644 index 00000000..880ede10 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_android/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='sjson-cpp Unit Tests' diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_emscripten/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_emscripten/CMakeLists.txt new file mode 100644 index 00000000..424b7d3d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_emscripten/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required (VERSION 3.2) +project(sjson-cpp_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_executable(${PROJECT_NAME} ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) + +target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) # Enable all warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow) # Enable shadowing warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Werror) # Treat warnings as errors + +# Exceptions are not enabled by default, enable them +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) +target_link_libraries(${PROJECT_NAME} PRIVATE "-s DISABLE_EXCEPTION_CATCHING=0") + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s NODERAWFS=1") # Enable the raw node file system +target_link_libraries(${PROJECT_NAME} PRIVATE -lnodefs.js) # Link the node file system + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s ENVIRONMENT=node") # Force the environment to node + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s ALLOW_MEMORY_GROWTH=1") # Allow dynamic memory allocation + +# Setup Catch2 so we can find and execute the unit tests with CTest +set(OptionalCatchTestLauncher node) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../external/catch2/contrib") +include(CTest) +include(ParseAndAddCatchTests) +ParseAndAddCatchTests(${PROJECT_NAME}) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.js + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wasm + DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_emscripten/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_emscripten/main.cpp new file mode 100644 index 00000000..72956288 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_emscripten/main.cpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_generic/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_generic/CMakeLists.txt new file mode 100644 index 00000000..4c66857e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_generic/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required (VERSION 3.2) +project(sjson-cpp_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_TEST_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../external/catch2/contrib") +include(CTest) +include(Catch) +catch_discover_tests(${PROJECT_NAME}) + +setup_default_compiler_flags(${PROJECT_NAME}) + +if(MSVC) + if(CPU_INSTRUCTION_SET MATCHES "arm64") + # Exceptions are not enabled by default for ARM targets, enable them + target_compile_options(${PROJECT_NAME} PRIVATE /EHsc) + endif() +endif() + +# Throw on failure to allow us to catch them and recover +add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_generic/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_generic/main.cpp new file mode 100644 index 00000000..990a85fb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_generic/main.cpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +#ifdef _WIN32 +#include +#endif + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + +#ifdef _WIN32 + if (IsDebuggerPresent()) + { + printf("Press any key to continue...\n"); + while (_kbhit() == 0); + } +#endif + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_ios/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_ios/CMakeLists.txt new file mode 100644 index 00000000..3e323cd4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_ios/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required (VERSION 3.2) +project(sjson-cpp_unit_tests) + +# iOS cmake toolchain does not support CMAKE_CXX_STANDARD +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Force enable debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + +# Enable optimizations in Release +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +set(MACOSX_BUNDLE_EXECUTABLE_NAME ${PROJECT_NAME}) +set(MACOSX_BUNDLE_INFO_STRING "com.sjson.cpp.unit-tests") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.sjson.cpp.unit-tests") +set(MACOSX_BUNDLE_BUNDLE_NAME "sjson-cpp-unit-tests") + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_TEST_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_ios/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_ios/main.cpp new file mode 100644 index 00000000..e2341ac4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/main_ios/main.cpp @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_header_fwd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_header_fwd.cpp new file mode 100644 index 00000000..24185ff0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_header_fwd.cpp @@ -0,0 +1,25 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_parser.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_parser.cpp new file mode 100644 index 00000000..7b286842 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_parser.cpp @@ -0,0 +1,666 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace sjson; + +static Parser parser_from_c_str(const char* c_str) +{ + return Parser(c_str, std::strlen(c_str)); +} + +TEST_CASE("Parser Misc", "[parser]") +{ + { + Parser parser = parser_from_c_str(""); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str(""); + CHECK(parser.remainder_is_comments_and_whitespace()); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str(" "); + CHECK(parser.remainder_is_comments_and_whitespace()); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("// lol \\n "); + CHECK(parser.remainder_is_comments_and_whitespace()); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("\"key-one\" = true"); + bool value = false; + CHECK(parser.read("key-one", value)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = /* bar */ true"); + bool value = false; + CHECK(parser.read("key", value)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = /* bar * true"); + bool value = false; + CHECK_FALSE(parser.read("key", value)); + CHECK_FALSE(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = // bar \ntrue"); + bool value = false; + CHECK(parser.read("key", value)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key /* bar */ = true"); + bool value = false; + CHECK(parser.read("key", value)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("/* bar */ key = true"); + bool value = false; + CHECK(parser.read("key", value)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } +} + +TEST_CASE("Parser Bool Reading", "[parser]") +{ + { + Parser parser = parser_from_c_str("key = true"); + bool value = false; + CHECK(parser.read("key", value)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = false"); + bool value = true; + CHECK(parser.read("key", value)); + CHECK(value == false); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = 0"); + bool value = true; + CHECK_FALSE(parser.try_read("key", value, false)); + CHECK(value == false); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = true"); + bool value = false; + CHECK(parser.try_read("key", value, false)); + CHECK(value == true); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } +} + +TEST_CASE("Parser String Reading", "[parser]") +{ + { + Parser parser = parser_from_c_str("key = \"Quoted string\""); + StringView value; + CHECK(parser.read("key", value)); + CHECK(value == "Quoted string"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + // Note: Escaped quotes \" are left escaped within the StringView because we do not allocate memory + Parser parser = parser_from_c_str("key = \"Quoted \\\" string\""); + StringView value; + CHECK(parser.read("key", value)); + CHECK(value == "Quoted \\\" string"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"New\\nline\""); + StringView value; + CHECK(parser.read("key", value)); + CHECK(value == "New\\nline"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"Tab\\tulator\""); + StringView value; + CHECK(parser.read("key", value)); + CHECK(value == "Tab\\tulator"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"Tab\\tulator\""); + StringView value; + CHECK(parser.read("key", value)); + CHECK(value == "Tab\\tulator"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = 0"); + StringView value; + CHECK_FALSE(parser.try_read("key", value, "default")); + CHECK(value == "default"); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"good\""); + StringView value; + CHECK(parser.try_read("key", value, "default")); + CHECK(value == "good"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"bad"); + StringView value; + CHECK_FALSE(parser.read("key", value)); + CHECK_FALSE(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = bad"); + StringView value; + CHECK_FALSE(parser.read("key", value)); + CHECK_FALSE(parser.is_valid()); + } +} + +TEST_CASE("Parser Number Reading", "[parser]") +{ + // Number reading + { + Parser parser = parser_from_c_str("key = 123.456789"); + double value = 0.0; + CHECK(parser.read("key", value)); + CHECK(value == 123.456789); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"nan\""); + double value = 0.0; + CHECK(parser.read("key", value)); + CHECK(std::isnan(value)); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"inf\""); + double value = 0.0; + CHECK(parser.read("key", value)); + CHECK(std::isinf(value)); + CHECK(value > 0.0); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"-inf\""); + double value = 0.0; + CHECK(parser.read("key", value)); + CHECK(std::isinf(value)); + CHECK(value < 0.0); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 123.456789"); + float value = 0.0F; + CHECK(parser.read("key", value)); + CHECK(value == 123.456789F); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"nan\""); + float value = 0.0F; + CHECK(parser.read("key", value)); + CHECK(std::isnan(value)); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"inf\""); + float value = 0.0F; + CHECK(parser.read("key", value)); + CHECK(std::isinf(value)); + CHECK(value > 0.0F); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = \"-inf\""); + float value = 0.0F; + CHECK(parser.read("key", value)); + CHECK(std::isinf(value)); + CHECK(value < 0.0F); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -123"); + int8_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == -123); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 123"); + uint8_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == 123); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -1234"); + int16_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == -1234); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 1234"); + uint16_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == 1234); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -123456"); + int32_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == -123456); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 123456"); + uint32_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == 123456); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -1234567890123456"); + int64_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == -1234567890123456LL); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 1234567890123456"); + uint64_t value = 0; + CHECK(parser.read("key", value)); + CHECK(value == 1234567890123456ULL); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + // Number try reading + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + double value = 0.0; + CHECK_FALSE(parser.try_read("key", value, 1.0)); + CHECK(value == 1.0); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 2.0"); + double value = 0.0; + CHECK(parser.try_read("key", value, 1.0)); + CHECK(value == 2.0); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + float value = 0.0F; + CHECK_FALSE(parser.try_read("key", value, 1.0F)); + CHECK(value == 1.0F); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 2.0"); + float value = 0.0F; + CHECK(parser.try_read("key", value, 1.0F)); + CHECK(value == 2.0F); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + int8_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -123"); + int8_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == -123); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + uint8_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 123"); + uint8_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == 123); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + int16_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -1234"); + int16_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == -1234); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + uint16_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 1234"); + uint16_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == 1234); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + int32_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -123456"); + int32_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == -123456); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + uint32_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 123456"); + uint32_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == 123456); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + int64_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = -1234567890123456"); + int64_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == -1234567890123456LL); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + uint64_t value = 0; + CHECK_FALSE(parser.try_read("key", value, 1)); + CHECK(value == 1); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = 1234567890123456"); + uint64_t value = 0; + CHECK(parser.try_read("key", value, 1)); + CHECK(value == 1234567890123456ULL); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } +} + +TEST_CASE("Parser Array Reading", "[parser]") +{ + { + Parser parser = parser_from_c_str("key = [ 123.456789, 456.789, 151.091 ]"); + double value[3] = { 0.0, 0.0, 0.0 }; + CHECK(parser.read("key", value, 3)); + CHECK(value[0] == 123.456789); + CHECK(value[1] == 456.789); + CHECK(value[2] == 151.091); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = [ \"123.456789\", \"456.789\", \"151.091\" ]"); + StringView value[3]; + CHECK(parser.read("key", value, 3)); + CHECK(value[0] == "123.456789"); + CHECK(value[1] == "456.789"); + CHECK(value[2] == "151.091"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + double value[3] = { 0.0, 0.0, 0.0 }; + CHECK_FALSE(parser.try_read("key", value, 3, 1.0)); + CHECK(value[0] == 1.0); + CHECK(value[1] == 1.0); + CHECK(value[2] == 1.0); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = [ 123.456789, 456.789, 151.091 ]"); + double value[3] = { 0.0, 0.0, 0.0 }; + CHECK(parser.try_read("key", value, 3, 1.0)); + CHECK(value[0] == 123.456789); + CHECK(value[1] == 456.789); + CHECK(value[2] == 151.091); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("bad_key = \"bad\""); + StringView value[3]; + CHECK_FALSE(parser.try_read("key", value, 3, "default")); + CHECK(value[0] == "default"); + CHECK(value[1] == "default"); + CHECK(value[2] == "default"); + CHECK_FALSE(parser.eof()); + CHECK(parser.is_valid()); + } + + { + Parser parser = parser_from_c_str("key = [ \"123.456789\", \"456.789\", \"151.091\" ]"); + StringView value[3]; + CHECK(parser.try_read("key", value, 3, "default")); + CHECK(value[0] == "123.456789"); + CHECK(value[1] == "456.789"); + CHECK(value[2] == "151.091"); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } + +#if 0 + { + Parser parser = parser_from_c_str("key = [ 123.456789, \"456.789\", false, [ 1.0, true ], { key0 = 1.0, key1 = false } ]"); + + CHECK(parser.array_begins("key")); + // TODO + CHECK(parser.array_ends()); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } +#endif +} + +TEST_CASE("Parser Null Reading", "[parser]") +{ + { + Parser parser = parser_from_c_str("key = null"); + bool value_bool = false; + CHECK_FALSE(parser.try_read("key", value_bool, true)); + CHECK(value_bool == true); + double value_dbl = 0.0; + CHECK_FALSE(parser.try_read("key", value_dbl, 1.0)); + CHECK(value_dbl == 1.0); + CHECK(parser.eof()); + CHECK(parser.is_valid()); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_string_view.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_string_view.cpp new file mode 100644 index 00000000..93463208 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_string_view.cpp @@ -0,0 +1,74 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include + +using namespace sjson; + +TEST_CASE("StringView", "[string]") +{ + CHECK(StringView() == StringView("")); + CHECK(StringView() == ""); + CHECK(StringView().size() == 0); + CHECK(StringView().c_str() != nullptr); + CHECK(StringView("").size() == 0); + CHECK(StringView("").c_str() != nullptr); + + const char* str0 = "this is a test string"; + const char* str1 = "this is not a test string"; + const char* str2 = "this is a test asset!"; + + CHECK(StringView(str0) == str0); + CHECK(StringView(str0) != str1); + CHECK(StringView(str0) != str2); + CHECK(StringView(str0) == StringView(str0)); + CHECK(StringView(str0) != StringView(str1)); + CHECK(StringView(str0) != StringView(str2)); + CHECK(StringView(str0).c_str() == str0); + CHECK(StringView(str0).size() == std::strlen(str0)); + CHECK(StringView(str0, 4) == StringView(str1, 4)); + CHECK(StringView(str0, 4) == StringView("this")); + + StringView view0(str0); + CHECK(view0 == str0); + view0 = str1; + CHECK(view0 == str1); + + CHECK(StringView().empty() == true); + CHECK(StringView("").empty() == true); + CHECK(view0.empty() == false); + + CHECK(view0[0] == 't'); + CHECK(view0[1] == 'h'); + CHECK(view0[2] == 'i'); + CHECK(view0[3] == 's'); + CHECK(view0[4] == ' '); + CHECK(view0[5] == 'i'); + CHECK(view0[view0.size() - 1] == 'g'); + CHECK_THROWS(view0[view0.size()]); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_writer.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_writer.cpp new file mode 100644 index 00000000..936a0ab2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tests/sources/test_writer.cpp @@ -0,0 +1,756 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette, Cody Jones, and sjson-cpp contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include +#include + +using namespace sjson; + +class StringStreamWriter final : public StreamWriter +{ +public: + StringStreamWriter() = default; + + virtual void write(const void* buffer, size_t buffer_size) override + { + m_buffer.sputn(reinterpret_cast(buffer), buffer_size); + } + + std::string str() const { return m_buffer.str(); } + +private: + std::stringbuf m_buffer; +}; + +TEST_CASE("Writer Object Bool Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", true); + CHECK(str_writer.str() == "key = true\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", false); + CHECK(str_writer.str() == "key = false\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = true; + CHECK(str_writer.str() == "key = true\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = false; + CHECK(str_writer.str() == "key = false\r\n"); + } +} + +TEST_CASE("Writer Object String Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", "some string"); + CHECK(str_writer.str() == "key = \"some string\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = "some string"; + CHECK(str_writer.str() == "key = \"some string\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", "some\tstring"); + CHECK(str_writer.str() == "key = \"some\tstring\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = "some\tstring"; + CHECK(str_writer.str() == "key = \"some\tstring\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", "some\nstring"); + CHECK(str_writer.str() == "key = \"some\nstring\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = "some\nstring"; + CHECK(str_writer.str() == "key = \"some\nstring\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", "some\"string"); + CHECK(str_writer.str() == "key = \"some\"string\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = "some\"string"; + CHECK(str_writer.str() == "key = \"some\"string\"\r\n"); + } +} + +TEST_CASE("Writer Object Number Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", 123.0); + CHECK(str_writer.str() == "key = 123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = 123.0; + CHECK(str_writer.str() == "key = 123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", 123.456); + CHECK(str_writer.str() == "key = 123.456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = 123.456; + CHECK(str_writer.str() == "key = 123.456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", std::nan("")); + CHECK(str_writer.str() == "key = \"nan\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = std::nan(""); + CHECK(str_writer.str() == "key = \"nan\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", std::numeric_limits::infinity()); + CHECK(str_writer.str() == "key = \"inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = std::numeric_limits::infinity(); + CHECK(str_writer.str() == "key = \"inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", -std::numeric_limits::infinity()); + CHECK(str_writer.str() == "key = \"-inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = -std::numeric_limits::infinity(); + CHECK(str_writer.str() == "key = \"-inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", 123.0F); + CHECK(str_writer.str() == "key = 123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = 123.0F; + CHECK(str_writer.str() == "key = 123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", 123.5F); + CHECK(str_writer.str() == "key = 123.5\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = 123.5F; + CHECK(str_writer.str() == "key = 123.5\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", std::nanf("")); + CHECK(str_writer.str() == "key = \"nan\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = std::nanf(""); + CHECK(str_writer.str() == "key = \"nan\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", std::numeric_limits::infinity()); + CHECK(str_writer.str() == "key = \"inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = std::numeric_limits::infinity(); + CHECK(str_writer.str() == "key = \"inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", -std::numeric_limits::infinity()); + CHECK(str_writer.str() == "key = \"-inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = -std::numeric_limits::infinity(); + CHECK(str_writer.str() == "key = \"-inf\"\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int8_t value = -123; + writer.insert("key", value); + CHECK(str_writer.str() == "key = -123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int8_t value = -123; + writer["key"] = value; + CHECK(str_writer.str() == "key = -123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint8_t value = 123; + writer.insert("key", value); + CHECK(str_writer.str() == "key = 123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint8_t value = 123; + writer["key"] = value; + CHECK(str_writer.str() == "key = 123\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int16_t value = -1234; + writer.insert("key", value); + CHECK(str_writer.str() == "key = -1234\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int16_t value = -1234; + writer["key"] = value; + CHECK(str_writer.str() == "key = -1234\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint16_t value = 1234; + writer.insert("key", value); + CHECK(str_writer.str() == "key = 1234\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint16_t value = 1234; + writer["key"] = value; + CHECK(str_writer.str() == "key = 1234\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int32_t value = -123456; + writer.insert("key", value); + CHECK(str_writer.str() == "key = -123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int32_t value = -123456; + writer["key"] = value; + CHECK(str_writer.str() == "key = -123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint32_t value = 123456; + writer.insert("key", value); + CHECK(str_writer.str() == "key = 123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint32_t value = 123456; + writer["key"] = value; + CHECK(str_writer.str() == "key = 123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int64_t value = -1234567890123456LL; + writer.insert("key", value); + CHECK(str_writer.str() == "key = -1234567890123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + int64_t value = -1234567890123456LL; + writer["key"] = value; + CHECK(str_writer.str() == "key = -1234567890123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint64_t value = 1234567890123456ULL; + writer.insert("key", value); + CHECK(str_writer.str() == "key = 1234567890123456\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + uint64_t value = 1234567890123456ULL; + writer["key"] = value; + CHECK(str_writer.str() == "key = 1234567890123456\r\n"); + } +} + +TEST_CASE("Writer Object Array Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& /*array_writer*/) + { + }); + CHECK(str_writer.str() == "key = [ ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(123.5); + array_writer.push(456.5); + }); + CHECK(str_writer.str() == "key = [ 123.5, 456.5 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = [](ArrayWriter& array_writer) + { + array_writer.push(123.5); + array_writer.push(456.5); + }; + CHECK(str_writer.str() == "key = [ 123.5, 456.5 ]\r\n"); + } +} + +TEST_CASE("Writer Object Object Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ObjectWriter& /*object_writer*/) + { + }); + CHECK(str_writer.str() == "key = {\r\n}\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ObjectWriter& object_writer) + { + object_writer["key0"] = 123.5; + object_writer["key1"] = 456.5; + }); + CHECK(str_writer.str() == "key = {\r\n\tkey0 = 123.5\r\n\tkey1 = 456.5\r\n}\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer["key"] = [](ObjectWriter& object_writer) + { + object_writer["key0"] = 123.5; + object_writer["key1"] = 456.5; + }; + CHECK(str_writer.str() == "key = {\r\n\tkey0 = 123.5\r\n\tkey1 = 456.5\r\n}\r\n"); + } +} + +TEST_CASE("Writer Array Bool Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(true); + }); + CHECK(str_writer.str() == "key = [ true ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(false); + }); + CHECK(str_writer.str() == "key = [ false ]\r\n"); + } +} + +TEST_CASE("Writer Array String Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push("some string"); + }); + CHECK(str_writer.str() == "key = [ \"some string\" ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push("some\tstring"); + }); + CHECK(str_writer.str() == "key = [ \"some\tstring\" ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push("some\nstring"); + }); + CHECK(str_writer.str() == "key = [ \"some\nstring\" ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push("some\"string"); + }); + CHECK(str_writer.str() == "key = [ \"some\"string\" ]\r\n"); + } +} + +TEST_CASE("Writer Array Number Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(123.0); + }); + CHECK(str_writer.str() == "key = [ 123 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(123.456); + }); + CHECK(str_writer.str() == "key = [ 123.456 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(std::nan("")); + array_writer.push(std::numeric_limits::infinity()); + array_writer.push(-std::numeric_limits::infinity()); + }); + CHECK(str_writer.str() == "key = [ \"nan\", \"inf\", \"-inf\" ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(123.0F); + }); + CHECK(str_writer.str() == "key = [ 123 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(123.5F); + }); + CHECK(str_writer.str() == "key = [ 123.5 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push(std::nanf("")); + array_writer.push(std::numeric_limits::infinity()); + array_writer.push(-std::numeric_limits::infinity()); + }); + CHECK(str_writer.str() == "key = [ \"nan\", \"inf\", \"-inf\" ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + int8_t value = -123; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ -123 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + uint8_t value = 123; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ 123 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + int16_t value = -1234; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ -1234 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + uint16_t value = 1234; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ 1234 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + int32_t value = -123456; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ -123456 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + uint32_t value = 123456; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ 123456 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + int64_t value = -1234567890123456LL; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ -1234567890123456 ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + uint64_t value = 1234567890123456ULL; + array_writer.push(value); + }); + CHECK(str_writer.str() == "key = [ 1234567890123456 ]\r\n"); + } +} + +TEST_CASE("Writer Array Array Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push([](ArrayWriter& /*array_writer*/) + { + }); + }); + CHECK(str_writer.str() == "key = [ [ ] ]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push([](ArrayWriter& array_writer1) + { + array_writer1.push(123.5); + array_writer1.push(456.5); + }); + }); + CHECK(str_writer.str() == "key = [ [ 123.5, 456.5 ] ]\r\n"); + } +} + +TEST_CASE("Writer Array Object Writing", "[writer]") +{ + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push([](ObjectWriter& /*object_writer*/) + { + }); + }); + CHECK(str_writer.str() == "key = [ \r\n\t{\r\n\t}\r\n]\r\n"); + } + + { + StringStreamWriter str_writer; + Writer writer(str_writer); + writer.insert("key", [](ArrayWriter& array_writer) + { + array_writer.push([](ObjectWriter& object_writer1) + { + object_writer1["key0"] = 123.5; + object_writer1["key1"] = 456.5; + }); + }); + CHECK(str_writer.str() == "key = [ \r\n\t{\r\n\t\tkey0 = 123.5\r\n\t\tkey1 = 456.5\r\n\t}\r\n]\r\n"); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/appveyor_ci.bat b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/appveyor_ci.bat new file mode 100644 index 00000000..179866bf --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/appveyor_ci.bat @@ -0,0 +1,57 @@ +@echo off + +REM Unpack arguments +SET WORKER_IMAGE=%1 +SET PLATFORM=%2 +SET CONFIG=%3 +SET TOOLCHAIN=%4 +SET PYTHON_PATH=%5 + +echo Worker image: %WORKER_IMAGE% +echo Platform: %PLATFORM% +echo Config: %CONFIG% +echo Toolchain: %TOOLCHAIN% +echo Python path: %PYTHON_PATH% + +REM Convert the build image and toolchain into our compiler string +IF /i %TOOLCHAIN%==msvc GOTO :msvc +IF /i %TOOLCHAIN%==clang GOTO :clang + +echo Unknown toolchain: %TOOLCHAIN% +exit /B 1 + +:msvc +IF /i %WORKER_IMAGE%=="Visual Studio 2015" SET COMPILER=vs2015 +IF /i %WORKER_IMAGE%=="Visual Studio 2017" SET COMPILER=vs2017 +IF /i %WORKER_IMAGE%=="Visual Studio 2019" SET COMPILER=vs2019 +IF /i %WORKER_IMAGE%=="Previous Visual Studio 2019" SET COMPILER=vs2019 +GOTO :next + +:clang +IF /i %WORKER_IMAGE%=="Visual Studio 2019" SET COMPILER=vs2019-clang +IF /i %WORKER_IMAGE%=="Previous Visual Studio 2019" SET COMPILER=vs2019-clang + +REM HACK!!! Disable clang build for now with appveyor since vcpkg breaks the compiler detection of cmake +REM Fake build success +exit /B 0 + +GOTO :next + +:next +REM Set our switch if we need to run unit tests +SET UNIT_TEST_FLAG=-unit_test +IF /i %PLATFORM%==arm64 SET UNIT_TEST_FLAG= + +REM If PYTHON_PATH isn't set, assume it is in PATH +IF NOT DEFINED PYTHON_PATH SET PYTHON_PATH=python.exe + +REM Build and run unit tests +%PYTHON_PATH% make.py -build %UNIT_TEST_FLAG% -compiler %COMPILER% -config %CONFIG% -cpu %PLATFORM% -clean +IF NOT %ERRORLEVEL% EQU 0 GOTO :build_failure + +REM Done! +exit /B 0 + +:build_failure +echo Build failed! +exit /B 1 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/release_scripts/test_everything.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/release_scripts/test_everything.py new file mode 100644 index 00000000..abd36d9d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/release_scripts/test_everything.py @@ -0,0 +1,114 @@ +import os +import platform +import shutil +import subprocess +import sys + +def get_platform_compilers(): + if platform.system() == 'Windows': + return [ 'vs2015', 'vs2017', 'vs2019', 'vs2019-clang' ] + elif platform.system() == 'Linux': + compilers = [] + if shutil.which('g++-5'): + compilers.append('gcc5') + if shutil.which('g++-6'): + compilers.append('gcc6') + if shutil.which('g++-7'): + compilers.append('gcc7') + if shutil.which('g++-8'): + compilers.append('gcc8') + if shutil.which('g++-9'): + compilers.append('gcc9') + if shutil.which('g++-10'): + compilers.append('gcc10') + + if shutil.which('clang++-4.0'): + compilers.append('clang4') + if shutil.which('clang++-5.0'): + compilers.append('clang5') + if shutil.which('clang++-6.0'): + compilers.append('clang6') + if shutil.which('clang++-7'): + compilers.append('clang7') + if shutil.which('clang++-8'): + compilers.append('clang8') + if shutil.which('clang++-9'): + compilers.append('clang9') + if shutil.which('clang++-10'): + compilers.append('clang10') + if shutil.which('clang++-11'): + compilers.append('clang11') + + return compilers + elif platform.system() == 'Darwin': + return [ 'osx' ] + else: + print('Unknown platform!') + sys.exit(1) + +def get_python_exe_name(): + if platform.system() == 'Windows': + return 'python' + else: + return 'python3' + +if __name__ == "__main__": + os.environ['PYTHONIOENCODING'] = 'utf_8' + + configs = [ 'debug', 'release' ] + archs = [ 'x86', 'x64' ] + compilers = get_platform_compilers() + python_exe = get_python_exe_name() + + if platform.system() == 'Darwin': + result = subprocess.check_output(['xcodebuild', '-version']).decode("utf-8") + if 'Xcode 11' in result: + archs.remove('x86') + + cmd_args = [] + for config in configs: + for arch in archs: + for compiler in compilers: + args = [python_exe, 'make.py', '-compiler', compiler, '-cpu', arch, '-config', config, '-build', '-unit_test', '-clean'] + cmd_args.append([x for x in args if x]) + + if platform.system() == 'Windows': + for config in configs: + # Windows ARM + args = [python_exe, 'make.py', '-compiler', 'vs2017', '-cpu', 'arm64', '-config', config, '-build', '-clean'] + cmd_args.append([x for x in args if x]) + + # Android + args = [python_exe, 'make.py', '-compiler', 'android', '-cpu', 'armv7', '-config', config, '-build', '-clean'] + cmd_args.append([x for x in args if x]) + args = [python_exe, 'make.py', '-compiler', 'android', '-cpu', 'arm64', '-config', config, '-build', '-clean'] + cmd_args.append([x for x in args if x]) + elif platform.system() == 'Darwin': + for config in configs: + # iOS + args = [python_exe, 'make.py', '-compiler', 'ios', '-config', config, '-build', '-clean'] + cmd_args.append([x for x in args if x]) + + if platform.system() == 'Darwin' or platform.system() == 'Linux': + for config in configs: + # Emscripten + args = [python_exe, 'make.py', '-compiler', 'emscripten', '-config', config, '-build', '-unit_test', '-clean'] + cmd_args.append([x for x in args if x]) + + root_dir = os.path.join(os.getcwd(), '../..') + os.chdir(root_dir) + + for args in cmd_args: + print('Running command: "{}" ...'.format(" ".join(args))) + try: + if 'android' in args: + subprocess.check_call(args) + else: + subprocess.check_output(args) + except subprocess.CalledProcessError as e: + print('Failed command: {}'.format(" ".join(args))) + print(e.output.decode(sys.stdout.encoding)) + sys.exit(1) + + print('Done!') + sys.exit(0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/setup_linux_compiler.sh b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/setup_linux_compiler.sh new file mode 100644 index 00000000..621bfda7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/setup_linux_compiler.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# Extract our command line arguments +COMPILER=$1 + +# Convert our GCC compiler into a list of packages it needs +if [[ $COMPILER == gcc5 ]]; then + PACKAGES="g++-5 g++-5-multilib g++-multilib" +elif [[ $COMPILER == gcc6 ]]; then + PACKAGES="g++-6 g++-6-multilib g++-multilib" +elif [[ $COMPILER == gcc7 ]]; then + PACKAGES="g++-7 g++-7-multilib g++-multilib" +elif [[ $COMPILER == gcc8 ]]; then + PACKAGES="g++-8 g++-8-multilib g++-multilib" +elif [[ $COMPILER == gcc9 ]]; then + PACKAGES="g++-9 g++-9-multilib g++-multilib" +elif [[ $COMPILER == gcc10 ]]; then + PACKAGES="g++-10 g++-10-multilib g++-multilib" +fi + +# If using clang, add our apt source key +if [[ $COMPILER == clang* ]]; then + curl -sSL "http://apt.llvm.org/llvm-snapshot.gpg.key" | sudo -E apt-key add - ; +fi + +# Convert our clang compiler into a list of packages it needs and its source +if [[ $COMPILER == clang4 ]]; then + # clang4 isn't available after xenial + PACKAGES="clang-4.0 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang5 ]]; then + PACKAGES="clang-5.0 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-5.0 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang6 ]]; then + PACKAGES="clang-6.0 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-6.0 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang7 ]]; then + PACKAGES="clang-7 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-7 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang8 ]]; then + PACKAGES="clang-8 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang9 ]]; then + PACKAGES="clang-9 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang10 ]]; then + PACKAGES="clang-10 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +elif [[ $COMPILER == clang11 ]]; then + PACKAGES="clang-11 libstdc++-5-dev libc6-dev-i386 g++-5-multilib g++-multilib" + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main" | sudo tee -a /etc/apt/sources.list > /dev/null ; +fi + +# Install the packages we need +sudo -E apt-add-repository -y "ppa:ubuntu-toolchain-r/test"; +sudo -E apt-get -yq update; +sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install $PACKAGES; diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/setup_osx_compiler.sh b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/setup_osx_compiler.sh new file mode 100644 index 00000000..dd98a3cb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/setup_osx_compiler.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Extract our command line arguments +COMPILER=$1 + +# Convert our compiler string into our XCode path +# Paths must match Github Action virtual images +if [[ $COMPILER == xcode10 ]]; then + XCODE_PATH="/Applications/Xcode_10.3.app" +elif [[ $COMPILER == xcode11 ]]; then + XCODE_PATH="/Applications/Xcode_11.7.app" +elif [[ $COMPILER == xcode12 ]]; then + XCODE_PATH="/Applications/Xcode_12.5.1.app" +elif [[ $COMPILER == xcode13 ]]; then + XCODE_PATH="/Applications/Xcode_13.2.1.app" +fi + +# Select our XCode version +sudo xcode-select -s $XCODE_PATH/Contents/Developer; diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/vs_visualizers/sjson-cpp.natvis b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/vs_visualizers/sjson-cpp.natvis new file mode 100644 index 00000000..094dc49c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/external/sjson-cpp/tools/vs_visualizers/sjson-cpp.natvis @@ -0,0 +1,9 @@ + + + + + + {m_c_str,[m_length]s8} + + + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compress.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compress.h new file mode 100644 index 00000000..a1bf48c7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compress.h @@ -0,0 +1,134 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_database.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/output_stats.h" +#include "acl/compression/track_array.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Compresses a track array with uniform sampling. + // + // This compression algorithm is the simplest by far and as such it offers + // the fastest compression and decompression. Every sample is retained and + // every track has the same number of samples playing back at the same + // sample rate. This means that when we sample at a particular time within + // the clip, we can trivially calculate the offsets required to read the + // desired data. All the data is sorted in order to ensure all reads are + // as contiguous as possible for optimal cache locality during decompression. + // + // allocator: The allocator instance to use to allocate and free memory. + // track_list: The track list to compress. + // settings: The compression settings to use. + // out_compressed_tracks: The resulting compressed tracks. The caller owns the returned memory and must free it. + // out_stats: Stat output structure. + ////////////////////////////////////////////////////////////////////////// + error_result compress_track_list(iallocator& allocator, const track_array& track_list, const compression_settings& settings, + compressed_tracks*& out_compressed_tracks, output_stats& out_stats); + + ////////////////////////////////////////////////////////////////////////// + // Compresses a transform track array using its additive base with uniform sampling. + // + // This compression algorithm is the simplest by far and as such it offers + // the fastest compression and decompression. Every sample is retained and + // every track has the same number of samples playing back at the same + // sample rate. This means that when we sample at a particular time within + // the clip, we can trivially calculate the offsets required to read the + // desired data. All the data is sorted in order to ensure all reads are + // as contiguous as possible for optimal cache locality during decompression. + // + // allocator: The allocator instance to use to allocate and free memory. + // track_list: The track list to compress. + // settings: The compression settings to use. + // out_compressed_tracks: The resulting compressed tracks. The caller owns the returned memory and must free it. + // out_stats: Stat output structure. + ////////////////////////////////////////////////////////////////////////// + error_result compress_track_list(iallocator& allocator, const track_array_qvvf& track_list, const compression_settings& settings, + const track_array_qvvf& additive_base_track_list, additive_clip_format8 additive_format, + compressed_tracks*& out_compressed_tracks, output_stats& out_stats); + + ////////////////////////////////////////////////////////////////////////// + // Takes a list of compressed track instances that contain the contributing error metadata and uses their data to build + // a new database instance. Each compressed track instance will be duplicated and split between a new instance and the + // database. + // + // allocator: The allocator instance to use + // settings: The settings to use when creating the database + // compressed_tracks_list: The list of compressed tracks to build the database with (must have contributing error metadata) + // num_compressed_tracks: The number of compressed track instances in the above list + // out_compressed_tracks: The output list of compressed tracks bound to the output database (array allocated by the caller (must be large enough); compressed_tracks instances allocated by the function) + // out_database: The output database (allocated by the function) + ////////////////////////////////////////////////////////////////////////// + error_result build_database(iallocator& allocator, const compression_database_settings& settings, + const compressed_tracks* const* compressed_tracks_list, uint32_t num_compressed_tracks, + compressed_tracks** out_compressed_tracks, compressed_database*& out_database); + + ////////////////////////////////////////////////////////////////////////// + // Takes a compressed database with inline bulk data and duplicates it into + // a new database instance where the bulk data lives in separate buffers. + // + // allocator: The allocator instance to use to allocate the new database and its bulk data. + // database: The source database to split with inline bulk data. + // out_split_database: The new database without inline bulk data. + // out_bulk_data_medium: The new database's bulk data for the medium importance tier. + // out_bulk_data_low: The new database's bulk data for the low importance tier. + ////////////////////////////////////////////////////////////////////////// + error_result split_database_bulk_data(iallocator& allocator, const compressed_database& database, compressed_database*& out_split_database, uint8_t*& out_bulk_data_medium, uint8_t*& out_bulk_data_low); + + ////////////////////////////////////////////////////////////////////////// + // Takes a compressed database and strips the specified quality tier from it. + // The database is duplicated including its remaining bulk data (if inline). + // Only the medium/lowest tiers can be stripped. + // + // allocator: The allocator instance to use to allocate the new database. + // database: The source database to strip. + // tier: The quality tier to strip. + // out_stripped_database: The new database without the specified quality tier. + ////////////////////////////////////////////////////////////////////////// + error_result strip_database_quality_tier(iallocator& allocator, const compressed_database& database, quality_tier tier, compressed_database*& out_stripped_database); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/compression/impl/compress.database.impl.h" +#include "acl/compression/impl/compress.scalar.impl.h" +#include "acl/compression/impl/compress.transform.impl.h" +#include "acl/compression/impl/compress.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compression_level.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compression_level.h new file mode 100644 index 00000000..972fd28d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compression_level.h @@ -0,0 +1,132 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // compression_level8 represents how aggressively we attempt to reduce the memory + // footprint. Higher levels will try more permutations and bit rates. The higher + // the level, the slower the compression but the smaller the memory footprint. + enum class compression_level8 : uint8_t + { + lowest = 0, // Same as medium for now + low = 1, // Same as medium for now + medium = 2, + high = 3, + highest = 4, + + //lossless = 255, // Not implemented, reserved + }; + + ////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // Returns a string representing the compression level. + // TODO: constexpr + inline const char* get_compression_level_name(compression_level8 level) + { + switch (level) + { + case compression_level8::lowest: return "lowest"; + case compression_level8::low: return "low"; + case compression_level8::medium: return "medium"; + case compression_level8::high: return "high"; + case compression_level8::highest: return "highest"; + default: return ""; + } + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the compression level from its string representation. + inline bool get_compression_level(const char* level_name, compression_level8& out_level) + { + ACL_ASSERT(level_name != nullptr, "Level name cannot be null"); + if (level_name == nullptr) + return false; + + const char* level_lowest = "Lowest"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* level_lowest_new = "lowest"; + if (std::strncmp(level_name, level_lowest, std::strlen(level_lowest)) == 0 + || std::strncmp(level_name, level_lowest_new, std::strlen(level_lowest_new)) == 0) + { + out_level = compression_level8::lowest; + return true; + } + + const char* level_low = "Low"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* level_low_new = "low"; + if (std::strncmp(level_name, level_low, std::strlen(level_low)) == 0 + || std::strncmp(level_name, level_low_new, std::strlen(level_low_new)) == 0) + { + out_level = compression_level8::low; + return true; + } + + const char* level_medium = "Medium"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* level_medium_new = "medium"; + if (std::strncmp(level_name, level_medium, std::strlen(level_medium)) == 0 + || std::strncmp(level_name, level_medium_new, std::strlen(level_medium_new)) == 0) + { + out_level = compression_level8::medium; + return true; + } + + const char* level_highest = "Highest"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* level_highest_new = "highest"; + if (std::strncmp(level_name, level_highest, std::strlen(level_highest)) == 0 + || std::strncmp(level_name, level_highest_new, std::strlen(level_highest_new)) == 0) + { + out_level = compression_level8::highest; + return true; + } + + const char* level_high = "High"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* level_high_new = "high"; + if (std::strncmp(level_name, level_high, std::strlen(level_high)) == 0 + || std::strncmp(level_name, level_high_new, std::strlen(level_high_new)) == 0) + { + out_level = compression_level8::high; + return true; + } + + return false; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compression_settings.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compression_settings.h new file mode 100644 index 00000000..d61e637d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/compression_settings.h @@ -0,0 +1,194 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error_result.h" +#include "acl/core/hash.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/core/range_reduction_types.h" +#include "acl/compression/compression_level.h" +#include "acl/compression/transform_error_metrics.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Encapsulates all the compression settings related to database usage. + struct compression_database_settings + { + ////////////////////////////////////////////////////////////////////////// + // What proportions we should use when distributing our frames based on + // their importance to the overall error contribution. If a sample doesn't + // go into the medium or low importance tiers, it will end up in the high + // importance tier stored within each compressed track instance. + // Proportion values must be between 0.0 and 1.0 and their sum as well. + // If the sum is less than 1.0, remaining frames are considered to have high + // importance. A low importance proportion of 30% means that the least important + // 30% of frames will end up in that corresponding database tier. + // Note that only movable frames can end up in the database as some frames must remain + // within the compressed track instance. A frame is movable if it isn't the first or last + // frame of its segment. + // Defaults to '0.0' (the medium importance tier is empty) + float medium_importance_tier_proportion = 0.0F; + + ////////////////////////////////////////////////////////////////////////// + // See above for details. + // Defaults to '0.5' (the least important 50% of frames are moved to the database) + float low_importance_tier_proportion = 0.5F; + + ////////////////////////////////////////////////////////////////////////// + // How large should each chunk be, in bytes. + // This value must be at least 4 KB and ideally it should be a multiple of + // the virtual memory page size used on the platform that will decompress + // from the database. + // Defaults to '1 MB' + uint32_t max_chunk_size = 1 * 1024 * 1024; + + ////////////////////////////////////////////////////////////////////////// + // Calculates a hash from the internal state to uniquely identify a configuration. + uint32_t get_hash() const; + + ////////////////////////////////////////////////////////////////////////// + // Checks if everything is valid and if it isn't, returns an error string. + // Returns nullptr if the settings are valid. + error_result is_valid() const; + }; + + ////////////////////////////////////////////////////////////////////////// + // Encapsulates all the optional metadata compression settings. + struct compression_metadata_settings + { + ////////////////////////////////////////////////////////////////////////// + // Whether to include the optional metadata for the track list name + // Defaults to 'false' + bool include_track_list_name = false; + + ////////////////////////////////////////////////////////////////////////// + // Whether to include the optional metadata for track names + // Defaults to 'false' + bool include_track_names = false; + + ////////////////////////////////////////////////////////////////////////// + // Whether to include the optional metadata for parent track indices + // Transform tracks only + // Defaults to 'false' + bool include_parent_track_indices = false; + + ////////////////////////////////////////////////////////////////////////// + // Whether to include the optional metadata for track descriptions + // For transforms, also enables the parent track indices metadata + // Defaults to 'false' + bool include_track_descriptions = false; + + ////////////////////////////////////////////////////////////////////////// + // Whether to include the optional metadata for the contributing error + // of each frame. These are sorted from lowest to largest error. + // This is required when the compressed tracks will later be merged into + // a database. + // Transform tracks only + // Defaults to 'false' + bool include_contributing_error = false; + + ////////////////////////////////////////////////////////////////////////// + // Calculates a hash from the internal state to uniquely identify a configuration. + uint32_t get_hash() const; + + ////////////////////////////////////////////////////////////////////////// + // Checks if everything is valid and if it isn't, returns an error string. + // Returns nullptr if the settings are valid. + error_result is_valid() const; + }; + + ////////////////////////////////////////////////////////////////////////// + // Encapsulates all the compression settings. + struct compression_settings + { + ////////////////////////////////////////////////////////////////////////// + // The compression level determines how aggressively we attempt to reduce the memory + // footprint. Higher levels will try more permutations and bit rates. The higher + // the level, the slower the compression but the smaller the memory footprint. + // Transform tracks only. + compression_level8 level = compression_level8::low; + + ////////////////////////////////////////////////////////////////////////// + // The rotation, translation, and scale formats to use. See functions get_rotation_format(..) and get_vector_format(..) + // Defaults to raw: 'quatf_full' and 'vector3f_full' + // Transform tracks only. + rotation_format8 rotation_format = rotation_format8::quatf_full; + vector_format8 translation_format = vector_format8::vector3f_full; + vector_format8 scale_format = vector_format8::vector3f_full; + + ////////////////////////////////////////////////////////////////////////// + // The error metric to use. + // Defaults to 'null', this value must be set manually! + // Transform tracks only. + itransform_error_metric* error_metric = nullptr; + + ////////////////////////////////////////////////////////////////////////// + // Whether or not to enable database support on the output compressed clip. + // This enables the required metadata which will later be stripped once + // the database is built. + // Transform tracks only. + bool enable_database_support = false; + + ////////////////////////////////////////////////////////////////////////// + // These are optional metadata that can be added to compressed clips. + compression_metadata_settings metadata; + + ////////////////////////////////////////////////////////////////////////// + // Calculates a hash from the internal state to uniquely identify a configuration. + uint32_t get_hash() const; + + ////////////////////////////////////////////////////////////////////////// + // Checks if everything is valid and if it isn't, returns an error string. + error_result is_valid() const; + }; + + ////////////////////////////////////////////////////////////////////////// + // Returns raw compression settings. No compression is performed and + // samples are all retained with full precision. + compression_settings get_raw_compression_settings(); + + ////////////////////////////////////////////////////////////////////////// + // Returns the recommended and default compression settings. These have + // been tested in a wide range of scenarios and perform best overall. + compression_settings get_default_compression_settings(); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/compression/impl/compression_settings.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/convert.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/convert.h new file mode 100644 index 00000000..e24d1b2b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/convert.h @@ -0,0 +1,59 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/compression/track_array.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error_result.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Convert a track array instance into a raw compressed tracks instance. + // This is a lossless process. + ////////////////////////////////////////////////////////////////////////// + error_result convert_track_list(iallocator& allocator, const track_array& track_list, compressed_tracks*& out_compressed_tracks); + + ////////////////////////////////////////////////////////////////////////// + // Convert a compressed tracks instance into a track array instance. + // This is a lossless process if all the metadata is present. + ////////////////////////////////////////////////////////////////////////// + error_result convert_track_list(iallocator& allocator, const compressed_tracks& tracks, track_array& out_track_list); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/compression/impl/convert.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/fwd.h new file mode 100644 index 00000000..6c7184ab --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/fwd.h @@ -0,0 +1,84 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" + +// track_type8 needs to be fully defined to be able to specialize some templates +#include "acl/core/track_types.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public ACL types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + enum class compression_level8 : uint8_t; + + struct compression_database_settings; + struct compression_metadata_settings; + struct compression_settings; + + enum class stat_logging; + struct output_stats; + + struct track_error; + + class track_array; + template class track_array_typed; + + using track_array_float1f = track_array_typed; + using track_array_float2f = track_array_typed; + using track_array_float3f = track_array_typed; + using track_array_float4f = track_array_typed; + using track_array_vector4f = track_array_typed; + using track_array_qvvf = track_array_typed; + + class track; + template class track_typed; + + using track_float1f = track_typed; + using track_float2f = track_typed; + using track_float3f = track_typed; + using track_float4f = track_typed; + using track_vector4f = track_typed; + using track_qvvf = track_typed; + + enum class additive_clip_format8 : uint8_t; + + class itransform_error_metric; + class qvvf_transform_error_metric; + class qvvf_matrix3x4f_transform_error_metric; + template class additive_qvvf_transform_error_metric; + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/animated_track_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/animated_track_utils.h new file mode 100644 index 00000000..fc9b4289 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/animated_track_utils.h @@ -0,0 +1,168 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/impl/clip_context.h" +#include "acl/compression/impl/segment_context.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + // We use template arguments to support lambdas and avoid using std::function since they might allocate memory + // without using our allocator. + // group_filter_action_type = std::function + template + inline void get_num_sub_tracks(const segment_context& segment, + group_filter_action_type& group_filter_action, + uint32_t& out_num_rotation_sub_tracks, uint32_t& out_num_translation_sub_tracks, uint32_t& out_num_scale_sub_tracks) + { + uint32_t num_rotation_sub_tracks = 0; + uint32_t num_translation_sub_tracks = 0; + uint32_t num_scale_sub_tracks = 0; + for (uint32_t bone_index = 0; bone_index < segment.num_bones; ++bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (bone_stream.output_index == k_invalid_track_index) + continue; // Stripped + + if (group_filter_action(animation_track_type8::rotation, bone_index)) + num_rotation_sub_tracks++; + + if (group_filter_action(animation_track_type8::translation, bone_index)) + num_translation_sub_tracks++; + + if (group_filter_action(animation_track_type8::scale, bone_index)) + num_scale_sub_tracks++; + } + + out_num_rotation_sub_tracks = num_rotation_sub_tracks; + out_num_translation_sub_tracks = num_translation_sub_tracks; + out_num_scale_sub_tracks = num_scale_sub_tracks; + } + + inline void get_num_animated_sub_tracks(const segment_context& segment, + uint32_t& out_num_animated_rotation_sub_tracks, uint32_t& out_num_animated_translation_sub_tracks, uint32_t& out_num_animated_scale_sub_tracks) + { + const auto animated_group_filter_action = [&segment](animation_track_type8 group_type, uint32_t bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (group_type == animation_track_type8::rotation) + return !bone_stream.is_rotation_constant; + else if (group_type == animation_track_type8::translation) + return !bone_stream.is_translation_constant; + else + return !bone_stream.is_scale_constant; + }; + + get_num_sub_tracks(segment, animated_group_filter_action, out_num_animated_rotation_sub_tracks, out_num_animated_translation_sub_tracks, out_num_animated_scale_sub_tracks); + } + + // We use template arguments to support lambdas and avoid using std::function since they might allocate memory + // without using our allocator. + // group_filter_action_type = std::function + // group_entry_action_type = std::function + // group_flush_action_type = std::function + template + inline void group_writer(const segment_context& segment, const uint32_t* output_bone_mapping, uint32_t num_output_bones, + group_filter_action_type& group_filter_action, group_entry_action_type& group_entry_action, group_flush_action_type& group_flush_action) + { + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Groups are sorted per sub-track type. All rotation groups come first followed by translations then scales. + // The last group of each sub-track may or may not have padding. The last group might be less than 4 sub-tracks. + + const auto group_writer_impl = [output_bone_mapping, num_output_bones, &group_filter_action, &group_entry_action, &group_flush_action](animation_track_type8 group_type) + { + uint32_t group_size = 0; + + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + + if (group_filter_action(group_type, bone_index)) + { + group_entry_action(group_type, group_size, bone_index); + group_size++; + } + + if (group_size == 4) + { + // Group full, write it out and move onto to the next group + group_flush_action(group_type, group_size); + group_size = 0; + } + } + + // If group has leftover tracks, write it out + if (group_size != 0) + group_flush_action(group_type, group_size); + }; + + // Output sub-tracks in natural order: rot, trans, scale + group_writer_impl(animation_track_type8::rotation); + group_writer_impl(animation_track_type8::translation); + + if (segment.clip->has_scale) + group_writer_impl(animation_track_type8::scale); + } + + // We use template arguments to support lambdas and avoid using std::function since they might allocate memory + // without using our allocator. + // group_filter_action_type = std::function + // group_entry_action_type = std::function + // group_flush_action_type = std::function + template + inline void animated_group_writer(const segment_context& segment, const uint32_t* output_bone_mapping, uint32_t num_output_bones, + group_filter_action_type& group_filter_action, group_entry_action_type& group_entry_action, group_flush_action_type& group_flush_action) + { + const auto animated_group_filter_action = [&segment, &group_filter_action](animation_track_type8 group_type, uint32_t bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (group_type == animation_track_type8::rotation) + return !bone_stream.is_rotation_constant && group_filter_action(group_type, bone_index); + else if (group_type == animation_track_type8::translation) + return !bone_stream.is_translation_constant && group_filter_action(group_type, bone_index); + else + return !bone_stream.is_scale_constant && group_filter_action(group_type, bone_index); + }; + + group_writer(segment, output_bone_mapping, num_output_bones, animated_group_filter_action, group_entry_action, group_flush_action); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/clip_context.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/clip_context.h new file mode 100644 index 00000000..4119a95f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/clip_context.h @@ -0,0 +1,397 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/additive_utils.h" +#include "acl/core/bitset.h" +#include "acl/core/iallocator.h" +#include "acl/core/iterator.h" +#include "acl/core/error.h" +#include "acl/core/track_formats.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/impl/segment_context.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + ////////////////////////////////////////////////////////////////////////// + // Simple iterator utility class to allow easy looping + class bone_chain_iterator + { + public: + bone_chain_iterator(const uint32_t* chain, bitset_description chain_desc, uint32_t bone_index, uint32_t offset) + : m_bone_chain(chain) + , m_bone_chain_desc(chain_desc) + , m_bone_index(bone_index) + , m_offset(offset) + {} + + bone_chain_iterator& operator++() + { + ACL_ASSERT(m_offset <= m_bone_index, "Cannot increment the iterator, it is no longer valid"); + + // Skip the current bone + m_offset++; + + // Iterate until we find the next bone part of the chain or until we reach the end of the chain + // TODO: Use clz or similar to find the next set bit starting at the current index + while (m_offset < m_bone_index && !bitset_test(m_bone_chain, m_bone_chain_desc, m_offset)) + m_offset++; + + return *this; + } + + uint32_t operator*() const + { + ACL_ASSERT(m_offset <= m_bone_index, "Returned bone index doesn't belong to the bone chain"); + ACL_ASSERT(bitset_test(m_bone_chain, m_bone_chain_desc, m_offset), "Returned bone index doesn't belong to the bone chain"); + return m_offset; + } + + // We only compare the offset in the bone chain. Two iterators on the same bone index + // from two different or equal chains will be equal. + bool operator==(const bone_chain_iterator& other) const { return m_offset == other.m_offset; } + bool operator!=(const bone_chain_iterator& other) const { return m_offset != other.m_offset; } + + private: + const uint32_t* m_bone_chain; + bitset_description m_bone_chain_desc; + uint32_t m_bone_index; + uint32_t m_offset; + }; + + ////////////////////////////////////////////////////////////////////////// + // Simple bone chain container to allow easy looping + // + // A bone chain allows looping over all bones up to a specific bone starting + // at the root bone. + ////////////////////////////////////////////////////////////////////////// + struct bone_chain + { + bone_chain(const uint32_t* chain, bitset_description chain_desc, uint32_t bone_index) + : m_bone_chain(chain) + , m_bone_chain_desc(chain_desc) + , m_bone_index(bone_index) + { + // We don't know where this bone chain starts, find the root bone + // TODO: Use clz or similar to find the next set bit starting at the current index + uint32_t root_index = 0; + while (!bitset_test(chain, chain_desc, root_index)) + root_index++; + + m_root_index = root_index; + } + + acl_impl::bone_chain_iterator begin() const { return acl_impl::bone_chain_iterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_root_index); } + acl_impl::bone_chain_iterator end() const { return acl_impl::bone_chain_iterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_bone_index + 1); } + + const uint32_t* m_bone_chain; + bitset_description m_bone_chain_desc; + uint32_t m_root_index; + uint32_t m_bone_index; + }; + + struct transform_metadata + { + const uint32_t* transform_chain = nullptr; + uint32_t parent_index = k_invalid_track_index; + float precision = 0.0F; + float shell_distance = 0.0F; + }; + + struct clip_context + { + segment_context* segments = nullptr; + transform_range* ranges = nullptr; + transform_metadata* metadata = nullptr; + uint32_t* leaf_transform_chains = nullptr; + + uint32_t num_segments = 0; + uint32_t num_bones = 0; + uint32_t num_samples = 0; + float sample_rate = 0.0F; + + float duration = 0.0F; + + bool are_rotations_normalized = false; + bool are_translations_normalized = false; + bool are_scales_normalized = false; + bool has_scale = false; + bool has_additive_base = false; + + additive_clip_format8 additive_format = additive_clip_format8::none; + + uint32_t num_leaf_transforms = 0; + + iallocator* allocator = nullptr; // Never null if the context is initialized + + // Stat tracking + uint32_t decomp_touched_bytes = 0; + uint32_t decomp_touched_cache_lines = 0; + + ////////////////////////////////////////////////////////////////////////// + + bool is_initialized() const { return allocator != nullptr; } + iterator segment_iterator() { return iterator(segments, num_segments); } + const_iterator segment_iterator() const { return const_iterator(segments, num_segments); } + + bone_chain get_bone_chain(uint32_t bone_index) const + { + ACL_ASSERT(bone_index < num_bones, "Invalid bone index: %u >= %u", bone_index, num_bones); + const transform_metadata& meta = metadata[bone_index]; + return bone_chain(meta.transform_chain, bitset_description::make_from_num_bits(num_bones), bone_index); + } + }; + + inline bool initialize_clip_context(iallocator& allocator, const track_array_qvvf& track_list, const compression_settings& settings, additive_clip_format8 additive_format, clip_context& out_clip_context) + { + const uint32_t num_transforms = track_list.get_num_tracks(); + const uint32_t num_samples = track_list.get_num_samples_per_track(); + const float sample_rate = track_list.get_sample_rate(); + + // Create a single segment with the whole clip + out_clip_context.segments = allocate_type_array(allocator, 1); + out_clip_context.ranges = nullptr; + out_clip_context.metadata = allocate_type_array(allocator, num_transforms); + out_clip_context.leaf_transform_chains = nullptr; + out_clip_context.num_segments = 1; + out_clip_context.num_bones = num_transforms; + out_clip_context.num_samples = num_samples; + out_clip_context.sample_rate = sample_rate; + out_clip_context.duration = track_list.get_duration(); + out_clip_context.are_rotations_normalized = false; + out_clip_context.are_translations_normalized = false; + out_clip_context.are_scales_normalized = false; + out_clip_context.has_additive_base = additive_format != additive_clip_format8::none; + out_clip_context.additive_format = additive_format; + out_clip_context.num_leaf_transforms = 0; + out_clip_context.allocator = &allocator; + + bool has_scale = false; + bool are_samples_valid = true; + const rtm::vector4f default_scale = get_default_scale(additive_format); + + segment_context& segment = out_clip_context.segments[0]; + + transform_streams* bone_streams = allocate_type_array(allocator, num_transforms); + + for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index) + { + const track_qvvf& track = track_list[transform_index]; + const track_desc_transformf& desc = track.get_description(); + + transform_streams& bone_stream = bone_streams[transform_index]; + + bone_stream.segment = &segment; + bone_stream.bone_index = transform_index; + bone_stream.parent_bone_index = desc.parent_index; + bone_stream.output_index = desc.output_index; + + bone_stream.rotations = rotation_track_stream(allocator, num_samples, sizeof(rtm::quatf), sample_rate, rotation_format8::quatf_full); + bone_stream.translations = translation_track_stream(allocator, num_samples, sizeof(rtm::vector4f), sample_rate, vector_format8::vector3f_full); + bone_stream.scales = scale_track_stream(allocator, num_samples, sizeof(rtm::vector4f), sample_rate, vector_format8::vector3f_full); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::qvvf& transform = track[sample_index]; + + // If we request raw data and we are already normalized, retain the original value + // otherwise we normalize for safety + rtm::quatf rotation; + if (settings.rotation_format != rotation_format8::quatf_full || !rtm::quat_is_normalized(transform.rotation)) + rotation = rtm::quat_normalize(transform.rotation); + else + rotation = transform.rotation; + + are_samples_valid &= rtm::quat_is_finite(rotation); + are_samples_valid &= rtm::vector_is_finite3(transform.translation); + are_samples_valid &= rtm::vector_is_finite3(transform.scale); + + bone_stream.rotations.set_raw_sample(sample_index, rotation); + bone_stream.translations.set_raw_sample(sample_index, transform.translation); + bone_stream.scales.set_raw_sample(sample_index, transform.scale); + } + + { + const rtm::qvvf first_transform = num_samples != 0 ? track[0] : rtm::qvv_identity(); + const rtm::quatf first_rotation = rtm::quat_normalize(first_transform.rotation); + + // If we request raw data, use a 0.0 threshold for safety + const float constant_rotation_threshold_angle = settings.rotation_format != rotation_format8::quatf_full ? desc.constant_rotation_threshold_angle : 0.0F; + const float constant_translation_threshold = settings.translation_format != vector_format8::vector3f_full ? desc.constant_translation_threshold : 0.0F; + const float constant_scale_threshold = settings.scale_format != vector_format8::vector3f_full ? desc.constant_scale_threshold : 0.0F; + + bone_stream.is_rotation_constant = num_samples <= 1; + bone_stream.is_rotation_default = bone_stream.is_rotation_constant && rtm::quat_near_identity(first_rotation, constant_rotation_threshold_angle); + bone_stream.is_translation_constant = num_samples <= 1; + bone_stream.is_translation_default = bone_stream.is_translation_constant && rtm::vector_all_near_equal3(first_transform.translation, rtm::vector_zero(), constant_translation_threshold); + bone_stream.is_scale_constant = num_samples <= 1; + bone_stream.is_scale_default = bone_stream.is_scale_constant && rtm::vector_all_near_equal3(first_transform.scale, default_scale, constant_scale_threshold); + } + + has_scale |= !bone_stream.is_scale_default; + + transform_metadata& metadata = out_clip_context.metadata[transform_index]; + metadata.transform_chain = nullptr; + metadata.parent_index = desc.parent_index; + metadata.precision = desc.precision; + metadata.shell_distance = desc.shell_distance; + } + + out_clip_context.has_scale = has_scale; + out_clip_context.decomp_touched_bytes = 0; + out_clip_context.decomp_touched_cache_lines = 0; + + segment.bone_streams = bone_streams; + segment.clip = &out_clip_context; + segment.ranges = nullptr; + segment.contributing_error = nullptr; + segment.num_samples = num_samples; + segment.num_bones = num_transforms; + segment.clip_sample_offset = 0; + segment.segment_index = 0; + segment.are_rotations_normalized = false; + segment.are_translations_normalized = false; + segment.are_scales_normalized = false; + + segment.animated_rotation_bit_size = 0; + segment.animated_translation_bit_size = 0; + segment.animated_scale_bit_size = 0; + segment.animated_pose_bit_size = 0; + segment.animated_data_size = 0; + segment.range_data_size = 0; + segment.total_header_size = 0; + + // Initialize our hierarchy information + if (num_transforms != 0) + { + // Calculate which bones are leaf bones that have no children + bitset_description bone_bitset_desc = bitset_description::make_from_num_bits(num_transforms); + const size_t bitset_size = bone_bitset_desc.get_size(); + uint32_t* is_leaf_bitset = allocate_type_array(allocator, bitset_size); + bitset_reset(is_leaf_bitset, bone_bitset_desc, false); + + // By default and if we find a child, we'll mark it as non-leaf + bitset_set_range(is_leaf_bitset, bone_bitset_desc, 0, num_transforms, true); + +#if defined(ACL_HAS_ASSERT_CHECKS) + uint32_t num_root_bones = 0; +#endif + + // Move and validate the input data + for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index) + { + const transform_metadata& metadata = out_clip_context.metadata[transform_index]; + + const bool is_root = metadata.parent_index == k_invalid_track_index; + + // If we have a parent, mark it as not being a leaf bone (it has at least one child) + if (!is_root) + bitset_set(is_leaf_bitset, bone_bitset_desc, metadata.parent_index, false); + +#if defined(ACL_HAS_ASSERT_CHECKS) + if (is_root) + num_root_bones++; +#endif + } + + const uint32_t num_leaf_transforms = bitset_count_set_bits(is_leaf_bitset, bone_bitset_desc); + out_clip_context.num_leaf_transforms = num_leaf_transforms; + + uint32_t* leaf_transform_chains = allocate_type_array(allocator, num_leaf_transforms * bitset_size); + out_clip_context.leaf_transform_chains = leaf_transform_chains; + + uint32_t leaf_index = 0; + for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index) + { + if (!bitset_test(is_leaf_bitset, bone_bitset_desc, transform_index)) + continue; // Skip non-leaf bones + + uint32_t* bone_chain = leaf_transform_chains + (leaf_index * bitset_size); + bitset_reset(bone_chain, bone_bitset_desc, false); + + uint32_t chain_bone_index = transform_index; + while (chain_bone_index != k_invalid_track_index) + { + bitset_set(bone_chain, bone_bitset_desc, chain_bone_index, true); + + transform_metadata& metadata = out_clip_context.metadata[chain_bone_index]; + + // We assign a bone chain the first time we find a bone that isn't part of one already + if (metadata.transform_chain == nullptr) + metadata.transform_chain = bone_chain; + + chain_bone_index = metadata.parent_index; + } + + leaf_index++; + } + + ACL_ASSERT(num_root_bones > 0, "No root bone found. The root bones must have a parent index = 0xFFFF"); + ACL_ASSERT(leaf_index == num_leaf_transforms, "Invalid number of leaf bone found"); + deallocate_type_array(allocator, is_leaf_bitset, bitset_size); + } + + return are_samples_valid; + } + + inline void destroy_clip_context(clip_context& context) + { + if (context.allocator == nullptr) + return; // Not initialized + + iallocator& allocator = *context.allocator; + + for (segment_context& segment : context.segment_iterator()) + destroy_segment_context(allocator, segment); + + deallocate_type_array(allocator, context.segments, context.num_segments); + deallocate_type_array(allocator, context.ranges, context.num_bones); + deallocate_type_array(allocator, context.metadata, context.num_bones); + + bitset_description bone_bitset_desc = bitset_description::make_from_num_bits(context.num_bones); + deallocate_type_array(allocator, context.leaf_transform_chains, size_t(context.num_leaf_transforms) * bone_bitset_desc.get_size()); + } + + constexpr bool segment_context_has_scale(const segment_context& segment) { return segment.clip->has_scale; } + constexpr bool bone_streams_has_scale(const transform_streams& bone_streams) { return segment_context_has_scale(*bone_streams.segment); } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compact_constant_streams.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compact_constant_streams.h new file mode 100644 index 00000000..129699b2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compact_constant_streams.h @@ -0,0 +1,165 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/track_formats.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/compression/impl/clip_context.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline bool is_rotation_track_constant(const rotation_track_stream& rotations, float threshold_angle) + { + // Calculating the average rotation and comparing every rotation in the track to it + // to determine if we are within the threshold seems overkill. We can't use the min/max for the range + // either because neither of those represents a valid rotation. Instead we grab + // the first rotation, and compare everything else to it. + auto sample_to_quat = [](const rotation_track_stream& track, uint32_t sample_index) + { + const rtm::vector4f rotation = track.get_raw_sample(sample_index); + + switch (track.get_rotation_format()) + { + case rotation_format8::quatf_full: + return rtm::vector_to_quat(rotation); + case rotation_format8::quatf_drop_w_full: + case rotation_format8::quatf_drop_w_variable: + return rtm::quat_from_positive_w(rotation); + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(track.get_rotation_format())); + return rtm::vector_to_quat(rotation); + } + }; + + const uint32_t num_samples = rotations.get_num_samples(); + if (num_samples <= 1) + return true; + + const rtm::quatf ref_rotation = sample_to_quat(rotations, 0); + const rtm::quatf inv_ref_rotation = rtm::quat_conjugate(ref_rotation); + + for (uint32_t sample_index = 1; sample_index < num_samples; ++sample_index) + { + const rtm::quatf rotation = sample_to_quat(rotations, sample_index); + const rtm::quatf delta = rtm::quat_normalize(rtm::quat_mul(inv_ref_rotation, rotation)); + if (!rtm::quat_near_identity(delta, threshold_angle)) + return false; + } + + return true; + } + + inline void compact_constant_streams(iallocator& allocator, clip_context& context, const track_array_qvvf& track_list, const compression_settings& settings) + { + ACL_ASSERT(context.num_segments == 1, "context must contain a single segment!"); + segment_context& segment = context.segments[0]; + + const uint32_t num_bones = context.num_bones; + const uint32_t num_samples = context.num_samples; + const rtm::vector4f default_scale = get_default_scale(context.additive_format); + uint32_t num_default_bone_scales = 0; + + // When a stream is constant, we only keep the first sample + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + const track_desc_transformf& desc = track_list[bone_index].get_description(); + + transform_streams& bone_stream = segment.bone_streams[bone_index]; + transform_range& bone_range = context.ranges[bone_index]; + + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(bone_stream.rotations.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", bone_stream.rotations.get_sample_size(), sizeof(rtm::vector4f)); + ACL_ASSERT(bone_stream.translations.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", bone_stream.translations.get_sample_size(), sizeof(rtm::vector4f)); + ACL_ASSERT(bone_stream.scales.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", bone_stream.scales.get_sample_size(), sizeof(rtm::vector4f)); + + // If we request raw data, use a 0.0 threshold for safety + const float constant_rotation_threshold_angle = settings.rotation_format != rotation_format8::quatf_full ? desc.constant_rotation_threshold_angle : 0.0F; + const float constant_translation_threshold = settings.translation_format != vector_format8::vector3f_full ? desc.constant_translation_threshold : 0.0F; + const float constant_scale_threshold = settings.scale_format != vector_format8::vector3f_full ? desc.constant_scale_threshold : 0.0F; + + if (is_rotation_track_constant(bone_stream.rotations, constant_rotation_threshold_angle)) + { + rotation_track_stream constant_stream(allocator, 1, bone_stream.rotations.get_sample_size(), bone_stream.rotations.get_sample_rate(), bone_stream.rotations.get_rotation_format()); + rtm::vector4f rotation = num_samples != 0 ? bone_stream.rotations.get_raw_sample(0) : rtm::quat_to_vector((rtm::quatf)rtm::quat_identity()); + constant_stream.set_raw_sample(0, rotation); + + bone_stream.rotations = std::move(constant_stream); + bone_stream.is_rotation_constant = true; + bone_stream.is_rotation_default = rtm::quat_near_identity(rtm::vector_to_quat(rotation), constant_rotation_threshold_angle); + + bone_range.rotation = track_stream_range::from_min_extent(rotation, rtm::vector_zero()); + } + + if (bone_range.translation.is_constant(constant_translation_threshold)) + { + translation_track_stream constant_stream(allocator, 1, bone_stream.translations.get_sample_size(), bone_stream.translations.get_sample_rate(), bone_stream.translations.get_vector_format()); + rtm::vector4f translation = num_samples != 0 ? bone_stream.translations.get_raw_sample(0) : rtm::vector_zero(); + constant_stream.set_raw_sample(0, translation); + + bone_stream.translations = std::move(constant_stream); + bone_stream.is_translation_constant = true; + bone_stream.is_translation_default = rtm::vector_all_near_equal3(translation, rtm::vector_zero(), constant_translation_threshold); + + bone_range.translation = track_stream_range::from_min_extent(translation, rtm::vector_zero()); + } + + if (bone_range.scale.is_constant(constant_scale_threshold)) + { + scale_track_stream constant_stream(allocator, 1, bone_stream.scales.get_sample_size(), bone_stream.scales.get_sample_rate(), bone_stream.scales.get_vector_format()); + rtm::vector4f scale = num_samples != 0 ? bone_stream.scales.get_raw_sample(0) : default_scale; + constant_stream.set_raw_sample(0, scale); + + bone_stream.scales = std::move(constant_stream); + bone_stream.is_scale_constant = true; + bone_stream.is_scale_default = rtm::vector_all_near_equal3(scale, default_scale, constant_scale_threshold); + + bone_range.scale = track_stream_range::from_min_extent(scale, rtm::vector_zero()); + + num_default_bone_scales += bone_stream.is_scale_default ? 1 : 0; + } + } + + context.has_scale = num_default_bone_scales != num_bones; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.database.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.database.impl.h new file mode 100644 index 00000000..96ce9fc1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.database.impl.h @@ -0,0 +1,1501 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compress.h + +#include "acl/version.h" +#include "acl/core/bitset.h" +#include "acl/core/compressed_database.h" +#include "acl/core/error_result.h" +#include "acl/core/hash.h" +#include "acl/core/iallocator.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct frame_tier_mapping + { + const uint8_t* animated_data; // Pointer to the segment's animated data which contains this frame + + uint32_t tracks_index; + uint32_t segment_index; + uint32_t clip_frame_index; + uint32_t segment_frame_index; + uint32_t frame_bit_size; // Size in bits of this frame + + float contributing_error; + }; + + struct database_tier_mapping + { + frame_tier_mapping* frames = nullptr; // Frames mapped to this tier + uint32_t num_frames = 0; // Number of frames mapped to this tier + + quality_tier tier = quality_tier::highest_importance; // Actual tier + + bool is_empty() const { return num_frames == 0; } + }; + + struct segment_contriguting_error + { + const frame_contributing_error* errors; // List of frame contributing errors for this segment, sorted lowest first + uint32_t num_frames; // Total number of frames in this segment + uint32_t num_movable; // Number of frames that can be moved + uint32_t num_assigned; // Number of frames already assigned + }; + + struct clip_contributing_error + { + segment_contriguting_error* segments; + uint32_t num_segments; + uint32_t num_frames; // Number of frames + uint32_t num_assigned; // Number of frames already assigned + }; + + struct frame_assignment_context + { + iallocator& allocator; + + const compressed_tracks* const* compressed_tracks_list; + uint32_t num_compressed_tracks; + + database_tier_mapping mappings[k_num_quality_tiers]; // 0 = high importance, 1 = medium importance, 2 = low importance + uint32_t num_movable_frames; + + clip_contributing_error* contributing_error_per_clip; + + frame_assignment_context(iallocator& allocator_, const compressed_tracks* const* compressed_tracks_list_, uint32_t num_compressed_tracks_, uint32_t num_movable_frames_) + : allocator(allocator_) + , compressed_tracks_list(compressed_tracks_list_) + , num_compressed_tracks(num_compressed_tracks_) + , num_movable_frames(num_movable_frames_) + , contributing_error_per_clip(allocate_type_array(allocator_, num_compressed_tracks_)) + { + mappings[0].tier = quality_tier::highest_importance; + mappings[1].tier = quality_tier::medium_importance; + mappings[2].tier = quality_tier::lowest_importance; + + // Setup our error metadata to make iterating on it easier and track what has been assigned + for (uint32_t list_index = 0; list_index < num_compressed_tracks_; ++list_index) + { + const compressed_tracks* tracks = compressed_tracks_list_[list_index]; + const tracks_header& header = get_tracks_header(*tracks); + const transform_tracks_header& transform_header = get_transform_tracks_header(*tracks); + const bool has_multiple_segments = transform_header.has_multiple_segments(); + const uint32_t* segment_start_indices = has_multiple_segments ? transform_header.get_segment_start_indices() : nullptr; + const optional_metadata_header& metadata_header = get_optional_metadata_header(*tracks); + const frame_contributing_error* contributing_errors = metadata_header.get_contributing_error(*tracks); + + const uint32_t num_segments = has_multiple_segments ? transform_header.num_segments : 1; // HACK to avoid static analysis warning + + clip_contributing_error& clip_error = contributing_error_per_clip[list_index]; + clip_error.segments = allocate_type_array(allocator_, num_segments); + clip_error.num_segments = num_segments; + clip_error.num_frames = 0; + clip_error.num_assigned = 0; + + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const uint32_t segment_start_frame_index = has_multiple_segments ? segment_start_indices[segment_index] : 0; + + uint32_t num_segment_frames; + if (num_segments == 1) + num_segment_frames = header.num_samples; // Only one segment, it has every frame + else if (segment_index + 1 == num_segments) + num_segment_frames = header.num_samples - segment_start_indices[segment_index]; // Last segment has the remaining frames + else + num_segment_frames = segment_start_indices[segment_index + 1] - segment_start_indices[segment_index]; + + const uint32_t num_movable = num_segment_frames >= 2 ? (num_segment_frames - 2) : 0; + + segment_contriguting_error& segment_error = clip_error.segments[segment_index]; + segment_error.errors = contributing_errors + segment_start_frame_index; + segment_error.num_frames = num_segment_frames; + segment_error.num_movable = num_movable; + segment_error.num_assigned = 0; + + clip_error.num_frames += num_segment_frames; + } + } + } + + ~frame_assignment_context() + { + for (uint32_t tier_index = 0; tier_index < k_num_quality_tiers; ++tier_index) + deallocate_type_array(allocator, mappings[tier_index].frames, mappings[tier_index].num_frames); + + for (uint32_t list_index = 0; list_index < num_compressed_tracks; ++list_index) + deallocate_type_array(allocator, contributing_error_per_clip[list_index].segments, contributing_error_per_clip[list_index].num_segments); + deallocate_type_array(allocator, contributing_error_per_clip, num_compressed_tracks); + } + + frame_assignment_context(const frame_assignment_context&) = delete; + frame_assignment_context& operator=(const frame_assignment_context&) = delete; + + database_tier_mapping& get_tier_mapping(quality_tier tier) { return mappings[(uint32_t)tier]; } + const database_tier_mapping& get_tier_mapping(quality_tier tier) const { return mappings[(uint32_t)tier]; } + + void set_tier_num_frames(quality_tier tier, uint32_t num_frames) + { + database_tier_mapping& mapping = mappings[(uint32_t)tier]; + ACL_ASSERT(mapping.frames == nullptr, "Tier already setup"); + + mapping.frames = allocate_type_array(allocator, num_frames); + mapping.num_frames = num_frames; + } + }; + + inline uint32_t calculate_num_frames(const compressed_tracks* const* compressed_tracks_list, uint32_t num_compressed_tracks) + { + uint32_t num_frames = 0; + + for (uint32_t list_index = 0; list_index < num_compressed_tracks; ++list_index) + { + const compressed_tracks* tracks = compressed_tracks_list[list_index]; + + const tracks_header& header = get_tracks_header(*tracks); + + num_frames += header.num_samples; + } + + return num_frames; + } + + inline uint32_t calculate_num_movable_frames(const compressed_tracks* const* compressed_tracks_list, uint32_t num_compressed_tracks) + { + uint32_t num_movable_frames = 0; + + for (uint32_t list_index = 0; list_index < num_compressed_tracks; ++list_index) + { + const compressed_tracks* tracks = compressed_tracks_list[list_index]; + + const tracks_header& header = get_tracks_header(*tracks); + const transform_tracks_header& transform_header = get_transform_tracks_header(*tracks); + + // A frame is movable if it isn't the first or last frame of a segment + // If we have more than 1 frame, we can remove 2 frames per segment + // We know that the only way to get a segment with 1 frame is if the whole clip contains + // a single frame and thus has one segment + // If we have 0 or 1 frame, none are movable + if (header.num_samples >= 2) + num_movable_frames += header.num_samples - (transform_header.num_segments * 2); + } + + return num_movable_frames; + } + + inline uint32_t calculate_num_segments(const compressed_tracks* const* compressed_tracks_list, uint32_t num_compressed_tracks) + { + uint32_t num_segments = 0; + + for (uint32_t list_index = 0; list_index < num_compressed_tracks; ++list_index) + { + const compressed_tracks* tracks = compressed_tracks_list[list_index]; + const transform_tracks_header& transform_header = get_transform_tracks_header(*tracks); + + num_segments += transform_header.num_segments; + } + + return num_segments; + } + + inline void assign_frames_to_tier(frame_assignment_context& context, database_tier_mapping& tier_mapping) + { + // Iterate until we've fully assigned every frame we can to this tier + for (uint32_t assigned_frame_count = 0; assigned_frame_count < tier_mapping.num_frames; ++assigned_frame_count) + { + frame_tier_mapping best_mapping{}; + best_mapping.contributing_error = std::numeric_limits::infinity(); + + // Iterate over every segment and find the one with the frame that has the lowest contributing error to assign to this tier + for (uint32_t list_index = 0; list_index < context.num_compressed_tracks; ++list_index) + { + const compressed_tracks* tracks = context.compressed_tracks_list[list_index]; + const transform_tracks_header& transforms_header = get_transform_tracks_header(*tracks); + const bool has_multiple_segments = transforms_header.has_multiple_segments(); + const uint32_t* segment_start_indices = has_multiple_segments ? transforms_header.get_segment_start_indices() : nullptr; + const segment_header* segment_headers = transforms_header.get_segment_headers(); + + const clip_contributing_error& clip_error = context.contributing_error_per_clip[list_index]; + + for (uint32_t segment_index = 0; segment_index < transforms_header.num_segments; ++segment_index) + { + const segment_contriguting_error& segment_error = clip_error.segments[segment_index]; + + // High importance frames can always be moved since they end up in our compressed tracks + const uint32_t num_movable = tier_mapping.tier == quality_tier::highest_importance ? segment_error.num_frames : segment_error.num_movable; + if (segment_error.num_assigned >= num_movable) + continue; // No more movable data in this segment, skip it + + const frame_contributing_error& contributing_error = segment_error.errors[segment_error.num_assigned]; + ACL_ASSERT(tier_mapping.tier == quality_tier::highest_importance || rtm::scalar_is_finite(contributing_error.error), "Error should be finite"); + + if (contributing_error.error <= best_mapping.contributing_error) + { + // This frame has a lower error, use it + const uint8_t* format_per_track_data; + const uint8_t* range_data; + const uint8_t* animated_data; + transforms_header.get_segment_data(segment_headers[segment_index], format_per_track_data, range_data, animated_data); + + const uint32_t segment_start_frame_index = has_multiple_segments ? segment_start_indices[segment_index] : 0; + + best_mapping.animated_data = animated_data; + best_mapping.tracks_index = list_index; + best_mapping.segment_index = segment_index; + best_mapping.frame_bit_size = segment_headers[segment_index].animated_pose_bit_size; + best_mapping.clip_frame_index = segment_start_frame_index + contributing_error.index; + best_mapping.segment_frame_index = contributing_error.index; + best_mapping.contributing_error = contributing_error.error; + } + } + } + + ACL_ASSERT(tier_mapping.tier == quality_tier::highest_importance || rtm::scalar_is_finite(best_mapping.contributing_error), "Error should be finite"); + + // Assigned our mapping + tier_mapping.frames[assigned_frame_count] = best_mapping; + + // Mark it as being assigned so we don't try to use it again + context.contributing_error_per_clip[best_mapping.tracks_index].segments[best_mapping.segment_index].num_assigned++; + context.contributing_error_per_clip[best_mapping.tracks_index].num_assigned++; + } + + // Once we have assigned every frame we could to this tier, sort them by clip, by segment, then by segment frame index + const auto sort_predicate = [](const frame_tier_mapping& lhs, const frame_tier_mapping& rhs) + { + // Sort by clip index first + if (lhs.tracks_index < rhs.tracks_index) + return true; + else if (lhs.tracks_index == rhs.tracks_index) + { + // Sort by segment index second + if (lhs.segment_index < rhs.segment_index) + return true; + else if (lhs.segment_index == rhs.segment_index) + { + // Sort by frame index third + if (lhs.segment_frame_index < rhs.segment_frame_index) + return true; + } + } + + return false; + }; + + std::sort(tier_mapping.frames, tier_mapping.frames + tier_mapping.num_frames, sort_predicate); + } + + inline void assign_frames_to_tiers(frame_assignment_context& context) + { + // Assign frames to our lowest importance tier first + assign_frames_to_tier(context, context.get_tier_mapping(quality_tier::lowest_importance)); + + // Assign frames to our medium importance tier next + assign_frames_to_tier(context, context.get_tier_mapping(quality_tier::medium_importance)); + + // Then our high importance tier + assign_frames_to_tier(context, context.get_tier_mapping(quality_tier::highest_importance)); + + // Sanity check that everything has been assigned +#if defined(ACL_HAS_ASSERT_CHECKS) + for (uint32_t list_index = 0; list_index < context.num_compressed_tracks; ++list_index) + { + const clip_contributing_error& clip_error = context.contributing_error_per_clip[list_index]; + ACL_ASSERT(clip_error.num_assigned == clip_error.num_frames, "Every frame should have been assigned"); + } +#endif + } + + inline uint32_t find_first_metadata_offset(const optional_metadata_header& header) + { + if (header.track_list_name.is_valid()) + return (uint32_t)header.track_list_name; + + if (header.track_name_offsets.is_valid()) + return (uint32_t)header.track_name_offsets; + + if (header.parent_track_indices.is_valid()) + return (uint32_t)header.parent_track_indices; + + if (header.track_descriptions.is_valid()) + return (uint32_t)header.track_descriptions; + + if (header.contributing_error.is_valid()) + return (uint32_t)header.contributing_error; + + ACL_ASSERT(false, "Expected metadata to be present"); + return ~0U; + } + + inline uint32_t get_metadata_track_list_name_size(const optional_metadata_header& header) + { + if (!header.track_list_name.is_valid()) + return 0; + + if (header.track_name_offsets.is_valid()) + return (uint32_t)header.track_name_offsets - (uint32_t)header.track_list_name; + + if (header.parent_track_indices.is_valid()) + return (uint32_t)header.parent_track_indices - (uint32_t)header.track_list_name; + + if (header.track_descriptions.is_valid()) + return (uint32_t)header.track_descriptions - (uint32_t)header.track_list_name; + + if (header.contributing_error.is_valid()) + return (uint32_t)header.contributing_error - (uint32_t)header.track_list_name; + + ACL_ASSERT(false, "Expected metadata to be present"); + return ~0U; + } + + inline uint32_t get_metadata_track_names_size(const optional_metadata_header& header) + { + if (!header.track_name_offsets.is_valid()) + return 0; + + if (header.parent_track_indices.is_valid()) + return (uint32_t)header.parent_track_indices - (uint32_t)header.track_name_offsets; + + if (header.track_descriptions.is_valid()) + return (uint32_t)header.track_descriptions - (uint32_t)header.track_name_offsets; + + if (header.contributing_error.is_valid()) + return (uint32_t)header.contributing_error - (uint32_t)header.track_name_offsets; + + ACL_ASSERT(false, "Expected metadata to be present"); + return ~0U; + } + + inline uint32_t get_metadata_parent_track_indices_size(const optional_metadata_header& header) + { + if (!header.parent_track_indices.is_valid()) + return 0; + + if (header.track_descriptions.is_valid()) + return (uint32_t)header.track_descriptions - (uint32_t)header.parent_track_indices; + + if (header.contributing_error.is_valid()) + return (uint32_t)header.contributing_error - (uint32_t)header.parent_track_indices; + + ACL_ASSERT(false, "Expected metadata to be present"); + return ~0U; + } + + inline uint32_t get_metadata_track_descriptions_size(const optional_metadata_header& header) + { + if (!header.track_descriptions.is_valid()) + return 0; + + if (header.contributing_error.is_valid()) + return (uint32_t)header.contributing_error - (uint32_t)header.track_descriptions; + + ACL_ASSERT(false, "Expected metadata to be present"); + return ~0U; + } + + inline uint32_t build_sample_indices(const database_tier_mapping& tier_mapping, uint32_t tracks_index, uint32_t segment_index) + { + // TODO: Binary search our first entry and bail out once done? + + const bitset_description desc = bitset_description::make_from_num_bits<32>(); + + uint32_t sample_indices = 0; + for (uint32_t frame_index = 0; frame_index < tier_mapping.num_frames; ++frame_index) + { + const frame_tier_mapping& frame = tier_mapping.frames[frame_index]; + if (frame.tracks_index != tracks_index) + continue; // This is not the tracks instance we care about + + if (frame.segment_index != segment_index) + continue; // This is not the segment we care about + + bitset_set(&sample_indices, desc, frame.segment_frame_index, true); + } + + return sample_indices; + } + + inline void write_segment_headers(const database_tier_mapping& tier_mapping, uint32_t tracks_index, const transform_tracks_header& input_transforms_header, const segment_header* headers, uint32_t segment_data_base_offset, segment_tier0_header* out_headers) + { + const bitset_description desc = bitset_description::make_from_num_bits<32>(); + + uint32_t segment_data_offset = segment_data_base_offset; + for (uint32_t segment_index = 0; segment_index < input_transforms_header.num_segments; ++segment_index) + { + const uint32_t animated_pose_bit_size = headers[segment_index].animated_pose_bit_size; + + out_headers[segment_index].animated_pose_bit_size = animated_pose_bit_size; + out_headers[segment_index].animated_rotation_bit_size = headers[segment_index].animated_rotation_bit_size; + out_headers[segment_index].animated_translation_bit_size = headers[segment_index].animated_translation_bit_size; + out_headers[segment_index].segment_data = segment_data_offset; + out_headers[segment_index].sample_indices = build_sample_indices(tier_mapping, tracks_index, segment_index); + + const uint8_t* format_per_track_data; + const uint8_t* range_data; + const uint8_t* animated_data; + input_transforms_header.get_segment_data(headers[segment_index], format_per_track_data, range_data, animated_data); + + // Range data, whether present or not, follows the per track metadata, use it to calculate our size + const uint32_t format_per_track_data_size = uint32_t(range_data - format_per_track_data); + + // Animated data follows the range data (present or not), use it to calculate our size + const uint32_t range_data_size = uint32_t(animated_data - range_data); + + const uint32_t num_animated_frames = bitset_count_set_bits(&out_headers[segment_index].sample_indices, desc); + const uint32_t animated_data_num_bits = num_animated_frames * animated_pose_bit_size; + const uint32_t animated_data_size = (animated_data_num_bits + 7) / 8; + + segment_data_offset += format_per_track_data_size; // Format per track data + + // TODO: Alignment only necessary with 16bit per component (segment constant tracks), need to fix scalar decoding path + segment_data_offset = align_to(segment_data_offset, 2); // Align range data + segment_data_offset += range_data_size; // Range data + + // TODO: Variable bit rate doesn't need alignment + segment_data_offset = align_to(segment_data_offset, 4); // Align animated data + segment_data_offset += animated_data_size; // Animated data + } + } + + inline void write_segment_data(const database_tier_mapping& tier_mapping, uint32_t tracks_index, + const transform_tracks_header& input_transforms_header, const segment_header* input_headers, + transform_tracks_header& output_transforms_header, const segment_tier0_header* output_headers) + { + for (uint32_t segment_index = 0; segment_index < input_transforms_header.num_segments; ++segment_index) + { + const uint8_t* input_format_per_track_data; + const uint8_t* input_range_data; + const uint8_t* input_animated_data; + input_transforms_header.get_segment_data(input_headers[segment_index], input_format_per_track_data, input_range_data, input_animated_data); + + uint8_t* output_format_per_track_data; + uint8_t* output_range_data; + uint8_t* output_animated_data; + output_transforms_header.get_segment_data(output_headers[segment_index], output_format_per_track_data, output_range_data, output_animated_data); + + // Range data, whether present or not, follows the per track metadata, use it to calculate our size + const uint32_t format_per_track_data_size = uint32_t(input_range_data - input_format_per_track_data); + + // Animated data follows the range data (present or not), use it to calculate our size + const uint32_t range_data_size = uint32_t(input_animated_data - input_range_data); + + // Copy our per track metadata, it does not change + std::memcpy(output_format_per_track_data, input_format_per_track_data, format_per_track_data_size); + + // Copy our range data whether it is present or not, it does not change + std::memcpy(output_range_data, input_range_data, range_data_size); + + // Populate our new animated data from our sorted frame mapping data + uint64_t output_animated_bit_offset = 0; + for (uint32_t frame_index = 0; frame_index < tier_mapping.num_frames; ++frame_index) + { + const frame_tier_mapping& frame = tier_mapping.frames[frame_index]; + if (frame.tracks_index != tracks_index) + continue; // This is not the tracks instance we care about + + if (frame.segment_index != segment_index) + continue; // This is not the segment we care about + + // Append this frame + const uint32_t input_animated_bit_offset = frame.segment_frame_index * frame.frame_bit_size; + memcpy_bits(output_animated_data, output_animated_bit_offset, input_animated_data, input_animated_bit_offset, frame.frame_bit_size); + output_animated_bit_offset += frame.frame_bit_size; + } + } + } + + inline void build_compressed_tracks(const frame_assignment_context& context, compressed_tracks** out_compressed_tracks) + { + const bitset_description desc = bitset_description::make_from_num_bits<32>(); + + const database_tier_mapping& tier_mapping = context.get_tier_mapping(quality_tier::highest_importance); + uint32_t clip_header_offset = 0; + + for (uint32_t list_index = 0; list_index < context.num_compressed_tracks; ++list_index) + { + const compressed_tracks* input_tracks = context.compressed_tracks_list[list_index]; + const clip_contributing_error& clip_error = context.contributing_error_per_clip[list_index]; + + const tracks_header& input_header = get_tracks_header(*input_tracks); + const transform_tracks_header& input_transforms_header = get_transform_tracks_header(*input_tracks); + const segment_header* input_segment_headers = input_transforms_header.get_segment_headers(); + const optional_metadata_header& input_metadata_header = get_optional_metadata_header(*input_tracks); + + const uint32_t num_sub_tracks_per_bone = input_header.get_has_scale() ? 3 : 2; + + // Calculate how many sub-track packed entries we have + // Each sub-track is 2 bits packed within a 32 bit entry + // For each sub-track type, we round up to simplify bookkeeping + // For example, if we have 3 tracks made up of rotation/translation we'll have one entry for each with unused padding + // All rotation types come first, followed by all translation types, and with scale types at the end when present + const uint32_t num_sub_track_entries = ((input_header.num_tracks + k_num_sub_tracks_per_packed_entry - 1) / k_num_sub_tracks_per_packed_entry) * num_sub_tracks_per_bone; + const uint32_t packed_sub_track_buffer_size = num_sub_track_entries * sizeof(packed_sub_track_types); + + // Adding an extra index at the end to delimit things, the index is always invalid: 0xFFFFFFFF + const uint32_t segment_start_indices_size = clip_error.num_segments > 1 ? (uint32_t(sizeof(uint32_t)) * (clip_error.num_segments + 1)) : 0; + const uint32_t segment_headers_size = sizeof(segment_tier0_header) * clip_error.num_segments; + + // Range data follows constant data, use that to calculate our size + const uint32_t constant_data_size = (uint32_t)input_transforms_header.clip_range_data_offset - (uint32_t)input_transforms_header.constant_track_data_offset; + + // The data from our first segment follows the clip range data, use that to calculate our size + const uint32_t clip_range_data_size = (uint32_t)input_segment_headers[0].segment_data - (uint32_t)input_transforms_header.clip_range_data_offset; + + // Calculate the new size of our clip + uint32_t buffer_size = 0; + + // Per clip data + buffer_size += sizeof(raw_buffer_header); // Header + buffer_size += sizeof(tracks_header); // Header + buffer_size += sizeof(transform_tracks_header); // Header + + buffer_size = align_to(buffer_size, 4); // Align segment start indices + buffer_size += segment_start_indices_size; // Segment start indices + buffer_size = align_to(buffer_size, 4); // Align segment headers + buffer_size += segment_headers_size; // Segment headers + + buffer_size = align_to(buffer_size, 4); // Align database header + buffer_size += sizeof(tracks_database_header); // Database header + + buffer_size = align_to(buffer_size, 4); // Align sub-track types + buffer_size += packed_sub_track_buffer_size; // Packed sub-track types sorted by type + buffer_size = align_to(buffer_size, 4); // Align constant track data + buffer_size += constant_data_size; // Constant track data + buffer_size = align_to(buffer_size, 4); // Align range data + buffer_size += clip_range_data_size; // Range data + + // Per segment data + for (uint32_t segment_index = 0; segment_index < input_transforms_header.num_segments; ++segment_index) + { + const uint8_t* format_per_track_data; + const uint8_t* range_data; + const uint8_t* animated_data; + input_transforms_header.get_segment_data(input_segment_headers[segment_index], format_per_track_data, range_data, animated_data); + + // Range data, whether present or not, follows the per track metadata, use it to calculate our size + const uint32_t format_per_track_data_size = uint32_t(range_data - format_per_track_data); + + // Animated data follows the range data (present or not), use it to calculate our size + const uint32_t range_data_size = uint32_t(animated_data - range_data); + + buffer_size += format_per_track_data_size; // Format per track data + + // TODO: Alignment only necessary with 16bit per component (segment constant tracks), need to fix scalar decoding path + buffer_size = align_to(buffer_size, 2); // Align range data + buffer_size += range_data_size; // Range data + + // Check our data mapping to find our how many frames we'll retain + const uint32_t sample_indices = build_sample_indices(tier_mapping, list_index, segment_index); + const uint32_t num_animated_frames = bitset_count_set_bits(&sample_indices, desc); + const uint32_t animated_data_size = ((num_animated_frames * input_segment_headers[segment_index].animated_pose_bit_size) + 7) / 8; + + // TODO: Variable bit rate doesn't need alignment + buffer_size = align_to(buffer_size, 4); // Align animated data + buffer_size += animated_data_size; // Animated track data + } + + // Optional metadata + const uint32_t metadata_start_offset = align_to(buffer_size, 4); + const uint32_t metadata_track_list_name_size = get_metadata_track_list_name_size(input_metadata_header); + const uint32_t metadata_track_names_size = get_metadata_track_names_size(input_metadata_header); + const uint32_t metadata_parent_track_indices_size = get_metadata_parent_track_indices_size(input_metadata_header); + const uint32_t metadata_track_descriptions_size = get_metadata_track_descriptions_size(input_metadata_header); + const uint32_t metadata_contributing_error_size = 0; // We'll strip it! + + uint32_t metadata_size = 0; + metadata_size += metadata_track_list_name_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_track_names_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_parent_track_indices_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_track_descriptions_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_contributing_error_size; + + if (metadata_size != 0) + { + buffer_size = align_to(buffer_size, 4); + buffer_size += metadata_size; + + buffer_size = align_to(buffer_size, 4); + buffer_size += sizeof(optional_metadata_header); + } + else + buffer_size += 15; // Ensure we have sufficient padding for unaligned 16 byte loads + + // Allocate our new buffer + uint8_t* buffer = allocate_type_array_aligned(context.allocator, buffer_size, alignof(compressed_tracks)); + std::memset(buffer, 0, buffer_size); + + uint8_t* buffer_start = buffer; + out_compressed_tracks[list_index] = reinterpret_cast(buffer); + + raw_buffer_header* buffer_header = safe_ptr_cast(buffer); + buffer += sizeof(raw_buffer_header); + + tracks_header* header = safe_ptr_cast(buffer); + buffer += sizeof(tracks_header); + + // Copy our header and update the parts that change + std::memcpy(header, &input_header, sizeof(tracks_header)); + + header->set_has_database(true); + header->set_has_metadata(metadata_size != 0); + + transform_tracks_header* transforms_header = safe_ptr_cast(buffer); + buffer += sizeof(transform_tracks_header); + + // Copy our header and update the parts that change + std::memcpy(transforms_header, &input_transforms_header, sizeof(transform_tracks_header)); + + const uint32_t segment_start_indices_offset = align_to(sizeof(transform_tracks_header), 4); // Relative to the start of our transform_tracks_header + transforms_header->database_header_offset = align_to(segment_start_indices_offset + segment_start_indices_size, 4); + transforms_header->segment_headers_offset = align_to(transforms_header->database_header_offset + uint32_t(sizeof(tracks_database_header)), 4); + transforms_header->sub_track_types_offset = align_to(transforms_header->segment_headers_offset + segment_headers_size, 4); + transforms_header->constant_track_data_offset = align_to(transforms_header->sub_track_types_offset + packed_sub_track_buffer_size, 4); + transforms_header->clip_range_data_offset = align_to(transforms_header->constant_track_data_offset + constant_data_size, 4); + + // Copy our segment start indices, they do not change + if (input_transforms_header.has_multiple_segments()) + std::memcpy(transforms_header->get_segment_start_indices(), input_transforms_header.get_segment_start_indices(), segment_start_indices_size); + + // Setup our database header + tracks_database_header* tracks_db_header = transforms_header->get_database_header(); + tracks_db_header->clip_header_offset = clip_header_offset; + + // Update our clip header offset + clip_header_offset += sizeof(database_runtime_clip_header); + clip_header_offset += sizeof(database_runtime_segment_header) * input_transforms_header.num_segments; + + // Write our new segment headers + const uint32_t segment_data_base_offset = transforms_header->clip_range_data_offset + clip_range_data_size; + write_segment_headers(tier_mapping, list_index, input_transforms_header, input_segment_headers, segment_data_base_offset, transforms_header->get_segment_tier0_headers()); + + // Copy our sub-track types, they do not change + std::memcpy(transforms_header->get_sub_track_types(), input_transforms_header.get_sub_track_types(), packed_sub_track_buffer_size); + + // Copy our constant track data, it does not change + std::memcpy(transforms_header->get_constant_track_data(), input_transforms_header.get_constant_track_data(), constant_data_size); + + // Copy our clip range data, it does not change + std::memcpy(transforms_header->get_clip_range_data(), input_transforms_header.get_clip_range_data(), clip_range_data_size); + + // Write our new segment data + write_segment_data(tier_mapping, list_index, input_transforms_header, input_segment_headers, *transforms_header, transforms_header->get_segment_tier0_headers()); + + if (metadata_size != 0) + { + optional_metadata_header* metadata_header = reinterpret_cast(buffer_start + buffer_size - sizeof(optional_metadata_header)); + uint32_t metadata_offset = metadata_start_offset; // Relative to the start of our compressed_tracks + + // Setup our metadata offsets + if (metadata_track_list_name_size != 0) + { + metadata_header->track_list_name = metadata_offset; + metadata_offset += metadata_track_list_name_size; + } + else + metadata_header->track_list_name = invalid_ptr_offset(); + + if (metadata_track_names_size != 0) + { + metadata_header->track_name_offsets = metadata_offset; + metadata_offset += metadata_track_names_size; + } + else + metadata_header->track_name_offsets = invalid_ptr_offset(); + + if (metadata_parent_track_indices_size != 0) + { + metadata_header->parent_track_indices = metadata_offset; + metadata_offset += metadata_parent_track_indices_size; + } + else + metadata_header->parent_track_indices = invalid_ptr_offset(); + + if (metadata_track_descriptions_size != 0) + { + metadata_header->track_descriptions = metadata_offset; + metadata_offset += metadata_track_descriptions_size; + } + else + metadata_header->track_descriptions = invalid_ptr_offset(); + + // Strip the contributing error data, no longer needed + metadata_header->contributing_error = invalid_ptr_offset(); + + ACL_ASSERT((metadata_offset - metadata_start_offset) == metadata_size, "Unexpected metadata size"); + + // Copy our metadata, it does not change + std::memcpy(metadata_header->get_track_list_name(*out_compressed_tracks[list_index]), input_metadata_header.get_track_list_name(*input_tracks), metadata_track_list_name_size); + std::memcpy(metadata_header->get_track_name_offsets(*out_compressed_tracks[list_index]), input_metadata_header.get_track_name_offsets(*input_tracks), metadata_track_names_size); + std::memcpy(metadata_header->get_parent_track_indices(*out_compressed_tracks[list_index]), input_metadata_header.get_parent_track_indices(*input_tracks), metadata_parent_track_indices_size); + std::memcpy(metadata_header->get_track_descriptions(*out_compressed_tracks[list_index]), input_metadata_header.get_track_descriptions(*input_tracks), metadata_track_descriptions_size); + } + + // Finish the compressed tracks raw buffer header + buffer_header->size = buffer_size; + buffer_header->hash = hash32(safe_ptr_cast(header), buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header + + ACL_ASSERT(out_compressed_tracks[list_index]->is_valid(true).empty(), "Failed to build compressed tracks"); + } + } + + // Returns the number of clips written + inline uint32_t write_database_clip_metadata(const compressed_tracks* const* db_compressed_tracks_list, uint32_t num_tracks, database_clip_metadata* clip_metadatas) + { + if (clip_metadatas == nullptr) + return num_tracks; // Nothing to write + + uint32_t clip_header_offset = 0; + + for (uint32_t tracks_index = 0; tracks_index < num_tracks; ++tracks_index) + { + const compressed_tracks* tracks = db_compressed_tracks_list[tracks_index]; + + database_clip_metadata& clip_metadata = clip_metadatas[tracks_index]; + clip_metadata.clip_hash = tracks->get_hash(); + clip_metadata.clip_header_offset = clip_header_offset; + + const transform_tracks_header& transforms_header = get_transform_tracks_header(*tracks); + clip_header_offset += sizeof(database_runtime_clip_header) + sizeof(database_runtime_segment_header) * transforms_header.num_segments; + } + + return num_tracks; + } + + // Returns a pointer to the first frame of the given segment and the number of frames contained + inline const frame_tier_mapping* find_segment_frames(const database_tier_mapping& tier_mapping, uint32_t tracks_index, uint32_t segment_index, uint32_t& out_num_frames) + { + for (uint32_t frame_index = 0; frame_index < tier_mapping.num_frames; ++frame_index) + { + const frame_tier_mapping& frame = tier_mapping.frames[frame_index]; + if (frame.tracks_index != tracks_index) + continue; // This is not the tracks instance we care about + + if (frame.segment_index != segment_index) + continue; // This is not the segment we care about + + // Found our first frame, count how many we have + uint32_t num_segment_frames = 0; + for (uint32_t frame_index2 = frame_index; frame_index2 < tier_mapping.num_frames; ++frame_index2) + { + const frame_tier_mapping& frame2 = tier_mapping.frames[frame_index2]; + if (frame2.tracks_index != tracks_index) + break; // This is not the tracks instance we care about + + if (frame2.segment_index != segment_index) + break; // This is not the segment we care about + + num_segment_frames++; + } + + // Done + out_num_frames = num_segment_frames; + return &frame; + } + + // This segment doesn't contain any frames + out_num_frames = 0; + return nullptr; + } + + // Returns the number of bits contained in the segment animated data + inline uint32_t calculate_segment_animated_data_bit_size(const frame_tier_mapping* frames, uint32_t num_frames) + { + if (num_frames == 0) + return 0; // Empty segment + + // Every frame in a segment has the same size + return frames->frame_bit_size * num_frames; + } + + // Returns a bitset with the which frames are present in this segment + inline uint32_t build_sample_indices(const frame_tier_mapping* frames, uint32_t num_frames) + { + const bitset_description desc = bitset_description::make_from_num_bits<32>(); + + uint32_t sample_indices = 0; + for (uint32_t frame_index = 0; frame_index < num_frames; ++frame_index) + { + const frame_tier_mapping& frame = frames[frame_index]; + + bitset_set(&sample_indices, desc, frame.segment_frame_index, true); + } + + return sample_indices; + } + + // Returns the number of bytes written + inline uint32_t write_segment_data(const frame_tier_mapping* frames, uint32_t num_frames, uint8_t* out_segment_data) + { + uint64_t num_bits_written = 0; + + for (uint32_t frame_index = 0; frame_index < num_frames; ++frame_index) + { + const frame_tier_mapping& frame = frames[frame_index]; + + const uint32_t frame_bit_offset = frame.segment_frame_index * frame.frame_bit_size; + memcpy_bits(out_segment_data, num_bits_written, frame.animated_data, frame_bit_offset, frame.frame_bit_size); + num_bits_written += frame.frame_bit_size; + } + + return uint32_t(num_bits_written + 7) / 8; + } + + // Returns the number of chunks written + inline uint32_t write_database_chunk_descriptions(const frame_assignment_context& context, const compression_database_settings& settings, quality_tier tier, database_chunk_description* chunk_descriptions) + { + ACL_ASSERT(tier != quality_tier::highest_importance, "No chunks for the high importance tier"); + const database_tier_mapping& tier_mapping = context.get_tier_mapping(tier); + if (tier_mapping.is_empty()) + return 0; // No data + + const uint32_t max_chunk_size = settings.max_chunk_size; + const uint32_t simd_padding = 15; + + uint32_t bulk_data_offset = 0; + uint32_t chunk_size = sizeof(database_chunk_header); + uint32_t num_chunks = 0; + + for (uint32_t tracks_index = 0; tracks_index < context.num_compressed_tracks; ++tracks_index) + { + const compressed_tracks* tracks = context.compressed_tracks_list[tracks_index]; + const transform_tracks_header& transforms_header = get_transform_tracks_header(*tracks); + + for (uint32_t segment_index = 0; segment_index < transforms_header.num_segments; ++segment_index) + { + uint32_t num_segment_frames; + const frame_tier_mapping* segment_frames = find_segment_frames(tier_mapping, tracks_index, segment_index, num_segment_frames); + + const uint32_t segment_data_bit_size = calculate_segment_animated_data_bit_size(segment_frames, num_segment_frames); + const uint32_t segment_data_size = (segment_data_bit_size + 7) / 8; + ACL_ASSERT(segment_data_size + simd_padding + uint32_t(sizeof(database_chunk_segment_header)) <= max_chunk_size, "Segment is larger than our max chunk size"); + + const uint32_t new_chunk_size = chunk_size + segment_data_size + simd_padding + sizeof(database_chunk_segment_header); + if (new_chunk_size >= max_chunk_size) + { + // Chunk is full, write it out and start a new one + if (chunk_descriptions != nullptr) + { + chunk_descriptions[num_chunks].size = max_chunk_size; + chunk_descriptions[num_chunks].offset = bulk_data_offset; + } + + bulk_data_offset += max_chunk_size; + chunk_size = sizeof(database_chunk_header); + num_chunks++; + } + + chunk_size += segment_data_size + sizeof(database_chunk_segment_header); + + ACL_ASSERT(chunk_size <= max_chunk_size, "Expected a valid chunk size, segment is larger than max chunk size?"); + } + } + + // If we have leftover data, finalize our last chunk + // Because of this, it might end up being larger than the max chunk size which is fine since we can't split a segment over two chunks + if (chunk_size != sizeof(database_chunk_header)) + { + if (chunk_descriptions != nullptr) + { + chunk_descriptions[num_chunks].size = chunk_size + simd_padding; // Last chunk needs padding + chunk_descriptions[num_chunks].offset = bulk_data_offset; + } + + num_chunks++; + } + + return num_chunks; + } + + // Returns the size of the bulk data + inline uint32_t write_database_bulk_data(const frame_assignment_context& context, const compression_database_settings& settings, quality_tier tier, const compressed_tracks* const* db_compressed_tracks_list, uint8_t* bulk_data) + { + ACL_ASSERT(tier != quality_tier::highest_importance, "No bulk data for the high importance tier"); + const database_tier_mapping& tier_mapping = context.get_tier_mapping(tier); + if (tier_mapping.is_empty()) + return 0; // No data + + const uint32_t max_chunk_size = settings.max_chunk_size; + const uint32_t simd_padding = 15; + + database_chunk_header* chunk_header = nullptr; + database_chunk_segment_header* segment_chunk_headers = nullptr; + + uint32_t bulk_data_offset = 0; + uint32_t chunk_sample_data_offset = 0; + uint32_t chunk_size = sizeof(database_chunk_header); + uint32_t chunk_index = 0; + + uint32_t clip_header_offset = 0; + + if (bulk_data != nullptr) + { + // Setup our chunk headers + chunk_header = safe_ptr_cast(bulk_data); + chunk_header->index = chunk_index; + chunk_header->size = 0; + chunk_header->num_segments = 0; + + segment_chunk_headers = chunk_header->get_segment_headers(); + } + + // We first iterate to find our chunk delimitations and write our headers + for (uint32_t tracks_index = 0; tracks_index < context.num_compressed_tracks; ++tracks_index) + { + const compressed_tracks* tracks = db_compressed_tracks_list[tracks_index]; + const transform_tracks_header& transforms_header = get_transform_tracks_header(*tracks); + + uint32_t segment_header_offset = clip_header_offset + sizeof(database_runtime_clip_header); + + for (uint32_t segment_index = 0; segment_index < transforms_header.num_segments; ++segment_index) + { + uint32_t num_segment_frames; + const frame_tier_mapping* segment_frames = find_segment_frames(tier_mapping, tracks_index, segment_index, num_segment_frames); + + const uint32_t segment_data_bit_size = calculate_segment_animated_data_bit_size(segment_frames, num_segment_frames); + const uint32_t segment_data_size = (segment_data_bit_size + 7) / 8; + ACL_ASSERT(segment_data_size + simd_padding + uint32_t(sizeof(database_chunk_segment_header)) <= max_chunk_size, "Segment is larger than our max chunk size"); + + const uint32_t new_chunk_size = chunk_size + segment_data_size + simd_padding + sizeof(database_chunk_segment_header); + if (new_chunk_size >= max_chunk_size) + { + // Finalize our chunk header + if (bulk_data != nullptr) + chunk_header->size = max_chunk_size; + + // Chunk is full, start a new one + bulk_data_offset += max_chunk_size; + chunk_sample_data_offset = 0; + chunk_size = sizeof(database_chunk_header); + chunk_index++; + + // Setup our chunk headers + if (bulk_data != nullptr) + { + chunk_header = safe_ptr_cast(bulk_data + bulk_data_offset); + chunk_header->index = chunk_index; + chunk_header->size = 0; + chunk_header->num_segments = 0; + + segment_chunk_headers = chunk_header->get_segment_headers(); + } + } + + if (bulk_data != nullptr) + { + // TODO: Should we skip segments with no data? + + // Update our chunk headers + database_chunk_segment_header& segment_chunk_header = segment_chunk_headers[chunk_header->num_segments]; + segment_chunk_header.clip_hash = tracks->get_hash(); + segment_chunk_header.sample_indices = build_sample_indices(segment_frames, num_segment_frames); + segment_chunk_header.samples_offset = chunk_sample_data_offset; // Relative to start of sample data for now + segment_chunk_header.clip_header_offset = clip_header_offset; + segment_chunk_header.segment_header_offset = segment_header_offset; + + chunk_header->num_segments++; + } + + chunk_size += segment_data_size + sizeof(database_chunk_segment_header); + chunk_sample_data_offset += segment_data_size; + segment_header_offset += sizeof(database_runtime_segment_header); + + ACL_ASSERT(chunk_size <= max_chunk_size, "Expected a valid chunk size, segment is larger than max chunk size?"); + } + + clip_header_offset = segment_header_offset; + } + + // If we have leftover data, finalize our last chunk + // Because of this, it might end up being larger than the max chunk size which is fine since we can't split a segment over two chunks + if (chunk_size != sizeof(database_chunk_header)) + { + chunk_size += simd_padding; // Last chunk needs padding + + if (bulk_data != nullptr) + { + // Finalize our chunk header + chunk_header->size = chunk_size; + } + + bulk_data_offset += chunk_size; + } + + // Now that our chunk headers are written, write our sample data + if (bulk_data != nullptr) + { + // Reset our header pointers + chunk_header = safe_ptr_cast(bulk_data); + segment_chunk_headers = chunk_header->get_segment_headers(); + + uint32_t chunk_segment_index = 0; + for (uint32_t tracks_index = 0; tracks_index < context.num_compressed_tracks; ++tracks_index) + { + const compressed_tracks* tracks = db_compressed_tracks_list[tracks_index]; + const transform_tracks_header& transforms_header = get_transform_tracks_header(*tracks); + + for (uint32_t segment_index = 0; segment_index < transforms_header.num_segments; ++segment_index) + { + uint32_t num_segment_frames; + const frame_tier_mapping* segment_frames = find_segment_frames(tier_mapping, tracks_index, segment_index, num_segment_frames); + + const uint32_t segment_data_bit_size = calculate_segment_animated_data_bit_size(segment_frames, num_segment_frames); + const uint32_t segment_data_size = (segment_data_bit_size + 7) / 8; + + if (chunk_segment_index >= chunk_header->num_segments) + { + // We hit the next chunk, update our pointers + chunk_header = add_offset_to_ptr(chunk_header, chunk_header->size); + segment_chunk_headers = chunk_header->get_segment_headers(); + chunk_segment_index = 0; + } + + // Calculate the finale offset for our chunk's data relative to the bulk data start and the final header size + const uint32_t chunk_data_offset = static_cast(reinterpret_cast(chunk_header) - bulk_data); + const uint32_t chunk_header_size = sizeof(database_chunk_header) + chunk_header->num_segments * sizeof(database_chunk_segment_header); + + // Update the sample offset from being relative to the start of the sample data to the start of the bulk data + database_chunk_segment_header& segment_chunk_header = segment_chunk_headers[chunk_segment_index]; + segment_chunk_header.samples_offset = chunk_data_offset + chunk_header_size + segment_chunk_header.samples_offset; + + uint8_t* animated_data = segment_chunk_header.samples_offset.add_to(bulk_data); + const uint32_t size = write_segment_data(segment_frames, num_segment_frames, animated_data); + ACL_ASSERT(size == segment_data_size, "Unexpected segment data size"); (void)size; (void)segment_data_size; + + chunk_segment_index++; + } + } + } + + return bulk_data_offset; + } + + inline compressed_database* build_compressed_database(const frame_assignment_context& context, const compression_database_settings& settings, const compressed_tracks* const* db_compressed_tracks_list) + { + // Find our chunk limits and calculate our database size + const uint32_t num_tracks = write_database_clip_metadata(db_compressed_tracks_list, context.num_compressed_tracks, nullptr); + const uint32_t num_segments = calculate_num_segments(db_compressed_tracks_list, context.num_compressed_tracks); + const uint32_t num_medium_chunks = write_database_chunk_descriptions(context, settings, quality_tier::medium_importance, nullptr); + const uint32_t num_low_chunks = write_database_chunk_descriptions(context, settings, quality_tier::lowest_importance, nullptr); + + // Pad medium tier bulk data to ensure alignment since the lowest tier follows + const uint32_t bulk_data_medium_size = write_database_bulk_data(context, settings, quality_tier::medium_importance, db_compressed_tracks_list, nullptr); + const uint32_t aligned_bulk_data_medium_size = align_to(bulk_data_medium_size, k_database_bulk_data_alignment); + + // No need to pad lowest tier since it is last + const uint32_t bulk_data_low_size = write_database_bulk_data(context, settings, quality_tier::lowest_importance, db_compressed_tracks_list, nullptr); + + uint32_t database_buffer_size = 0; + database_buffer_size += sizeof(raw_buffer_header); // Header + database_buffer_size += sizeof(database_header); // Header + + database_buffer_size = align_to(database_buffer_size, 4); // Align chunk descriptions + database_buffer_size += num_medium_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer_size = align_to(database_buffer_size, 4); // Align chunk descriptions + database_buffer_size += num_low_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer_size = align_to(database_buffer_size, 4); // Align clip hashes + database_buffer_size += num_tracks * sizeof(database_clip_metadata); // Clip metadata + + database_buffer_size = align_to(database_buffer_size, k_database_bulk_data_alignment); // Align bulk data + database_buffer_size += aligned_bulk_data_medium_size; // Bulk data + database_buffer_size += bulk_data_low_size; // Bulk data + + uint8_t* database_buffer = allocate_type_array_aligned(context.allocator, database_buffer_size, alignof(compressed_database)); + std::memset(database_buffer, 0, database_buffer_size); + + compressed_database* database = reinterpret_cast(database_buffer); + + const uint8_t* database_buffer_start = database_buffer; + + raw_buffer_header* database_buffer_header = safe_ptr_cast(database_buffer); + database_buffer += sizeof(raw_buffer_header); + + const uint8_t* db_header_start = database_buffer; + database_header* db_header = safe_ptr_cast(database_buffer); + database_buffer += sizeof(database_header); + + // Write our header + db_header->tag = static_cast(buffer_tag32::compressed_database); + db_header->version = compressed_tracks_version16::latest; + db_header->num_chunks[0] = num_medium_chunks; + db_header->num_chunks[1] = num_low_chunks; + db_header->max_chunk_size = settings.max_chunk_size; + db_header->num_clips = num_tracks; + db_header->num_segments = num_segments; + db_header->bulk_data_size[0] = aligned_bulk_data_medium_size; + db_header->bulk_data_size[1] = bulk_data_low_size; + db_header->set_is_bulk_data_inline(true); // Data is always inline when compressing + + database_buffer = align_to(database_buffer, 4); // Align chunk descriptions + database_buffer += num_medium_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer = align_to(database_buffer, 4); // Align chunk descriptions + database_buffer += num_low_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer = align_to(database_buffer, 4); // Align clip hashes + db_header->clip_metadata_offset = uint32_t(database_buffer - db_header_start); // Clip metadata + database_buffer += num_tracks * sizeof(database_clip_metadata); // Clip metadata + + database_buffer = align_to(database_buffer, k_database_bulk_data_alignment); // Align bulk data + if (aligned_bulk_data_medium_size != 0) + db_header->bulk_data_offset[0] = uint32_t(database_buffer - db_header_start); // Bulk data + else + db_header->bulk_data_offset[0] = invalid_ptr_offset(); + database_buffer += aligned_bulk_data_medium_size; // Bulk data + + if (bulk_data_low_size != 0) + db_header->bulk_data_offset[1] = uint32_t(database_buffer - db_header_start); // Bulk data + else + db_header->bulk_data_offset[1] = invalid_ptr_offset(); + database_buffer += bulk_data_low_size; // Bulk data + + // Write our chunk descriptions + const uint32_t num_written_medium_chunks = write_database_chunk_descriptions(context, settings, quality_tier::medium_importance, db_header->get_chunk_descriptions_medium()); + ACL_ASSERT(num_written_medium_chunks == num_medium_chunks, "Unexpected amount of data written"); (void)num_written_medium_chunks; + + const uint32_t num_written_low_chunks = write_database_chunk_descriptions(context, settings, quality_tier::lowest_importance, db_header->get_chunk_descriptions_low()); + ACL_ASSERT(num_written_low_chunks == num_low_chunks, "Unexpected amount of data written"); (void)num_written_low_chunks; + + // Write our clip metadata + const uint32_t num_written_tracks = write_database_clip_metadata(db_compressed_tracks_list, context.num_compressed_tracks, db_header->get_clip_metadatas()); + ACL_ASSERT(num_written_tracks == num_tracks, "Unexpected amount of data written"); (void)num_written_tracks; + + // Write our bulk data + const uint32_t written_bulk_data_medium_size = write_database_bulk_data(context, settings, quality_tier::medium_importance, db_compressed_tracks_list, db_header->get_bulk_data_medium()); + ACL_ASSERT(written_bulk_data_medium_size == bulk_data_medium_size, "Unexpected amount of data written"); (void)written_bulk_data_medium_size; + db_header->bulk_data_hash[0] = hash32(db_header->get_bulk_data_medium(), aligned_bulk_data_medium_size); + + const uint32_t written_bulk_data_low_size = write_database_bulk_data(context, settings, quality_tier::lowest_importance, db_compressed_tracks_list, db_header->get_bulk_data_low()); + ACL_ASSERT(written_bulk_data_low_size == bulk_data_low_size, "Unexpected amount of data written"); (void)written_bulk_data_low_size; + db_header->bulk_data_hash[1] = hash32(db_header->get_bulk_data_low(), bulk_data_low_size); + + ACL_ASSERT(uint32_t(database_buffer - database_buffer_start) == database_buffer_size, "Unexpected amount of data written"); (void)database_buffer_start; + +#if defined(ACL_HAS_ASSERT_CHECKS) + // Make sure nobody overwrote our padding (contained in last chunk if we have data) + if (bulk_data_low_size != 0) + { + for (const uint8_t* padding = database_buffer - 15; padding < database_buffer; ++padding) + ACL_ASSERT(*padding == 0, "Padding was overwritten"); + } +#endif + + // Finish the raw buffer header + database_buffer_header->size = database_buffer_size; + database_buffer_header->hash = hash32(safe_ptr_cast(db_header), database_buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header + + ACL_ASSERT(database->is_valid(true).empty(), "Failed to build compressed database"); + + return database; + } + } + + inline error_result build_database(iallocator& allocator, const compression_database_settings& settings, + const compressed_tracks* const* compressed_tracks_list, uint32_t num_compressed_tracks, + compressed_tracks** out_compressed_tracks, compressed_database*& out_database) + { + using namespace acl_impl; + + // Reset everything just to be safe + for (uint32_t list_index = 0; list_index < num_compressed_tracks; ++list_index) + out_compressed_tracks[list_index] = nullptr; + out_database = nullptr; + + // Validate everything and early out if something isn't right + const error_result settings_result = settings.is_valid(); + if (settings_result.any()) + return error_result("Compression database settings are invalid"); + + if (compressed_tracks_list == nullptr || num_compressed_tracks == 0) + return error_result("No compressed track list provided"); + + for (uint32_t list_index = 0; list_index < num_compressed_tracks; ++list_index) + { + const compressed_tracks* tracks = compressed_tracks_list[list_index]; + if (tracks == nullptr) + return error_result("Compressed track list contains a null entry"); + + const error_result tracks_result = tracks->is_valid(false); + if (tracks_result.any()) + return error_result("Compressed track instance is invalid"); + + if (tracks->has_database()) + return error_result("Compressed track instance is already bound to a database"); + + const tracks_header& header = get_tracks_header(*tracks); + if (!header.get_has_metadata()) + return error_result("Compressed track instance does not contain any metadata"); + + const optional_metadata_header& metadata_header = get_optional_metadata_header(*tracks); + if (!metadata_header.contributing_error.is_valid()) + return error_result("Compressed track instance does not contain contributing error metadata"); + } + + // Calculate how many frames are movable to the database + // A frame is movable if it isn't the first or last frame of a segment + const uint32_t num_frames = calculate_num_frames(compressed_tracks_list, num_compressed_tracks); + if (num_frames == 0) + return error_result("All compressed track lists are empty"); + + const uint32_t num_movable_frames = calculate_num_movable_frames(compressed_tracks_list, num_compressed_tracks); + ACL_ASSERT(num_movable_frames < num_frames, "Cannot move out more frames than we have"); + + // Calculate how many frames we'll move to every tier + const uint32_t num_low_importance_frames = std::min(num_movable_frames, uint32_t(settings.low_importance_tier_proportion * float(num_frames))); + const uint32_t num_medium_importance_frames = std::min(num_movable_frames - num_low_importance_frames, uint32_t(settings.medium_importance_tier_proportion * float(num_frames))); + ACL_ASSERT(num_low_importance_frames + num_medium_importance_frames <= num_movable_frames, "Cannot move out more frames than we have"); + + // Non-movable frames end up being high importance and remain in the compressed clip + const uint32_t num_high_importance_frames = num_frames - num_medium_importance_frames - num_low_importance_frames; + + frame_assignment_context context(allocator, compressed_tracks_list, num_compressed_tracks, num_movable_frames); + context.set_tier_num_frames(quality_tier::highest_importance, num_high_importance_frames); + context.set_tier_num_frames(quality_tier::medium_importance, num_medium_importance_frames); + context.set_tier_num_frames(quality_tier::lowest_importance, num_low_importance_frames); + + // Assign every frame to its tier + assign_frames_to_tiers(context); + + // Build our new compressed track instances with the high importance tier data + build_compressed_tracks(context, out_compressed_tracks); + + // Build our database with the lower tier data + out_database = build_compressed_database(context, settings, out_compressed_tracks); + + return error_result(); + } + + inline error_result split_database_bulk_data(iallocator& allocator, const compressed_database& database, compressed_database*& out_split_database, uint8_t*& out_bulk_data_medium, uint8_t*& out_bulk_data_low) + { + using namespace acl_impl; + + const error_result result = database.is_valid(true); + if (result.any()) + return result; + + if (!database.is_bulk_data_inline()) + return error_result("Bulk data is not inline in source database"); + + const uint32_t total_size = database.get_total_size(); + const uint32_t bulk_data_medium_size = database.get_bulk_data_size(quality_tier::medium_importance); + const uint32_t bulk_data_low_size = database.get_bulk_data_size(quality_tier::lowest_importance); + const uint32_t db_size = total_size - bulk_data_medium_size - bulk_data_low_size; + + // Allocate and setup our new database + uint8_t* database_buffer = allocate_type_array_aligned(allocator, db_size, alignof(compressed_database)); + out_split_database = reinterpret_cast(database_buffer); + + std::memcpy(database_buffer, &database, db_size); + + raw_buffer_header* database_buffer_header = safe_ptr_cast(database_buffer); + database_buffer += sizeof(raw_buffer_header); + + database_header* db_header = safe_ptr_cast(database_buffer); + database_buffer += sizeof(database_header); + + db_header->bulk_data_offset[0] = invalid_ptr_offset(); + db_header->bulk_data_offset[1] = invalid_ptr_offset(); + db_header->set_is_bulk_data_inline(false); + + database_buffer_header->size = db_size; + database_buffer_header->hash = hash32(safe_ptr_cast(db_header), db_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header + ACL_ASSERT(out_split_database->is_valid(true).empty(), "Failed to split database"); + + // Allocate and setup our new bulk data + uint8_t* bulk_data_medium_buffer = bulk_data_medium_size != 0 ? allocate_type_array_aligned(allocator, bulk_data_medium_size, k_database_bulk_data_alignment) : nullptr; + out_bulk_data_medium = bulk_data_medium_buffer; + + std::memcpy(bulk_data_medium_buffer, database.get_bulk_data(quality_tier::medium_importance), bulk_data_medium_size); + + uint8_t* bulk_data_low_buffer = bulk_data_low_size != 0 ? allocate_type_array_aligned(allocator, bulk_data_low_size, k_database_bulk_data_alignment) : nullptr; + out_bulk_data_low = bulk_data_low_buffer; + + std::memcpy(bulk_data_low_buffer, database.get_bulk_data(quality_tier::lowest_importance), bulk_data_low_size); + +#if defined(ACL_HAS_ASSERT_CHECKS) + const uint32_t bulk_data_medium_hash = hash32(bulk_data_medium_buffer, bulk_data_medium_size); + ACL_ASSERT(bulk_data_medium_hash == database.get_bulk_data_hash(quality_tier::medium_importance), "Bulk data hash mismatch"); + + const uint32_t bulk_data_low_hash = hash32(bulk_data_low_buffer, bulk_data_low_size); + ACL_ASSERT(bulk_data_low_hash == database.get_bulk_data_hash(quality_tier::lowest_importance), "Bulk data hash mismatch"); +#endif + + return error_result(); + } + + inline error_result strip_database_quality_tier(iallocator& allocator, const compressed_database& database, quality_tier tier, compressed_database*& out_stripped_database) + { + using namespace acl_impl; + + const error_result result = database.is_valid(true); + if (result.any()) + return result; + + if (tier == quality_tier::highest_importance) + return error_result("The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + + if (!database.has_bulk_data(tier)) + return error_result("Cannot strip an empty quality tier"); + + const uint32_t tier_index = uint32_t(tier) - 1; + const bool is_bulk_data_inline = database.is_bulk_data_inline(); + const database_header& ref_header = get_database_header(database); + const uint32_t num_medium_chunks = tier != quality_tier::medium_importance ? database.get_num_chunks(quality_tier::medium_importance) : 0; + const uint32_t num_low_chunks = tier != quality_tier::lowest_importance ? database.get_num_chunks(quality_tier::lowest_importance) : 0; + const uint32_t num_tracks = ref_header.num_clips; + + // Bulk data sizes are already padded for alignment + const uint32_t bulk_data_medium_size = tier != quality_tier::medium_importance ? database.get_bulk_data_size(quality_tier::medium_importance) : 0; + const uint32_t bulk_data_low_size = tier != quality_tier::lowest_importance ? database.get_bulk_data_size(quality_tier::lowest_importance) : 0; + + uint32_t database_buffer_size = 0; + database_buffer_size += sizeof(raw_buffer_header); // Header + database_buffer_size += sizeof(database_header); // Header + + database_buffer_size = align_to(database_buffer_size, 4); // Align chunk descriptions + database_buffer_size += num_medium_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer_size = align_to(database_buffer_size, 4); // Align chunk descriptions + database_buffer_size += num_low_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer_size = align_to(database_buffer_size, 4); // Align clip hashes + database_buffer_size += num_tracks * sizeof(database_clip_metadata); // Clip metadata + + database_buffer_size = align_to(database_buffer_size, k_database_bulk_data_alignment); // Align bulk data + database_buffer_size += bulk_data_medium_size; // Bulk data + database_buffer_size += bulk_data_low_size; // Bulk data + + // Allocate and setup our new database + uint8_t* database_buffer = allocate_type_array_aligned(allocator, database_buffer_size, alignof(compressed_database)); + std::memset(database_buffer, 0, database_buffer_size); + out_stripped_database = reinterpret_cast(database_buffer); + + raw_buffer_header* database_buffer_header = safe_ptr_cast(database_buffer); + database_buffer += sizeof(raw_buffer_header); + + const uint8_t* db_header_start = database_buffer; + database_header* db_header = safe_ptr_cast(database_buffer); + database_buffer += sizeof(database_header); + + // Copy our header + std::memcpy(db_header, &get_database_header(database), sizeof(database_header)); + + database_buffer = align_to(database_buffer, 4); // Align chunk descriptions + database_buffer += num_medium_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer = align_to(database_buffer, 4); // Align chunk descriptions + database_buffer += num_low_chunks * sizeof(database_chunk_description); // Chunk descriptions + + database_buffer = align_to(database_buffer, 4); // Align clip hashes + db_header->clip_metadata_offset = uint32_t(database_buffer - db_header_start); // Clip metadata + database_buffer += num_tracks * sizeof(database_clip_metadata); // Clip metadata + + database_buffer = align_to(database_buffer, k_database_bulk_data_alignment); // Align bulk data + if (bulk_data_medium_size != 0) + db_header->bulk_data_offset[0] = uint32_t(database_buffer - db_header_start); // Bulk data + else + db_header->bulk_data_offset[0] = invalid_ptr_offset(); + database_buffer += bulk_data_medium_size; // Bulk data + + if (bulk_data_low_size != 0) + db_header->bulk_data_offset[1] = uint32_t(database_buffer - db_header_start); // Bulk data + else + db_header->bulk_data_offset[1] = invalid_ptr_offset(); + database_buffer += bulk_data_low_size; // Bulk data + + // Zero out our stripped tier + db_header->num_chunks[tier_index] = 0; + db_header->bulk_data_size[tier_index] = 0; + db_header->bulk_data_offset[tier_index] = invalid_ptr_offset(); + db_header->bulk_data_hash[tier_index] = hash32(nullptr, 0); + + // Copy our chunk descriptions + if (tier != quality_tier::medium_importance) + std::memcpy(db_header->get_chunk_descriptions_medium(), ref_header.get_chunk_descriptions_medium(), num_medium_chunks * sizeof(database_chunk_description)); + else if (tier != quality_tier::lowest_importance) + std::memcpy(db_header->get_chunk_descriptions_low(), ref_header.get_chunk_descriptions_low(), num_low_chunks * sizeof(database_chunk_description)); + + // Copy our clip metadata + std::memcpy(db_header->get_clip_metadatas(), ref_header.get_clip_metadatas(), num_tracks * sizeof(database_clip_metadata)); + + // Copy our bulk data + if (is_bulk_data_inline) + { + // Copy the remaining bulk data + if (tier != quality_tier::medium_importance) + { + const uint8_t* src_bulk_data = database.get_bulk_data(quality_tier::medium_importance); + uint8_t* dst_bulk_data = db_header->get_bulk_data_medium(); + std::memcpy(dst_bulk_data, src_bulk_data, bulk_data_medium_size); + } + else if (tier != quality_tier::lowest_importance) + { + const uint8_t* src_bulk_data = database.get_bulk_data(quality_tier::lowest_importance); + uint8_t* dst_bulk_data = db_header->get_bulk_data_low(); + std::memcpy(dst_bulk_data, src_bulk_data, bulk_data_low_size); + } + } + + database_buffer_header->size = database_buffer_size; + database_buffer_header->hash = hash32(safe_ptr_cast(db_header), database_buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header + ACL_ASSERT(out_stripped_database->is_valid(true).empty(), "Failed to strip database"); + +#if defined(ACL_HAS_ASSERT_CHECKS) + if (is_bulk_data_inline) + { + if (tier != quality_tier::medium_importance) + { + const uint32_t bulk_data_size = out_stripped_database->get_bulk_data_size(quality_tier::medium_importance); + const uint8_t* bulk_data = out_stripped_database->get_bulk_data(quality_tier::medium_importance); + const uint32_t bulk_data_hash = hash32(bulk_data, bulk_data_size); + ACL_ASSERT(bulk_data_hash == database.get_bulk_data_hash(quality_tier::medium_importance), "Bulk data hash mismatch"); + } + else if (tier != quality_tier::lowest_importance) + { + const uint32_t bulk_data_size = out_stripped_database->get_bulk_data_size(quality_tier::lowest_importance); + const uint8_t* bulk_data = out_stripped_database->get_bulk_data(quality_tier::lowest_importance); + const uint32_t bulk_data_hash = hash32(bulk_data, bulk_data_size); + ACL_ASSERT(bulk_data_hash == database.get_bulk_data_hash(quality_tier::lowest_importance), "Bulk data hash mismatch"); + } + } +#endif + + return error_result(); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.impl.h new file mode 100644 index 00000000..9c47a70a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.impl.h @@ -0,0 +1,89 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compress.h + +#include "acl/version.h" +#include "acl/core/buffer_tag.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error.h" +#include "acl/core/error_result.h" +#include "acl/core/floating_point_exceptions.h" +#include "acl/core/iallocator.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/output_stats.h" +#include "acl/compression/track_array.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline error_result compress_track_list(iallocator& allocator, const track_array& track_list, const compression_settings& settings, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) + { + using namespace acl_impl; + + error_result result = track_list.is_valid(); + if (result.any()) + return result; + + // Disable floating point exceptions during compression because we leverage all SIMD lanes + // and we might intentionally divide by zero, etc. + scope_disable_fp_exceptions fp_off; + + if (track_list.get_track_category() == track_category8::transformf) + result = compress_transform_track_list(allocator, track_array_cast(track_list), settings, nullptr, additive_clip_format8::none, out_compressed_tracks, out_stats); + else + result = compress_scalar_track_list(allocator, track_list, settings, out_compressed_tracks, out_stats); + + return result; + } + + inline error_result compress_track_list(iallocator& allocator, const track_array_qvvf& track_list, const compression_settings& settings, const track_array_qvvf& additive_base_track_list, additive_clip_format8 additive_format, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) + { + using namespace acl_impl; + + error_result result = track_list.is_valid(); + if (result.any()) + return result; + + if (additive_format != additive_clip_format8::none) + { + result = additive_base_track_list.is_valid(); + if (result.any()) + return result; + } + + // Disable floating point exceptions during compression because we leverage all SIMD lanes + // and we might intentionally divide by zero, etc. + scope_disable_fp_exceptions fp_off; + + return compress_transform_track_list(allocator, track_list, settings, &additive_base_track_list, additive_format, out_compressed_tracks, out_stats); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.scalar.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.scalar.impl.h new file mode 100644 index 00000000..84829c2c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.scalar.impl.h @@ -0,0 +1,254 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compress.h + +#include "acl/version.h" +#include "acl/core/buffer_tag.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/error.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/scope_profiler.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/output_stats.h" +#include "acl/compression/track_array.h" +#include "acl/compression/impl/track_list_context.h" +#include "acl/compression/impl/constant_track_impl.h" +#include "acl/compression/impl/normalize_track_impl.h" +#include "acl/compression/impl/quantize_track_impl.h" +#include "acl/compression/impl/track_range_impl.h" +#include "acl/compression/impl/write_compression_stats_impl.h" +#include "acl/compression/impl/write_track_data_impl.h" +#include "acl/compression/impl/write_track_metadata.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline error_result compress_scalar_track_list(iallocator& allocator, const track_array& track_list, const compression_settings& settings, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) + { + (void)out_stats; + +#if defined(SJSON_CPP_WRITER) + scope_profiler compression_time; +#endif + + track_list_context context; + if (!initialize_context(allocator, track_list, context)) + return error_result("Some samples are not finite"); + + extract_track_ranges(context); + extract_constant_tracks(context); + normalize_tracks(context); + quantize_tracks(context); + + // Done transforming our input tracks, time to pack them into their final form + const uint32_t per_track_metadata_size = write_track_metadata(context, nullptr); + const uint32_t constant_values_size = write_track_constant_values(context, nullptr); + const uint32_t range_values_size = write_track_range_values(context, nullptr); + const uint32_t animated_num_bits = write_track_animated_values(context, nullptr); + const uint32_t animated_values_size = (animated_num_bits + 7) / 8; // Round up to nearest byte + const uint32_t num_bits_per_frame = context.num_samples != 0 ? (animated_num_bits / context.num_samples) : 0; + + uint32_t buffer_size = 0; + buffer_size += sizeof(raw_buffer_header); // Header + buffer_size += sizeof(tracks_header); // Header + buffer_size += sizeof(scalar_tracks_header); // Header + ACL_ASSERT(is_aligned_to(buffer_size, alignof(track_metadata)), "Invalid alignment"); + buffer_size += per_track_metadata_size; // Per track metadata + buffer_size = align_to(buffer_size, 4); // Align constant values + buffer_size += constant_values_size; // Constant values + ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment"); + buffer_size += range_values_size; // Range values + ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment"); + buffer_size += animated_values_size; // Animated values + + // Optional metadata + const uint32_t metadata_start_offset = align_to(buffer_size, 4); + const uint32_t metadata_track_list_name_size = settings.metadata.include_track_list_name ? write_track_list_name(track_list, nullptr) : 0; + const uint32_t metadata_track_names_size = settings.metadata.include_track_names ? write_track_names(track_list, context.track_output_indices, context.num_output_tracks, nullptr) : 0; + const uint32_t metadata_track_descriptions_size = settings.metadata.include_track_descriptions ? write_track_descriptions(track_list, context.track_output_indices, context.num_output_tracks, nullptr) : 0; + + uint32_t metadata_size = 0; + metadata_size += metadata_track_list_name_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_track_names_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_track_descriptions_size; + + if (metadata_size != 0) + { + buffer_size = align_to(buffer_size, 4); + buffer_size += metadata_size; + + buffer_size = align_to(buffer_size, 4); + buffer_size += sizeof(optional_metadata_header); + } + else + buffer_size += 15; // Ensure we have sufficient padding for unaligned 16 byte loads + + uint8_t* buffer = allocate_type_array_aligned(allocator, buffer_size, alignof(compressed_tracks)); + std::memset(buffer, 0, buffer_size); + + uint8_t* buffer_start = buffer; + out_compressed_tracks = reinterpret_cast(buffer); + + raw_buffer_header* buffer_header = safe_ptr_cast(buffer); + buffer += sizeof(raw_buffer_header); + + tracks_header* header = safe_ptr_cast(buffer); + buffer += sizeof(tracks_header); + + // Write our primary header + header->tag = static_cast(buffer_tag32::compressed_tracks); + header->version = compressed_tracks_version16::latest; + header->algorithm_type = algorithm_type8::uniformly_sampled; + header->track_type = track_list.get_track_type(); + header->num_tracks = context.num_output_tracks; + header->num_samples = context.num_output_tracks != 0 ? context.num_samples : 0; + header->sample_rate = context.num_output_tracks != 0 ? context.sample_rate : 0.0F; + header->set_has_metadata(metadata_size != 0); + + // Write our scalar tracks header + scalar_tracks_header* scalars_header = safe_ptr_cast(buffer); + buffer += sizeof(scalar_tracks_header); + + scalars_header->num_bits_per_frame = num_bits_per_frame; + + const uint8_t* packed_data_start_offset = buffer - sizeof(scalar_tracks_header); // Relative to our header + scalars_header->metadata_per_track = uint32_t(buffer - packed_data_start_offset); + buffer += per_track_metadata_size; + buffer = align_to(buffer, 4); + scalars_header->track_constant_values = uint32_t(buffer - packed_data_start_offset); + buffer += constant_values_size; + scalars_header->track_range_values = uint32_t(buffer - packed_data_start_offset); + buffer += range_values_size; + scalars_header->track_animated_values = uint32_t(buffer - packed_data_start_offset); + buffer += animated_values_size; + + if (metadata_size != 0) + { + buffer = align_to(buffer, 4); + buffer += metadata_size; + + buffer = align_to(buffer, 4); + buffer += sizeof(optional_metadata_header); + } + else + buffer += 15; + + (void)buffer_start; // Avoid VS2017 bug, it falsely reports this variable as unused even when asserts are enabled + ACL_ASSERT((buffer_start + buffer_size) == buffer, "Buffer size and pointer mismatch"); + + // Write our compressed data + track_metadata* per_track_metadata = scalars_header->get_track_metadata(); + write_track_metadata(context, per_track_metadata); + + float* constant_values = scalars_header->get_track_constant_values(); + write_track_constant_values(context, constant_values); + + float* range_values = scalars_header->get_track_range_values(); + write_track_range_values(context, range_values); + + uint8_t* animated_values = scalars_header->get_track_animated_values(); + write_track_animated_values(context, animated_values); + + // Optional metadata header is last + uint32_t writter_metadata_track_list_name_size = 0; + uint32_t written_metadata_track_names_size = 0; + uint32_t written_metadata_track_descriptions_size = 0; + if (metadata_size != 0) + { + optional_metadata_header* metadada_header = reinterpret_cast(buffer_start + buffer_size - sizeof(optional_metadata_header)); + uint32_t metadata_offset = metadata_start_offset; + + if (settings.metadata.include_track_list_name) + { + metadada_header->track_list_name = metadata_offset; + writter_metadata_track_list_name_size = write_track_list_name(track_list, metadada_header->get_track_list_name(*out_compressed_tracks)); + metadata_offset += writter_metadata_track_list_name_size; + } + else + metadada_header->track_list_name = invalid_ptr_offset(); + + if (settings.metadata.include_track_names) + { + metadata_offset = align_to(metadata_offset, 4); + metadada_header->track_name_offsets = metadata_offset; + written_metadata_track_names_size = write_track_names(track_list, context.track_output_indices, context.num_output_tracks, metadada_header->get_track_name_offsets(*out_compressed_tracks)); + metadata_offset += written_metadata_track_names_size; + } + else + metadada_header->track_name_offsets = invalid_ptr_offset(); + + metadada_header->parent_track_indices = invalid_ptr_offset(); // Not supported for scalar tracks + + if (settings.metadata.include_track_descriptions) + { + metadata_offset = align_to(metadata_offset, 4); + metadada_header->track_descriptions = metadata_offset; + written_metadata_track_descriptions_size = write_track_descriptions(track_list, context.track_output_indices, context.num_output_tracks, metadada_header->get_track_descriptions(*out_compressed_tracks)); + metadata_offset += written_metadata_track_descriptions_size; + } + else + metadada_header->track_descriptions = invalid_ptr_offset(); + } + + ACL_ASSERT(writter_metadata_track_list_name_size == metadata_track_list_name_size, "Wrote too little or too much data"); + ACL_ASSERT(written_metadata_track_names_size == metadata_track_names_size, "Wrote too little or too much data"); + ACL_ASSERT(written_metadata_track_descriptions_size == metadata_track_descriptions_size, "Wrote too little or too much data"); + + // Finish the raw buffer header + buffer_header->size = buffer_size; + buffer_header->hash = hash32(safe_ptr_cast(header), buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header + +#if defined(ACL_HAS_ASSERT_CHECKS) + if (metadata_size == 0) + { + for (const uint8_t* padding = buffer - 15; padding < buffer; ++padding) + ACL_ASSERT(*padding == 0, "Padding was overwritten"); + } +#endif + +#if defined(SJSON_CPP_WRITER) + compression_time.stop(); + + if (out_stats.logging != stat_logging::none) + write_compression_stats(context, *out_compressed_tracks, compression_time, out_stats); +#endif + + return error_result(); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.transform.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.transform.impl.h new file mode 100644 index 00000000..da1d16ed --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compress.transform.impl.h @@ -0,0 +1,478 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compress.h + +#include "acl/version.h" +#include "acl/core/buffer_tag.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/error.h" +#include "acl/core/error_result.h" +#include "acl/core/floating_point_exceptions.h" +#include "acl/core/iallocator.h" +#include "acl/core/scope_profiler.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/output_stats.h" +#include "acl/compression/track_array.h" +#include "acl/compression/impl/clip_context.h" +#include "acl/compression/impl/track_stream.h" +#include "acl/compression/impl/convert_rotation_streams.h" +#include "acl/compression/impl/compact_constant_streams.h" +#include "acl/compression/impl/normalize_streams.h" +#include "acl/compression/impl/quantize_streams.h" +#include "acl/compression/impl/segment_streams.h" +#include "acl/compression/impl/write_segment_data.h" +#include "acl/compression/impl/write_stats.h" +#include "acl/compression/impl/write_stream_data.h" +#include "acl/compression/impl/write_sub_track_types.h" +#include "acl/compression/impl/write_track_metadata.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline error_result compress_transform_track_list(iallocator& allocator, const track_array_qvvf& track_list, compression_settings settings, + const track_array_qvvf* additive_base_track_list, additive_clip_format8 additive_format, + compressed_tracks*& out_compressed_tracks, output_stats& out_stats) + { + error_result result = settings.is_valid(); + if (result.any()) + return result; + +#if defined(SJSON_CPP_WRITER) + scope_profiler compression_time; +#endif + + // Segmenting settings are an implementation detail + compression_segmenting_settings segmenting_settings; + + // If we enable database support, include the metadata we need + if (settings.enable_database_support) + settings.metadata.include_contributing_error = true; + + // If every track is retains full precision, we disable segmenting since it provides no benefit + if (!is_rotation_format_variable(settings.rotation_format) && !is_vector_format_variable(settings.translation_format) && !is_vector_format_variable(settings.scale_format)) + { + if (settings.metadata.include_contributing_error) + return error_result("Raw tracks have no contributing error"); + + segmenting_settings.ideal_num_samples = 0xFFFFFFFF; + segmenting_settings.max_num_samples = 0xFFFFFFFF; + } + + if (settings.metadata.include_contributing_error && segmenting_settings.max_num_samples > 32) + return error_result("Cannot have more than 32 samples per segment when calculating the contributing error per frame"); + + // If we want the optional track descriptions, make sure to include the parent track indices + if (settings.metadata.include_track_descriptions) + settings.metadata.include_parent_track_indices = true; + + ACL_ASSERT(settings.is_valid().empty(), "Invalid compression settings"); + ACL_ASSERT(segmenting_settings.is_valid().empty(), "Invalid segmenting settings"); + + // Variable bit rate tracks need range reduction + // Full precision tracks do not need range reduction since samples are stored raw + range_reduction_flags8 range_reduction = range_reduction_flags8::none; + if (is_rotation_format_variable(settings.rotation_format)) + range_reduction |= range_reduction_flags8::rotations; + + if (is_vector_format_variable(settings.translation_format)) + range_reduction |= range_reduction_flags8::translations; + + if (is_vector_format_variable(settings.scale_format)) + range_reduction |= range_reduction_flags8::scales; + + // If we have no additive base, our additive format is always none + if (additive_base_track_list == nullptr || additive_base_track_list->is_empty()) + additive_format = additive_clip_format8::none; + + clip_context raw_clip_context; + if (!initialize_clip_context(allocator, track_list, settings, additive_format, raw_clip_context)) + return error_result("Some samples are not finite"); + + clip_context lossy_clip_context; + initialize_clip_context(allocator, track_list, settings, additive_format, lossy_clip_context); + + const bool is_additive = additive_format != additive_clip_format8::none; + clip_context additive_base_clip_context; + if (is_additive && !initialize_clip_context(allocator, *additive_base_track_list, settings, additive_format, additive_base_clip_context)) + return error_result("Some base samples are not finite"); + + // Convert our rotations if we need to + convert_rotation_streams(allocator, lossy_clip_context, settings.rotation_format); + + // Extract our clip ranges now, we need it for compacting the constant streams + extract_clip_bone_ranges(allocator, lossy_clip_context); + + // Compact and collapse the constant streams + compact_constant_streams(allocator, lossy_clip_context, track_list, settings); + + uint32_t clip_range_data_size = 0; + if (range_reduction != range_reduction_flags8::none) + { + // Normalize our samples into the clip wide ranges per bone + normalize_clip_streams(lossy_clip_context, range_reduction); + clip_range_data_size = get_clip_range_data_size(lossy_clip_context, range_reduction, settings.rotation_format); + } + + segment_streams(allocator, lossy_clip_context, segmenting_settings); + + // If we have a single segment, skip segment range reduction since it won't help + if (range_reduction != range_reduction_flags8::none && lossy_clip_context.num_segments > 1) + { + // Extract and fixup our segment wide ranges per bone + extract_segment_bone_ranges(allocator, lossy_clip_context); + + // Normalize our samples into the segment wide ranges per bone + normalize_segment_streams(lossy_clip_context, range_reduction); + } + + quantize_streams(allocator, lossy_clip_context, settings, raw_clip_context, additive_base_clip_context, out_stats); + + uint32_t num_output_bones = 0; + uint32_t* output_bone_mapping = create_output_track_mapping(allocator, track_list, num_output_bones); + + const uint32_t constant_data_size = get_constant_data_size(lossy_clip_context); + + calculate_animated_data_size(lossy_clip_context, output_bone_mapping, num_output_bones); + + uint32_t num_animated_variable_sub_tracks_padded = 0; + const uint32_t format_per_track_data_size = get_format_per_track_data_size(lossy_clip_context, settings.rotation_format, settings.translation_format, settings.scale_format, &num_animated_variable_sub_tracks_padded); + + const uint32_t num_sub_tracks_per_bone = lossy_clip_context.has_scale ? 3 : 2; + + // Calculate how many sub-track packed entries we have + // Each sub-track is 2 bits packed within a 32 bit entry + // For each sub-track type, we round up to simplify bookkeeping + // For example, if we have 3 tracks made up of rotation/translation we'll have one entry for each with unused padding + // All rotation types come first, followed by all translation types, and with scale types at the end when present + const uint32_t num_sub_track_entries = ((num_output_bones + k_num_sub_tracks_per_packed_entry - 1) / k_num_sub_tracks_per_packed_entry) * num_sub_tracks_per_bone; + const uint32_t packed_sub_track_buffer_size = num_sub_track_entries * sizeof(packed_sub_track_types); + + // Adding an extra index at the end to delimit things, the index is always invalid: 0xFFFFFFFF + const uint32_t segment_start_indices_size = lossy_clip_context.num_segments > 1 ? (uint32_t(sizeof(uint32_t)) * (lossy_clip_context.num_segments + 1)) : 0; + const uint32_t segment_headers_size = sizeof(segment_header) * lossy_clip_context.num_segments; + + uint32_t buffer_size = 0; + // Per clip data + buffer_size += sizeof(raw_buffer_header); // Header + buffer_size += sizeof(tracks_header); // Header + buffer_size += sizeof(transform_tracks_header); // Header + + const uint32_t clip_header_size = buffer_size; + + buffer_size = align_to(buffer_size, 4); // Align segment start indices + buffer_size += segment_start_indices_size; // Segment start indices + buffer_size = align_to(buffer_size, 4); // Align segment headers + buffer_size += segment_headers_size; // Segment headers + buffer_size = align_to(buffer_size, 4); // Align sub-track types + + const uint32_t clip_segment_header_size = buffer_size - clip_header_size; + + buffer_size += packed_sub_track_buffer_size; // Packed sub-track types sorted by type + buffer_size = align_to(buffer_size, 4); // Align constant track data + buffer_size += constant_data_size; // Constant track data + buffer_size = align_to(buffer_size, 4); // Align range data + buffer_size += clip_range_data_size; // Range data + + const uint32_t clip_data_size = buffer_size - clip_segment_header_size - clip_header_size; + + if (are_all_enum_flags_set(out_stats.logging, stat_logging::detailed)) + { + constexpr uint32_t k_cache_line_byte_size = 64; + lossy_clip_context.decomp_touched_bytes = clip_header_size + clip_data_size; + lossy_clip_context.decomp_touched_bytes += sizeof(uint32_t) * 4; // We touch at most 4 segment start indices + lossy_clip_context.decomp_touched_bytes += sizeof(segment_header) * 2; // We touch at most 2 segment headers + lossy_clip_context.decomp_touched_cache_lines = align_to(clip_header_size, k_cache_line_byte_size) / k_cache_line_byte_size; + lossy_clip_context.decomp_touched_cache_lines += align_to(clip_data_size, k_cache_line_byte_size) / k_cache_line_byte_size; + lossy_clip_context.decomp_touched_cache_lines += 1; // All 4 segment start indices should fit in a cache line + lossy_clip_context.decomp_touched_cache_lines += 1; // Both segment headers should fit in a cache line + } + + // Per segment data + for (segment_context& segment : lossy_clip_context.segment_iterator()) + { + const uint32_t header_start = buffer_size; + + buffer_size += format_per_track_data_size; // Format per track data + + // TODO: Alignment only necessary with 16bit per component (segment constant tracks), need to fix scalar decoding path + buffer_size = align_to(buffer_size, 2); // Align range data + buffer_size += segment.range_data_size; // Range data + + const uint32_t header_end = buffer_size; + + // TODO: Variable bit rate doesn't need alignment + buffer_size = align_to(buffer_size, 4); // Align animated data + buffer_size += segment.animated_data_size; // Animated track data + + segment.segment_data_size = buffer_size - header_start; + segment.total_header_size = header_end - header_start; + } + + const uint32_t segment_data_size = buffer_size - clip_data_size - clip_segment_header_size - clip_header_size; + + // Optional metadata + const uint32_t metadata_start_offset = align_to(buffer_size, 4); + const uint32_t metadata_track_list_name_size = settings.metadata.include_track_list_name ? write_track_list_name(track_list, nullptr) : 0; + const uint32_t metadata_track_names_size = settings.metadata.include_track_names ? write_track_names(track_list, output_bone_mapping, num_output_bones, nullptr) : 0; + const uint32_t metadata_parent_track_indices_size = settings.metadata.include_parent_track_indices ? write_parent_track_indices(track_list, output_bone_mapping, num_output_bones, nullptr) : 0; + const uint32_t metadata_track_descriptions_size = settings.metadata.include_track_descriptions ? write_track_descriptions(track_list, output_bone_mapping, num_output_bones, nullptr) : 0; + const uint32_t metadata_contributing_error_size = settings.metadata.include_contributing_error ? write_contributing_error(lossy_clip_context, nullptr) : 0; + + uint32_t metadata_size = 0; + metadata_size += metadata_track_list_name_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_track_names_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_parent_track_indices_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_track_descriptions_size; + metadata_size = align_to(metadata_size, 4); + metadata_size += metadata_contributing_error_size; + + if (metadata_size != 0) + { + buffer_size = align_to(buffer_size, 4); + buffer_size += metadata_size; + + buffer_size = align_to(buffer_size, 4); + buffer_size += sizeof(optional_metadata_header); + } + else + buffer_size += 15; // Ensure we have sufficient padding for unaligned 16 byte loads + + uint8_t* buffer = allocate_type_array_aligned(allocator, buffer_size, alignof(compressed_tracks)); + std::memset(buffer, 0, buffer_size); + + uint8_t* buffer_start = buffer; + out_compressed_tracks = reinterpret_cast(buffer); + + raw_buffer_header* buffer_header = safe_ptr_cast(buffer); + buffer += sizeof(raw_buffer_header); + + tracks_header* header = safe_ptr_cast(buffer); + buffer += sizeof(tracks_header); + + // Write our primary header + header->tag = static_cast(buffer_tag32::compressed_tracks); + header->version = compressed_tracks_version16::latest; + header->algorithm_type = algorithm_type8::uniformly_sampled; + header->track_type = track_list.get_track_type(); + header->num_tracks = num_output_bones; + header->num_samples = num_output_bones != 0 ? track_list.get_num_samples_per_track() : 0; + header->sample_rate = num_output_bones != 0 ? track_list.get_sample_rate() : 0.0F; + header->set_rotation_format(settings.rotation_format); + header->set_translation_format(settings.translation_format); + header->set_scale_format(settings.scale_format); + header->set_has_scale(lossy_clip_context.has_scale); + // Our default scale is 1.0 if we have no additive base or if we don't use 'additive1', otherwise it is 0.0 + header->set_default_scale(!is_additive || additive_format != additive_clip_format8::additive1 ? 1 : 0); + header->set_has_database(false); + header->set_has_metadata(metadata_size != 0); + + // Write our transform tracks header + transform_tracks_header* transforms_header = safe_ptr_cast(buffer); + buffer += sizeof(transform_tracks_header); + + transforms_header->num_segments = lossy_clip_context.num_segments; + transforms_header->num_animated_variable_sub_tracks = num_animated_variable_sub_tracks_padded; + get_num_constant_samples(lossy_clip_context, transforms_header->num_constant_rotation_samples, transforms_header->num_constant_translation_samples, transforms_header->num_constant_scale_samples); + get_num_animated_sub_tracks(lossy_clip_context.segments[0], + transforms_header->num_animated_rotation_sub_tracks, transforms_header->num_animated_translation_sub_tracks, transforms_header->num_animated_scale_sub_tracks); + + const uint32_t segment_start_indices_offset = align_to(sizeof(transform_tracks_header), 4); // Relative to the start of our transform_tracks_header + transforms_header->database_header_offset = invalid_ptr_offset(); + transforms_header->segment_headers_offset = align_to(segment_start_indices_offset + segment_start_indices_size, 4); + transforms_header->sub_track_types_offset = align_to(transforms_header->segment_headers_offset + segment_headers_size, 4); + transforms_header->constant_track_data_offset = align_to(transforms_header->sub_track_types_offset + packed_sub_track_buffer_size, 4); + transforms_header->clip_range_data_offset = align_to(transforms_header->constant_track_data_offset + constant_data_size, 4); + + uint32_t written_segment_start_indices_size = 0; + if (lossy_clip_context.num_segments > 1) + written_segment_start_indices_size = write_segment_start_indices(lossy_clip_context, transforms_header->get_segment_start_indices()); + + const uint32_t segment_data_start_offset = transforms_header->clip_range_data_offset + clip_range_data_size; + const uint32_t written_segment_headers_size = write_segment_headers(lossy_clip_context, settings, transforms_header->get_segment_headers(), segment_data_start_offset); + + uint32_t written_sub_track_buffer_size = 0; + written_sub_track_buffer_size += write_packed_sub_track_types(lossy_clip_context, transforms_header->get_sub_track_types(), output_bone_mapping, num_output_bones); + + uint32_t written_constant_data_size = 0; + if (constant_data_size != 0) + written_constant_data_size = write_constant_track_data(lossy_clip_context, settings.rotation_format, transforms_header->get_constant_track_data(), constant_data_size, output_bone_mapping, num_output_bones); + + uint32_t written_clip_range_data_size = 0; + if (range_reduction != range_reduction_flags8::none) + written_clip_range_data_size = write_clip_range_data(lossy_clip_context, range_reduction, transforms_header->get_clip_range_data(), clip_range_data_size, output_bone_mapping, num_output_bones); + + const uint32_t written_segment_data_size = write_segment_data(lossy_clip_context, settings, range_reduction, transforms_header->get_segment_headers(), *transforms_header, output_bone_mapping, num_output_bones); + + // Optional metadata header is last + uint32_t writter_metadata_track_list_name_size = 0; + uint32_t written_metadata_track_names_size = 0; + uint32_t written_metadata_parent_track_indices_size = 0; + uint32_t written_metadata_track_descriptions_size = 0; + uint32_t written_metadata_contributing_error_size = 0; + if (metadata_size != 0) + { + optional_metadata_header* metadata_header = reinterpret_cast(buffer_start + buffer_size - sizeof(optional_metadata_header)); + uint32_t metadata_offset = metadata_start_offset; // Relative to the start of our compressed_tracks + + if (settings.metadata.include_track_list_name) + { + metadata_header->track_list_name = metadata_offset; + writter_metadata_track_list_name_size = write_track_list_name(track_list, metadata_header->get_track_list_name(*out_compressed_tracks)); + metadata_offset += writter_metadata_track_list_name_size; + } + else + metadata_header->track_list_name = invalid_ptr_offset(); + + if (settings.metadata.include_track_names) + { + metadata_offset = align_to(metadata_offset, 4); + metadata_header->track_name_offsets = metadata_offset; + written_metadata_track_names_size = write_track_names(track_list, output_bone_mapping, num_output_bones, metadata_header->get_track_name_offsets(*out_compressed_tracks)); + metadata_offset += written_metadata_track_names_size; + } + else + metadata_header->track_name_offsets = invalid_ptr_offset(); + + if (settings.metadata.include_parent_track_indices) + { + metadata_offset = align_to(metadata_offset, 4); + metadata_header->parent_track_indices = metadata_offset; + written_metadata_parent_track_indices_size = write_parent_track_indices(track_list, output_bone_mapping, num_output_bones, metadata_header->get_parent_track_indices(*out_compressed_tracks)); + metadata_offset += written_metadata_parent_track_indices_size; + } + else + metadata_header->parent_track_indices = invalid_ptr_offset(); + + if (settings.metadata.include_track_descriptions) + { + metadata_offset = align_to(metadata_offset, 4); + metadata_header->track_descriptions = metadata_offset; + written_metadata_track_descriptions_size = write_track_descriptions(track_list, output_bone_mapping, num_output_bones, metadata_header->get_track_descriptions(*out_compressed_tracks)); + metadata_offset += written_metadata_track_descriptions_size; + } + else + metadata_header->track_descriptions = invalid_ptr_offset(); + + if (settings.metadata.include_contributing_error) + { + metadata_offset = align_to(metadata_offset, 4); + metadata_header->contributing_error = metadata_offset; + written_metadata_contributing_error_size = write_contributing_error(lossy_clip_context, metadata_header->get_contributing_error(*out_compressed_tracks)); + metadata_offset += written_metadata_contributing_error_size; + } + else + metadata_header->contributing_error = invalid_ptr_offset(); + } + + // Finish the compressed tracks raw buffer header + buffer_header->size = buffer_size; + buffer_header->hash = hash32(safe_ptr_cast(header), buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header + +#if defined(ACL_HAS_ASSERT_CHECKS) + { + // Make sure we wrote the right amount of data + buffer = align_to(buffer, 4); // Align segment start indices + buffer += written_segment_start_indices_size; + buffer = align_to(buffer, 4); // Align segment headers + buffer += written_segment_headers_size; + buffer = align_to(buffer, 4); // Align sub-track types + buffer += written_sub_track_buffer_size; + buffer = align_to(buffer, 4); // Align constant track data + buffer += written_constant_data_size; + buffer = align_to(buffer, 4); // Align range data + buffer += written_clip_range_data_size; + buffer += written_segment_data_size; + + if (metadata_size != 0) + { + buffer = align_to(buffer, 4); + buffer += metadata_size; + + buffer = align_to(buffer, 4); + buffer += sizeof(optional_metadata_header); + } + else + buffer += 15; // Ensure we have sufficient padding for unaligned 16 byte loads + + (void)buffer_start; // Avoid VS2017 bug, it falsely reports this variable as unused even when asserts are enabled + ACL_ASSERT(written_segment_start_indices_size == segment_start_indices_size, "Wrote too little or too much data"); + ACL_ASSERT(written_segment_headers_size == segment_headers_size, "Wrote too little or too much data"); + ACL_ASSERT(written_segment_data_size == segment_data_size, "Wrote too little or too much data"); + ACL_ASSERT(written_sub_track_buffer_size == packed_sub_track_buffer_size, "Wrote too little or too much data"); + ACL_ASSERT(written_constant_data_size == constant_data_size, "Wrote too little or too much data"); + ACL_ASSERT(written_clip_range_data_size == clip_range_data_size, "Wrote too little or too much data"); + ACL_ASSERT(writter_metadata_track_list_name_size == metadata_track_list_name_size, "Wrote too little or too much data"); + ACL_ASSERT(written_metadata_track_names_size == metadata_track_names_size, "Wrote too little or too much data"); + ACL_ASSERT(written_metadata_parent_track_indices_size == metadata_parent_track_indices_size, "Wrote too little or too much data"); + ACL_ASSERT(written_metadata_track_descriptions_size == metadata_track_descriptions_size, "Wrote too little or too much data"); + ACL_ASSERT(written_metadata_contributing_error_size == metadata_contributing_error_size, "Wrote too little or too much data"); + ACL_ASSERT(uint32_t(buffer - buffer_start) == buffer_size, "Wrote too little or too much data"); + + if (metadata_size == 0) + { + for (const uint8_t* padding = buffer - 15; padding < buffer; ++padding) + ACL_ASSERT(*padding == 0, "Padding was overwritten"); + } + } +#else + (void)written_segment_start_indices_size; + (void)written_segment_headers_size; + (void)written_sub_track_buffer_size; + (void)written_constant_data_size; + (void)written_clip_range_data_size; + (void)written_segment_data_size; + (void)segment_data_size; + (void)buffer_start; +#endif + +#if defined(SJSON_CPP_WRITER) + compression_time.stop(); + + if (out_stats.logging != stat_logging::none) + write_stats(allocator, track_list, lossy_clip_context, *out_compressed_tracks, settings, segmenting_settings, range_reduction, raw_clip_context, additive_base_clip_context, compression_time, out_stats); +#endif + + deallocate_type_array(allocator, output_bone_mapping, num_output_bones); + destroy_clip_context(lossy_clip_context); + destroy_clip_context(raw_clip_context); + destroy_clip_context(additive_base_clip_context); + + return error_result(); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compression_settings.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compression_settings.impl.h new file mode 100644 index 00000000..1dbf7535 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/compression_settings.impl.h @@ -0,0 +1,134 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compression_settings.h + +#include "acl/version.h" +#include "acl/core/error_result.h" +#include "acl/core/hash.h" +#include "acl/core/track_formats.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline uint32_t compression_database_settings::get_hash() const + { + uint32_t hash_value = 0; + hash_value = hash_combine(hash_value, hash32(max_chunk_size)); + hash_value = hash_combine(hash_value, hash32(medium_importance_tier_proportion)); + hash_value = hash_combine(hash_value, hash32(low_importance_tier_proportion)); + return hash_value; + } + + inline error_result compression_database_settings::is_valid() const + { + if (max_chunk_size < 4 * 1024) + return error_result("max_chunk_size must be greater or equal to 4 KB"); + + if (align_to(max_chunk_size, 4 * 1024) != max_chunk_size) + return error_result("max_chunk_size must be a multiple of 4 KB"); + + if (!rtm::scalar_is_finite(medium_importance_tier_proportion) || medium_importance_tier_proportion < 0.0F || medium_importance_tier_proportion > 1.0F) + return error_result("medium_importance_tier_proportion must be in the range [0.0, 1.0]"); + + if (!rtm::scalar_is_finite(low_importance_tier_proportion) || low_importance_tier_proportion < 0.0F || low_importance_tier_proportion > 1.0F) + return error_result("low_importance_tier_proportion must be in the range [0.0, 1.0]"); + + // Add an epsilon to account for arithmetic imprecision + const float database_proportion = low_importance_tier_proportion + medium_importance_tier_proportion; + const float epsilon = 1.0e-5F; + if (database_proportion < epsilon || database_proportion > (1.0F + epsilon)) + return error_result("The sum of medium_importance_tier_proportion + low_importance_tier_proportion must be in the range [0.0, 1.0]"); + + return error_result(); + } + + inline uint32_t compression_metadata_settings::get_hash() const + { + uint32_t hash_value = 0; + hash_value = hash_combine(hash_value, hash32(include_track_list_name)); + hash_value = hash_combine(hash_value, hash32(include_track_names)); + hash_value = hash_combine(hash_value, hash32(include_parent_track_indices)); + hash_value = hash_combine(hash_value, hash32(include_track_descriptions)); + hash_value = hash_combine(hash_value, hash32(include_contributing_error)); + + return hash_value; + } + + inline error_result compression_metadata_settings::is_valid() const + { + return error_result(); + } + + inline uint32_t compression_settings::get_hash() const + { + uint32_t hash_value = 0; + hash_value = hash_combine(hash_value, hash32(level)); + hash_value = hash_combine(hash_value, hash32(rotation_format)); + hash_value = hash_combine(hash_value, hash32(translation_format)); + hash_value = hash_combine(hash_value, hash32(scale_format)); + + if (error_metric != nullptr) + hash_value = hash_combine(hash_value, error_metric->get_hash()); + + hash_value = hash_combine(hash_value, enable_database_support); + hash_value = hash_combine(hash_value, metadata.get_hash()); + + return hash_value; + } + + inline error_result compression_settings::is_valid() const + { + if (error_metric == nullptr) + return error_result("error_metric cannot be NULL"); + + const error_result metadata_result = metadata.is_valid(); + if (metadata_result.any()) + return metadata_result; + + return error_result(); + } + + inline compression_settings get_raw_compression_settings() + { + return compression_settings(); + } + + inline compression_settings get_default_compression_settings() + { + compression_settings settings; + settings.level = compression_level8::medium; + settings.rotation_format = rotation_format8::quatf_drop_w_variable; + settings.translation_format = vector_format8::vector3f_variable; + settings.scale_format = vector_format8::vector3f_variable; + return settings; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/constant_track_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/constant_track_impl.h new file mode 100644 index 00000000..039e4f85 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/constant_track_impl.h @@ -0,0 +1,83 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/bitset.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/track_desc.h" +#include "acl/compression/impl/track_list_context.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline bool is_scalarf_track_constant(const track& track_, const track_range& range) + { + const track_desc_scalarf& desc = track_.get_description(); + return range.is_constant(desc.precision); + } + + inline void extract_constant_tracks(track_list_context& context) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + const bitset_description bitset_desc = bitset_description::make_from_num_bits(context.num_tracks); + + context.constant_tracks_bitset = allocate_type_array(*context.allocator, bitset_desc.get_size()); + bitset_reset(context.constant_tracks_bitset, bitset_desc, false); + + for (uint32_t track_index = 0; track_index < context.num_tracks; ++track_index) + { + const track& mut_track = context.track_list[track_index]; + const track_range& range = context.range_list[track_index]; + + bool is_constant = false; + switch (range.category) + { + case track_category8::scalarf: + is_constant = is_scalarf_track_constant(mut_track, range); + break; + default: + ACL_ASSERT(false, "Invalid track category"); + break; + } + + bitset_set(context.constant_tracks_bitset, bitset_desc, track_index, is_constant); + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/convert.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/convert.impl.h new file mode 100644 index 00000000..20ed7702 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/convert.impl.h @@ -0,0 +1,247 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from convert.h + +#include "acl/version.h" +#include "acl/compression/compress.h" +#include "acl/compression/track_array.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/track_formats.h" +#include "acl/core/impl/debug_track_writer.h" +#include "acl/decompression/decompress.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct raw_sampling_decompression_settings final : public decompression_settings + { + // Disable normalization. This is only safe if we know the input data is already normalized! + static constexpr bool normalize_rotations() { return false; } + + // Only support raw formats. This ensures we don't interpolate when we can avoid it to maintain the original + // accuracy and it strips the code we don't need for faster decompression. + static constexpr bool is_rotation_format_supported(rotation_format8 format) { return format == rotation_format8::quatf_full; } + static constexpr bool is_translation_format_supported(vector_format8 format) { return format == vector_format8::vector3f_full; } + static constexpr bool is_scale_format_supported(vector_format8 format) { return format == vector_format8::vector3f_full; } + }; + } + + inline error_result convert_track_list(iallocator& allocator, const track_array& track_list, compressed_tracks*& out_compressed_tracks) + { + compression_settings settings = get_raw_compression_settings(); + + qvvf_transform_error_metric error_metric; + settings.error_metric = &error_metric; + + // Include all the metadata + settings.metadata.include_track_list_name = true; + settings.metadata.include_track_names = true; + settings.metadata.include_parent_track_indices = true; + settings.metadata.include_track_descriptions = true; + + output_stats stats; + return compress_track_list(allocator, track_list, settings, out_compressed_tracks, stats); + } + + inline error_result convert_track_list(iallocator& allocator, const compressed_tracks& tracks, track_array& out_track_list) + { + error_result error = tracks.is_valid(false); + if (error.any()) + return error; + + const uint32_t num_tracks = tracks.get_num_tracks(); + const track_type8 track_type = tracks.get_track_type(); + const uint32_t num_samples = tracks.get_num_samples_per_track(); + const float sample_rate = tracks.get_sample_rate(); + const float duration = tracks.get_duration(); + + track_array result(allocator, num_tracks); + result.set_name(string(allocator, tracks.get_name())); + + // Setup our track metadata and allocate memory + bool success = true; + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + track& track_ = result[track_index]; + + track_desc_scalarf desc_scalar; + track_desc_transformf desc_transform; + + bool got_description = false; + switch (track_type) + { + case track_type8::float1f: + got_description = tracks.get_track_description(track_index, desc_scalar); + track_ = track_float1f::make_reserve(desc_scalar, allocator, num_samples, sample_rate); + break; + case track_type8::float2f: + got_description = tracks.get_track_description(track_index, desc_scalar); + track_ = track_float2f::make_reserve(desc_scalar, allocator, num_samples, sample_rate); + break; + case track_type8::float3f: + got_description = tracks.get_track_description(track_index, desc_scalar); + track_ = track_float3f::make_reserve(desc_scalar, allocator, num_samples, sample_rate); + break; + case track_type8::float4f: + got_description = tracks.get_track_description(track_index, desc_scalar); + track_ = track_float4f::make_reserve(desc_scalar, allocator, num_samples, sample_rate); + break; + case track_type8::vector4f: + got_description = tracks.get_track_description(track_index, desc_scalar); + track_ = track_vector4f::make_reserve(desc_scalar, allocator, num_samples, sample_rate); + break; + case track_type8::qvvf: + got_description = tracks.get_track_description(track_index, desc_transform); + track_ = track_qvvf::make_reserve(desc_transform, allocator, num_samples, sample_rate); + break; + default: + ACL_ASSERT(false, "Unexpected track type"); + break; + } + + success &= got_description; + + track_.set_name(string(allocator, tracks.get_track_name(track_index))); + } + + if (!success) + return error_result("Metadata was missing from the input"); + + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + // Decompress and populate our track data + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(tracks); + if (header.get_rotation_format() == rotation_format8::quatf_full && header.get_translation_format() == vector_format8::vector3f_full && header.get_scale_format() == vector_format8::vector3f_full) + { + // Our input data uses full precision, retain it + decompression_context context; + context.initialize(tracks); + + acl_impl::debug_track_writer writer(allocator, track_type, num_tracks); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration); + + // Round to nearest to land directly on a sample + context.seek(sample_time, sample_rounding_policy::nearest); + + context.decompress_tracks(writer); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + track& track_ = result[track_index]; + + switch (track_type) + { + case track_type8::float1f: + *reinterpret_cast(track_[sample_index]) = writer.read_float1(track_index); + break; + case track_type8::float2f: + rtm::vector_store2(writer.read_float2(track_index), reinterpret_cast(track_[sample_index])); + break; + case track_type8::float3f: + rtm::vector_store3(writer.read_float3(track_index), reinterpret_cast(track_[sample_index])); + break; + case track_type8::float4f: + rtm::vector_store(writer.read_float4(track_index), reinterpret_cast(track_[sample_index])); + break; + case track_type8::vector4f: + *reinterpret_cast(track_[sample_index]) = writer.read_vector4(track_index); + break; + case track_type8::qvvf: + *reinterpret_cast(track_[sample_index]) = writer.read_qvv(track_index); + break; + default: + ACL_ASSERT(false, "Unexpected track type"); + break; + } + } + } + } + else + { + decompression_context context; + context.initialize(tracks); + + acl_impl::debug_track_writer writer(allocator, track_type, num_tracks); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration); + + // Round to nearest to land directly on a sample + context.seek(sample_time, sample_rounding_policy::nearest); + + context.decompress_tracks(writer); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + track& track_ = result[track_index]; + + switch (track_type) + { + case track_type8::float1f: + *reinterpret_cast(track_[sample_index]) = writer.read_float1(track_index); + break; + case track_type8::float2f: + rtm::vector_store2(writer.read_float2(track_index), reinterpret_cast(track_[sample_index])); + break; + case track_type8::float3f: + rtm::vector_store3(writer.read_float3(track_index), reinterpret_cast(track_[sample_index])); + break; + case track_type8::float4f: + rtm::vector_store(writer.read_float4(track_index), reinterpret_cast(track_[sample_index])); + break; + case track_type8::vector4f: + *reinterpret_cast(track_[sample_index]) = writer.read_vector4(track_index); + break; + case track_type8::qvvf: + *reinterpret_cast(track_[sample_index]) = writer.read_qvv(track_index); + break; + default: + ACL_ASSERT(false, "Unexpected track type"); + break; + } + } + } + } + + out_track_list = std::move(result); + return error_result(); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/convert_rotation_streams.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/convert_rotation_streams.h new file mode 100644 index 00000000..7202eb37 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/convert_rotation_streams.h @@ -0,0 +1,116 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/track_formats.h" +#include "acl/compression/impl/clip_context.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline rtm::vector4f RTM_SIMD_CALL convert_rotation(rtm::vector4f_arg0 rotation, rotation_format8 from, rotation_format8 to) + { + ACL_ASSERT(from == rotation_format8::quatf_full, "Source rotation format must be a full precision quaternion"); + (void)from; + + const rotation_format8 high_precision_format = get_rotation_variant(to) == rotation_variant8::quat ? rotation_format8::quatf_full : rotation_format8::quatf_drop_w_full; + switch (high_precision_format) + { + case rotation_format8::quatf_full: + // Original format, nothing to do + return rotation; + case rotation_format8::quatf_drop_w_full: + // Drop W, we just ensure it is positive and write it back, the W component can be ignored afterwards + return rtm::quat_to_vector(rtm::quat_ensure_positive_w(rtm::vector_to_quat(rotation))); + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(to)); + return rotation; + } + } + + inline void convert_rotation_streams(iallocator& allocator, segment_context& segment, rotation_format8 rotation_format) + { + const rotation_format8 high_precision_format = get_rotation_variant(rotation_format) == rotation_variant8::quat ? rotation_format8::quatf_full : rotation_format8::quatf_drop_w_full; + + for (transform_streams& bone_stream : segment.bone_iterator()) + { + // We convert our rotation stream in place. We assume that the original format is quatf_full stored as rtm::quatf + // For all other formats, we keep the same sample size and either keep Quat_32 or use rtm::vector4f + ACL_ASSERT(bone_stream.rotations.get_sample_size() == sizeof(rtm::quatf), "Unexpected rotation sample size. %u != %zu", bone_stream.rotations.get_sample_size(), sizeof(rtm::quatf)); + + const uint32_t num_samples = bone_stream.rotations.get_num_samples(); + const float sample_rate = bone_stream.rotations.get_sample_rate(); + rotation_track_stream converted_stream(allocator, num_samples, sizeof(rtm::quatf), sample_rate, high_precision_format); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + rtm::quatf rotation = bone_stream.rotations.get_raw_sample(sample_index); + + switch (high_precision_format) + { + case rotation_format8::quatf_full: + // Original format, nothing to do + break; + case rotation_format8::quatf_drop_w_full: + // Drop W, we just ensure it is positive and write it back, the W component can be ignored afterwards + rotation = rtm::quat_ensure_positive_w(rotation); + break; + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(high_precision_format)); + break; + } + + converted_stream.set_raw_sample(sample_index, rotation); + } + + bone_stream.rotations = std::move(converted_stream); + } + } + + inline void convert_rotation_streams(iallocator& allocator, clip_context& context, rotation_format8 rotation_format) + { + for (segment_context& segment : context.segment_iterator()) + convert_rotation_streams(allocator, segment, rotation_format); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/normalize_streams.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/normalize_streams.h new file mode 100644 index 00000000..df24fa3e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/normalize_streams.h @@ -0,0 +1,406 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/enum_utils.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/core/range_reduction_types.h" +#include "acl/compression/impl/clip_context.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline track_stream_range calculate_track_range(const track_stream& stream, bool is_vector4) + { + rtm::vector4f min = rtm::vector_set(1e10F); + rtm::vector4f max = rtm::vector_set(-1e10F); + + const uint32_t num_samples = stream.get_num_samples(); + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::vector4f sample = stream.get_raw_sample(sample_index); + + min = rtm::vector_min(min, sample); + max = rtm::vector_max(max, sample); + } + + // Set the 4th component to zero if we don't need it + if (!is_vector4) + { + min = rtm::vector_set_w(min, 0.0F); + max = rtm::vector_set_w(max, 0.0F); + } + + return track_stream_range::from_min_max(min, max); + } + + inline void extract_bone_ranges_impl(const segment_context& segment, transform_range* bone_ranges) + { + const bool has_scale = segment_context_has_scale(segment); + + for (uint32_t bone_index = 0; bone_index < segment.num_bones; ++bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + transform_range& bone_range = bone_ranges[bone_index]; + + bone_range.rotation = calculate_track_range(bone_stream.rotations, true); + bone_range.translation = calculate_track_range(bone_stream.translations, false); + + if (has_scale) + bone_range.scale = calculate_track_range(bone_stream.scales, false); + else + bone_range.scale = track_stream_range(); + } + } + + inline void extract_clip_bone_ranges(iallocator& allocator, clip_context& context) + { + context.ranges = allocate_type_array(allocator, context.num_bones); + + ACL_ASSERT(context.num_segments == 1, "context must contain a single segment!"); + const segment_context& segment = context.segments[0]; + + acl_impl::extract_bone_ranges_impl(segment, context.ranges); + } + + inline void extract_segment_bone_ranges(iallocator& allocator, clip_context& context) + { + const rtm::vector4f one = rtm::vector_set(1.0F); + const rtm::vector4f zero = rtm::vector_zero(); + const float max_range_value_flt = float((1 << k_segment_range_reduction_num_bits_per_component) - 1); + const rtm::vector4f max_range_value = rtm::vector_set(max_range_value_flt); + const rtm::vector4f inv_max_range_value = rtm::vector_set(1.0F / max_range_value_flt); + + // Segment ranges are always normalized and live between [0.0 ... 1.0] + + auto fixup_range = [&](const track_stream_range& range) + { + // In our compressed format, we store the minimum value of the track range quantized on 8 bits. + // To get the best accuracy, we pick the value closest to the true minimum that is slightly lower. + // This is to ensure that we encompass the lowest value even after quantization. + const rtm::vector4f range_min = range.get_min(); + const rtm::vector4f scaled_min = rtm::vector_mul(range_min, max_range_value); + const rtm::vector4f quantized_min0 = rtm::vector_clamp(rtm::vector_floor(scaled_min), zero, max_range_value); + const rtm::vector4f quantized_min1 = rtm::vector_max(rtm::vector_sub(quantized_min0, one), zero); + + const rtm::vector4f padded_range_min0 = rtm::vector_mul(quantized_min0, inv_max_range_value); + const rtm::vector4f padded_range_min1 = rtm::vector_mul(quantized_min1, inv_max_range_value); + + // Check if min0 is below or equal to our original range minimum value, if it is, it is good + // enough to use otherwise min1 is guaranteed to be lower. + const rtm::mask4f is_min0_lower_mask = rtm::vector_less_equal(padded_range_min0, range_min); + const rtm::vector4f padded_range_min = rtm::vector_select(is_min0_lower_mask, padded_range_min0, padded_range_min1); + + // The story is different for the extent. We do not store the max, instead we use the extent + // for performance reasons: a single mul/add is required to reconstruct the original value. + // Now that our minimum value changed, our extent also changed. + // We want to pick the extent value that brings us closest to our original max value while + // being slightly larger to encompass it. + const rtm::vector4f range_max = range.get_max(); + const rtm::vector4f range_extent = rtm::vector_sub(range_max, padded_range_min); + const rtm::vector4f scaled_extent = rtm::vector_mul(range_extent, max_range_value); + const rtm::vector4f quantized_extent0 = rtm::vector_clamp(rtm::vector_ceil(scaled_extent), zero, max_range_value); + const rtm::vector4f quantized_extent1 = rtm::vector_min(rtm::vector_add(quantized_extent0, one), max_range_value); + + const rtm::vector4f padded_range_extent0 = rtm::vector_mul(quantized_extent0, inv_max_range_value); + const rtm::vector4f padded_range_extent1 = rtm::vector_mul(quantized_extent1, inv_max_range_value); + + // Check if extent0 is above or equal to our original range maximum value, if it is, it is good + // enough to use otherwise extent1 is guaranteed to be higher. + const rtm::mask4f is_extent0_higher_mask = rtm::vector_greater_equal(padded_range_extent0, range_max); + const rtm::vector4f padded_range_extent = rtm::vector_select(is_extent0_higher_mask, padded_range_extent0, padded_range_extent1); + + return track_stream_range::from_min_extent(padded_range_min, padded_range_extent); + }; + + for (segment_context& segment : context.segment_iterator()) + { + segment.ranges = allocate_type_array(allocator, segment.num_bones); + + acl_impl::extract_bone_ranges_impl(segment, segment.ranges); + + for (uint32_t bone_index = 0; bone_index < segment.num_bones; ++bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + transform_range& bone_range = segment.ranges[bone_index]; + + if (!bone_stream.is_rotation_constant && context.are_rotations_normalized) + bone_range.rotation = fixup_range(bone_range.rotation); + + if (!bone_stream.is_translation_constant && context.are_translations_normalized) + bone_range.translation = fixup_range(bone_range.translation); + + if (!bone_stream.is_scale_constant && context.are_scales_normalized) + bone_range.scale = fixup_range(bone_range.scale); + } + } + } + + inline rtm::vector4f RTM_SIMD_CALL normalize_sample(rtm::vector4f_arg0 sample, const track_stream_range& range) + { + const rtm::vector4f range_min = range.get_min(); + const rtm::vector4f range_extent = range.get_extent(); + const rtm::mask4f is_range_zero_mask = rtm::vector_less_than(range_extent, rtm::vector_set(0.000000001F)); + + rtm::vector4f normalized_sample = rtm::vector_div(rtm::vector_sub(sample, range_min), range_extent); + // Clamp because the division might be imprecise + normalized_sample = rtm::vector_min(normalized_sample, rtm::vector_set(1.0F)); + return rtm::vector_select(is_range_zero_mask, rtm::vector_zero(), normalized_sample); + } + + inline void normalize_rotation_streams(transform_streams* bone_streams, const transform_range* bone_ranges, uint32_t num_bones) + { + const rtm::vector4f one = rtm::vector_set(1.0F); + const rtm::vector4f zero = rtm::vector_zero(); + + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + transform_streams& bone_stream = bone_streams[bone_index]; + const transform_range& bone_range = bone_ranges[bone_index]; + + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(bone_stream.rotations.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", bone_stream.rotations.get_sample_size(), sizeof(rtm::vector4f)); + + // Constant or default tracks are not normalized + if (bone_stream.is_rotation_constant) + continue; + + const uint32_t num_samples = bone_stream.rotations.get_num_samples(); + + const rtm::vector4f range_min = bone_range.rotation.get_min(); + const rtm::vector4f range_extent = bone_range.rotation.get_extent(); + const rtm::mask4f is_range_zero_mask = rtm::vector_less_than(range_extent, rtm::vector_set(0.000000001F)); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + // normalized value is between [0.0 .. 1.0] + // value = (normalized value * range extent) + range min + // normalized value = (value - range min) / range extent + const rtm::vector4f rotation = bone_stream.rotations.get_raw_sample(sample_index); + rtm::vector4f normalized_rotation = rtm::vector_div(rtm::vector_sub(rotation, range_min), range_extent); + // Clamp because the division might be imprecise + normalized_rotation = rtm::vector_min(normalized_rotation, one); + normalized_rotation = rtm::vector_select(is_range_zero_mask, zero, normalized_rotation); + +#if defined(ACL_HAS_ASSERT_CHECKS) + switch (bone_stream.rotations.get_rotation_format()) + { + case rotation_format8::quatf_full: + ACL_ASSERT(rtm::vector_all_greater_equal(normalized_rotation, zero) && rtm::vector_all_less_equal(normalized_rotation, one), "Invalid normalized rotation. 0.0 <= [%f, %f, %f, %f] <= 1.0", (float)rtm::vector_get_x(normalized_rotation), (float)rtm::vector_get_y(normalized_rotation), (float)rtm::vector_get_z(normalized_rotation), (float)rtm::vector_get_w(normalized_rotation)); + break; + case rotation_format8::quatf_drop_w_full: + case rotation_format8::quatf_drop_w_variable: + ACL_ASSERT(rtm::vector_all_greater_equal3(normalized_rotation, zero) && rtm::vector_all_less_equal3(normalized_rotation, one), "Invalid normalized rotation. 0.0 <= [%f, %f, %f] <= 1.0", (float)rtm::vector_get_x(normalized_rotation), (float)rtm::vector_get_y(normalized_rotation), (float)rtm::vector_get_z(normalized_rotation)); + break; + } +#endif + + bone_stream.rotations.set_raw_sample(sample_index, normalized_rotation); + } + } + } + + inline void normalize_translation_streams(transform_streams* bone_streams, const transform_range* bone_ranges, uint32_t num_bones) + { + const rtm::vector4f one = rtm::vector_set(1.0F); + const rtm::vector4f zero = rtm::vector_zero(); + + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + transform_streams& bone_stream = bone_streams[bone_index]; + const transform_range& bone_range = bone_ranges[bone_index]; + + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(bone_stream.translations.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", bone_stream.translations.get_sample_size(), sizeof(rtm::vector4f)); + + // Constant or default tracks are not normalized + if (bone_stream.is_translation_constant) + continue; + + const uint32_t num_samples = bone_stream.translations.get_num_samples(); + + const rtm::vector4f range_min = bone_range.translation.get_min(); + const rtm::vector4f range_extent = bone_range.translation.get_extent(); + const rtm::mask4f is_range_zero_mask = rtm::vector_less_than(range_extent, rtm::vector_set(0.000000001F)); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + // normalized value is between [0.0 .. 1.0] + // value = (normalized value * range extent) + range min + // normalized value = (value - range min) / range extent + const rtm::vector4f translation = bone_stream.translations.get_raw_sample(sample_index); + rtm::vector4f normalized_translation = rtm::vector_div(rtm::vector_sub(translation, range_min), range_extent); + // Clamp because the division might be imprecise + normalized_translation = rtm::vector_min(normalized_translation, one); + normalized_translation = rtm::vector_select(is_range_zero_mask, zero, normalized_translation); + + ACL_ASSERT(rtm::vector_all_greater_equal3(normalized_translation, zero) && rtm::vector_all_less_equal3(normalized_translation, one), "Invalid normalized translation. 0.0 <= [%f, %f, %f] <= 1.0", (float)rtm::vector_get_x(normalized_translation), (float)rtm::vector_get_y(normalized_translation), (float)rtm::vector_get_z(normalized_translation)); + + bone_stream.translations.set_raw_sample(sample_index, normalized_translation); + } + } + } + + inline void normalize_scale_streams(transform_streams* bone_streams, const transform_range* bone_ranges, uint32_t num_bones) + { + const rtm::vector4f one = rtm::vector_set(1.0F); + const rtm::vector4f zero = rtm::vector_zero(); + + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + transform_streams& bone_stream = bone_streams[bone_index]; + const transform_range& bone_range = bone_ranges[bone_index]; + + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(bone_stream.scales.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", bone_stream.scales.get_sample_size(), sizeof(rtm::vector4f)); + + // Constant or default tracks are not normalized + if (bone_stream.is_scale_constant) + continue; + + const uint32_t num_samples = bone_stream.scales.get_num_samples(); + + const rtm::vector4f range_min = bone_range.scale.get_min(); + const rtm::vector4f range_extent = bone_range.scale.get_extent(); + const rtm::mask4f is_range_zero_mask = rtm::vector_less_than(range_extent, rtm::vector_set(0.000000001F)); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + // normalized value is between [0.0 .. 1.0] + // value = (normalized value * range extent) + range min + // normalized value = (value - range min) / range extent + const rtm::vector4f scale = bone_stream.scales.get_raw_sample(sample_index); + rtm::vector4f normalized_scale = rtm::vector_div(rtm::vector_sub(scale, range_min), range_extent); + // Clamp because the division might be imprecise + normalized_scale = rtm::vector_min(normalized_scale, one); + normalized_scale = rtm::vector_select(is_range_zero_mask, zero, normalized_scale); + + ACL_ASSERT(rtm::vector_all_greater_equal3(normalized_scale, zero) && rtm::vector_all_less_equal3(normalized_scale, one), "Invalid normalized scale. 0.0 <= [%f, %f, %f] <= 1.0", (float)rtm::vector_get_x(normalized_scale), (float)rtm::vector_get_y(normalized_scale), (float)rtm::vector_get_z(normalized_scale)); + + bone_stream.scales.set_raw_sample(sample_index, normalized_scale); + } + } + } + + inline void normalize_clip_streams(clip_context& context, range_reduction_flags8 range_reduction) + { + ACL_ASSERT(context.num_segments == 1, "context must contain a single segment!"); + segment_context& segment = context.segments[0]; + + const bool has_scale = segment_context_has_scale(segment); + + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations)) + { + normalize_rotation_streams(segment.bone_streams, context.ranges, segment.num_bones); + context.are_rotations_normalized = true; + } + + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations)) + { + normalize_translation_streams(segment.bone_streams, context.ranges, segment.num_bones); + context.are_translations_normalized = true; + } + + if (has_scale && are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales)) + { + normalize_scale_streams(segment.bone_streams, context.ranges, segment.num_bones); + context.are_scales_normalized = true; + } + } + + inline void normalize_segment_streams(clip_context& context, range_reduction_flags8 range_reduction) + { + for (segment_context& segment : context.segment_iterator()) + { + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations)) + { + normalize_rotation_streams(segment.bone_streams, segment.ranges, segment.num_bones); + segment.are_rotations_normalized = true; + } + + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations)) + { + normalize_translation_streams(segment.bone_streams, segment.ranges, segment.num_bones); + segment.are_translations_normalized = true; + } + + const bool has_scale = segment_context_has_scale(segment); + if (has_scale && are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales)) + { + normalize_scale_streams(segment.bone_streams, segment.ranges, segment.num_bones); + segment.are_scales_normalized = true; + } + + uint32_t range_data_size = 0; + uint32_t range_data_rotation_num = 0; + + for (uint32_t bone_index = 0; bone_index < segment.num_bones; ++bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (bone_stream.is_stripped_from_output()) + continue; + + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) && !bone_stream.is_rotation_constant) + { + ACL_ASSERT(bone_stream.rotations.get_rotation_format() != rotation_format8::quatf_full, "Normalization only supported on drop W variants"); + range_data_size += k_segment_range_reduction_num_bytes_per_component * 6; + range_data_rotation_num++; + } + + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) && !bone_stream.is_translation_constant) + range_data_size += k_segment_range_reduction_num_bytes_per_component * 6; + + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) && !bone_stream.is_scale_constant) + range_data_size += k_segment_range_reduction_num_bytes_per_component * 6; + } + + // The last partial rotation group is padded to 4 elements to keep decompression fast + const uint32_t partial_group_size_rotation = range_data_rotation_num % 4; + if (partial_group_size_rotation != 0) + range_data_size += (4 - partial_group_size_rotation) * k_segment_range_reduction_num_bytes_per_component * 6; + + segment.range_data_size = range_data_size; + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/normalize_track_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/normalize_track_impl.h new file mode 100644 index 00000000..23bf8322 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/normalize_track_impl.h @@ -0,0 +1,107 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/impl/track_list_context.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline void normalize_scalarf_track(track& mut_track, const scalarf_range& range) + { + using namespace rtm; + + const vector4f one = rtm::vector_set(1.0F); + const vector4f zero = vector_zero(); + + track_vector4f& typed_track = track_cast(mut_track); + + const uint32_t num_samples = mut_track.get_num_samples(); + + const vector4f range_min = range.get_min(); + const vector4f range_extent = range.get_extent(); + const mask4f is_range_zero_mask = vector_less_than(range_extent, rtm::vector_set(0.000000001F)); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + // normalized value is between [0.0 .. 1.0] + // value = (normalized value * range extent) + range min + // normalized value = (value - range min) / range extent + const vector4f sample = typed_track[sample_index]; + + vector4f normalized_sample = vector_div(vector_sub(sample, range_min), range_extent); + + // Clamp because the division might be imprecise + normalized_sample = vector_min(normalized_sample, one); + normalized_sample = vector_select(is_range_zero_mask, zero, normalized_sample); + + ACL_ASSERT(vector_all_greater_equal(normalized_sample, zero) && vector_all_less_equal(normalized_sample, one), "Invalid normalized value. 0.0 <= [%f, %f, %f, %f] <= 1.0", (float)vector_get_x(normalized_sample), (float)vector_get_y(normalized_sample), (float)vector_get_z(normalized_sample), (float)vector_get_w(normalized_sample)); + + typed_track[sample_index] = normalized_sample; + } + } + + inline void normalize_tracks(track_list_context& context) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + for (uint32_t track_index = 0; track_index < context.num_tracks; ++track_index) + { + const bool is_track_constant = context.is_constant(track_index); + if (is_track_constant) + continue; // Constant tracks don't need to be modified + + const track_range& range = context.range_list[track_index]; + track& mut_track = context.track_list[track_index]; + + switch (range.category) + { + case track_category8::scalarf: + normalize_scalarf_track(mut_track, range.range.scalarf); + break; + default: + ACL_ASSERT(false, "Invalid track category"); + break; + } + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/quantize_streams.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/quantize_streams.h new file mode 100644 index 00000000..0725bb91 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/quantize_streams.h @@ -0,0 +1,1819 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/track_formats.h" +#include "acl/core/utils.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/math/quat_packing.h" +#include "acl/math/vector4_packing.h" +#include "acl/compression/impl/track_bit_rate_database.h" +#include "acl/compression/impl/transform_bit_rate_permutations.h" +#include "acl/compression/impl/clip_context.h" +#include "acl/compression/impl/sample_streams.h" +#include "acl/compression/impl/normalize_streams.h" +#include "acl/compression/impl/convert_rotation_streams.h" +#include "acl/compression/transform_error_metrics.h" +#include "acl/compression/compression_settings.h" + +#include +#include + +#include +#include +#include + +// 0 = no debug info, 1 = basic info, 2 = verbose +#define ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION 0 + +// 0 = no debug into, 1 = basic info +#define ACL_IMPL_DEBUG_CONTRIBUTING_ERROR 0 + +// 0 = no profiling, 1 = we perform quantization 10 times in a row for every segment +#define ACL_IMPL_PROFILE_MATH 0 + +#if ACL_IMPL_PROFILE_MATH && defined(__ANDROID__) +#include +#endif + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct quantization_context + { + iallocator& allocator; + clip_context& clip; + const clip_context& raw_clip; + const clip_context& additive_base_clip; + segment_context* segment; + transform_streams* bone_streams; + const transform_metadata* metadata; + uint32_t num_bones; + const itransform_error_metric* error_metric; + + track_bit_rate_database bit_rate_database; + single_track_query local_query; + every_track_query all_local_query; + hierarchical_track_query object_query; + + uint32_t num_samples; // Num samples within our segment + uint32_t segment_sample_start_index; + float sample_rate; + float clip_duration; + float error_threshold; // Error threshold of the current bone being optimized + bool has_scale; + bool has_additive_base; + bool needs_conversion; + + rotation_format8 rotation_format; + vector_format8 translation_format; + vector_format8 scale_format; + compression_level8 compression_level; + + const transform_streams* raw_bone_streams; + + rtm::qvvf* additive_local_pose; // 1 per transform + rtm::qvvf* raw_local_pose; // 1 per transform + rtm::qvvf* lossy_local_pose; // 1 per transform + + rtm::qvvf* lossy_transforms_start; // 1 per transform, for calculating the contributing error + rtm::qvvf* lossy_transforms_end; // 1 per transform, for calculating the contributing error + + uint8_t* raw_local_transforms; // 1 per transform per sample in segment + uint8_t* base_local_transforms; // 1 per transform per sample in segment + uint8_t* raw_object_transforms; // 1 per transform per sample in segment + uint8_t* base_object_transforms; // 1 per transform per sample in segment + + uint8_t* local_transforms_converted; // 1 per transform + uint8_t* lossy_object_pose; // 1 per transform + size_t metric_transform_size; + + BoneBitRate* bit_rate_per_bone; // 1 per transform + uint32_t* parent_transform_indices; // 1 per transform + uint32_t* self_transform_indices; // 1 per transform + + uint32_t* chain_bone_indices; // 1 per transform + uint32_t num_bones_in_chain; + uint32_t padding1 = 0; // unused + + quantization_context(iallocator& allocator_, clip_context& clip_, const clip_context& raw_clip_, const clip_context& additive_base_clip_, const compression_settings& settings_) + : allocator(allocator_) + , clip(clip_) + , raw_clip(raw_clip_) + , additive_base_clip(additive_base_clip_) + , segment(nullptr) + , bone_streams(nullptr) + , metadata(clip_.metadata) + , num_bones(clip_.num_bones) + , error_metric(settings_.error_metric) + , bit_rate_database(allocator_, settings_.rotation_format, settings_.translation_format, settings_.scale_format, clip_.segments->bone_streams, raw_clip_.segments->bone_streams, clip_.num_bones, clip_.segments->num_samples) + , local_query() + , all_local_query(allocator_) + , object_query(allocator_) + , num_samples(~0U) + , segment_sample_start_index(~0U) + , sample_rate(clip_.sample_rate) + , clip_duration(clip_.duration) + , error_threshold(0.0F) + , has_scale(clip_.has_scale) + , has_additive_base(clip_.has_additive_base) + , rotation_format(settings_.rotation_format) + , translation_format(settings_.translation_format) + , scale_format(settings_.scale_format) + , compression_level(settings_.level) + , raw_bone_streams(raw_clip_.segments[0].bone_streams) + , lossy_transforms_start(nullptr) + , lossy_transforms_end(nullptr) + , num_bones_in_chain(0) + { + local_query.bind(bit_rate_database); + object_query.bind(bit_rate_database); + + needs_conversion = settings_.error_metric->needs_conversion(clip_.has_scale); + const size_t metric_transform_size_ = settings_.error_metric->get_transform_size(clip_.has_scale); + metric_transform_size = metric_transform_size_; + + additive_local_pose = clip_.has_additive_base ? allocate_type_array(allocator, num_bones) : nullptr; + raw_local_pose = allocate_type_array(allocator, num_bones); + lossy_local_pose = allocate_type_array(allocator, num_bones); + raw_local_transforms = allocate_type_array_aligned(allocator, metric_transform_size_ * num_bones * clip_.segments->num_samples, 64); + base_local_transforms = clip_.has_additive_base ? allocate_type_array_aligned(allocator, metric_transform_size_ * num_bones * clip_.segments->num_samples, 64) : nullptr; + raw_object_transforms = allocate_type_array_aligned(allocator, metric_transform_size_ * num_bones * clip_.segments->num_samples, 64); + base_object_transforms = clip_.has_additive_base ? allocate_type_array_aligned(allocator, metric_transform_size_ * num_bones * clip_.segments->num_samples, 64) : nullptr; + local_transforms_converted = needs_conversion ? allocate_type_array_aligned(allocator, metric_transform_size_ * num_bones, 64) : nullptr; + lossy_object_pose = allocate_type_array_aligned(allocator, metric_transform_size_ * num_bones, 64); + bit_rate_per_bone = allocate_type_array(allocator, num_bones); + parent_transform_indices = allocate_type_array(allocator, num_bones); + self_transform_indices = allocate_type_array(allocator, num_bones); + chain_bone_indices = allocate_type_array(allocator, num_bones); + + for (uint32_t transform_index = 0; transform_index < num_bones; ++transform_index) + { + const transform_metadata& metadata_ = clip_.metadata[transform_index]; + parent_transform_indices[transform_index] = metadata_.parent_index; + self_transform_indices[transform_index] = transform_index; + } + } + + ~quantization_context() + { + deallocate_type_array(allocator, additive_local_pose, num_bones); + deallocate_type_array(allocator, raw_local_pose, num_bones); + deallocate_type_array(allocator, lossy_local_pose, num_bones); + deallocate_type_array(allocator, lossy_transforms_start, num_bones); + deallocate_type_array(allocator, lossy_transforms_end, num_bones); + deallocate_type_array(allocator, raw_local_transforms, metric_transform_size * num_bones * clip.segments->num_samples); + deallocate_type_array(allocator, base_local_transforms, metric_transform_size * num_bones * clip.segments->num_samples); + deallocate_type_array(allocator, raw_object_transforms, metric_transform_size * num_bones * clip.segments->num_samples); + deallocate_type_array(allocator, base_object_transforms, metric_transform_size * num_bones * clip.segments->num_samples); + deallocate_type_array(allocator, local_transforms_converted, metric_transform_size * num_bones); + deallocate_type_array(allocator, lossy_object_pose, metric_transform_size * num_bones); + deallocate_type_array(allocator, bit_rate_per_bone, num_bones); + deallocate_type_array(allocator, parent_transform_indices, num_bones); + deallocate_type_array(allocator, self_transform_indices, num_bones); + deallocate_type_array(allocator, chain_bone_indices, num_bones); + } + + void set_segment(segment_context& segment_) + { + segment = &segment_; + bone_streams = segment_.bone_streams; + num_samples = segment_.num_samples; + segment_sample_start_index = segment_.clip_sample_offset; + bit_rate_database.set_segment(segment_.bone_streams, segment_.num_bones, segment_.num_samples); + + // Cache every raw local/object transforms and the base local transforms since they never change + const itransform_error_metric* error_metric_ = error_metric; + const size_t sample_transform_size = metric_transform_size * num_bones; + + const auto convert_transforms_impl = std::mem_fn(has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale); + const auto apply_additive_to_base_impl = std::mem_fn(has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale); + const auto local_to_object_space_impl = std::mem_fn(has_scale ? &itransform_error_metric::local_to_object_space : &itransform_error_metric::local_to_object_space_no_scale); + + itransform_error_metric::convert_transforms_args convert_transforms_args_raw; + convert_transforms_args_raw.dirty_transform_indices = self_transform_indices; + convert_transforms_args_raw.num_dirty_transforms = num_bones; + convert_transforms_args_raw.transforms = raw_local_pose; + convert_transforms_args_raw.num_transforms = num_bones; + + itransform_error_metric::convert_transforms_args convert_transforms_args_base = convert_transforms_args_raw; + convert_transforms_args_base.transforms = additive_local_pose; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_raw; + apply_additive_to_base_args_raw.dirty_transform_indices = self_transform_indices; + apply_additive_to_base_args_raw.num_dirty_transforms = num_bones; + apply_additive_to_base_args_raw.local_transforms = nullptr; + apply_additive_to_base_args_raw.base_transforms = nullptr; + apply_additive_to_base_args_raw.num_transforms = num_bones; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw; + local_to_object_space_args_raw.dirty_transform_indices = self_transform_indices; + local_to_object_space_args_raw.num_dirty_transforms = num_bones; + local_to_object_space_args_raw.parent_transform_indices = parent_transform_indices; + local_to_object_space_args_raw.local_transforms = nullptr; + local_to_object_space_args_raw.num_transforms = num_bones; + + for (uint32_t sample_index = 0; sample_index < segment_.num_samples; ++sample_index) + { + // Sample our streams and calculate the error + // The sample time is calculated from the full clip duration to be consistent with decompression + const float sample_time = rtm::scalar_min(float(segment_.clip_sample_offset + sample_index) / sample_rate, clip_duration); + + sample_streams(raw_bone_streams, num_bones, sample_time, raw_local_pose); + + uint8_t* sample_raw_local_transforms = raw_local_transforms + (sample_index * sample_transform_size); + + if (needs_conversion) + convert_transforms_impl(error_metric_, convert_transforms_args_raw, sample_raw_local_transforms); + else + std::memcpy(sample_raw_local_transforms, raw_local_pose, sample_transform_size); + + if (has_additive_base) + { + const float normalized_sample_time = additive_base_clip.num_samples > 1 ? (sample_time / clip_duration) : 0.0F; + const float additive_sample_time = additive_base_clip.num_samples > 1 ? (normalized_sample_time * additive_base_clip.duration) : 0.0F; + sample_streams(additive_base_clip.segments[0].bone_streams, num_bones, additive_sample_time, additive_local_pose); + + uint8_t* sample_base_local_transforms = base_local_transforms + (sample_index * sample_transform_size); + + if (needs_conversion) + convert_transforms_impl(error_metric_, convert_transforms_args_base, sample_base_local_transforms); + else + std::memcpy(sample_base_local_transforms, additive_local_pose, sample_transform_size); + + apply_additive_to_base_args_raw.local_transforms = sample_raw_local_transforms; + apply_additive_to_base_args_raw.base_transforms = sample_base_local_transforms; + apply_additive_to_base_impl(error_metric_, apply_additive_to_base_args_raw, sample_raw_local_transforms); + } + + local_to_object_space_args_raw.local_transforms = sample_raw_local_transforms; + + uint8_t* sample_raw_object_transforms = raw_object_transforms + (sample_index * sample_transform_size); + local_to_object_space_impl(error_metric_, local_to_object_space_args_raw, sample_raw_object_transforms); + } + } + + bool is_valid() const { return segment != nullptr; } + + quantization_context(const quantization_context&) = delete; + quantization_context(quantization_context&&) = delete; + quantization_context& operator=(const quantization_context&) = delete; + quantization_context& operator=(quantization_context&&) = delete; + }; + + inline void quantize_fixed_rotation_stream(iallocator& allocator, const rotation_track_stream& raw_stream, rotation_format8 rotation_format, rotation_track_stream& out_quantized_stream) + { + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(raw_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", raw_stream.get_sample_size(), sizeof(rtm::vector4f)); + + const uint32_t num_samples = raw_stream.get_num_samples(); + const uint32_t rotation_sample_size = get_packed_rotation_size(rotation_format); + const float sample_rate = raw_stream.get_sample_rate(); + rotation_track_stream quantized_stream(allocator, num_samples, rotation_sample_size, sample_rate, rotation_format); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::quatf rotation = raw_stream.get_raw_sample(sample_index); + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(sample_index); + + switch (rotation_format) + { + case rotation_format8::quatf_full: + pack_vector4_128(rtm::quat_to_vector(rotation), quantized_ptr); + break; + case rotation_format8::quatf_drop_w_full: + pack_vector3_96(rtm::quat_to_vector(rotation), quantized_ptr); + break; + case rotation_format8::quatf_drop_w_variable: + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(rotation_format)); + break; + } + } + + out_quantized_stream = std::move(quantized_stream); + } + + inline void quantize_fixed_rotation_stream(quantization_context& context, uint32_t bone_index, rotation_format8 rotation_format) + { + ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index); + + transform_streams& bone_stream = context.bone_streams[bone_index]; + + // Default tracks aren't quantized + if (bone_stream.is_rotation_default) + return; + + quantize_fixed_rotation_stream(context.allocator, bone_stream.rotations, rotation_format, bone_stream.rotations); + } + + inline void quantize_variable_rotation_stream(quantization_context& context, const rotation_track_stream& raw_clip_stream, const rotation_track_stream& raw_segment_stream, const track_stream_range& clip_range, uint8_t bit_rate, rotation_track_stream& out_quantized_stream) + { + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(raw_segment_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", raw_segment_stream.get_sample_size(), sizeof(rtm::vector4f)); + + const uint32_t num_samples = is_constant_bit_rate(bit_rate) ? 1 : raw_segment_stream.get_num_samples(); + const uint32_t sample_size = sizeof(uint64_t) * 2; + const float sample_rate = raw_segment_stream.get_sample_rate(); + rotation_track_stream quantized_stream(context.allocator, num_samples, sample_size, sample_rate, rotation_format8::quatf_drop_w_variable, bit_rate); + + if (is_constant_bit_rate(bit_rate)) + { + rtm::vector4f rotation = raw_clip_stream.get_raw_sample(context.segment_sample_start_index); + rotation = convert_rotation(rotation, rotation_format8::quatf_full, rotation_format8::quatf_drop_w_variable); + + const rtm::vector4f normalized_rotation = normalize_sample(rotation, clip_range); + + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(0); + pack_vector3_u48_unsafe(normalized_rotation, quantized_ptr); + } + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(sample_index); + + if (is_raw_bit_rate(bit_rate)) + { + rtm::vector4f rotation = raw_clip_stream.get_raw_sample(context.segment_sample_start_index + sample_index); + rotation = convert_rotation(rotation, rotation_format8::quatf_full, rotation_format8::quatf_drop_w_variable); + pack_vector3_96(rotation, quantized_ptr); + } + else + { + const rtm::quatf rotation = raw_segment_stream.get_raw_sample(sample_index); + pack_vector3_uXX_unsafe(rtm::quat_to_vector(rotation), num_bits_at_bit_rate, quantized_ptr); + } + } + } + + out_quantized_stream = std::move(quantized_stream); + } + + inline void quantize_variable_rotation_stream(quantization_context& context, uint32_t bone_index, uint8_t bit_rate) + { + ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index); + + transform_streams& bone_stream = context.bone_streams[bone_index]; + + // Default tracks aren't quantized + if (bone_stream.is_rotation_default) + return; + + const transform_streams& raw_bone_stream = context.raw_bone_streams[bone_index]; + const rotation_format8 highest_bit_rate = get_highest_variant_precision(rotation_variant8::quat_drop_w); + const track_stream_range& bone_range = context.clip.ranges[bone_index].rotation; + + // If our format is variable, we keep them fixed at the highest bit rate in the variant + if (bone_stream.is_rotation_constant) + quantize_fixed_rotation_stream(context.allocator, bone_stream.rotations, highest_bit_rate, bone_stream.rotations); + else + quantize_variable_rotation_stream(context, raw_bone_stream.rotations, bone_stream.rotations, bone_range, bit_rate, bone_stream.rotations); + } + + inline void quantize_fixed_translation_stream(iallocator& allocator, const translation_track_stream& raw_stream, vector_format8 translation_format, translation_track_stream& out_quantized_stream) + { + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(raw_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", raw_stream.get_sample_size(), sizeof(rtm::vector4f)); + ACL_ASSERT(raw_stream.get_vector_format() == vector_format8::vector3f_full, "Expected a vector3f_full vector format, found: %s", get_vector_format_name(raw_stream.get_vector_format())); + + const uint32_t num_samples = raw_stream.get_num_samples(); + const uint32_t sample_size = get_packed_vector_size(translation_format); + const float sample_rate = raw_stream.get_sample_rate(); + translation_track_stream quantized_stream(allocator, num_samples, sample_size, sample_rate, translation_format); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::vector4f translation = raw_stream.get_raw_sample(sample_index); + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(sample_index); + + switch (translation_format) + { + case vector_format8::vector3f_full: + pack_vector3_96(translation, quantized_ptr); + break; + case vector_format8::vector3f_variable: + default: + ACL_ASSERT(false, "Invalid or unsupported vector format: %s", get_vector_format_name(translation_format)); + break; + } + } + + out_quantized_stream = std::move(quantized_stream); + } + + inline void quantize_fixed_translation_stream(quantization_context& context, uint32_t bone_index, vector_format8 translation_format) + { + ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index); + + transform_streams& bone_stream = context.bone_streams[bone_index]; + + // Default tracks aren't quantized + if (bone_stream.is_translation_default) + return; + + // Constant translation tracks store the remaining sample with full precision + const vector_format8 format = bone_stream.is_translation_constant ? vector_format8::vector3f_full : translation_format; + + quantize_fixed_translation_stream(context.allocator, bone_stream.translations, format, bone_stream.translations); + } + + inline void quantize_variable_translation_stream(quantization_context& context, const translation_track_stream& raw_clip_stream, const translation_track_stream& raw_segment_stream, const track_stream_range& clip_range, uint8_t bit_rate, translation_track_stream& out_quantized_stream) + { + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(raw_segment_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", raw_segment_stream.get_sample_size(), sizeof(rtm::vector4f)); + ACL_ASSERT(raw_segment_stream.get_vector_format() == vector_format8::vector3f_full, "Expected a vector3f_full vector format, found: %s", get_vector_format_name(raw_segment_stream.get_vector_format())); + + const uint32_t num_samples = is_constant_bit_rate(bit_rate) ? 1 : raw_segment_stream.get_num_samples(); + const uint32_t sample_size = sizeof(uint64_t) * 2; + const float sample_rate = raw_segment_stream.get_sample_rate(); + translation_track_stream quantized_stream(context.allocator, num_samples, sample_size, sample_rate, vector_format8::vector3f_variable, bit_rate); + + if (is_constant_bit_rate(bit_rate)) + { + const rtm::vector4f translation = raw_clip_stream.get_raw_sample(context.segment_sample_start_index); + const rtm::vector4f normalized_translation = normalize_sample(translation, clip_range); + + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(0); + pack_vector3_u48_unsafe(normalized_translation, quantized_ptr); + } + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(sample_index); + + if (is_raw_bit_rate(bit_rate)) + { + const rtm::vector4f translation = raw_clip_stream.get_raw_sample(context.segment_sample_start_index + sample_index); + pack_vector3_96(translation, quantized_ptr); + } + else + { + const rtm::vector4f translation = raw_segment_stream.get_raw_sample(sample_index); + pack_vector3_uXX_unsafe(translation, num_bits_at_bit_rate, quantized_ptr); + } + } + } + + out_quantized_stream = std::move(quantized_stream); + } + + inline void quantize_variable_translation_stream(quantization_context& context, uint32_t bone_index, uint8_t bit_rate) + { + ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index); + + transform_streams& bone_stream = context.bone_streams[bone_index]; + + // Default tracks aren't quantized + if (bone_stream.is_translation_default) + return; + + const track_stream_range& bone_range = context.clip.ranges[bone_index].translation; + const transform_streams& raw_bone_stream = context.raw_bone_streams[bone_index]; + + // Constant translation tracks store the remaining sample with full precision + if (bone_stream.is_translation_constant) + quantize_fixed_translation_stream(context.allocator, bone_stream.translations, vector_format8::vector3f_full, bone_stream.translations); + else + quantize_variable_translation_stream(context, raw_bone_stream.translations, bone_stream.translations, bone_range, bit_rate, bone_stream.translations); + } + + inline void quantize_fixed_scale_stream(iallocator& allocator, const scale_track_stream& raw_stream, vector_format8 scale_format, scale_track_stream& out_quantized_stream) + { + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(raw_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", raw_stream.get_sample_size(), sizeof(rtm::vector4f)); + ACL_ASSERT(raw_stream.get_vector_format() == vector_format8::vector3f_full, "Expected a vector3f_full vector format, found: %s", get_vector_format_name(raw_stream.get_vector_format())); + + const uint32_t num_samples = raw_stream.get_num_samples(); + const uint32_t sample_size = get_packed_vector_size(scale_format); + const float sample_rate = raw_stream.get_sample_rate(); + scale_track_stream quantized_stream(allocator, num_samples, sample_size, sample_rate, scale_format); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::vector4f scale = raw_stream.get_raw_sample(sample_index); + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(sample_index); + + switch (scale_format) + { + case vector_format8::vector3f_full: + pack_vector3_96(scale, quantized_ptr); + break; + case vector_format8::vector3f_variable: + default: + ACL_ASSERT(false, "Invalid or unsupported vector format: %s", get_vector_format_name(scale_format)); + break; + } + } + + out_quantized_stream = std::move(quantized_stream); + } + + inline void quantize_fixed_scale_stream(quantization_context& context, uint32_t bone_index, vector_format8 scale_format) + { + ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index); + + transform_streams& bone_stream = context.bone_streams[bone_index]; + + // Default tracks aren't quantized + if (bone_stream.is_scale_default) + return; + + // Constant scale tracks store the remaining sample with full precision + const vector_format8 format = bone_stream.is_scale_constant ? vector_format8::vector3f_full : scale_format; + + quantize_fixed_scale_stream(context.allocator, bone_stream.scales, format, bone_stream.scales); + } + + inline void quantize_variable_scale_stream(quantization_context& context, const scale_track_stream& raw_clip_stream, const scale_track_stream& raw_segment_stream, const track_stream_range& clip_range, uint8_t bit_rate, scale_track_stream& out_quantized_stream) + { + // We expect all our samples to have the same width of sizeof(rtm::vector4f) + ACL_ASSERT(raw_segment_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", raw_segment_stream.get_sample_size(), sizeof(rtm::vector4f)); + ACL_ASSERT(raw_segment_stream.get_vector_format() == vector_format8::vector3f_full, "Expected a vector3f_full vector format, found: %s", get_vector_format_name(raw_segment_stream.get_vector_format())); + + const uint32_t num_samples = is_constant_bit_rate(bit_rate) ? 1 : raw_segment_stream.get_num_samples(); + const uint32_t sample_size = sizeof(uint64_t) * 2; + const float sample_rate = raw_segment_stream.get_sample_rate(); + scale_track_stream quantized_stream(context.allocator, num_samples, sample_size, sample_rate, vector_format8::vector3f_variable, bit_rate); + + if (is_constant_bit_rate(bit_rate)) + { + const rtm::vector4f scale = raw_clip_stream.get_raw_sample(context.segment_sample_start_index); + const rtm::vector4f normalized_scale = normalize_sample(scale, clip_range); + + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(0); + pack_vector3_u48_unsafe(normalized_scale, quantized_ptr); + } + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + uint8_t* quantized_ptr = quantized_stream.get_raw_sample_ptr(sample_index); + + if (is_raw_bit_rate(bit_rate)) + { + const rtm::vector4f scale = raw_clip_stream.get_raw_sample(context.segment_sample_start_index + sample_index); + pack_vector3_96(scale, quantized_ptr); + } + else + { + const rtm::vector4f scale = raw_segment_stream.get_raw_sample(sample_index); + pack_vector3_uXX_unsafe(scale, num_bits_at_bit_rate, quantized_ptr); + } + } + } + + out_quantized_stream = std::move(quantized_stream); + } + + inline void quantize_variable_scale_stream(quantization_context& context, uint32_t bone_index, uint8_t bit_rate) + { + ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index); + + transform_streams& bone_stream = context.bone_streams[bone_index]; + + // Default tracks aren't quantized + if (bone_stream.is_scale_default) + return; + + const track_stream_range& bone_range = context.clip.ranges[bone_index].scale; + const transform_streams& raw_bone_stream = context.raw_bone_streams[bone_index]; + + // Constant scale tracks store the remaining sample with full precision + if (bone_stream.is_scale_constant) + quantize_fixed_scale_stream(context.allocator, bone_stream.scales, vector_format8::vector3f_full, bone_stream.scales); + else + quantize_variable_scale_stream(context, raw_bone_stream.scales, bone_stream.scales, bone_range, bit_rate, bone_stream.scales); + } + + enum class error_scan_stop_condition { until_error_too_high, until_end_of_segment }; + + inline float calculate_max_error_at_bit_rate_local(quantization_context& context, uint32_t target_bone_index, error_scan_stop_condition stop_condition) + { + const itransform_error_metric* error_metric = context.error_metric; + const bool needs_conversion = context.needs_conversion; + const bool has_additive_base = context.has_additive_base; + const transform_metadata& target_bone = context.metadata[target_bone_index]; + const uint32_t num_transforms = context.num_bones; + const size_t sample_transform_size = context.metric_transform_size * context.num_bones; + const float sample_rate = context.sample_rate; + const float clip_duration = context.clip_duration; + const rtm::scalarf error_threshold = rtm::scalar_set(context.error_threshold); + + const auto convert_transforms_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale); + const auto apply_additive_to_base_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale); + const auto calculate_error_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::calculate_error : &itransform_error_metric::calculate_error_no_scale); + + itransform_error_metric::convert_transforms_args convert_transforms_args_lossy; + convert_transforms_args_lossy.dirty_transform_indices = &target_bone_index; + convert_transforms_args_lossy.num_dirty_transforms = 1; + convert_transforms_args_lossy.transforms = context.lossy_local_pose; + convert_transforms_args_lossy.num_transforms = num_transforms; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy; + apply_additive_to_base_args_lossy.dirty_transform_indices = &target_bone_index; + apply_additive_to_base_args_lossy.num_dirty_transforms = 1; + apply_additive_to_base_args_lossy.local_transforms = needs_conversion ? (const void*)context.local_transforms_converted : (const void*)context.lossy_local_pose; + apply_additive_to_base_args_lossy.base_transforms = nullptr; + apply_additive_to_base_args_lossy.num_transforms = num_transforms; + + itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = nullptr; + calculate_error_args.transform1 = needs_conversion ? (const void*)(context.local_transforms_converted + (context.metric_transform_size * target_bone_index)) : (const void*)(context.lossy_local_pose + target_bone_index); + calculate_error_args.construct_sphere_shell(target_bone.shell_distance); + + const uint8_t* raw_transform = context.raw_local_transforms + (target_bone_index * context.metric_transform_size); + const uint8_t* base_transforms = context.base_local_transforms; + + context.local_query.build(target_bone_index, context.bit_rate_per_bone[target_bone_index]); + + float sample_indexf = float(context.segment_sample_start_index); + rtm::scalarf max_error = rtm::scalar_set(0.0F); + + for (uint32_t sample_index = 0; sample_index < context.num_samples; ++sample_index) + { + // Sample our streams and calculate the error + // The sample time is calculated from the full clip duration to be consistent with decompression + const float sample_time = rtm::scalar_min(sample_indexf / sample_rate, clip_duration); + + context.bit_rate_database.sample(context.local_query, sample_time, context.lossy_local_pose, num_transforms); + + if (needs_conversion) + convert_transforms_impl(error_metric, convert_transforms_args_lossy, context.local_transforms_converted); + + if (has_additive_base) + { + apply_additive_to_base_args_lossy.base_transforms = base_transforms; + base_transforms += sample_transform_size; + + apply_additive_to_base_impl(error_metric, apply_additive_to_base_args_lossy, context.lossy_local_pose); + } + + calculate_error_args.transform0 = raw_transform; + raw_transform += sample_transform_size; + +#if defined(RTM_COMPILER_MSVC) && defined(RTM_ARCH_X86) && RTM_COMPILER_MSVC == RTM_COMPILER_MSVC_2015 + // VS2015 fails to generate the right x86 assembly, branch instead + (void)calculate_error_impl; + const rtm::scalarf error = context.has_scale ? error_metric->calculate_error(calculate_error_args) : error_metric->calculate_error_no_scale(calculate_error_args); +#else + const rtm::scalarf error = calculate_error_impl(error_metric, calculate_error_args); +#endif + + max_error = rtm::scalar_max(max_error, error); + if (stop_condition == error_scan_stop_condition::until_error_too_high && rtm::scalar_greater_equal(error, error_threshold)) + break; + + sample_indexf += 1.0F; + } + + return rtm::scalar_cast(max_error); + } + + inline float calculate_max_error_at_bit_rate_object(quantization_context& context, uint32_t target_bone_index, error_scan_stop_condition stop_condition) + { + const itransform_error_metric* error_metric = context.error_metric; + const bool needs_conversion = context.needs_conversion; + const bool has_additive_base = context.has_additive_base; + const transform_metadata& target_bone = context.metadata[target_bone_index]; + const size_t sample_transform_size = context.metric_transform_size * context.num_bones; + const float sample_rate = context.sample_rate; + const float clip_duration = context.clip_duration; + const rtm::scalarf error_threshold = rtm::scalar_set(context.error_threshold); + + const auto convert_transforms_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale); + const auto apply_additive_to_base_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale); + const auto local_to_object_space_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::local_to_object_space : &itransform_error_metric::local_to_object_space_no_scale); + const auto calculate_error_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::calculate_error : &itransform_error_metric::calculate_error_no_scale); + + itransform_error_metric::convert_transforms_args convert_transforms_args_lossy; + convert_transforms_args_lossy.dirty_transform_indices = context.chain_bone_indices; + convert_transforms_args_lossy.num_dirty_transforms = context.num_bones_in_chain; + convert_transforms_args_lossy.transforms = context.lossy_local_pose; + convert_transforms_args_lossy.num_transforms = context.num_bones; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy; + apply_additive_to_base_args_lossy.dirty_transform_indices = context.chain_bone_indices; + apply_additive_to_base_args_lossy.num_dirty_transforms = context.num_bones_in_chain; + apply_additive_to_base_args_lossy.local_transforms = needs_conversion ? (const void*)(context.local_transforms_converted) : (const void*)context.lossy_local_pose; + apply_additive_to_base_args_lossy.base_transforms = nullptr; + apply_additive_to_base_args_lossy.num_transforms = context.num_bones; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy; + local_to_object_space_args_lossy.dirty_transform_indices = context.chain_bone_indices; + local_to_object_space_args_lossy.num_dirty_transforms = context.num_bones_in_chain; + local_to_object_space_args_lossy.parent_transform_indices = context.parent_transform_indices; + local_to_object_space_args_lossy.local_transforms = needs_conversion ? (const void*)(context.local_transforms_converted) : (const void*)context.lossy_local_pose; + local_to_object_space_args_lossy.num_transforms = context.num_bones; + + itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = nullptr; + calculate_error_args.transform1 = context.lossy_object_pose + (target_bone_index * context.metric_transform_size); + calculate_error_args.construct_sphere_shell(target_bone.shell_distance); + + const uint8_t* raw_transform = context.raw_object_transforms + (target_bone_index * context.metric_transform_size); + const uint8_t* base_transforms = context.base_local_transforms; + + context.object_query.build(target_bone_index, context.bit_rate_per_bone, context.bone_streams); + + float sample_indexf = float(context.segment_sample_start_index); + rtm::scalarf max_error = rtm::scalar_set(0.0F); + + for (uint32_t sample_index = 0; sample_index < context.num_samples; ++sample_index) + { + // Sample our streams and calculate the error + // The sample time is calculated from the full clip duration to be consistent with decompression + const float sample_time = rtm::scalar_min(sample_indexf / sample_rate, clip_duration); + + context.bit_rate_database.sample(context.object_query, sample_time, context.lossy_local_pose, context.num_bones); + + if (needs_conversion) + convert_transforms_impl(error_metric, convert_transforms_args_lossy, context.local_transforms_converted); + + if (has_additive_base) + { + apply_additive_to_base_args_lossy.base_transforms = base_transforms; + base_transforms += sample_transform_size; + + apply_additive_to_base_impl(error_metric, apply_additive_to_base_args_lossy, context.lossy_local_pose); + } + + local_to_object_space_impl(error_metric, local_to_object_space_args_lossy, context.lossy_object_pose); + + calculate_error_args.transform0 = raw_transform; + raw_transform += sample_transform_size; + +#if defined(RTM_COMPILER_MSVC) && defined(RTM_ARCH_X86) && RTM_COMPILER_MSVC == RTM_COMPILER_MSVC_2015 + // VS2015 fails to generate the right x86 assembly, branch instead + (void)calculate_error_impl; + const rtm::scalarf error = context.has_scale ? error_metric->calculate_error(calculate_error_args) : error_metric->calculate_error_no_scale(calculate_error_args); +#else + const rtm::scalarf error = calculate_error_impl(error_metric, calculate_error_args); +#endif + + max_error = rtm::scalar_max(max_error, error); + if (stop_condition == error_scan_stop_condition::until_error_too_high && rtm::scalar_greater_equal(error, error_threshold)) + break; + + sample_indexf += 1.0F; + } + + return rtm::scalar_cast(max_error); + } + + inline void calculate_local_space_bit_rates(quantization_context& context) + { + // To minimize the bit rate, we first start by trying every permutation in local space + // until our error is acceptable. + // We try permutations from the lowest memory footprint to the highest. + + const uint32_t num_bones = context.num_bones; + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + // Update our error threshold + const float error_threshold = context.metadata[bone_index].precision; + context.error_threshold = error_threshold; + + // Bit rates at this point are one of three value: + // 0: if the segment track is normalized, it can be constant within the segment + // 1: if the segment track isn't normalized, it starts at the lowest bit rate + // 255: if the track is constant/default for the whole clip + const BoneBitRate bone_bit_rates = context.bit_rate_per_bone[bone_index]; + + if (bone_bit_rates.rotation == k_invalid_bit_rate && bone_bit_rates.translation == k_invalid_bit_rate && bone_bit_rates.scale == k_invalid_bit_rate) + { +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + printf("%u: Best bit rates: %u | %u | %u\n", bone_index, bone_bit_rates.rotation, bone_bit_rates.translation, bone_bit_rates.scale); +#endif + continue; // Every track bit rate is constant/default, nothing else to do + } + + BoneBitRate best_bit_rates = bone_bit_rates; + float best_error = 1.0E10F; + uint32_t prev_transform_size = ~0U; + bool is_error_good_enough = false; + + if (context.has_scale) + { + const size_t num_permutations = get_array_size(acl_impl::k_local_bit_rate_permutations); + for (size_t permutation_index = 0; permutation_index < num_permutations; ++permutation_index) + { + const uint8_t rotation_bit_rate = acl_impl::k_local_bit_rate_permutations[permutation_index][0]; + if (bone_bit_rates.rotation == 1) + { + if (rotation_bit_rate == 0) + continue; // Skip permutations we aren't interested in + } + else if (bone_bit_rates.rotation == k_invalid_bit_rate) + { + if (rotation_bit_rate != 0) + continue; // Skip permutations we aren't interested in + } + + const uint8_t translation_bit_rate = acl_impl::k_local_bit_rate_permutations[permutation_index][1]; + if (bone_bit_rates.translation == 1) + { + if (translation_bit_rate == 0) + continue; // Skip permutations we aren't interested in + } + else if (bone_bit_rates.translation == k_invalid_bit_rate) + { + if (translation_bit_rate != 0) + continue; // Skip permutations we aren't interested in + } + + const uint8_t scale_bit_rate = acl_impl::k_local_bit_rate_permutations[permutation_index][2]; + if (bone_bit_rates.scale == 1) + { + if (scale_bit_rate == 0) + continue; // Skip permutations we aren't interested in + } + else if (bone_bit_rates.scale == k_invalid_bit_rate) + { + if (scale_bit_rate != 0) + continue; // Skip permutations we aren't interested in + } + + const uint32_t rotation_size = get_num_bits_at_bit_rate(rotation_bit_rate); + const uint32_t translation_size = get_num_bits_at_bit_rate(translation_bit_rate); + const uint32_t scale_size = get_num_bits_at_bit_rate(scale_bit_rate); + const uint32_t transform_size = rotation_size + translation_size + scale_size; + + if (transform_size != prev_transform_size && is_error_good_enough) + { + // We already found the lowest transform size and we tried every permutation with that same size + break; + } + + prev_transform_size = transform_size; + + context.bit_rate_per_bone[bone_index].rotation = bone_bit_rates.rotation != k_invalid_bit_rate ? rotation_bit_rate : k_invalid_bit_rate; + context.bit_rate_per_bone[bone_index].translation = bone_bit_rates.translation != k_invalid_bit_rate ? translation_bit_rate : k_invalid_bit_rate; + context.bit_rate_per_bone[bone_index].scale = bone_bit_rates.scale != k_invalid_bit_rate ? scale_bit_rate : k_invalid_bit_rate; + + const float error = calculate_max_error_at_bit_rate_local(context, bone_index, error_scan_stop_condition::until_error_too_high); + +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION > 1 + printf("%u: %u | %u | %u (%u) = %f\n", bone_index, rotation_bit_rate, translation_bit_rate, scale_bit_rate, transform_size, error); +#endif + + if (error < best_error) + { + best_error = error; + best_bit_rates = context.bit_rate_per_bone[bone_index]; + is_error_good_enough = error < error_threshold; + } + } + } + else + { + const size_t num_permutations = get_array_size(acl_impl::k_local_bit_rate_permutations_no_scale); + for (size_t permutation_index = 0; permutation_index < num_permutations; ++permutation_index) + { + const uint8_t rotation_bit_rate = acl_impl::k_local_bit_rate_permutations_no_scale[permutation_index][0]; + if (bone_bit_rates.rotation == 1) + { + if (rotation_bit_rate == 0) + continue; // Skip permutations we aren't interested in + } + else if (bone_bit_rates.rotation == k_invalid_bit_rate) + { + if (rotation_bit_rate != 0) + continue; // Skip permutations we aren't interested in + } + + const uint8_t translation_bit_rate = acl_impl::k_local_bit_rate_permutations_no_scale[permutation_index][1]; + if (bone_bit_rates.translation == 1) + { + if (translation_bit_rate == 0) + continue; // Skip permutations we aren't interested in + } + else if (bone_bit_rates.translation == k_invalid_bit_rate) + { + if (translation_bit_rate != 0) + continue; // Skip permutations we aren't interested in + } + + const uint32_t rotation_size = get_num_bits_at_bit_rate(rotation_bit_rate); + const uint32_t translation_size = get_num_bits_at_bit_rate(translation_bit_rate); + const uint32_t transform_size = rotation_size + translation_size; + + if (transform_size != prev_transform_size && is_error_good_enough) + { + // We already found the lowest transform size and we tried every permutation with that same size + break; + } + + prev_transform_size = transform_size; + + context.bit_rate_per_bone[bone_index].rotation = bone_bit_rates.rotation != k_invalid_bit_rate ? rotation_bit_rate : k_invalid_bit_rate; + context.bit_rate_per_bone[bone_index].translation = bone_bit_rates.translation != k_invalid_bit_rate ? translation_bit_rate : k_invalid_bit_rate; + + const float error = calculate_max_error_at_bit_rate_local(context, bone_index, error_scan_stop_condition::until_error_too_high); + +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION > 1 + printf("%u: %u | %u | %u (%u) = %f\n", bone_index, rotation_bit_rate, translation_bit_rate, k_invalid_bit_rate, transform_size, error); +#endif + + if (error < best_error) + { + best_error = error; + best_bit_rates = context.bit_rate_per_bone[bone_index]; + is_error_good_enough = error < error_threshold; + } + } + } + +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + printf("%u: Best bit rates: %u | %u | %u\n", bone_index, best_bit_rates.rotation, best_bit_rates.translation, best_bit_rates.scale); +#endif + + context.bit_rate_per_bone[bone_index] = best_bit_rates; + } + } + + constexpr uint32_t increment_and_clamp_bit_rate(uint32_t bit_rate, uint32_t increment) + { + return bit_rate >= k_highest_bit_rate ? bit_rate : std::min(bit_rate + increment, k_highest_bit_rate); + } + + inline float increase_bone_bit_rate(quantization_context& context, uint32_t bone_index, uint32_t num_increments, float old_error, BoneBitRate& out_best_bit_rates) + { + const BoneBitRate bone_bit_rates = context.bit_rate_per_bone[bone_index]; + const uint32_t num_scale_increments = context.has_scale ? num_increments : 0; + + BoneBitRate best_bit_rates = bone_bit_rates; + float best_error = old_error; + + for (uint32_t rotation_increment = 0; rotation_increment <= num_increments; ++rotation_increment) + { + const uint32_t rotation_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.rotation, rotation_increment); + + for (uint32_t translation_increment = 0; translation_increment <= num_increments; ++translation_increment) + { + const uint32_t translation_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.translation, translation_increment); + + for (uint32_t scale_increment = 0; scale_increment <= num_scale_increments; ++scale_increment) + { + const uint32_t scale_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.scale, scale_increment); + + if (rotation_increment + translation_increment + scale_increment != num_increments) + { + if (scale_bit_rate >= k_highest_bit_rate) + break; + else + continue; + } + + context.bit_rate_per_bone[bone_index] = BoneBitRate{ (uint8_t)rotation_bit_rate, (uint8_t)translation_bit_rate, (uint8_t)scale_bit_rate }; + const float error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_error_too_high); + + if (error < best_error) + { + best_error = error; + best_bit_rates = context.bit_rate_per_bone[bone_index]; + } + + context.bit_rate_per_bone[bone_index] = bone_bit_rates; + + if (scale_bit_rate >= k_highest_bit_rate) + break; + } + + if (translation_bit_rate >= k_highest_bit_rate) + break; + } + + if (rotation_bit_rate >= k_highest_bit_rate) + break; + } + + out_best_bit_rates = best_bit_rates; + return best_error; + } + + inline float calculate_bone_permutation_error(quantization_context& context, BoneBitRate* permutation_bit_rates, uint8_t* bone_chain_permutation, uint32_t bone_index, BoneBitRate* best_bit_rates, float old_error) + { + const float error_threshold = context.error_threshold; + float best_error = old_error; + + do + { + // Copy our current bit rates to the permutation rates + std::memcpy(permutation_bit_rates, context.bit_rate_per_bone, sizeof(BoneBitRate) * context.num_bones); + + bool is_permutation_valid = false; + const uint32_t num_bones_in_chain = context.num_bones_in_chain; + for (uint32_t chain_link_index = 0; chain_link_index < num_bones_in_chain; ++chain_link_index) + { + if (bone_chain_permutation[chain_link_index] != 0) + { + // Increase bit rate + const uint32_t chain_bone_index = context.chain_bone_indices[chain_link_index]; + BoneBitRate chain_bone_best_bit_rates; + increase_bone_bit_rate(context, chain_bone_index, bone_chain_permutation[chain_link_index], old_error, chain_bone_best_bit_rates); + is_permutation_valid |= chain_bone_best_bit_rates.rotation != permutation_bit_rates[chain_bone_index].rotation; + is_permutation_valid |= chain_bone_best_bit_rates.translation != permutation_bit_rates[chain_bone_index].translation; + is_permutation_valid |= chain_bone_best_bit_rates.scale != permutation_bit_rates[chain_bone_index].scale; + permutation_bit_rates[chain_bone_index] = chain_bone_best_bit_rates; + } + } + + if (!is_permutation_valid) + continue; // Couldn't increase any bit rate, skip this permutation + + // Measure error + std::swap(context.bit_rate_per_bone, permutation_bit_rates); + const float permutation_error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_error_too_high); + std::swap(context.bit_rate_per_bone, permutation_bit_rates); + + if (permutation_error < best_error) + { + best_error = permutation_error; + std::memcpy(best_bit_rates, permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones); + + if (permutation_error < error_threshold) + break; + } + } while (std::next_permutation(bone_chain_permutation, bone_chain_permutation + context.num_bones_in_chain)); + + return best_error; + } + + inline uint32_t calculate_bone_chain_indices(const clip_context& clip, uint32_t bone_index, uint32_t* out_chain_bone_indices) + { + const bone_chain bone_chain = clip.get_bone_chain(bone_index); + + uint32_t num_bones_in_chain = 0; + for (uint32_t chain_bone_index : bone_chain) + out_chain_bone_indices[num_bones_in_chain++] = chain_bone_index; + + return num_bones_in_chain; + } + + inline void initialize_bone_bit_rates(const segment_context& segment, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, BoneBitRate* out_bit_rate_per_bone) + { + const bool is_rotation_variable = is_rotation_format_variable(rotation_format); + const bool is_translation_variable = is_vector_format_variable(translation_format); + const bool is_scale_variable = segment_context_has_scale(segment) && is_vector_format_variable(scale_format); + + const uint32_t num_bones = segment.num_bones; + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + BoneBitRate& bone_bit_rate = out_bit_rate_per_bone[bone_index]; + + const bool rotation_supports_constant_tracks = segment.are_rotations_normalized; + if (is_rotation_variable && !segment.bone_streams[bone_index].is_rotation_constant) + bone_bit_rate.rotation = rotation_supports_constant_tracks ? 0 : k_lowest_bit_rate; + else + bone_bit_rate.rotation = k_invalid_bit_rate; + + const bool translation_supports_constant_tracks = segment.are_translations_normalized; + if (is_translation_variable && !segment.bone_streams[bone_index].is_translation_constant) + bone_bit_rate.translation = translation_supports_constant_tracks ? 0 : k_lowest_bit_rate; + else + bone_bit_rate.translation = k_invalid_bit_rate; + + const bool scale_supports_constant_tracks = segment.are_scales_normalized; + if (is_scale_variable && !segment.bone_streams[bone_index].is_scale_constant) + bone_bit_rate.scale = scale_supports_constant_tracks ? 0 : k_lowest_bit_rate; + else + bone_bit_rate.scale = k_invalid_bit_rate; + } + } + + inline void quantize_all_streams(quantization_context& context) + { + ACL_ASSERT(context.is_valid(), "quantization_context isn't valid"); + + const bool is_rotation_variable = is_rotation_format_variable(context.rotation_format); + const bool is_translation_variable = is_vector_format_variable(context.translation_format); + const bool is_scale_variable = is_vector_format_variable(context.scale_format); + + for (uint32_t bone_index = 0; bone_index < context.num_bones; ++bone_index) + { + const BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[bone_index]; + + if (is_rotation_variable) + quantize_variable_rotation_stream(context, bone_index, bone_bit_rate.rotation); + else + quantize_fixed_rotation_stream(context, bone_index, context.rotation_format); + + if (is_translation_variable) + quantize_variable_translation_stream(context, bone_index, bone_bit_rate.translation); + else + quantize_fixed_translation_stream(context, bone_index, context.translation_format); + + if (context.has_scale) + { + if (is_scale_variable) + quantize_variable_scale_stream(context, bone_index, bone_bit_rate.scale); + else + quantize_fixed_scale_stream(context, bone_index, context.scale_format); + } + } + } + + inline void find_optimal_bit_rates(quantization_context& context) + { + ACL_ASSERT(context.is_valid(), "quantization_context isn't valid"); + + initialize_bone_bit_rates(*context.segment, context.rotation_format, context.translation_format, context.scale_format, context.bit_rate_per_bone); + + // First iterate over all bones and find the optimal bit rate for each track using the local space error. + // We use the local space error to prime the algorithm. If each parent bone has infinite precision, + // the local space error is equivalent. Since parents are lossy, it is a good approximation. It means + // that whatever bit rate we find for a bone, it cannot be lower to reach our error threshold since + // a lossy parent means we need to be equally or more accurate to maintain the threshold. + // + // In practice, the error from a child can compensate the error introduced by the parent but + // this is unlikely to hold true for a whole track at every key. We thus make the assumption + // that increasing the precision is always good regardless of the hierarchy level. + + calculate_local_space_bit_rates(context); + + // Now that we found an approximate lower bound for the bit rates, we start at the root and perform a brute force search. + // For each bone, we do the following: + // - If object space error meets our error threshold, do nothing + // - Iterate over each bone in the chain and increment the bit rate by 1 (rotation or translation, pick lowest error) + // - Pick the bone that improved the error the most and increment the bit rate by 1 + // - Repeat until we meet our error threshold + // + // The root is already optimal from the previous step since the local space error is equal to the object space error. + // Next we'll add one bone to the chain under the root. Performing the above steps, we perform an exhaustive search + // to find the smallest memory footprint that will meet our error threshold. No combination with a lower memory footprint + // could yield a smaller error. + // Next we'll add another bone to the chain. By performing these steps recursively, we can ensure that the accuracy always + // increases and the memory footprint is always as low as possible. + + // 3 bone chain expansion: + // 3: [bone 0] + 1 [bone 1] + 0 [bone 2] + 0 (3) + // [bone 0] + 0 [bone 1] + 1 [bone 2] + 0 (3) + // [bone 0] + 0 [bone 1] + 0 [bone 2] + 1 (3) + // 6: [bone 0] + 2 [bone 1] + 0 [bone 2] + 0 (6) + // [bone 0] + 1 [bone 1] + 1 [bone 2] + 0 (6) + // [bone 0] + 1 [bone 1] + 0 [bone 2] + 1 (6) + // [bone 0] + 0 [bone 1] + 1 [bone 2] + 1 (6) + // [bone 0] + 0 [bone 1] + 2 [bone 2] + 0 (6) + // [bone 0] + 0 [bone 1] + 0 [bone 2] + 2 (6) + //10: [bone 0] + 3 [bone 1] + 0 [bone 2] + 0 (9) + // [bone 0] + 2 [bone 1] + 1 [bone 2] + 0 (9) + // [bone 0] + 2 [bone 1] + 0 [bone 2] + 1 (9) + // [bone 0] + 1 [bone 1] + 2 [bone 2] + 0 (9) + // [bone 0] + 1 [bone 1] + 1 [bone 2] + 1 (9) + // [bone 0] + 1 [bone 1] + 0 [bone 2] + 2 (9) + // [bone 0] + 0 [bone 1] + 3 [bone 2] + 0 (9) + // [bone 0] + 0 [bone 1] + 2 [bone 2] + 1 (9) + // [bone 0] + 0 [bone 1] + 1 [bone 2] + 2 (9) + // [bone 0] + 0 [bone 1] + 0 [bone 2] + 3 (9) + + uint8_t* bone_chain_permutation = allocate_type_array(context.allocator, context.num_bones); + BoneBitRate* permutation_bit_rates = allocate_type_array(context.allocator, context.num_bones); + BoneBitRate* best_permutation_bit_rates = allocate_type_array(context.allocator, context.num_bones); + BoneBitRate* best_bit_rates = allocate_type_array(context.allocator, context.num_bones); + std::memcpy(best_bit_rates, context.bit_rate_per_bone, sizeof(BoneBitRate) * context.num_bones); + + const uint32_t num_bones = context.num_bones; + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + // Update our context with the new bone data + const float error_threshold = context.metadata[bone_index].precision; + context.error_threshold = error_threshold; + + const uint32_t num_bones_in_chain = calculate_bone_chain_indices(context.clip, bone_index, context.chain_bone_indices); + context.num_bones_in_chain = num_bones_in_chain; + + float error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_error_too_high); + if (error < error_threshold) + continue; + + const float initial_error = error; + + while (error >= error_threshold) + { + // Generate permutations for up to 3 bit rate increments + // Perform an exhaustive search of the permutations and pick the best result + // If our best error is under the threshold, we are done, otherwise we will try again from there + const float original_error = error; + float best_error = error; + + // The first permutation increases the bit rate of a single track/bone + std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0)); + bone_chain_permutation[num_bones_in_chain - 1] = 1; + error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error); + if (error < best_error) + { + best_error = error; + std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones); + + if (error < error_threshold) + break; + } + + if (context.compression_level >= compression_level8::high) + { + // The second permutation increases the bit rate of 2 track/bones + std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0)); + bone_chain_permutation[num_bones_in_chain - 1] = 2; + error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error); + if (error < best_error) + { + best_error = error; + std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones); + + if (error < error_threshold) + break; + } + + if (num_bones_in_chain > 1) + { + std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0)); + bone_chain_permutation[num_bones_in_chain - 2] = 1; + bone_chain_permutation[num_bones_in_chain - 1] = 1; + error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error); + if (error < best_error) + { + best_error = error; + std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones); + + if (error < error_threshold) + break; + } + } + } + + if (context.compression_level >= compression_level8::highest) + { + // The third permutation increases the bit rate of 3 track/bones + std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0)); + bone_chain_permutation[num_bones_in_chain - 1] = 3; + error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error); + if (error < best_error) + { + best_error = error; + std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones); + + if (error < error_threshold) + break; + } + + if (num_bones_in_chain > 1) + { + std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0)); + bone_chain_permutation[num_bones_in_chain - 2] = 2; + bone_chain_permutation[num_bones_in_chain - 1] = 1; + error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error); + if (error < best_error) + { + best_error = error; + std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones); + + if (error < error_threshold) + break; + } + + if (num_bones_in_chain > 2) + { + std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0)); + bone_chain_permutation[num_bones_in_chain - 3] = 1; + bone_chain_permutation[num_bones_in_chain - 2] = 1; + bone_chain_permutation[num_bones_in_chain - 1] = 1; + error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error); + if (error < best_error) + { + best_error = error; + std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones); + + if (error < error_threshold) + break; + } + } + } + } + + if (best_error >= original_error) + break; // No progress made + + error = best_error; + if (error < original_error) + { +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + std::swap(context.bit_rate_per_bone, best_bit_rates); + float new_error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment); + std::swap(context.bit_rate_per_bone, best_bit_rates); + + for (uint32_t i = 0; i < context.num_bones; ++i) + { + const BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[i]; + const BoneBitRate& best_bone_bit_rate = best_bit_rates[i]; + bool rotation_differs = bone_bit_rate.rotation != best_bone_bit_rate.rotation; + bool translation_differs = bone_bit_rate.translation != best_bone_bit_rate.translation; + bool scale_differs = bone_bit_rate.scale != best_bone_bit_rate.scale; + if (rotation_differs || translation_differs || scale_differs) + printf("%u: %u | %u | %u => %u %u %u (%f)\n", i, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, best_bone_bit_rate.rotation, best_bone_bit_rate.translation, best_bone_bit_rate.scale, new_error); + } +#endif + + std::memcpy(context.bit_rate_per_bone, best_bit_rates, sizeof(BoneBitRate) * num_bones); + } + } + + if (error < initial_error) + { +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + std::swap(context.bit_rate_per_bone, best_bit_rates); + float new_error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment); + std::swap(context.bit_rate_per_bone, best_bit_rates); + + for (uint32_t i = 0; i < context.num_bones; ++i) + { + const BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[i]; + const BoneBitRate& best_bone_bit_rate = best_bit_rates[i]; + bool rotation_differs = bone_bit_rate.rotation != best_bone_bit_rate.rotation; + bool translation_differs = bone_bit_rate.translation != best_bone_bit_rate.translation; + bool scale_differs = bone_bit_rate.scale != best_bone_bit_rate.scale; + if (rotation_differs || translation_differs || scale_differs) + printf("%u: %u | %u | %u => %u %u %u (%f)\n", i, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, best_bone_bit_rate.rotation, best_bone_bit_rate.translation, best_bone_bit_rate.scale, new_error); + } +#endif + + std::memcpy(context.bit_rate_per_bone, best_bit_rates, sizeof(BoneBitRate) * num_bones); + } + + // Our error remains too high, this should be rare. + // Attempt to increase the bit rate as much as we can while still back tracking if it doesn't help. + error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment); + while (error >= error_threshold) + { + // From child to parent, increase the bit rate indiscriminately + uint32_t num_maxed_out = 0; + for (int32_t chain_link_index = num_bones_in_chain - 1; chain_link_index >= 0; --chain_link_index) + { + const uint32_t chain_bone_index = context.chain_bone_indices[chain_link_index]; + + // Work with a copy. We'll increase the bit rate as much as we can and retain the values + // that yield the smallest error BUT increasing the bit rate does NOT always means + // that the error will reduce and improve. It could get worse in which case we'll do nothing. + + BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[chain_bone_index]; + + // Copy original values + BoneBitRate best_bone_bit_rate = bone_bit_rate; + float best_bit_rate_error = error; + + while (error >= error_threshold) + { + static_assert(offsetof(BoneBitRate, rotation) == 0 && offsetof(BoneBitRate, scale) == sizeof(BoneBitRate) - 1, "Invalid BoneBitRate offsets"); + uint8_t& smallest_bit_rate = *std::min_element(&bone_bit_rate.rotation, &bone_bit_rate.scale + 1); + + if (smallest_bit_rate >= k_highest_bit_rate) + { + num_maxed_out++; + break; + } + + // If rotation == translation and translation has room, bias translation + // This seems to yield an overall tiny win but it isn't always the case. + // TODO: Brute force this? + if (bone_bit_rate.rotation == bone_bit_rate.translation && bone_bit_rate.translation < k_highest_bit_rate && bone_bit_rate.scale >= k_highest_bit_rate) + bone_bit_rate.translation++; + else + smallest_bit_rate++; + + ACL_ASSERT((bone_bit_rate.rotation <= k_highest_bit_rate || bone_bit_rate.rotation == k_invalid_bit_rate) && (bone_bit_rate.translation <= k_highest_bit_rate || bone_bit_rate.translation == k_invalid_bit_rate) && (bone_bit_rate.scale <= k_highest_bit_rate || bone_bit_rate.scale == k_invalid_bit_rate), "Invalid bit rate! [%u, %u, %u]", bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale); + + error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment); + + if (error < best_bit_rate_error) + { + best_bone_bit_rate = bone_bit_rate; + best_bit_rate_error = error; + +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + printf("%u: => %u %u %u (%f)\n", chain_bone_index, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, error); + for (uint32_t i = chain_link_index + 1; i < num_bones_in_chain; ++i) + { + const uint32_t chain_bone_index2 = context.chain_bone_indices[chain_link_index]; + float error2 = calculate_max_error_at_bit_rate_object(context, chain_bone_index2, error_scan_stop_condition::until_end_of_segment); + printf(" %u: => (%f)\n", i, error2); + } +#endif + } + } + + // Only retain the lowest error bit rates + bone_bit_rate = best_bone_bit_rate; + error = best_bit_rate_error; + + if (error < error_threshold) + break; + } + + if (num_maxed_out == num_bones_in_chain) + break; + + // TODO: Try to lower the bit rate again in the reverse direction? + } + + // Despite our best efforts, we failed to meet the threshold with our heuristics. + // No longer attempt to find what is best for size, max out the bit rates until we meet the threshold. + // Only do this if the rotation format is full precision quaternions. This last step is not guaranteed + // to reach the error threshold but it will very likely increase the memory footprint. Even if we do + // reach the error threshold for the given bone, another sibling bone already processed might now + // have an error higher than it used to if quantization caused its error to compensate. More often than + // not, sibling bones will remain fairly close in their error. Some packed rotation formats, namely + // drop W component can have a high error even with raw values, it is assumed that if such a format + // is used then a best effort approach to reach the error threshold is entirely fine. + if (error >= error_threshold && context.rotation_format == rotation_format8::quatf_full) + { + // From child to parent, max out the bit rate + for (int32_t chain_link_index = num_bones_in_chain - 1; chain_link_index >= 0; --chain_link_index) + { + const uint32_t chain_bone_index = context.chain_bone_indices[chain_link_index]; + BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[chain_bone_index]; + bone_bit_rate.rotation = std::max(bone_bit_rate.rotation, k_highest_bit_rate); + bone_bit_rate.translation = std::max(bone_bit_rate.translation, k_highest_bit_rate); + bone_bit_rate.scale = std::max(bone_bit_rate.scale, k_highest_bit_rate); + + error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment); + if (error < error_threshold) + break; + } + } + } + +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + printf("Variable quantization optimization results:\n"); + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + // Update our context with the new bone data + const float error_threshold = context.metadata[bone_index].precision; + context.error_threshold = error_threshold; + + const uint32_t num_bones_in_chain = calculate_bone_chain_indices(context.clip, bone_index, context.chain_bone_indices); + context.num_bones_in_chain = num_bones_in_chain; + + float error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment); + const BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[bone_index]; + printf("%u: %u | %u | %u => %f %s\n", bone_index, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, error, error >= error_threshold ? "!" : ""); + } +#endif + + deallocate_type_array(context.allocator, bone_chain_permutation, num_bones); + deallocate_type_array(context.allocator, permutation_bit_rates, num_bones); + deallocate_type_array(context.allocator, best_permutation_bit_rates, num_bones); + deallocate_type_array(context.allocator, best_bit_rates, num_bones); + } + + // Partitioning will be done as follow in two phases: calculating the error contribution for every frame and a global optimization pass. + // + // Phase 1: + // For every clip we compress, when requested, we'll calculate the contributing error of every frame and store it in optional metadata. + // Later this metadata will be used to create a database. + // To calculate the contributing error, we iterate over every segment. + // For every frame we can remove (e.g. not first/last of clip/segment), calculate the resulting error of every frame between the remaining + // frames. For example, say we have frame 0, 1, 2, 3, and 4. We first try removing frame 1 and calculate the error. We repeat this for + // frame 2 and 3 individually. The frame with the lowest error is the least important one and can be removed first. Suppose it is frame 1, + // we'll append in the metadata for that segment that the first frame to remove is frame 1 and the contributing error we just calculated. + // We then loop again over every frame we can remove. We'll try removing frame 2 by measuring the error at frame 1 and 2 once it is removed + // (by interpolating between frame 0 and 3). The resulting error is the largest error of every frame we test. + // And we then try removing frame 3 on its own. Once we have a contributing error for frame 2 and 3, we again pick the lowest and append it + // to our segment metadata. We repeat this until every frame has been sorted. Each segment is processed individually. + // This metadata is stored at the end of the clip. + // + // Phase 2: + // A new create database function will be added that takes a list of compressed clips as inputs and outputs a list of compressed clips + // as well as a new database. Every input clip must contain the metadata from phase 1. + // Using our tier distribution information, we can calculate how many frames we have that can be moved to the database and how many + // each tier will contain. We then start with the highest tier (tier 2) which will contain the least important frames. These frames contribute + // the least to our accuracy (they have the lowest contributing error as calculated in phase 1). For every tier, we iterate over every clip/segment + // and find the lowest contributing error. One we found it, we assign it to our tier until the tier is full. Repeat for every tier. + // Once every tier has been populated, we can calculate where our chunk limits are and assign each frame to its chunk. + // We can then iterate over every clip/segment/frame and copy the compressed bits to its final destination: our new clip, or our database chunk + // for tier 1 or 2. We copy bit by bit to tightly pack things. Our new clips are mostly unchanged. The header and its metadata is nearly identical + // except it now contains sample contained information. Each segment has a few frames stripped if they got moved to the database. Finally, the optional + // database metadata is stripped. + // Once every clip has been populated, we can finalize them and calculate their final hash value. + // Once every chunk has been populated, we can finalize them and the database as well. + // + // This is thus a globally optimal process. If we wish to retain the 20% most important frames in tier 0 (our compressed clips) and 80% in the database, we can. + // Some clips might see more than 80% of their frames moved to the database while others might see less. + + inline void find_contributing_error(quantization_context& context) + { + ACL_ASSERT(context.num_samples <= 32, "Expected no more than 32 samples per track"); + + if (context.segment->contributing_error == nullptr) + context.segment->contributing_error = allocate_type_array(context.allocator, 32); // Always no more than 32 frames per segment + + const uint32_t num_frames = context.num_samples; + const uint32_t num_bones = context.num_bones; + const bitset_description desc = bitset_description::make_from_num_bits<32>(); + constexpr float infinity = std::numeric_limits::infinity(); + + frame_contributing_error* contributing_error = context.segment->contributing_error; + uint32_t frames_retained = ~0U; // By default, every frame is present + + // First and last frame of the segment cannot be removed and thus contribute infinite error + // TODO: We could retain only the first/last frames of the clip instead but it would mean interpolating + // with a frame from the previous/next segments + contributing_error[0] = frame_contributing_error{ 0, infinity }; + contributing_error[num_frames - 1] = frame_contributing_error{ num_frames - 1, infinity }; + + // START OF ERROR METRIC STUFF + const itransform_error_metric* error_metric = context.error_metric; + const bool needs_conversion = context.needs_conversion; + const bool has_additive_base = context.has_additive_base; + const size_t sample_transform_size = context.metric_transform_size * num_bones; + const float sample_rate = context.sample_rate; + const float clip_duration = context.clip_duration; + + const auto convert_transforms_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale); + const auto apply_additive_to_base_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale); + const auto local_to_object_space_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::local_to_object_space : &itransform_error_metric::local_to_object_space_no_scale); + const auto calculate_error_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::calculate_error : &itransform_error_metric::calculate_error_no_scale); + + itransform_error_metric::convert_transforms_args convert_transforms_args_lossy; + convert_transforms_args_lossy.dirty_transform_indices = context.self_transform_indices; + convert_transforms_args_lossy.num_dirty_transforms = num_bones; + convert_transforms_args_lossy.transforms = context.lossy_local_pose; + convert_transforms_args_lossy.num_transforms = num_bones; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy; + apply_additive_to_base_args_lossy.dirty_transform_indices = context.self_transform_indices; + apply_additive_to_base_args_lossy.num_dirty_transforms = num_bones; + apply_additive_to_base_args_lossy.local_transforms = needs_conversion ? (const void*)(context.local_transforms_converted) : (const void*)context.lossy_local_pose; + apply_additive_to_base_args_lossy.base_transforms = nullptr; + apply_additive_to_base_args_lossy.num_transforms = num_bones; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy; + local_to_object_space_args_lossy.dirty_transform_indices = context.self_transform_indices; + local_to_object_space_args_lossy.num_dirty_transforms = num_bones; + local_to_object_space_args_lossy.parent_transform_indices = context.parent_transform_indices; + local_to_object_space_args_lossy.local_transforms = needs_conversion ? (const void*)(context.local_transforms_converted) : (const void*)context.lossy_local_pose; + local_to_object_space_args_lossy.num_transforms = num_bones; + + const uint8_t* raw_transform = context.raw_object_transforms; + const uint8_t* base_transforms = context.base_local_transforms; + + if (context.lossy_transforms_start == nullptr) + { + // This is the first time we call this, initialize what we'll need and re-use + context.all_local_query.bind(context.bit_rate_database); + + context.lossy_transforms_start = allocate_type_array(context.allocator, num_bones); + context.lossy_transforms_end = allocate_type_array(context.allocator, num_bones); + } + + rtm::qvvf* lossy_transforms_start = context.lossy_transforms_start; + rtm::qvvf* lossy_transforms_end = context.lossy_transforms_end; + + context.all_local_query.build(context.bit_rate_per_bone); + // END OF ERROR METRIC STUFF + + // We iterate until every frame but the first and last have been removed + for (uint32_t iteration_count = 1; iteration_count < num_frames - 1; ++iteration_count) + { + frame_contributing_error best_error{ num_frames, infinity }; + +#if ACL_IMPL_DEBUG_CONTRIBUTING_ERROR + printf("Contributing error for segment %u (%u frames), iteration %u ...\n", context.segment->segment_index, num_frames, iteration_count); +#endif + + // Calculate how much error each frame contributes as we remove them, skip the first and last + for (uint32_t frame_index = 1; frame_index < num_frames - 1; ++frame_index) + { + // We'll attempt to remove the current frame if it hasn't been removed already + if (!bitset_test(&frames_retained, desc, frame_index)) + continue; // This frame has already been removed, skip it + + // Find the first frame before the current one that is retained, it'll be our interpolation start point + uint32_t interp_start_frame_index = frame_index - 1; + while (!bitset_test(&frames_retained, desc, interp_start_frame_index)) + interp_start_frame_index--; // This frame isn't being retained, skip it + + // Find the first frame after the current one that is retained, it'll be out interpolation end point + uint32_t interp_end_frame_index = frame_index + 1; + while (!bitset_test(&frames_retained, desc, interp_end_frame_index)) + interp_end_frame_index++; // This frame isn't being retained, skip it + + // TODO: We could cache the contributing error and only invalidate it when we remove a frame + + // The sample time is calculated from the full clip duration to be consistent with decompression + const float interp_start_time = rtm::scalar_min(float(interp_start_frame_index) / sample_rate, clip_duration); + const float interp_end_time = rtm::scalar_min(float(interp_end_frame_index) / sample_rate, clip_duration); + + // We'll calculate the resulting error on every frame already removed that lives in between the remaining + // two interpolation frames. + context.bit_rate_database.sample(context.all_local_query, interp_start_time, lossy_transforms_start, num_bones); + context.bit_rate_database.sample(context.all_local_query, interp_end_time, lossy_transforms_end, num_bones); + + // We'll retain the worst error as the current frame's contributing error. + rtm::scalarf max_contributing_error = rtm::scalar_set(0.0F); + + for (uint32_t interp_frame_index = interp_start_frame_index + 1; interp_frame_index < interp_end_frame_index; ++interp_frame_index) + { + // Calculate our interpolation alpha + const float interpolation_alpha = find_linear_interpolation_alpha(float(interp_frame_index), interp_start_frame_index, interp_end_frame_index, sample_rounding_policy::none); + + // Interpolate our transforms in local space before we convert or apply the additive and transform to object space + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + // TODO: Implement qvv_lerp(..) + const rtm::quatf interp_rotation = rtm::quat_lerp(lossy_transforms_start[bone_index].rotation, lossy_transforms_end[bone_index].rotation, interpolation_alpha); + const rtm::vector4f interp_translation = rtm::vector_lerp(lossy_transforms_start[bone_index].translation, lossy_transforms_end[bone_index].translation, interpolation_alpha); + const rtm::vector4f interp_scale = rtm::vector_lerp(lossy_transforms_start[bone_index].scale, lossy_transforms_end[bone_index].scale, interpolation_alpha); + + context.lossy_local_pose[bone_index] = rtm::qvv_set(interp_rotation, interp_translation, interp_scale); + } + + // Convert to our object space representation + if (needs_conversion) + convert_transforms_impl(error_metric, convert_transforms_args_lossy, context.local_transforms_converted); + + if (has_additive_base) + { + apply_additive_to_base_args_lossy.base_transforms = base_transforms + (interp_frame_index * sample_transform_size); + + apply_additive_to_base_impl(error_metric, apply_additive_to_base_args_lossy, context.lossy_local_pose); + } + + local_to_object_space_impl(error_metric, local_to_object_space_args_lossy, context.lossy_object_pose); + + // Calculate our error + const uint8_t* raw_frame_transform = raw_transform + (interp_frame_index * sample_transform_size); + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + const transform_metadata& target_bone = context.metadata[bone_index]; + + itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = raw_frame_transform + (bone_index * context.metric_transform_size); + calculate_error_args.transform1 = context.lossy_object_pose + (bone_index * context.metric_transform_size); + calculate_error_args.construct_sphere_shell(target_bone.shell_distance); + +#if defined(RTM_COMPILER_MSVC) && defined(RTM_ARCH_X86) && RTM_COMPILER_MSVC == RTM_COMPILER_MSVC_2015 + // VS2015 fails to generate the right x86 assembly, branch instead + (void)calculate_error_impl; + const rtm::scalarf error = context.has_scale ? error_metric->calculate_error(calculate_error_args) : error_metric->calculate_error_no_scale(calculate_error_args); +#else + const rtm::scalarf error = calculate_error_impl(error_metric, calculate_error_args); +#endif + + max_contributing_error = rtm::scalar_max(max_contributing_error, error); + } + } + + const float max_contributing_errorf = rtm::scalar_cast(max_contributing_error); + +#if ACL_IMPL_DEBUG_CONTRIBUTING_ERROR + printf(" Error between frame [%u, %u] while testing %u: %f\n", interp_start_frame_index, interp_end_frame_index, frame_index, max_contributing_errorf); +#endif + + // If our current frame's contributing error is lowest, it is the best candidate for removal + if (max_contributing_errorf < best_error.error) + best_error = frame_contributing_error{ frame_index, max_contributing_errorf }; + } + + ACL_ASSERT(best_error.index != num_frames, "Failed to find the best contributing error"); + +#if ACL_IMPL_DEBUG_CONTRIBUTING_ERROR + printf(" Best frame to remove: %u (%f)\n", best_error.index, best_error.error); +#endif + + // We found the best frame to remove, remove it + contributing_error[best_error.index] = best_error; + bitset_set(&frames_retained, desc, best_error.index, false); + } + + // We found the contributing error for every frame, sort them by lowest error first + auto sort_predicate = [](const frame_contributing_error& lhs, const frame_contributing_error& rhs) { return lhs.error < rhs.error; }; + std::sort(contributing_error, contributing_error + num_frames, sort_predicate); + } + + inline void quantize_streams(iallocator& allocator, clip_context& clip, const compression_settings& settings, const clip_context& raw_clip_context, const clip_context& additive_base_clip_context, const output_stats& out_stats) + { + (void)out_stats; + + if (clip.num_bones == 0) + return; + + const bool is_rotation_variable = is_rotation_format_variable(settings.rotation_format); + const bool is_translation_variable = is_vector_format_variable(settings.translation_format); + const bool is_scale_variable = is_vector_format_variable(settings.scale_format); + const bool is_any_variable = is_rotation_variable || is_translation_variable || is_scale_variable; + + quantization_context context(allocator, clip, raw_clip_context, additive_base_clip_context, settings); + + for (segment_context& segment : clip.segment_iterator()) + { +#if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION + printf("Quantizing segment %u...\n", segment.segment_index); +#endif + +#if ACL_IMPL_PROFILE_MATH + { + scope_profiler timer; + + for (int32_t i = 0; i < 10; ++i) + { + context.set_segment(segment); + + if (is_any_variable) + find_optimal_bit_rates(context); + } + + timer.stop(); + +#if defined(__ANDROID__) + __android_log_print(ANDROID_LOG_INFO, "acl", "Quantization optimization for segment %u took: %.4f ms", segment.segment_index, timer.get_elapsed_milliseconds()); +#else + printf("Quantization optimization for segment %u took: %.4f ms\n", segment.segment_index, timer.get_elapsed_milliseconds()); +#endif + } +#endif + + context.set_segment(segment); + + // If we use a variable bit rate, run our optimization algorithm to find the optimal bit rates + if (is_any_variable) + find_optimal_bit_rates(context); + + // If we need the contributing error of each frame, find it now before we quantize + if (settings.metadata.include_contributing_error) + find_contributing_error(context); + + // Quantize our streams now that we found the optimal bit rates + quantize_all_streams(context); + } + +#if defined(SJSON_CPP_WRITER) + if (are_all_enum_flags_set(out_stats.logging, stat_logging::detailed)) + { + sjson::ObjectWriter& writer = *out_stats.writer; + writer["track_bit_rate_database_size"] = static_cast(context.bit_rate_database.get_allocated_size()); + + size_t transform_cache_size = 0; + transform_cache_size += sizeof(rtm::qvvf) * context.num_bones; // raw_local_pose + transform_cache_size += sizeof(rtm::qvvf) * context.num_bones; // lossy_local_pose + transform_cache_size += context.metric_transform_size * context.num_bones; // lossy_object_pose + transform_cache_size += context.metric_transform_size * context.num_bones * context.clip.segments->num_samples; // raw_local_transforms + transform_cache_size += context.metric_transform_size * context.num_bones * context.clip.segments->num_samples; // raw_object_transforms + + if (context.needs_conversion) + transform_cache_size += context.metric_transform_size * context.num_bones; // local_transforms_converted + + if (context.has_additive_base) + { + transform_cache_size += sizeof(rtm::qvvf) * context.num_bones; // additive_local_pose + transform_cache_size += context.metric_transform_size * context.num_bones * context.clip.segments->num_samples; // base_local_transforms + transform_cache_size += context.metric_transform_size * context.num_bones * context.clip.segments->num_samples; // base_object_transforms + } + + writer["transform_cache_size"] = static_cast(transform_cache_size); + } +#endif + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/quantize_track_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/quantize_track_impl.h new file mode 100644 index 00000000..c50da9bf --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/quantize_track_impl.h @@ -0,0 +1,192 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/track_types.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/compression/impl/track_list_context.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct quantization_scales + { + rtm::vector4f max_value; + rtm::vector4f inv_max_value; + + explicit quantization_scales(uint32_t num_bits) + { + ACL_ASSERT(num_bits > 0, "Cannot decay with 0 bits"); + ACL_ASSERT(num_bits < 31, "Attempting to decay on too many bits"); + + const float max_value_ = rtm::scalar_safe_to_float((1 << num_bits) - 1); + max_value = rtm::vector_set(max_value_); + inv_max_value = rtm::vector_set(1.0F / max_value_); + } + }; + + // Decays the input value through quantization by packing and unpacking a normalized input value + inline rtm::vector4f RTM_SIMD_CALL decay_vector4_uXX(rtm::vector4f_arg0 value, const quantization_scales& scales) + { + using namespace rtm; + + ACL_ASSERT(vector_all_greater_equal(value, vector_zero()) && vector_all_less_equal(value, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f, %f", (float)vector_get_x(value), (float)vector_get_y(value), (float)vector_get_z(value), (float)vector_get_w(value)); + + const vector4f packed_value = vector_round_symmetric(vector_mul(value, scales.max_value)); + const vector4f decayed_value = vector_mul(packed_value, scales.inv_max_value); + return decayed_value; + } + + // Packs a normalized input value through quantization + inline rtm::vector4f RTM_SIMD_CALL pack_vector4_uXX(rtm::vector4f_arg0 value, const quantization_scales& scales) + { + using namespace rtm; + + ACL_ASSERT(vector_all_greater_equal(value, vector_zero()) && vector_all_less_equal(value, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f, %f", (float)vector_get_x(value), (float)vector_get_y(value), (float)vector_get_z(value), (float)vector_get_w(value)); + + return vector_round_symmetric(vector_mul(value, scales.max_value)); + } + + inline void quantize_scalarf_track(track_list_context& context, uint32_t track_index) + { + using namespace rtm; + + const track& ref_track = (*context.reference_list)[track_index]; + track_vector4f& mut_track = track_cast(context.track_list[track_index]); + + const vector4f precision = vector_load1(&mut_track.get_description().precision); + const uint32_t ref_element_size = ref_track.get_sample_size(); + const uint32_t num_samples = mut_track.get_num_samples(); + + const scalarf_range& range = context.range_list[track_index].range.scalarf; + const vector4f range_min = range.get_min(); + const vector4f range_extent = range.get_extent(); + + const vector4f zero = vector_zero(); + const mask4f all_true_mask = mask_set(true, true, true, true); + mask4f sample_mask = mask_set(false, false, false, false); + std::memcpy(&sample_mask, &all_true_mask, ref_element_size); + + vector4f raw_sample = zero; + uint8_t best_bit_rate = k_highest_bit_rate; // Default to raw if we fail to find something better + + // First we look for the best bit rate possible that keeps us within our precision target + for (uint8_t bit_rate = k_highest_bit_rate - 1; bit_rate != 0; --bit_rate) // Skip the raw bit rate and the constant bit rate + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + const quantization_scales scales(num_bits_at_bit_rate); + + bool is_error_to_high = false; + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + std::memcpy(&raw_sample, ref_track[sample_index], ref_element_size); + + const vector4f normalized_sample = mut_track[sample_index]; + + // Decay our value through quantization + const vector4f decayed_normalized_sample = decay_vector4_uXX(normalized_sample, scales); + + // Undo normalization + const vector4f decayed_sample = vector_mul_add(decayed_normalized_sample, range_extent, range_min); + + const vector4f delta = vector_abs(vector_sub(raw_sample, decayed_sample)); + const vector4f masked_delta = vector_select(sample_mask, delta, zero); + if (!vector_all_less_equal(masked_delta, precision)) + { + is_error_to_high = true; + break; + } + } + + if (is_error_to_high) + break; // Our error is too high, use the previous bit rate + + // We were accurate enough, this is the best bit rate so far + best_bit_rate = bit_rate; + } + + context.bit_rate_list[track_index].scalar.value = best_bit_rate; + + // Done, update our track with the final result + if (best_bit_rate == k_highest_bit_rate) + { + // We can't quantize this track, keep it raw + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + std::memcpy(&mut_track[sample_index], ref_track[sample_index], ref_element_size); + } + else + { + // Use the selected bit rate to quantize our track + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(best_bit_rate); + const quantization_scales scales(num_bits_at_bit_rate); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + mut_track[sample_index] = pack_vector4_uXX(mut_track[sample_index], scales); + } + } + + inline void quantize_tracks(track_list_context& context) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + context.bit_rate_list = allocate_type_array(*context.allocator, context.num_tracks); + + for (uint32_t track_index = 0; track_index < context.num_tracks; ++track_index) + { + const bool is_track_constant = context.is_constant(track_index); + if (is_track_constant) + continue; // Constant tracks don't need to be modified + + const track_range& range = context.range_list[track_index]; + + switch (range.category) + { + case track_category8::scalarf: + quantize_scalarf_track(context, track_index); + break; + default: + ACL_ASSERT(false, "Invalid track category"); + break; + } + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/sample_streams.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/sample_streams.h new file mode 100644 index 00000000..07e2fa54 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/sample_streams.h @@ -0,0 +1,906 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/track_formats.h" +#include "acl/core/utils.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/math/quat_packing.h" +#include "acl/math/vector4_packing.h" +#include "acl/compression/impl/track_stream.h" +#include "acl/compression/impl/normalize_streams.h" +#include "acl/compression/impl/convert_rotation_streams.h" + +#include +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline rtm::vector4f RTM_SIMD_CALL load_rotation_sample(const uint8_t* ptr, rotation_format8 format, uint8_t bit_rate) + { + switch (format) + { + case rotation_format8::quatf_full: + return unpack_vector4_128(ptr); + case rotation_format8::quatf_drop_w_full: + return unpack_vector3_96_unsafe(ptr); + case rotation_format8::quatf_drop_w_variable: + ACL_ASSERT(bit_rate != k_invalid_bit_rate, "Invalid bit rate!"); + if (is_constant_bit_rate(bit_rate)) + { + return unpack_vector3_u48_unsafe(ptr); + } + else if (is_raw_bit_rate(bit_rate)) + return unpack_vector3_96_unsafe(ptr); + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + return unpack_vector3_uXX_unsafe(num_bits_at_bit_rate, ptr, 0); + } + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(format)); + return rtm::vector_zero(); + } + } + + inline rtm::vector4f RTM_SIMD_CALL load_vector_sample(const uint8_t* ptr, vector_format8 format, uint8_t bit_rate) + { + switch (format) + { + case vector_format8::vector3f_full: + return unpack_vector3_96_unsafe(ptr); + case vector_format8::vector3f_variable: + ACL_ASSERT(bit_rate != k_invalid_bit_rate, "Invalid bit rate!"); + if (is_constant_bit_rate(bit_rate)) + return unpack_vector3_u48_unsafe(ptr); + else if (is_raw_bit_rate(bit_rate)) + return unpack_vector3_96_unsafe(ptr); + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + return unpack_vector3_uXX_unsafe(num_bits_at_bit_rate, ptr, 0); + } + default: + ACL_ASSERT(false, "Invalid or unsupported vector format: %s", get_vector_format_name(format)); + return rtm::vector_zero(); + } + } + + inline rtm::quatf RTM_SIMD_CALL rotation_to_quat_32(rtm::vector4f_arg0 rotation, rotation_format8 format) + { + switch (format) + { + case rotation_format8::quatf_full: + return rtm::vector_to_quat(rotation); + case rotation_format8::quatf_drop_w_full: + case rotation_format8::quatf_drop_w_variable: + return rtm::quat_from_positive_w(rotation); + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(format)); + return rtm::quat_identity(); + } + } + + // Gets a rotation sample from the format/bit rate stored + inline rtm::quatf RTM_SIMD_CALL get_rotation_sample(const transform_streams& bone_steams, uint32_t sample_index) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + + const rotation_format8 format = bone_steams.rotations.get_rotation_format(); + const uint8_t bit_rate = bone_steams.rotations.get_bit_rate(); + + if (format == rotation_format8::quatf_drop_w_variable && is_constant_bit_rate(bit_rate)) + sample_index = 0; + + const uint8_t* quantized_ptr = bone_steams.rotations.get_raw_sample_ptr(sample_index); + + rtm::vector4f packed_rotation = acl_impl::load_rotation_sample(quantized_ptr, format, bit_rate); + + if (!bone_steams.is_rotation_constant && clip->are_rotations_normalized && !is_raw_bit_rate(bit_rate)) + { + if (segment->are_rotations_normalized && !is_constant_bit_rate(bit_rate)) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.rotation.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.rotation.get_extent(); + + packed_rotation = rtm::vector_mul_add(packed_rotation, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.rotation.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.rotation.get_extent(); + + packed_rotation = rtm::vector_mul_add(packed_rotation, clip_range_extent, clip_range_min); + } + + return acl_impl::rotation_to_quat_32(packed_rotation, format); + } + + // Gets a rotation sample at the specified bit rate + inline rtm::quatf RTM_SIMD_CALL get_rotation_sample(const transform_streams& bone_steams, const transform_streams& raw_bone_steams, uint32_t sample_index, uint8_t bit_rate) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + const rotation_format8 format = bone_steams.rotations.get_rotation_format(); + + rtm::vector4f rotation; + if (is_constant_bit_rate(bit_rate)) + { + const uint8_t* quantized_ptr = raw_bone_steams.rotations.get_raw_sample_ptr(segment->clip_sample_offset); + rotation = acl_impl::load_rotation_sample(quantized_ptr, rotation_format8::quatf_full, k_invalid_bit_rate); + rotation = convert_rotation(rotation, rotation_format8::quatf_full, format); + } + else if (is_raw_bit_rate(bit_rate)) + { + const uint8_t* quantized_ptr = raw_bone_steams.rotations.get_raw_sample_ptr(segment->clip_sample_offset + sample_index); + rotation = acl_impl::load_rotation_sample(quantized_ptr, rotation_format8::quatf_full, k_invalid_bit_rate); + rotation = convert_rotation(rotation, rotation_format8::quatf_full, format); + } + else + { + const uint8_t* quantized_ptr = bone_steams.rotations.get_raw_sample_ptr(sample_index); + rotation = acl_impl::load_rotation_sample(quantized_ptr, format, 0); + } + + // Pack and unpack at our desired bit rate + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + rtm::vector4f packed_rotation; + + if (is_constant_bit_rate(bit_rate)) + { + const transform_range& clip_bone_range = segment->clip->ranges[bone_steams.bone_index]; + const rtm::vector4f normalized_rotation = normalize_sample(rotation, clip_bone_range.rotation); + + packed_rotation = decay_vector3_u48(normalized_rotation); + } + else if (is_raw_bit_rate(bit_rate)) + packed_rotation = rotation; + else + packed_rotation = decay_vector3_uXX(rotation, num_bits_at_bit_rate); + + if (!is_raw_bit_rate(bit_rate)) + { + if (segment->are_rotations_normalized && !is_constant_bit_rate(bit_rate)) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.rotation.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.rotation.get_extent(); + + packed_rotation = rtm::vector_mul_add(packed_rotation, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.rotation.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.rotation.get_extent(); + + packed_rotation = rtm::vector_mul_add(packed_rotation, clip_range_extent, clip_range_min); + } + + return acl_impl::rotation_to_quat_32(packed_rotation, format); + } + + // Gets a rotation sample with the desired format + inline rtm::quatf RTM_SIMD_CALL get_rotation_sample(const transform_streams& bone_steams, uint32_t sample_index, rotation_format8 desired_format) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + + const uint8_t* quantized_ptr = bone_steams.rotations.get_raw_sample_ptr(sample_index); + const rotation_format8 format = bone_steams.rotations.get_rotation_format(); + + const rtm::vector4f rotation = acl_impl::load_rotation_sample(quantized_ptr, format, 0); + + // Pack and unpack in our desired format + rtm::vector4f packed_rotation; + + switch (desired_format) + { + case rotation_format8::quatf_full: + case rotation_format8::quatf_drop_w_full: + packed_rotation = rotation; + break; + default: + ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(desired_format)); + packed_rotation = rtm::vector_zero(); + break; + } + + const bool are_rotations_normalized = clip->are_rotations_normalized && !bone_steams.is_rotation_constant; + if (are_rotations_normalized) + { + if (segment->are_rotations_normalized) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.rotation.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.rotation.get_extent(); + + packed_rotation = rtm::vector_mul_add(packed_rotation, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.rotation.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.rotation.get_extent(); + + packed_rotation = rtm::vector_mul_add(packed_rotation, clip_range_extent, clip_range_min); + } + + return acl_impl::rotation_to_quat_32(packed_rotation, format); + } + + // Gets a translation sample from the format/bit rate stored + inline rtm::vector4f RTM_SIMD_CALL get_translation_sample(const transform_streams& bone_steams, uint32_t sample_index) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + const bool are_translations_normalized = clip->are_translations_normalized; + + const vector_format8 format = bone_steams.translations.get_vector_format(); + const uint8_t bit_rate = bone_steams.translations.get_bit_rate(); + + if (format == vector_format8::vector3f_variable && is_constant_bit_rate(bit_rate)) + sample_index = 0; + + const uint8_t* quantized_ptr = bone_steams.translations.get_raw_sample_ptr(sample_index); + + rtm::vector4f packed_translation = acl_impl::load_vector_sample(quantized_ptr, format, bit_rate); + + if (!bone_steams.is_translation_constant && are_translations_normalized && !is_raw_bit_rate(bit_rate)) + { + if (segment->are_translations_normalized && !is_constant_bit_rate(bit_rate)) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.translation.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.translation.get_extent(); + + packed_translation = rtm::vector_mul_add(packed_translation, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.translation.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.translation.get_extent(); + + packed_translation = rtm::vector_mul_add(packed_translation, clip_range_extent, clip_range_min); + } + + return packed_translation; + } + + // Gets a translation sample at the specified bit rate + inline rtm::vector4f RTM_SIMD_CALL get_translation_sample(const transform_streams& bone_steams, const transform_streams& raw_bone_steams, uint32_t sample_index, uint8_t bit_rate) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + const vector_format8 format = bone_steams.translations.get_vector_format(); + + const uint8_t* quantized_ptr; + if (is_constant_bit_rate(bit_rate)) + quantized_ptr = raw_bone_steams.translations.get_raw_sample_ptr(segment->clip_sample_offset); + else if (is_raw_bit_rate(bit_rate)) + quantized_ptr = raw_bone_steams.translations.get_raw_sample_ptr(segment->clip_sample_offset + sample_index); + else + quantized_ptr = bone_steams.translations.get_raw_sample_ptr(sample_index); + + const rtm::vector4f translation = acl_impl::load_vector_sample(quantized_ptr, format, 0); + + ACL_ASSERT(clip->are_translations_normalized, "Translations must be normalized to support variable bit rates."); + + // Pack and unpack at our desired bit rate + rtm::vector4f packed_translation; + + if (is_constant_bit_rate(bit_rate)) + { + ACL_ASSERT(segment->are_translations_normalized, "Translations must be normalized to support variable bit rates."); + + const transform_range& clip_bone_range = segment->clip->ranges[bone_steams.bone_index]; + const rtm::vector4f normalized_translation = normalize_sample(translation, clip_bone_range.translation); + + packed_translation = decay_vector3_u48(normalized_translation); + } + else if (is_raw_bit_rate(bit_rate)) + packed_translation = translation; + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + packed_translation = decay_vector3_uXX(translation, num_bits_at_bit_rate); + } + + if (!is_raw_bit_rate(bit_rate)) + { + if (segment->are_translations_normalized && !is_constant_bit_rate(bit_rate)) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.translation.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.translation.get_extent(); + + packed_translation = rtm::vector_mul_add(packed_translation, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.translation.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.translation.get_extent(); + + packed_translation = rtm::vector_mul_add(packed_translation, clip_range_extent, clip_range_min); + } + + return packed_translation; + } + + // Gets a translation sample with the desired format + inline rtm::vector4f RTM_SIMD_CALL get_translation_sample(const transform_streams& bone_steams, uint32_t sample_index, vector_format8 desired_format) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + const bool are_translations_normalized = clip->are_translations_normalized && !bone_steams.is_translation_constant; + const uint8_t* quantized_ptr = bone_steams.translations.get_raw_sample_ptr(sample_index); + const vector_format8 format = bone_steams.translations.get_vector_format(); + + const rtm::vector4f translation = acl_impl::load_vector_sample(quantized_ptr, format, 0); + + // Pack and unpack in our desired format + rtm::vector4f packed_translation; + + switch (desired_format) + { + case vector_format8::vector3f_full: + packed_translation = translation; + break; + default: + ACL_ASSERT(false, "Invalid or unsupported vector format: %s", get_vector_format_name(desired_format)); + packed_translation = rtm::vector_zero(); + break; + } + + if (are_translations_normalized) + { + if (segment->are_translations_normalized) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + rtm::vector4f segment_range_min = segment_bone_range.translation.get_min(); + rtm::vector4f segment_range_extent = segment_bone_range.translation.get_extent(); + + packed_translation = rtm::vector_mul_add(packed_translation, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + rtm::vector4f clip_range_min = clip_bone_range.translation.get_min(); + rtm::vector4f clip_range_extent = clip_bone_range.translation.get_extent(); + + packed_translation = rtm::vector_mul_add(packed_translation, clip_range_extent, clip_range_min); + } + + return packed_translation; + } + + // Gets a scale sample from the format/bit rate stored + inline rtm::vector4f RTM_SIMD_CALL get_scale_sample(const transform_streams& bone_steams, uint32_t sample_index) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + + const vector_format8 format = bone_steams.scales.get_vector_format(); + const uint8_t bit_rate = bone_steams.scales.get_bit_rate(); + + if (format == vector_format8::vector3f_variable && is_constant_bit_rate(bit_rate)) + sample_index = 0; + + const uint8_t* quantized_ptr = bone_steams.scales.get_raw_sample_ptr(sample_index); + + rtm::vector4f packed_scale = acl_impl::load_vector_sample(quantized_ptr, format, bit_rate); + + if (!bone_steams.is_scale_constant && clip->are_scales_normalized && !is_raw_bit_rate(bit_rate)) + { + if (segment->are_scales_normalized && !is_constant_bit_rate(bit_rate)) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.scale.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.scale.get_extent(); + + packed_scale = rtm::vector_mul_add(packed_scale, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.scale.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.scale.get_extent(); + + packed_scale = rtm::vector_mul_add(packed_scale, clip_range_extent, clip_range_min); + } + + return packed_scale; + } + + // Gets a scale sample at the specified bit rate + inline rtm::vector4f RTM_SIMD_CALL get_scale_sample(const transform_streams& bone_steams, const transform_streams& raw_bone_steams, uint32_t sample_index, uint8_t bit_rate) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + const vector_format8 format = bone_steams.scales.get_vector_format(); + + const uint8_t* quantized_ptr; + if (is_constant_bit_rate(bit_rate)) + quantized_ptr = raw_bone_steams.scales.get_raw_sample_ptr(segment->clip_sample_offset); + else if (is_raw_bit_rate(bit_rate)) + quantized_ptr = raw_bone_steams.scales.get_raw_sample_ptr(segment->clip_sample_offset + sample_index); + else + quantized_ptr = bone_steams.scales.get_raw_sample_ptr(sample_index); + + const rtm::vector4f scale = acl_impl::load_vector_sample(quantized_ptr, format, 0); + + ACL_ASSERT(clip->are_scales_normalized, "Scales must be normalized to support variable bit rates."); + + // Pack and unpack at our desired bit rate + rtm::vector4f packed_scale; + + if (is_constant_bit_rate(bit_rate)) + { + ACL_ASSERT(segment->are_scales_normalized, "Translations must be normalized to support variable bit rates."); + + const transform_range& clip_bone_range = segment->clip->ranges[bone_steams.bone_index]; + const rtm::vector4f normalized_scale = normalize_sample(scale, clip_bone_range.scale); + + packed_scale = decay_vector3_u48(normalized_scale); + } + else if (is_raw_bit_rate(bit_rate)) + packed_scale = scale; + else + { + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate); + packed_scale = decay_vector3_uXX(scale, num_bits_at_bit_rate); + } + + if (!is_raw_bit_rate(bit_rate)) + { + if (segment->are_scales_normalized && !is_constant_bit_rate(bit_rate)) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + const rtm::vector4f segment_range_min = segment_bone_range.scale.get_min(); + const rtm::vector4f segment_range_extent = segment_bone_range.scale.get_extent(); + + packed_scale = rtm::vector_mul_add(packed_scale, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + const rtm::vector4f clip_range_min = clip_bone_range.scale.get_min(); + const rtm::vector4f clip_range_extent = clip_bone_range.scale.get_extent(); + + packed_scale = rtm::vector_mul_add(packed_scale, clip_range_extent, clip_range_min); + } + + return packed_scale; + } + + // Gets a scale sample with the desired format + inline rtm::vector4f RTM_SIMD_CALL get_scale_sample(const transform_streams& bone_steams, uint32_t sample_index, vector_format8 desired_format) + { + const segment_context* segment = bone_steams.segment; + const clip_context* clip = segment->clip; + const bool are_scales_normalized = clip->are_scales_normalized && !bone_steams.is_scale_constant; + const uint8_t* quantized_ptr = bone_steams.scales.get_raw_sample_ptr(sample_index); + const vector_format8 format = bone_steams.scales.get_vector_format(); + + const rtm::vector4f scale = acl_impl::load_vector_sample(quantized_ptr, format, 0); + + // Pack and unpack in our desired format + rtm::vector4f packed_scale; + + switch (desired_format) + { + case vector_format8::vector3f_full: + packed_scale = scale; + break; + default: + ACL_ASSERT(false, "Invalid or unsupported vector format: %s", get_vector_format_name(desired_format)); + packed_scale = scale; + break; + } + + if (are_scales_normalized) + { + if (segment->are_scales_normalized) + { + const transform_range& segment_bone_range = segment->ranges[bone_steams.bone_index]; + + rtm::vector4f segment_range_min = segment_bone_range.scale.get_min(); + rtm::vector4f segment_range_extent = segment_bone_range.scale.get_extent(); + + packed_scale = rtm::vector_mul_add(packed_scale, segment_range_extent, segment_range_min); + } + + const transform_range& clip_bone_range = clip->ranges[bone_steams.bone_index]; + + rtm::vector4f clip_range_min = clip_bone_range.scale.get_min(); + rtm::vector4f clip_range_extent = clip_bone_range.scale.get_extent(); + + packed_scale = rtm::vector_mul_add(packed_scale, clip_range_extent, clip_range_min); + } + + return packed_scale; + } + + struct sample_context + { + uint32_t track_index; + + uint32_t sample_key; + float sample_time; + + BoneBitRate bit_rates; + }; + + inline uint32_t get_uniform_sample_key(const segment_context& segment, float sample_time) + { + uint32_t key0 = 0; + uint32_t key1 = 0; + float interpolation_alpha = 0.0F; + + // Our samples are uniform, grab the nearest samples + const clip_context* clip = segment.clip; + find_linear_interpolation_samples_with_sample_rate(clip->num_samples, clip->sample_rate, sample_time, sample_rounding_policy::nearest, key0, key1, interpolation_alpha); + + // Offset for the current segment and clamp + key0 = key0 - segment.clip_sample_offset; + if (key0 >= segment.num_samples) + { + key0 = 0; + interpolation_alpha = 1.0F; + } + + key1 = key1 - segment.clip_sample_offset; + if (key1 >= segment.num_samples) + { + key1 = segment.num_samples - 1; + interpolation_alpha = 0.0F; + } + + // When we sample uniformly, we always round to the nearest sample. + // As such, we don't need to interpolate. + return interpolation_alpha == 0.0F ? key0 : key1; + } + + RTM_FORCE_INLINE rtm::quatf RTM_SIMD_CALL sample_rotation(const sample_context& context, const transform_streams& bone_stream) + { + rtm::quatf rotation; + if (bone_stream.is_rotation_default) + rotation = rtm::quat_identity(); + else if (bone_stream.is_rotation_constant) + rotation = rtm::quat_normalize(get_rotation_sample(bone_stream, 0)); + else + { + rotation = get_rotation_sample(bone_stream, context.sample_key); + rotation = rtm::quat_normalize(rotation); + } + + return rotation; + } + + RTM_FORCE_INLINE rtm::quatf RTM_SIMD_CALL sample_rotation(const sample_context& context, const transform_streams& bone_stream, const transform_streams& raw_bone_stream, bool is_rotation_variable, rotation_format8 rotation_format) + { + rtm::quatf rotation; + if (bone_stream.is_rotation_default) + rotation = rtm::quat_identity(); + else if (bone_stream.is_rotation_constant) + { + if (is_rotation_variable) + rotation = get_rotation_sample(raw_bone_stream, 0); + else + rotation = get_rotation_sample(raw_bone_stream, 0, rotation_format); + + rotation = rtm::quat_normalize(rotation); + } + else + { + if (is_rotation_variable) + rotation = get_rotation_sample(bone_stream, raw_bone_stream, context.sample_key, context.bit_rates.rotation); + else + rotation = get_rotation_sample(bone_stream, context.sample_key, rotation_format); + + rotation = rtm::quat_normalize(rotation); + } + + return rotation; + } + + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL sample_translation(const sample_context& context, const transform_streams& bone_stream) + { + if (bone_stream.is_translation_default) + return rtm::vector_zero(); + else if (bone_stream.is_translation_constant) + return get_translation_sample(bone_stream, 0); + else + return get_translation_sample(bone_stream, context.sample_key); + } + + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL sample_translation(const sample_context& context, const transform_streams& bone_stream, const transform_streams& raw_bone_stream, bool is_translation_variable, vector_format8 translation_format) + { + if (bone_stream.is_translation_default) + return rtm::vector_zero(); + else if (bone_stream.is_translation_constant) + return get_translation_sample(raw_bone_stream, 0, vector_format8::vector3f_full); + else if (is_translation_variable) + return get_translation_sample(bone_stream, raw_bone_stream, context.sample_key, context.bit_rates.translation); + else + return get_translation_sample(bone_stream, context.sample_key, translation_format); + } + + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL sample_scale(const sample_context& context, const transform_streams& bone_stream, rtm::vector4f_arg0 default_scale) + { + if (bone_stream.is_scale_default) + return default_scale; + else if (bone_stream.is_scale_constant) + return get_scale_sample(bone_stream, 0); + else + return get_scale_sample(bone_stream, context.sample_key); + } + + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL sample_scale(const sample_context& context, const transform_streams& bone_stream, const transform_streams& raw_bone_stream, bool is_scale_variable, vector_format8 scale_format, rtm::vector4f_arg0 default_scale) + { + if (bone_stream.is_scale_default) + return default_scale; + else if (bone_stream.is_scale_constant) + return get_scale_sample(raw_bone_stream, 0, vector_format8::vector3f_full); + else if (is_scale_variable) + return get_scale_sample(bone_stream, raw_bone_stream, context.sample_key, context.bit_rates.scale); + else + return get_scale_sample(bone_stream, context.sample_key, scale_format); + } + + inline void sample_streams(const transform_streams* bone_streams, uint32_t num_bones, float sample_time, rtm::qvvf* out_local_pose) + { + const segment_context* segment_context = bone_streams->segment; + const rtm::vector4f default_scale = get_default_scale(segment_context->clip->additive_format); + const bool has_scale = segment_context->clip->has_scale; + + // With uniform sample distributions, we do not interpolate. + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + acl_impl::sample_context context; + context.sample_key = sample_key; + context.sample_time = sample_time; + + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + context.track_index = bone_index; + + const transform_streams& bone_stream = bone_streams[bone_index]; + + const rtm::quatf rotation = acl_impl::sample_rotation(context, bone_stream); + const rtm::vector4f translation = acl_impl::sample_translation(context, bone_stream); + const rtm::vector4f scale = has_scale ? acl_impl::sample_scale(context, bone_stream, default_scale) : default_scale; + + out_local_pose[bone_index] = rtm::qvv_set(rotation, translation, scale); + } + } + + inline void sample_stream(const transform_streams* bone_streams, uint32_t num_bones, float sample_time, uint32_t bone_index, rtm::qvvf* out_local_pose) + { + (void)num_bones; + + const segment_context* segment_context = bone_streams->segment; + const rtm::vector4f default_scale = get_default_scale(segment_context->clip->additive_format); + const bool has_scale = segment_context->clip->has_scale; + + // With uniform sample distributions, we do not interpolate. + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + acl_impl::sample_context context; + context.track_index = bone_index; + context.sample_key = sample_key; + context.sample_time = sample_time; + + const transform_streams& bone_stream = bone_streams[bone_index]; + + const rtm::quatf rotation = acl_impl::sample_rotation(context, bone_stream); + const rtm::vector4f translation = acl_impl::sample_translation(context, bone_stream); + const rtm::vector4f scale = has_scale ? acl_impl::sample_scale(context, bone_stream, default_scale) : default_scale; + + out_local_pose[bone_index] = rtm::qvv_set(rotation, translation, scale); + } + + inline void sample_streams_hierarchical(const transform_streams* bone_streams, uint32_t num_bones, float sample_time, uint32_t bone_index, rtm::qvvf* out_local_pose) + { + (void)num_bones; + + const segment_context* segment_context = bone_streams->segment; + const rtm::vector4f default_scale = get_default_scale(segment_context->clip->additive_format); + const bool has_scale = segment_context->clip->has_scale; + + // With uniform sample distributions, we do not interpolate. + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + acl_impl::sample_context context; + context.sample_key = sample_key; + context.sample_time = sample_time; + + uint32_t current_bone_index = bone_index; + while (current_bone_index != k_invalid_track_index) + { + context.track_index = current_bone_index; + + const transform_streams& bone_stream = bone_streams[current_bone_index]; + + const rtm::quatf rotation = acl_impl::sample_rotation(context, bone_stream); + const rtm::vector4f translation = acl_impl::sample_translation(context, bone_stream); + const rtm::vector4f scale = has_scale ? acl_impl::sample_scale(context, bone_stream, default_scale) : default_scale; + + out_local_pose[current_bone_index] = rtm::qvv_set(rotation, translation, scale); + current_bone_index = bone_stream.parent_bone_index; + } + } + + inline void sample_streams(const transform_streams* bone_streams, const transform_streams* raw_bone_steams, uint32_t num_bones, float sample_time, const BoneBitRate* bit_rates, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, rtm::qvvf* out_local_pose) + { + const bool is_rotation_variable = is_rotation_format_variable(rotation_format); + const bool is_translation_variable = is_vector_format_variable(translation_format); + const bool is_scale_variable = is_vector_format_variable(scale_format); + + const segment_context* segment_context = bone_streams->segment; + const rtm::vector4f default_scale = get_default_scale(segment_context->clip->additive_format); + const bool has_scale = segment_context->clip->has_scale; + + // With uniform sample distributions, we do not interpolate. + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + acl_impl::sample_context context; + context.sample_key = sample_key; + context.sample_time = sample_time; + + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + context.track_index = bone_index; + context.bit_rates = bit_rates[bone_index]; + + const transform_streams& bone_stream = bone_streams[bone_index]; + const transform_streams& raw_bone_steam = raw_bone_steams[bone_index]; + + const rtm::quatf rotation = acl_impl::sample_rotation(context, bone_stream, raw_bone_steam, is_rotation_variable, rotation_format); + const rtm::vector4f translation = acl_impl::sample_translation(context, bone_stream, raw_bone_steam, is_translation_variable, translation_format); + const rtm::vector4f scale = has_scale ? acl_impl::sample_scale(context, bone_stream, raw_bone_steam, is_scale_variable, scale_format, default_scale) : default_scale; + + out_local_pose[bone_index] = rtm::qvv_set(rotation, translation, scale); + } + } + + inline void sample_stream(const transform_streams* bone_streams, const transform_streams* raw_bone_steams, uint32_t num_bones, float sample_time, uint32_t bone_index, const BoneBitRate* bit_rates, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, rtm::qvvf* out_local_pose) + { + (void)num_bones; + + const bool is_rotation_variable = is_rotation_format_variable(rotation_format); + const bool is_translation_variable = is_vector_format_variable(translation_format); + const bool is_scale_variable = is_vector_format_variable(scale_format); + + const segment_context* segment_context = bone_streams->segment; + const rtm::vector4f default_scale = get_default_scale(segment_context->clip->additive_format); + const bool has_scale = segment_context->clip->has_scale; + + // With uniform sample distributions, we do not interpolate. + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + acl_impl::sample_context context; + context.track_index = bone_index; + context.sample_key = sample_key; + context.sample_time = sample_time; + context.bit_rates = bit_rates[bone_index]; + + const transform_streams& bone_stream = bone_streams[bone_index]; + const transform_streams& raw_bone_stream = raw_bone_steams[bone_index]; + + const rtm::quatf rotation = acl_impl::sample_rotation(context, bone_stream, raw_bone_stream, is_rotation_variable, rotation_format); + const rtm::vector4f translation = acl_impl::sample_translation(context, bone_stream, raw_bone_stream, is_translation_variable, translation_format); + const rtm::vector4f scale = has_scale ? acl_impl::sample_scale(context, bone_stream, raw_bone_stream, is_scale_variable, scale_format, default_scale) : default_scale; + + out_local_pose[bone_index] = rtm::qvv_set(rotation, translation, scale); + } + + inline void sample_streams_hierarchical(const transform_streams* bone_streams, const transform_streams* raw_bone_steams, uint32_t num_bones, float sample_time, uint32_t bone_index, const BoneBitRate* bit_rates, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, rtm::qvvf* out_local_pose) + { + (void)num_bones; + + const bool is_rotation_variable = is_rotation_format_variable(rotation_format); + const bool is_translation_variable = is_vector_format_variable(translation_format); + const bool is_scale_variable = is_vector_format_variable(scale_format); + + const segment_context* segment_context = bone_streams->segment; + const rtm::vector4f default_scale = get_default_scale(segment_context->clip->additive_format); + const bool has_scale = segment_context->clip->has_scale; + + // With uniform sample distributions, we do not interpolate. + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + acl_impl::sample_context context; + context.sample_key = sample_key; + context.sample_time = sample_time; + + uint32_t current_bone_index = bone_index; + while (current_bone_index != k_invalid_track_index) + { + context.track_index = current_bone_index; + context.bit_rates = bit_rates[current_bone_index]; + + const transform_streams& bone_stream = bone_streams[current_bone_index]; + const transform_streams& raw_bone_stream = raw_bone_steams[current_bone_index]; + + const rtm::quatf rotation = acl_impl::sample_rotation(context, bone_stream, raw_bone_stream, is_rotation_variable, rotation_format); + const rtm::vector4f translation = acl_impl::sample_translation(context, bone_stream, raw_bone_stream, is_translation_variable, translation_format); + const rtm::vector4f scale = has_scale ? acl_impl::sample_scale(context, bone_stream, raw_bone_stream, is_scale_variable, scale_format, default_scale) : default_scale; + + out_local_pose[current_bone_index] = rtm::qvv_set(rotation, translation, scale); + current_bone_index = bone_stream.parent_bone_index; + } + } + + inline void sample_streams(const transform_streams* bone_streams, uint32_t num_bones, uint32_t sample_index, rtm::qvvf* out_local_pose) + { + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + const transform_streams& bone_stream = bone_streams[bone_index]; + + const uint32_t rotation_sample_index = !bone_stream.is_rotation_constant ? sample_index : 0; + const rtm::quatf rotation = get_rotation_sample(bone_stream, rotation_sample_index); + + const uint32_t translation_sample_index = !bone_stream.is_translation_constant ? sample_index : 0; + const rtm::vector4f translation = get_translation_sample(bone_stream, translation_sample_index); + + const uint32_t scale_sample_index = !bone_stream.is_scale_constant ? sample_index : 0; + const rtm::vector4f scale = get_scale_sample(bone_stream, scale_sample_index); + + out_local_pose[bone_index] = rtm::qvv_set(rotation, translation, scale); + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/segment_context.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/segment_context.h new file mode 100644 index 00000000..f7eb8ee3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/segment_context.h @@ -0,0 +1,125 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/hash.h" +#include "acl/core/iallocator.h" +#include "acl/core/iterator.h" +#include "acl/core/quality_tiers.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/impl/compressed_headers.h" +#include "acl/compression/impl/track_stream.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct clip_context; + + ////////////////////////////////////////////////////////////////////////// + // Encapsulates all the compression settings related to segmenting. + // Segmenting ensures that large clips are split into smaller segments and + // compressed independently to allow a smaller memory footprint as well as + // faster compression and decompression. + // See also: https://nfrechette.github.io/2016/11/10/anim_compression_uniform_segmenting/ + struct compression_segmenting_settings + { + ////////////////////////////////////////////////////////////////////////// + // How many samples to try and fit in our segments. + // Defaults to '16' + uint32_t ideal_num_samples = 16; + + ////////////////////////////////////////////////////////////////////////// + // Maximum number of samples per segment. + // Defaults to '31' + uint32_t max_num_samples = 31; + + ////////////////////////////////////////////////////////////////////////// + // Checks if everything is valid and if it isn't, returns an error string. + // Returns nullptr if the settings are valid. + inline error_result is_valid() const + { + if (ideal_num_samples < 8) + return error_result("ideal_num_samples must be greater or equal to 8"); + + if (ideal_num_samples > max_num_samples) + return error_result("ideal_num_samples must be smaller or equal to max_num_samples"); + + return error_result(); + } + }; + + struct segment_context + { + clip_context* clip = nullptr; + transform_streams* bone_streams = nullptr; + transform_range* ranges = nullptr; + frame_contributing_error* contributing_error = nullptr; // Optional if we request it in the compression settings + + uint32_t num_samples = 0; + uint32_t num_bones = 0; + + uint32_t clip_sample_offset = 0; + uint32_t segment_index = 0; + + bool are_rotations_normalized = false; + bool are_translations_normalized = false; + bool are_scales_normalized = false; + + // Stat tracking + uint32_t animated_rotation_bit_size = 0; // Tier 0 + uint32_t animated_translation_bit_size = 0; // Tier 0 + uint32_t animated_scale_bit_size = 0; // Tier 0 + uint32_t animated_pose_bit_size = 0; // Tier 0 + uint32_t animated_data_size = 0; // Tier 0 + uint32_t range_data_size = 0; + uint32_t segment_data_size = 0; + uint32_t total_header_size = 0; + + ////////////////////////////////////////////////////////////////////////// + iterator bone_iterator() { return iterator(bone_streams, num_bones); } + const_iterator const_bone_iterator() const { return const_iterator(bone_streams, num_bones); } + }; + + inline void destroy_segment_context(iallocator& allocator, segment_context& segment) + { + deallocate_type_array(allocator, segment.bone_streams, segment.num_bones); + deallocate_type_array(allocator, segment.ranges, segment.num_bones); + deallocate_type_array(allocator, segment.contributing_error, segment.num_samples); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/segment_streams.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/segment_streams.h new file mode 100644 index 00000000..14ec3102 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/segment_streams.h @@ -0,0 +1,192 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/impl/clip_context.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline void segment_streams(iallocator& allocator, clip_context& clip, const compression_segmenting_settings& settings) + { + ACL_ASSERT(clip.num_segments == 1, "clip_context must have a single segment."); + ACL_ASSERT(settings.ideal_num_samples <= settings.max_num_samples, "Invalid num samples for segmenting settings. %u > %u", settings.ideal_num_samples, settings.max_num_samples); + + if (clip.num_samples <= settings.max_num_samples) + return; + + ////////////////////////////////////////////////////////////////////////// + // This algorithm is simple in nature. Its primary aim is to avoid having + // the last segment being partial if multiple segments are present. + // The extra samples from the last segment will be redistributed evenly + // starting with the first segment. + // As such, in order to quickly find which segment contains a particular sample + // you can simply divide the number of samples by the number of segments to get + // the floored value of number of samples per segment. This guarantees an accurate estimate. + // You can then query the segment start index by dividing the desired sample index + // with the floored value. If the sample isn't in the current segment, it will live in one of its neighbors. + // TODO: Can we provide a tighter guarantee? + ////////////////////////////////////////////////////////////////////////// + + uint32_t num_segments = (clip.num_samples + settings.ideal_num_samples - 1) / settings.ideal_num_samples; + const uint32_t max_num_samples = num_segments * settings.ideal_num_samples; + + const uint32_t original_num_segments = num_segments; + uint32_t* num_samples_per_segment = allocate_type_array(allocator, num_segments); + std::fill(num_samples_per_segment, num_samples_per_segment + num_segments, settings.ideal_num_samples); + + const uint32_t num_leftover_samples = settings.ideal_num_samples - (max_num_samples - clip.num_samples); + if (num_leftover_samples != 0) + num_samples_per_segment[num_segments - 1] = num_leftover_samples; + + const uint32_t slack = settings.max_num_samples - settings.ideal_num_samples; + if ((num_segments - 1) * slack >= num_leftover_samples) + { + // Enough segments to distribute the leftover samples of the last segment + while (num_samples_per_segment[num_segments - 1] != 0) + { + for (uint32_t segment_index = 0; segment_index < num_segments - 1 && num_samples_per_segment[num_segments - 1] != 0; ++segment_index) + { + num_samples_per_segment[segment_index]++; + num_samples_per_segment[num_segments - 1]--; + } + } + + num_segments--; + } + + ACL_ASSERT(num_segments != 1, "Expected a number of segments greater than 1."); + + segment_context* clip_segment = clip.segments; + clip.segments = allocate_type_array(allocator, num_segments); + clip.num_segments = num_segments; + + uint32_t clip_sample_index = 0; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const uint32_t num_samples_in_segment = num_samples_per_segment[segment_index]; + + segment_context& segment = clip.segments[segment_index]; + segment.clip = &clip; + segment.bone_streams = allocate_type_array(allocator, clip.num_bones); + segment.ranges = nullptr; + segment.contributing_error = nullptr; + segment.num_bones = clip.num_bones; + segment.num_samples = num_samples_in_segment; + segment.clip_sample_offset = clip_sample_index; + segment.segment_index = segment_index; + segment.are_rotations_normalized = false; + segment.are_translations_normalized = false; + segment.are_scales_normalized = false; + segment.animated_rotation_bit_size = 0; + segment.animated_translation_bit_size = 0; + segment.animated_scale_bit_size = 0; + segment.animated_pose_bit_size = 0; + segment.animated_data_size = 0; + segment.range_data_size = 0; + segment.total_header_size = 0; + + for (uint32_t bone_index = 0; bone_index < clip.num_bones; ++bone_index) + { + const transform_streams& clip_bone_stream = clip_segment->bone_streams[bone_index]; + transform_streams& segment_bone_stream = segment.bone_streams[bone_index]; + + segment_bone_stream.segment = &segment; + segment_bone_stream.bone_index = bone_index; + segment_bone_stream.parent_bone_index = clip_bone_stream.parent_bone_index; + segment_bone_stream.output_index = clip_bone_stream.output_index; + + if (clip_bone_stream.is_rotation_constant) + { + segment_bone_stream.rotations = clip_bone_stream.rotations.duplicate(); + } + else + { + const uint32_t sample_size = clip_bone_stream.rotations.get_sample_size(); + rotation_track_stream rotations(allocator, num_samples_in_segment, sample_size, clip_bone_stream.rotations.get_sample_rate(), clip_bone_stream.rotations.get_rotation_format(), clip_bone_stream.rotations.get_bit_rate()); + std::memcpy(rotations.get_raw_sample_ptr(0), clip_bone_stream.rotations.get_raw_sample_ptr(clip_sample_index), size_t(num_samples_in_segment) * sample_size); + + segment_bone_stream.rotations = std::move(rotations); + } + + if (clip_bone_stream.is_translation_constant) + { + segment_bone_stream.translations = clip_bone_stream.translations.duplicate(); + } + else + { + const uint32_t sample_size = clip_bone_stream.translations.get_sample_size(); + translation_track_stream translations(allocator, num_samples_in_segment, sample_size, clip_bone_stream.translations.get_sample_rate(), clip_bone_stream.translations.get_vector_format(), clip_bone_stream.translations.get_bit_rate()); + std::memcpy(translations.get_raw_sample_ptr(0), clip_bone_stream.translations.get_raw_sample_ptr(clip_sample_index), size_t(num_samples_in_segment) * sample_size); + + segment_bone_stream.translations = std::move(translations); + } + + if (clip_bone_stream.is_scale_constant) + { + segment_bone_stream.scales = clip_bone_stream.scales.duplicate(); + } + else + { + const uint32_t sample_size = clip_bone_stream.scales.get_sample_size(); + scale_track_stream scales(allocator, num_samples_in_segment, sample_size, clip_bone_stream.scales.get_sample_rate(), clip_bone_stream.scales.get_vector_format(), clip_bone_stream.scales.get_bit_rate()); + std::memcpy(scales.get_raw_sample_ptr(0), clip_bone_stream.scales.get_raw_sample_ptr(clip_sample_index), size_t(num_samples_in_segment) * sample_size); + + segment_bone_stream.scales = std::move(scales); + } + + segment_bone_stream.is_rotation_constant = clip_bone_stream.is_rotation_constant; + segment_bone_stream.is_rotation_default = clip_bone_stream.is_rotation_default; + segment_bone_stream.is_translation_constant = clip_bone_stream.is_translation_constant; + segment_bone_stream.is_translation_default = clip_bone_stream.is_translation_default; + segment_bone_stream.is_scale_constant = clip_bone_stream.is_scale_constant; + segment_bone_stream.is_scale_default = clip_bone_stream.is_scale_default; + } + + clip_sample_index += num_samples_in_segment; + } + + deallocate_type_array(allocator, num_samples_per_segment, original_num_segments); + destroy_segment_context(allocator, *clip_segment); + deallocate_type_array(allocator, clip_segment, 1); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track.impl.h new file mode 100644 index 00000000..f9fba758 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track.impl.h @@ -0,0 +1,359 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from track.h + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/string.h" +#include "acl/core/track_desc.h" +#include "acl/core/track_traits.h" +#include "acl/core/track_types.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline track::track() noexcept + : m_allocator(nullptr) + , m_data(nullptr) + , m_num_samples(0) + , m_stride(0) + , m_data_size(0) + , m_sample_rate(0.0F) + , m_type(track_type8::float1f) + , m_category(track_category8::scalarf) + , m_sample_size(0) + , m_desc() + , m_name() + {} + + inline track::track(track&& other) noexcept + : m_allocator(other.m_allocator) + , m_data(other.m_data) + , m_num_samples(other.m_num_samples) + , m_stride(other.m_stride) + , m_data_size(other.m_data_size) + , m_sample_rate(other.m_sample_rate) + , m_type(other.m_type) + , m_category(other.m_category) + , m_sample_size(other.m_sample_size) + , m_desc(other.m_desc) + , m_name(std::move(other.m_name)) + { + other.m_allocator = nullptr; + other.m_data = nullptr; + } + + inline track::~track() + { + if (is_owner()) + { + // We own the memory, free it + m_allocator->deallocate(m_data, m_data_size); + } + } + + inline track& track::operator=(track&& other) noexcept + { + std::swap(m_allocator, other.m_allocator); + std::swap(m_data, other.m_data); + std::swap(m_num_samples, other.m_num_samples); + std::swap(m_stride, other.m_stride); + std::swap(m_data_size, other.m_data_size); + std::swap(m_sample_rate, other.m_sample_rate); + std::swap(m_type, other.m_type); + std::swap(m_category, other.m_category); + std::swap(m_sample_size, other.m_sample_size); + std::swap(m_desc, other.m_desc); + std::swap(m_name, other.m_name); + return *this; + } + + inline void* track::operator[](uint32_t index) + { + ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples); + return m_data + (size_t(index) * m_stride); + } + + inline const void* track::operator[](uint32_t index) const + { + ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples); + return m_data + (size_t(index) * m_stride); + } + + inline uint32_t track::get_output_index() const + { + switch (m_category) + { + default: + case track_category8::scalarf: return m_desc.scalar.output_index; + case track_category8::transformf: return m_desc.transform.output_index; + } + } + + template<> + inline track_desc_scalarf& track::get_description() + { + ACL_ASSERT(track_desc_scalarf::category == m_category, "Unexpected track category"); + return m_desc.scalar; + } + + template<> + inline track_desc_transformf& track::get_description() + { + ACL_ASSERT(track_desc_transformf::category == m_category, "Unexpected track category"); + return m_desc.transform; + } + + template<> + inline const track_desc_scalarf& track::get_description() const + { + ACL_ASSERT(track_desc_scalarf::category == m_category, "Unexpected track category"); + return m_desc.scalar; + } + + template<> + inline const track_desc_transformf& track::get_description() const + { + ACL_ASSERT(track_desc_transformf::category == m_category, "Unexpected track category"); + return m_desc.transform; + } + + inline track track::get_copy(iallocator& allocator) const + { + track track_; + get_copy_impl(allocator, track_); + return track_; + } + + inline track track::get_ref() const + { + track track_; + get_ref_impl(track_); + return track_; + } + + inline error_result track::is_valid() const + { + if (m_data == nullptr) + return error_result(); + + if (m_num_samples == 0xFFFFFFFFU) + return error_result("Too many samples"); + + if (m_sample_rate <= 0.0F || !rtm::scalar_is_finite(m_sample_rate)) + return error_result("Invalid sample rate"); + + switch (m_category) + { + case track_category8::scalarf: return m_desc.scalar.is_valid(); + case track_category8::transformf: return m_desc.transform.is_valid(); + default: return error_result("Invalid category"); + } + } + + inline track::track(track_type8 type, track_category8 category) noexcept + : m_allocator(nullptr) + , m_data(nullptr) + , m_num_samples(0) + , m_stride(0) + , m_data_size(0) + , m_sample_rate(0.0F) + , m_type(type) + , m_category(category) + , m_sample_size(0) + , m_desc() + , m_name() + {} + + inline track::track(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, track_type8 type, track_category8 category, uint8_t sample_size) noexcept + : m_allocator(allocator) + , m_data(data) + , m_num_samples(num_samples) + , m_stride(stride) + , m_data_size(data_size) + , m_sample_rate(sample_rate) + , m_type(type) + , m_category(category) + , m_sample_size(sample_size) + , m_desc() + , m_name() + {} + + inline void track::get_copy_impl(iallocator& allocator, track& out_track) const + { + out_track.m_allocator = &allocator; + out_track.m_data = reinterpret_cast(allocator.allocate(m_data_size)); + out_track.m_num_samples = m_num_samples; + out_track.m_stride = m_stride; + out_track.m_data_size = m_data_size; + out_track.m_sample_rate = m_sample_rate; + out_track.m_type = m_type; + out_track.m_category = m_category; + out_track.m_sample_size = m_sample_size; + out_track.m_desc = m_desc; + out_track.m_name = m_name.get_copy(allocator); + + std::memcpy(out_track.m_data, m_data, m_data_size); + } + + inline void track::get_ref_impl(track& out_track) const + { + out_track.m_allocator = nullptr; + out_track.m_data = m_data; + out_track.m_num_samples = m_num_samples; + out_track.m_stride = m_stride; + out_track.m_data_size = m_data_size; + out_track.m_sample_rate = m_sample_rate; + out_track.m_type = m_type; + out_track.m_category = m_category; + out_track.m_sample_size = m_sample_size; + out_track.m_desc = m_desc; + out_track.m_name = m_name.get_copy(); + } + + template + inline typename track_typed::sample_type& track_typed::operator[](uint32_t index) + { + ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples); + return *reinterpret_cast(m_data + (size_t(index) * m_stride)); + } + + template + inline const typename track_typed::sample_type& track_typed::operator[](uint32_t index) const + { + ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples); + return *reinterpret_cast(m_data + (size_t(index) * m_stride)); + } + + template + inline typename track_typed::desc_type& track_typed::get_description() + { + return track::get_description(); + } + + template + inline const typename track_typed::desc_type& track_typed::get_description() const + { + return track::get_description(); + } + + template + inline track_typed track_typed::get_copy(iallocator& allocator) const + { + track_typed track_; + track::get_copy_impl(allocator, track_); + return track_; + } + + template + inline track_typed track_typed::get_ref() const + { + track_typed track_; + track::get_ref_impl(track_); + return track_; + } + + template + inline track_typed track_typed::make_copy(const typename track_typed::desc_type& desc, iallocator& allocator, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride) + { + const size_t num_samples_ = num_samples; + const size_t data_size = num_samples_ * sizeof(sample_type); + const uint8_t* data_raw = reinterpret_cast(data); + + // Copy the data manually to avoid preserving the stride + sample_type* data_copy = reinterpret_cast(allocator.allocate(data_size)); + for (size_t index = 0; index < num_samples_; ++index) + data_copy[index] = *reinterpret_cast(data_raw + (index * stride)); + + return track_typed(&allocator, reinterpret_cast(data_copy), num_samples, sizeof(sample_type), data_size, sample_rate, desc); + } + + template + inline track_typed track_typed::make_reserve(const typename track_typed::desc_type& desc, iallocator& allocator, uint32_t num_samples, float sample_rate) + { + const size_t data_size = size_t(num_samples) * sizeof(sample_type); + return track_typed(&allocator, reinterpret_cast(allocator.allocate(data_size)), num_samples, sizeof(sample_type), data_size, sample_rate, desc); + } + + template + inline track_typed track_typed::make_owner(const typename track_typed::desc_type& desc, iallocator& allocator, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride) + { + const size_t data_size = size_t(num_samples) * stride; + return track_typed(&allocator, reinterpret_cast(data), num_samples, stride, data_size, sample_rate, desc); + } + + template + inline track_typed track_typed::make_ref(const typename track_typed::desc_type& desc, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride) + { + const size_t data_size = size_t(num_samples) * stride; + return track_typed(nullptr, reinterpret_cast(data), num_samples, stride, data_size, sample_rate, desc); + } + + template + inline track_typed::track_typed(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, const typename track_typed::desc_type& desc) noexcept + : track(allocator, data, num_samples, stride, data_size, sample_rate, type, category, sizeof(sample_type)) + { + m_desc = track_desc_untyped(desc); + } + + template + inline track_type& track_cast(track& track_) + { + ACL_ASSERT(track_type::type == track_.get_type() || track_.is_empty(), "Unexpected track type"); + return static_cast(track_); + } + + template + inline const track_type& track_cast(const track& track_) + { + ACL_ASSERT(track_type::type == track_.get_type() || track_.is_empty(), "Unexpected track type"); + return static_cast(track_); + } + + template + inline track_type* track_cast(track* track_) + { + if (track_ == nullptr || (track_type::type != track_->get_type() && !track_->is_empty())) + return nullptr; + + return static_cast(track_); + } + + template + inline const track_type* track_cast(const track* track_) + { + if (track_ == nullptr || (track_type::type != track_->get_type() && !track_->is_empty())) + return nullptr; + + return static_cast(track_); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_array.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_array.impl.h new file mode 100644 index 00000000..dff8fe5b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_array.impl.h @@ -0,0 +1,425 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from track_array.h + +#include "acl/version.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/track_types.h" +#include "acl/core/track_writer.h" + +#include +#include +#include + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline track_array::track_array() noexcept + : m_allocator(nullptr) + , m_tracks(nullptr) + , m_num_tracks(0) + , m_name() + {} + + inline track_array::track_array(iallocator& allocator, uint32_t num_tracks) + : m_allocator(&allocator) + , m_tracks(allocate_type_array(allocator, num_tracks)) + , m_num_tracks(num_tracks) + , m_name() + {} + + inline track_array::track_array(track_array&& other) noexcept + : m_allocator(other.m_allocator) + , m_tracks(other.m_tracks) + , m_num_tracks(other.m_num_tracks) + , m_name(std::move(other.m_name)) + { + other.m_allocator = nullptr; // Make sure we don't free our data since we no longer own it + } + + inline track_array::~track_array() + { + if (m_allocator != nullptr) + deallocate_type_array(*m_allocator, m_tracks, m_num_tracks); + } + + inline track_array& track_array::operator=(track_array&& other) noexcept + { + std::swap(m_allocator, other.m_allocator); + std::swap(m_tracks, other.m_tracks); + std::swap(m_num_tracks, other.m_num_tracks); + std::swap(m_name, other.m_name); + return *this; + } + + inline track& track_array::operator[](uint32_t index) + { + ACL_ASSERT(index < m_num_tracks, "Invalid track index. %u >= %u", index, m_num_tracks); + return m_tracks[index]; + } + + inline const track& track_array::operator[](uint32_t index) const + { + ACL_ASSERT(index < m_num_tracks, "Invalid track index. %u >= %u", index, m_num_tracks); + return m_tracks[index]; + } + + inline error_result track_array::is_valid() const + { + const track_type8 type = get_track_type(); + const uint32_t num_samples = get_num_samples_per_track(); + const float sample_rate = get_sample_rate(); + + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track& track_ = m_tracks[track_index]; + if (track_.get_type() != type) + return error_result("Tracks must all have the same type within an array"); + + if (track_.get_num_samples() != num_samples) + return error_result("Track array requires the same number of samples in every track"); + + if (track_.get_sample_rate() != sample_rate) + return error_result("Track array requires the same sample rate in every track"); + + const error_result result = track_.is_valid(); + if (result.any()) + return result; + + if (track_.get_category() == track_category8::transformf) + { + const track_desc_transformf& desc = track_.get_description(); + if (desc.parent_index != k_invalid_track_index && desc.parent_index >= m_num_tracks) + return error_result("Invalid parent_index. It must be 'k_invalid_track_index' or a valid track index"); + } + } + + // Validate output indices + uint32_t num_outputs = 0; + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track& track_ = m_tracks[track_index]; + const uint32_t output_index = track_.get_output_index(); + if (output_index != k_invalid_track_index && output_index >= m_num_tracks) + return error_result("The output_index must be 'k_invalid_track_index' or less than the number of bones"); + + if (output_index != k_invalid_track_index) + { + for (uint32_t track_index2 = track_index + 1; track_index2 < m_num_tracks; ++track_index2) + { + const track& track2_ = m_tracks[track_index2]; + const uint32_t output_index2 = track2_.get_output_index(); + if (output_index == output_index2) + return error_result("Duplicate output_index found"); + } + + num_outputs++; + } + } + + for (uint32_t output_index = 0; output_index < num_outputs; ++output_index) + { + bool found = false; + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track& track_ = m_tracks[track_index]; + const uint32_t output_index_ = track_.get_output_index(); + if (output_index == output_index_) + { + found = true; + break; + } + } + + if (!found) + return error_result("Output indices are not contiguous"); + } + + return error_result(); + } + + template + inline void track_array::sample_tracks(float sample_time, sample_rounding_policy rounding_policy, track_writer_type& writer) const + { + static_assert(std::is_base_of::value, "track_writer_type must derive from track_writer"); + ACL_ASSERT(is_valid().empty(), "Invalid track array"); + + const uint32_t num_samples = get_num_samples_per_track(); + const float sample_rate = get_sample_rate(); + const track_type8 track_type = get_track_type(); + + // Clamp for safety, the caller should normally handle this but in practice, it often isn't the case + const float duration = get_duration(); + sample_time = rtm::scalar_clamp(sample_time, 0.0F, duration); + + uint32_t key_frame0; + uint32_t key_frame1; + float interpolation_alpha; + find_linear_interpolation_samples_with_sample_rate(num_samples, sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, interpolation_alpha); + + switch (track_type) + { + case track_type8::float1f: + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track_float1f& track__ = track_cast(m_tracks[track_index]); + + const rtm::scalarf value0 = rtm::scalar_load(&track__[key_frame0]); + const rtm::scalarf value1 = rtm::scalar_load(&track__[key_frame1]); + const rtm::scalarf value = rtm::scalar_lerp(value0, value1, rtm::scalar_set(interpolation_alpha)); + writer.write_float1(track_index, value); + } + break; + case track_type8::float2f: + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track_float2f& track__ = track_cast(m_tracks[track_index]); + + const rtm::vector4f value0 = rtm::vector_load2(&track__[key_frame0]); + const rtm::vector4f value1 = rtm::vector_load2(&track__[key_frame1]); + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_float2(track_index, value); + } + break; + case track_type8::float3f: + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track_float3f& track__ = track_cast(m_tracks[track_index]); + + const rtm::vector4f value0 = rtm::vector_load3(&track__[key_frame0]); + const rtm::vector4f value1 = rtm::vector_load3(&track__[key_frame1]); + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_float3(track_index, value); + } + break; + case track_type8::float4f: + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track_float4f& track__ = track_cast(m_tracks[track_index]); + + const rtm::vector4f value0 = rtm::vector_load(&track__[key_frame0]); + const rtm::vector4f value1 = rtm::vector_load(&track__[key_frame1]); + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_float4(track_index, value); + } + break; + case track_type8::vector4f: + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track_vector4f& track__ = track_cast(m_tracks[track_index]); + + const rtm::vector4f value0 = track__[key_frame0]; + const rtm::vector4f value1 = track__[key_frame1]; + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_vector4(track_index, value); + } + break; + case track_type8::qvvf: + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track_qvvf& track__ = track_cast(m_tracks[track_index]); + + const rtm::qvvf& value0 = track__[key_frame0]; + const rtm::qvvf& value1 = track__[key_frame1]; + const rtm::quatf rotation = rtm::quat_lerp(value0.rotation, value1.rotation, interpolation_alpha); + const rtm::vector4f translation = rtm::vector_lerp(value0.translation, value1.translation, interpolation_alpha); + const rtm::vector4f scale = rtm::vector_lerp(value0.scale, value1.scale, interpolation_alpha); + writer.write_rotation(track_index, rotation); + writer.write_translation(track_index, translation); + writer.write_scale(track_index, scale); + } + break; + default: + ACL_ASSERT(false, "Invalid track type"); + break; + } + } + + template + inline void track_array::sample_track(uint32_t track_index, float sample_time, sample_rounding_policy rounding_policy, track_writer_type& writer) const + { + static_assert(std::is_base_of::value, "track_writer_type must derive from track_writer"); + ACL_ASSERT(is_valid().empty(), "Invalid track array"); + ACL_ASSERT(track_index < m_num_tracks, "Invalid track index"); + + const track& track_ = m_tracks[track_index]; + const uint32_t num_samples = track_.get_num_samples(); + const float sample_rate = track_.get_sample_rate(); + + // Clamp for safety, the caller should normally handle this but in practice, it often isn't the case + const float duration = calculate_duration(num_samples, sample_rate); + sample_time = rtm::scalar_clamp(sample_time, 0.0F, duration); + + uint32_t key_frame0; + uint32_t key_frame1; + float interpolation_alpha; + find_linear_interpolation_samples_with_sample_rate(num_samples, sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, interpolation_alpha); + + switch (track_.get_type()) + { + case track_type8::float1f: + { + const track_float1f& track__ = track_cast(track_); + + const rtm::scalarf value0 = rtm::scalar_load(&track__[key_frame0]); + const rtm::scalarf value1 = rtm::scalar_load(&track__[key_frame1]); + const rtm::scalarf value = rtm::scalar_lerp(value0, value1, rtm::scalar_set(interpolation_alpha)); + writer.write_float1(track_index, value); + break; + } + case track_type8::float2f: + { + const track_float2f& track__ = track_cast(track_); + + const rtm::vector4f value0 = rtm::vector_load2(&track__[key_frame0]); + const rtm::vector4f value1 = rtm::vector_load2(&track__[key_frame1]); + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_float2(track_index, value); + break; + } + case track_type8::float3f: + { + const track_float3f& track__ = track_cast(track_); + + const rtm::vector4f value0 = rtm::vector_load3(&track__[key_frame0]); + const rtm::vector4f value1 = rtm::vector_load3(&track__[key_frame1]); + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_float3(track_index, value); + break; + } + case track_type8::float4f: + { + const track_float4f& track__ = track_cast(track_); + + const rtm::vector4f value0 = rtm::vector_load(&track__[key_frame0]); + const rtm::vector4f value1 = rtm::vector_load(&track__[key_frame1]); + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_float4(track_index, value); + break; + } + case track_type8::vector4f: + { + const track_vector4f& track__ = track_cast(track_); + + const rtm::vector4f value0 = track__[key_frame0]; + const rtm::vector4f value1 = track__[key_frame1]; + const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha); + writer.write_vector4(track_index, value); + break; + } + case track_type8::qvvf: + { + const track_qvvf& track__ = track_cast(track_); + + const rtm::qvvf& value0 = track__[key_frame0]; + const rtm::qvvf& value1 = track__[key_frame1]; + const rtm::quatf rotation = rtm::quat_lerp(value0.rotation, value1.rotation, interpolation_alpha); + const rtm::vector4f translation = rtm::vector_lerp(value0.translation, value1.translation, interpolation_alpha); + const rtm::vector4f scale = rtm::vector_lerp(value0.scale, value1.scale, interpolation_alpha); + writer.write_rotation(track_index, rotation); + writer.write_translation(track_index, translation); + writer.write_scale(track_index, scale); + break; + } + default: + ACL_ASSERT(false, "Invalid track type"); + break; + } + } + + inline uint32_t track_array::get_raw_size() const + { + const uint32_t num_samples = get_num_samples_per_track(); + const track_type8 track_type = get_track_type(); + + uint32_t total_size = 0; + for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index) + { + const track& track_ = m_tracks[track_index]; + + if (track_type == track_type8::qvvf) + total_size += num_samples * 10 * uint32_t(sizeof(float)); // 4 rotation floats, 3 translation floats, 3 scale floats + else + total_size += num_samples * track_.get_sample_size(); + } + + return total_size; + } + + template + inline typename track_array_typed::track_member_type& track_array_typed::operator[](uint32_t index) + { + ACL_ASSERT(index < m_num_tracks, "Invalid track index. %u >= %u", index, m_num_tracks); + return track_cast(m_tracks[index]); + } + + template + inline const typename track_array_typed::track_member_type& track_array_typed::operator[](uint32_t index) const + { + ACL_ASSERT(index < m_num_tracks, "Invalid track index. %u >= %u", index, m_num_tracks); + return track_cast(m_tracks[index]); + } + + template + inline track_array_type& track_array_cast(track_array& track_array_) + { + ACL_ASSERT(track_array_type::type == track_array_.get_track_type() || track_array_.is_empty(), "Unexpected track type"); + return static_cast(track_array_); + } + + template + inline const track_array_type& track_array_cast(const track_array& track_array_) + { + ACL_ASSERT(track_array_type::type == track_array_.get_track_type() || track_array_.is_empty(), "Unexpected track type"); + return static_cast(track_array_); + } + + template + inline track_array_type* track_array_cast(track_array* track_array_) + { + if (track_array_ == nullptr || (track_array_type::type != track_array_->get_track_type() && !track_array_->is_empty())) + return nullptr; + + return static_cast(track_array_); + } + + template + inline const track_array_type* track_array_cast(const track_array* track_array_) + { + if (track_array_ == nullptr || (track_array_type::type != track_array_->get_track_type() && !track_array_->is_empty())) + return nullptr; + + return static_cast(track_array_); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_bit_rate_database.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_bit_rate_database.h new file mode 100644 index 00000000..bc3e23df --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_bit_rate_database.h @@ -0,0 +1,965 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/track_formats.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/compression/impl/sample_streams.h" +#include "acl/compression/impl/track_stream.h" + +#include +#include +#include + +#include + +// 0 = disabled, 1 = enabled +#define ACL_IMPL_DEBUG_DATABASE_IMPL 0 + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + class track_bit_rate_database; + + class hierarchical_track_query + { + public: + explicit hierarchical_track_query(iallocator& allocator) + : m_allocator(allocator) + , m_database(nullptr) + , m_track_index(0xFFFFFFFFU) + , m_bit_rates(nullptr) + , m_indices(nullptr) + , m_num_transforms(0) + {} + + ~hierarchical_track_query() + { + deallocate_type_array(m_allocator, m_indices, m_num_transforms); + } + + void bind(track_bit_rate_database& database); + void build(uint32_t track_index, const BoneBitRate* bit_rates, const transform_streams* bone_streams); + + private: + hierarchical_track_query(const hierarchical_track_query&) = delete; + hierarchical_track_query& operator=(const hierarchical_track_query&) = delete; + + struct transform_indices + { + uint32_t rotation_cache_index; + uint32_t translation_cache_index; + uint32_t scale_cache_index; + }; + + iallocator& m_allocator; + track_bit_rate_database* m_database; + uint32_t m_track_index; + const BoneBitRate* m_bit_rates; + transform_indices* m_indices; + uint32_t m_num_transforms; + + friend track_bit_rate_database; + }; + + class single_track_query + { + public: + single_track_query() + : m_database(nullptr) + , m_track_index(0xFFFFFFFFU) + , m_bit_rates() + , m_rotation_cache_index(0xFFFFFFFFU) + , m_translation_cache_index(0xFFFFFFFFU) + , m_scale_cache_index(0xFFFFFFFFU) + {} + + inline uint32_t get_track_index() const { return m_track_index; } + inline const BoneBitRate& get_bit_rates() const { return m_bit_rates; } + + void bind(track_bit_rate_database& database); + void build(uint32_t track_index, const BoneBitRate& bit_rates); + + private: + single_track_query(const single_track_query&) = delete; + single_track_query& operator=(const single_track_query&) = delete; + + track_bit_rate_database* m_database; + uint32_t m_track_index; + BoneBitRate m_bit_rates; + + uint32_t m_rotation_cache_index; + uint32_t m_translation_cache_index; + uint32_t m_scale_cache_index; + + friend track_bit_rate_database; + }; + + class every_track_query + { + public: + explicit every_track_query(iallocator& allocator) + : m_allocator(allocator) + , m_database(nullptr) + , m_bit_rates(nullptr) + , m_indices(nullptr) + , m_num_transforms(0) + {} + + ~every_track_query() + { + deallocate_type_array(m_allocator, m_indices, m_num_transforms); + } + + void bind(track_bit_rate_database& database); + void build(const BoneBitRate* bit_rates); + + private: + every_track_query(const every_track_query&) = delete; + every_track_query& operator=(const every_track_query&) = delete; + + struct transform_indices + { + uint32_t rotation_cache_index; + uint32_t translation_cache_index; + uint32_t scale_cache_index; + }; + + iallocator& m_allocator; + track_bit_rate_database* m_database; + const BoneBitRate* m_bit_rates; + transform_indices* m_indices; + uint32_t m_num_transforms; + + friend track_bit_rate_database; + }; + + union bit_rates_union + { + // We are 0xFF, 0xFF, 0xFF, 0xFF if we are uninitialized and 0xFF, 0xFF, 0xFF, 0x00 if all three tracks are not variable + uint32_t value; + uint8_t bit_rates[4]; + + bit_rates_union() : value(0xFFFFFFFFU) {} + explicit bit_rates_union(const BoneBitRate& input) : bit_rates{ input.rotation, input.translation, input.scale, 0 } {} + + inline bool operator==(bit_rates_union other) const { return value == other.value; } + inline bool operator!=(bit_rates_union other) const { return value != other.value; } + }; + + ////////////////////////////////////////////////////////////////////////// + // This class manages bit rate queries against tracks. + // It will cache recently requested bit rates to speed up repeating queries. + ////////////////////////////////////////////////////////////////////////// + class track_bit_rate_database + { + public: + track_bit_rate_database(iallocator& allocator, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, const transform_streams* bone_streams, const transform_streams* raw_bone_steams, uint32_t num_transforms, uint32_t num_samples_per_track); + ~track_bit_rate_database(); + + void set_segment(const transform_streams* bone_streams, uint32_t num_transforms, uint32_t num_samples_per_track); + + void sample(const single_track_query& query, float sample_time, rtm::qvvf* out_transforms, uint32_t num_transforms); + void sample(const hierarchical_track_query& query, float sample_time, rtm::qvvf* out_transforms, uint32_t num_transforms); + void sample(const every_track_query& query, float sample_time, rtm::qvvf* out_transforms, uint32_t num_transforms); + + size_t get_allocated_size() const; + + private: + track_bit_rate_database(const track_bit_rate_database&) = delete; + track_bit_rate_database& operator=(const track_bit_rate_database&) = delete; + + void find_cache_entries(uint32_t track_index, const BoneBitRate& bit_rates, uint32_t& out_rotation_cache_index, uint32_t& out_translation_cache_index, uint32_t& out_scale_cache_index); + + RTM_FORCE_INLINE rtm::quatf RTM_SIMD_CALL sample_rotation(const sample_context& context, uint32_t rotation_cache_index); + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL sample_translation(const sample_context& context, uint32_t translation_cache_index); + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL sample_scale(const sample_context& context, uint32_t scale_cache_index); + + struct transform_cache_entry + { + // Each transform has a rotation/translation/scale. + // We cache up to 4 different bit rates for each. + // We also keep a generation id to determine the least recently used bit rates to evict from the cache. + + bit_rates_union rotation_bit_rates; + uint32_t rotation_generation_ids[4]; + + bit_rates_union translation_bit_rates; + uint32_t translation_generation_ids[4]; + + bit_rates_union scale_bit_rates; + uint32_t scale_generation_ids[4]; + + transform_cache_entry() + : rotation_bit_rates() + , rotation_generation_ids{ 0, 0, 0, 0 } + , translation_bit_rates() + , translation_generation_ids{ 0, 0, 0, 0 } + , scale_bit_rates() + , scale_generation_ids{ 0, 0, 0, 0 } + {} + + static int32_t find_bit_rate_index(const bit_rates_union& bit_rates, uint32_t search_bit_rate); + }; + + rtm::vector4f m_default_scale; + + iallocator& m_allocator; + const transform_streams* m_mutable_bone_streams; + const transform_streams* m_raw_bone_streams; + + uint32_t m_num_transforms; + uint32_t m_num_samples_per_track; + uint32_t m_num_entries_per_transform; + uint32_t m_track_size; + + bitset_description m_bitset_desc; + bitset_index_ref m_bitref_constant; + rotation_format8 m_rotation_format; + vector_format8 m_translation_format; + vector_format8 m_scale_format; + bool m_is_rotation_variable; + bool m_is_translation_variable; + bool m_is_scale_variable; + bool m_has_scale; + + uint32_t m_generation_id; + + transform_cache_entry* m_transforms; + + uint32_t* m_track_entry_bitsets; + size_t m_track_bitsets_size; + + uint8_t* m_data; + size_t m_data_size; + size_t m_num_cached_tracks; + + static constexpr uint32_t k_num_bit_rates_cached_per_track = 4; + + friend single_track_query; + friend hierarchical_track_query; + friend every_track_query; + }; + + ////////////////////////////////////////////////////////////////////////// + // Implementation + + inline void hierarchical_track_query::bind(track_bit_rate_database& database) + { + ACL_ASSERT(m_database == nullptr, "Query already bound"); + m_database = &database; + + m_indices = allocate_type_array(database.m_allocator, database.m_num_transforms); + m_num_transforms = database.m_num_transforms; + } + + inline void hierarchical_track_query::build(uint32_t track_index, const BoneBitRate* bit_rates, const transform_streams* bone_streams) + { + ACL_ASSERT(m_database != nullptr, "Query not bound to a database"); + ACL_ASSERT(track_index < m_num_transforms, "Invalid track index"); + + m_track_index = track_index; + m_bit_rates = bit_rates; + + uint32_t current_track_index = track_index; + while (current_track_index != k_invalid_track_index) + { + const BoneBitRate& current_bit_rates = bit_rates[current_track_index]; + transform_indices& indices = m_indices[current_track_index]; + + m_database->find_cache_entries(current_track_index, current_bit_rates, indices.rotation_cache_index, indices.translation_cache_index, indices.scale_cache_index); + + const transform_streams& bone_stream = bone_streams[current_track_index]; + current_track_index = bone_stream.parent_bone_index; + } + } + + inline void single_track_query::bind(track_bit_rate_database& database) + { + ACL_ASSERT(m_database == nullptr, "Query already bound"); + m_database = &database; + } + + inline void single_track_query::build(uint32_t track_index, const BoneBitRate& bit_rates) + { + ACL_ASSERT(m_database != nullptr, "Query not bound to a database"); + + m_track_index = track_index; + m_bit_rates = bit_rates; + + m_database->find_cache_entries(track_index, bit_rates, m_rotation_cache_index, m_translation_cache_index, m_scale_cache_index); + } + + inline void every_track_query::bind(track_bit_rate_database& database) + { + ACL_ASSERT(m_database == nullptr, "Query already bound"); + m_database = &database; + + m_indices = allocate_type_array(database.m_allocator, database.m_num_transforms); + m_num_transforms = database.m_num_transforms; + } + + inline void every_track_query::build(const BoneBitRate* bit_rates) + { + ACL_ASSERT(m_database != nullptr, "Query not bound to a database"); + + m_bit_rates = bit_rates; + + for (uint32_t transform_index = 0; transform_index < m_num_transforms; ++transform_index) + { + const BoneBitRate& current_bit_rates = bit_rates[transform_index]; + transform_indices& indices = m_indices[transform_index]; + + m_database->find_cache_entries(transform_index, current_bit_rates, indices.rotation_cache_index, indices.translation_cache_index, indices.scale_cache_index); + } + } + + inline int32_t track_bit_rate_database::transform_cache_entry::find_bit_rate_index(const bit_rates_union& bit_rates, uint32_t search_bit_rate) + { + for (int32_t i = 0; i < 4; ++i) + { + if (bit_rates.bit_rates[i] == search_bit_rate) + return i; + } + + return -1; + } + + inline track_bit_rate_database::track_bit_rate_database(iallocator& allocator, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, const transform_streams* bone_streams, const transform_streams* raw_bone_steams, uint32_t num_transforms, uint32_t num_samples_per_track) + : m_allocator(allocator) + , m_mutable_bone_streams(bone_streams) + , m_raw_bone_streams(raw_bone_steams) + , m_num_transforms(num_transforms) + , m_num_samples_per_track(num_samples_per_track) + { + m_transforms = allocate_type_array(allocator, num_transforms); + + const bool has_scale = raw_bone_steams->segment->clip->has_scale; + m_has_scale = has_scale; + m_default_scale = get_default_scale(bone_streams->segment->clip->additive_format); + + const uint32_t num_tracks_per_transform = has_scale ? 3 : 2; + const uint32_t num_entries_per_transform = num_tracks_per_transform * k_num_bit_rates_cached_per_track; + m_num_entries_per_transform = num_entries_per_transform; + + const uint32_t num_cached_tracks = num_transforms * m_num_entries_per_transform; + + m_bitset_desc = bitset_description::make_from_num_bits(num_samples_per_track); + m_bitref_constant = num_samples_per_track != 0 ? bitset_index_ref(m_bitset_desc, 0) : bitset_index_ref(); + + m_rotation_format = rotation_format; + m_translation_format = translation_format; + m_scale_format = scale_format; + m_is_rotation_variable = is_rotation_format_variable(rotation_format); + m_is_translation_variable = is_vector_format_variable(translation_format); + m_is_scale_variable = is_vector_format_variable(scale_format); + + m_generation_id = 1; + + const uint32_t track_bitsets_size = m_bitset_desc.get_size() * num_cached_tracks; + m_track_entry_bitsets = allocate_type_array(allocator, track_bitsets_size); + m_track_bitsets_size = track_bitsets_size; + + // We allocate a single float buffer to accommodate 4 bit rates for every rot/trans/scale track of each transform. + // Each track is padded and aligned to ensure that it starts on a cache line boundary. + const uint32_t track_size = align_to(sizeof(rtm::vector4f) * num_samples_per_track, 64); + m_track_size = track_size; + + const uint32_t data_size = track_size * num_cached_tracks; + m_data = reinterpret_cast(allocator.allocate(data_size, 64)); + m_data_size = data_size; + m_num_cached_tracks = num_cached_tracks; + } + + inline track_bit_rate_database::~track_bit_rate_database() + { + deallocate_type_array(m_allocator, m_transforms, m_num_transforms); + deallocate_type_array(m_allocator, m_track_entry_bitsets, m_track_bitsets_size); + m_allocator.deallocate(m_data, m_data_size); + } + + inline void track_bit_rate_database::set_segment(const transform_streams* bone_streams, uint32_t num_transforms, uint32_t num_samples_per_track) + { + ACL_ASSERT(bone_streams != nullptr, "Bone streams cannot be null"); + ACL_ASSERT(num_transforms == m_num_transforms, "The number of transforms isn't consistent, we will corrupt the heap"); + ACL_ASSERT(num_samples_per_track <= m_num_samples_per_track, "Not enough memory has been reserved, we will corrupt the heap"); + (void)num_transforms; + (void)num_samples_per_track; + (void)m_num_samples_per_track; + + m_mutable_bone_streams = bone_streams; + + // Reset our cache + for (uint32_t transform_index = 0; transform_index < m_num_transforms; ++transform_index) + m_transforms[transform_index] = transform_cache_entry(); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Switching segment, resetting the database...\n"); +#endif + } + + inline void track_bit_rate_database::find_cache_entries(uint32_t track_index, const BoneBitRate& bit_rates, uint32_t& out_rotation_cache_index, uint32_t& out_translation_cache_index, uint32_t& out_scale_cache_index) + { + // Memory layout: + // track 0 + // rotation + // entry 0 0 + // entry 1 1 + // entry 2 2 + // entry 3 3 + // translation + // entry 0 4 + // entry 1 5 + // entry 2 6 + // entry 3 7 + // scale + // entry 0 8 + // entry 1 9 + // entry 2 10 + // entry 3 11 + // track 1 + // ... + + const uint32_t num_entries_per_transform = m_num_entries_per_transform; + const uint32_t base_track_offset = track_index * num_entries_per_transform; + const uint32_t base_rotation_offset = base_track_offset + (0 * k_num_bit_rates_cached_per_track); + const uint32_t base_translation_offset = base_track_offset + (1 * k_num_bit_rates_cached_per_track); + + const size_t bitset_size = m_bitset_desc.get_size(); + + transform_cache_entry& entry = m_transforms[track_index]; + + uint32_t rotation_cache_index; + if (bit_rates.rotation == k_invalid_bit_rate) + { + // Constant/default tracks or tracks that do not use a variable bit rate can use a single slot + rotation_cache_index = base_rotation_offset; + ACL_ASSERT(rotation_cache_index < m_num_cached_tracks, "Invalid cache index"); + + if (entry.rotation_generation_ids[0] == 0) + { + // The first time around, we invalidate all our cached samples and they will remain valid until we change segment + uint32_t* validity_bitset = m_track_entry_bitsets + (bitset_size * rotation_cache_index); + bitset_reset(validity_bitset, m_bitset_desc, false); + + entry.rotation_generation_ids[0] = m_generation_id++; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Reserved cache index %u for rotation track %u...\n", rotation_cache_index, track_index); +#endif + } + } + else + { + const int32_t slot_index = entry.find_bit_rate_index(entry.rotation_bit_rates, bit_rates.rotation); + if (slot_index >= 0) + rotation_cache_index = base_rotation_offset + slot_index; + else + { + // Failed to find a cached entry for this track and bit rate, clear the oldest entry + uint32_t oldest_generation_id = entry.rotation_generation_ids[0]; + uint32_t oldest_index = 0; + for (uint32_t i = 1; i < 4; ++i) + { + if (entry.rotation_generation_ids[i] < oldest_generation_id) + { + oldest_generation_id = entry.rotation_generation_ids[i]; + oldest_index = i; + } + } + + rotation_cache_index = base_rotation_offset + oldest_index; + ACL_ASSERT(rotation_cache_index < m_num_cached_tracks, "Invalid cache index"); + + entry.rotation_bit_rates.bit_rates[oldest_index] = bit_rates.rotation; + entry.rotation_generation_ids[oldest_index] = m_generation_id++; + + uint32_t* validity_bitset = m_track_entry_bitsets + (bitset_size * rotation_cache_index); + bitset_reset(validity_bitset, m_bitset_desc, false); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Reserved cache index %u for rotation track %u...\n", rotation_cache_index, track_index); +#endif + } + } + + out_rotation_cache_index = rotation_cache_index; + + uint32_t translation_cache_index; + if (bit_rates.translation == k_invalid_bit_rate) + { + // Constant/default tracks or tracks that do not use a variable bit rate can use a single slot + translation_cache_index = base_translation_offset; + ACL_ASSERT(translation_cache_index < m_num_cached_tracks, "Invalid cache index"); + + if (entry.translation_generation_ids[0] == 0) + { + // The first time around, we invalidate all our cached samples and they will remain valid until we change segment + uint32_t* validity_bitset = m_track_entry_bitsets + (bitset_size * translation_cache_index); + bitset_reset(validity_bitset, m_bitset_desc, false); + + entry.translation_generation_ids[0] = m_generation_id++; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Reserved cache index %u for translation track %u...\n", translation_cache_index, track_index); +#endif + } + } + else + { + const int32_t slot_index = entry.find_bit_rate_index(entry.translation_bit_rates, bit_rates.translation); + if (slot_index >= 0) + translation_cache_index = base_translation_offset + slot_index; + else + { + // Failed to find a cached entry for this track and bit rate, clear the oldest entry + uint32_t oldest_generation_id = entry.translation_generation_ids[0]; + uint32_t oldest_index = 0; + for (uint32_t i = 1; i < 4; ++i) + { + if (entry.translation_generation_ids[i] < oldest_generation_id) + { + oldest_generation_id = entry.translation_generation_ids[i]; + oldest_index = i; + } + } + + translation_cache_index = base_translation_offset + oldest_index; + ACL_ASSERT(translation_cache_index < m_num_cached_tracks, "Invalid cache index"); + + entry.translation_bit_rates.bit_rates[oldest_index] = bit_rates.translation; + entry.translation_generation_ids[oldest_index] = m_generation_id++; + + uint32_t* validity_bitset = m_track_entry_bitsets + (bitset_size * translation_cache_index); + bitset_reset(validity_bitset, m_bitset_desc, false); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Reserved cache index %u for translation track %u...\n", translation_cache_index, track_index); +#endif + } + } + + out_translation_cache_index = translation_cache_index; + + uint32_t scale_cache_index; + if (!m_has_scale) + scale_cache_index = 0xFFFFFFFFU; + else + { + const uint32_t base_scale_offset = base_track_offset + (2 * k_num_bit_rates_cached_per_track); + + if (bit_rates.scale == k_invalid_bit_rate) + { + // Constant/default tracks or tracks that do not use a variable bit rate can use a single slot + scale_cache_index = base_scale_offset; + ACL_ASSERT(scale_cache_index < m_num_cached_tracks, "Invalid cache index"); + + if (entry.scale_generation_ids[0] == 0) + { + // The first time around, we invalidate all our cached samples and they will remain valid until we change segment + uint32_t* validity_bitset = m_track_entry_bitsets + (bitset_size * scale_cache_index); + bitset_reset(validity_bitset, m_bitset_desc, false); + + entry.scale_generation_ids[0] = m_generation_id++; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Reserved cache index %u for scale track %u...\n", scale_cache_index, track_index); +#endif + } + } + else + { + const int32_t slot_index = entry.find_bit_rate_index(entry.scale_bit_rates, bit_rates.scale); + if (slot_index >= 0) + scale_cache_index = base_scale_offset + slot_index; + else + { + // Failed to find a cached entry for this track and bit rate, clear the oldest entry + uint32_t oldest_generation_id = entry.scale_generation_ids[0]; + uint32_t oldest_index = 0; + for (uint32_t i = 1; i < 4; ++i) + { + if (entry.scale_generation_ids[i] < oldest_generation_id) + { + oldest_generation_id = entry.scale_generation_ids[i]; + oldest_index = i; + } + } + + scale_cache_index = base_scale_offset + oldest_index; + ACL_ASSERT(scale_cache_index < m_num_cached_tracks, "Invalid cache index"); + + entry.scale_bit_rates.bit_rates[oldest_index] = bit_rates.scale; + entry.scale_generation_ids[oldest_index] = m_generation_id++; + + uint32_t* validity_bitset = m_track_entry_bitsets + (bitset_size * scale_cache_index); + bitset_reset(validity_bitset, m_bitset_desc, false); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Reserved cache index %u for scale track %u...\n", scale_cache_index, track_index); +#endif + } + } + } + + out_scale_cache_index = scale_cache_index; + + ACL_ASSERT(m_generation_id < (0xFFFFFFFFU - 8), "Generation ID is about to wrap, bad things will happen"); + } + + RTM_FORCE_INLINE rtm::quatf RTM_SIMD_CALL track_bit_rate_database::sample_rotation(const sample_context& context, uint32_t rotation_cache_index) + { + const uint32_t track_index = context.track_index; + const transform_streams& bone_stream = m_mutable_bone_streams[track_index]; + const size_t rotation_cache_index_ = rotation_cache_index; + + rtm::quatf rotation; + if (bone_stream.is_rotation_default) + rotation = rtm::quat_identity(); + else if (bone_stream.is_rotation_constant) + { + uint32_t* validity_bitset = m_track_entry_bitsets + (m_bitset_desc.get_size() * rotation_cache_index_); + rtm::quatf* cached_samples = safe_ptr_cast(m_data + (m_track_size * rotation_cache_index_)); + + if (bitset_test(validity_bitset, m_bitref_constant)) + { + // Cached + rotation = cached_samples[0]; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Hit cache for constant sample for rotation track %u...\n", track_index); +#endif + } + else + { + // Not cached + if (m_is_rotation_variable) + rotation = get_rotation_sample(m_raw_bone_streams[track_index], 0); + else + rotation = get_rotation_sample(m_raw_bone_streams[track_index], 0, m_rotation_format); + + rotation = rtm::quat_normalize(rotation); + + cached_samples[0] = rotation; + bitset_set(validity_bitset, m_bitref_constant, true); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Cached constant sample for rotation track %u (%.5f, %.5f, %.5f, %.5f)...\n", track_index, quat_get_x(rotation), quat_get_y(rotation), quat_get_z(rotation), quat_get_w(rotation)); +#endif + } + } + else + { + const transform_streams& raw_bone_stream = m_raw_bone_streams[track_index]; + + uint32_t* validity_bitset = m_track_entry_bitsets + (m_bitset_desc.get_size() * rotation_cache_index_); + rtm::quatf* cached_samples = safe_ptr_cast(m_data + (m_track_size * rotation_cache_index_)); + + const bitset_index_ref bitref0(m_bitset_desc, context.sample_key); + if (bitset_test(validity_bitset, bitref0)) + { + // Cached + rotation = cached_samples[context.sample_key]; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Hit cache for sample %u for rotation track %u at bit rate %u...\n", key0, track_index, context.bit_rates.rotation); +#endif + } + else + { + // Not cached + if (m_is_rotation_variable) + rotation = get_rotation_sample(bone_stream, raw_bone_stream, context.sample_key, context.bit_rates.rotation); + else + rotation = get_rotation_sample(bone_stream, context.sample_key, m_rotation_format); + + rotation = rtm::quat_normalize(rotation); + + cached_samples[context.sample_key] = rotation; + bitset_set(validity_bitset, bitref0, true); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Cached sample %u for rotation track %u at bit rate %u (%.5f, %.5f, %.5f, %.5f)...\n", context.sample_key, track_index, context.bit_rates.rotation, quat_get_x(rotation), quat_get_y(rotation), quat_get_z(rotation), quat_get_w(rotation)); +#endif + } + } + + return rotation; + } + + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL track_bit_rate_database::sample_translation(const sample_context& context, uint32_t translation_cache_index) + { + const uint32_t track_index = context.track_index; + const transform_streams& bone_stream = m_mutable_bone_streams[track_index]; + const size_t translation_cache_index_ = translation_cache_index; + + rtm::vector4f translation; + if (bone_stream.is_translation_default) + translation = rtm::vector_zero(); + else if (bone_stream.is_translation_constant) + { + uint32_t* validity_bitset = m_track_entry_bitsets + (m_bitset_desc.get_size() * translation_cache_index_); + rtm::vector4f* cached_samples = safe_ptr_cast(m_data + (m_track_size * translation_cache_index_)); + + if (bitset_test(validity_bitset, m_bitref_constant)) + { + // Cached + translation = cached_samples[0]; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Hit cache for constant sample for translation track %u...\n", track_index); +#endif + } + else + { + // Not cached + translation = get_translation_sample(m_raw_bone_streams[track_index], 0, vector_format8::vector3f_full); + + cached_samples[0] = translation; + bitset_set(validity_bitset, m_bitref_constant, true); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Cached constant sample for translation track %u (%.5f, %.5f, %.5f)...\n", track_index, vector_get_x(translation), vector_get_y(translation), vector_get_z(translation)); +#endif + } + } + else + { + const transform_streams& raw_bone_stream = m_raw_bone_streams[track_index]; + + uint32_t* validity_bitset = m_track_entry_bitsets + (m_bitset_desc.get_size() * translation_cache_index_); + rtm::vector4f* cached_samples = safe_ptr_cast(m_data + (m_track_size * translation_cache_index_)); + + const bitset_index_ref bitref0(m_bitset_desc, context.sample_key); + if (bitset_test(validity_bitset, bitref0)) + { + // Cached + translation = cached_samples[context.sample_key]; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Hit cache for sample %u for translation track %u at bit rate %u...\n", key0, track_index, context.bit_rates.translation); +#endif + } + else + { + // Not cached + if (m_is_translation_variable) + translation = get_translation_sample(bone_stream, raw_bone_stream, context.sample_key, context.bit_rates.translation); + else + translation = get_translation_sample(bone_stream, context.sample_key, m_translation_format); + + cached_samples[context.sample_key] = translation; + bitset_set(validity_bitset, bitref0, true); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Cached sample %u for translation track %u at bit rate %u (%.5f, %.5f, %.5f)...\n", context.sample_key, track_index, context.bit_rates.translation, vector_get_x(translation), vector_get_y(translation), vector_get_z(translation)); +#endif + } + } + + return translation; + } + + RTM_FORCE_INLINE rtm::vector4f RTM_SIMD_CALL track_bit_rate_database::sample_scale(const sample_context& context, uint32_t scale_cache_index) + { + const uint32_t track_index = context.track_index; + const transform_streams& bone_stream = m_mutable_bone_streams[track_index]; + const size_t scale_cache_index_ = scale_cache_index; + + rtm::vector4f scale; + if (bone_stream.is_scale_default) + scale = m_default_scale; + else if (bone_stream.is_scale_constant) + { + uint32_t* validity_bitset = m_track_entry_bitsets + (m_bitset_desc.get_size() * scale_cache_index_); + rtm::vector4f* cached_samples = safe_ptr_cast(m_data + (m_track_size * scale_cache_index_)); + + if (bitset_test(validity_bitset, m_bitref_constant)) + { + // Cached + scale = cached_samples[0]; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Hit cache for constant sample for scale track %u...\n", track_index); +#endif + } + else + { + // Not cached + scale = get_scale_sample(m_raw_bone_streams[track_index], 0, vector_format8::vector3f_full); + + cached_samples[0] = scale; + bitset_set(validity_bitset, m_bitref_constant, true); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Cached constant sample for scale track %u (%.5f, %.5f, %.5f)...\n", track_index, vector_get_x(scale), vector_get_y(scale), vector_get_z(scale)); +#endif + } + } + else + { + const transform_streams& raw_bone_stream = m_raw_bone_streams[track_index]; + + uint32_t* validity_bitset = m_track_entry_bitsets + (m_bitset_desc.get_size() * scale_cache_index_); + rtm::vector4f* cached_samples = safe_ptr_cast(m_data + (m_track_size * scale_cache_index_)); + + const bitset_index_ref bitref0(m_bitset_desc, context.sample_key); + if (bitset_test(validity_bitset, bitref0)) + { + // Cached + scale = cached_samples[context.sample_key]; + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Hit cache for sample %u for translation track %u at bit rate %u...\n", key0, track_index, context.bit_rates.scale); +#endif + } + else + { + // Not cached + if (m_is_scale_variable) + scale = get_scale_sample(bone_stream, raw_bone_stream, context.sample_key, context.bit_rates.scale); + else + scale = get_scale_sample(bone_stream, context.sample_key, m_scale_format); + + cached_samples[context.sample_key] = scale; + bitset_set(validity_bitset, bitref0, true); + +#if ACL_IMPL_DEBUG_DATABASE_IMPL + printf("Cached sample %u for translation track %u at bit rate %u (%.5f, %.5f, %.5f)...\n", context.sample_key, track_index, context.bit_rates.scale, vector_get_x(scale), vector_get_y(scale), vector_get_z(scale)); +#endif + } + } + + return scale; + } + + inline void track_bit_rate_database::sample(const single_track_query& query, float sample_time, rtm::qvvf* out_local_pose, uint32_t num_transforms) + { + ACL_ASSERT(query.m_database == this, "Query has not been built for this database"); + ACL_ASSERT(out_local_pose != nullptr, "Cannot write to null output local pose"); + ACL_ASSERT(num_transforms > 0, "Cannot write to empty output local pose"); + (void)num_transforms; + + const segment_context* segment_context = m_mutable_bone_streams->segment; + + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + sample_context context; + context.track_index = query.m_track_index; + context.sample_key = sample_key; + context.sample_time = sample_time; + context.bit_rates = query.m_bit_rates; + + const rtm::quatf rotation = sample_rotation(context, query.m_rotation_cache_index); + const rtm::vector4f translation = sample_translation(context, query.m_translation_cache_index); + const rtm::vector4f scale = sample_scale(context, query.m_scale_cache_index); + + out_local_pose[query.m_track_index] = rtm::qvv_set(rotation, translation, scale); + } + + inline void track_bit_rate_database::sample(const hierarchical_track_query& query, float sample_time, rtm::qvvf* out_local_pose, uint32_t num_transforms) + { + ACL_ASSERT(out_local_pose != nullptr, "Cannot write to null output local pose"); + ACL_ASSERT(num_transforms > 0, "Cannot write to empty output local pose"); + (void)num_transforms; + + const segment_context* segment_context = m_mutable_bone_streams->segment; + + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + sample_context context; + context.sample_key = sample_key; + context.sample_time = sample_time; + + uint32_t current_track_index = query.m_track_index; + while (current_track_index != k_invalid_track_index) + { + const transform_streams& bone_stream = m_mutable_bone_streams[current_track_index]; + const hierarchical_track_query::transform_indices& indices = query.m_indices[current_track_index]; + + context.track_index = current_track_index; + context.bit_rates = query.m_bit_rates[current_track_index]; + + const rtm::quatf rotation = sample_rotation(context, indices.rotation_cache_index); + const rtm::vector4f translation = sample_translation(context, indices.translation_cache_index); + const rtm::vector4f scale = sample_scale(context, indices.scale_cache_index); + + out_local_pose[current_track_index] = rtm::qvv_set(rotation, translation, scale); + current_track_index = bone_stream.parent_bone_index; + } + } + + inline void track_bit_rate_database::sample(const every_track_query& query, float sample_time, rtm::qvvf* out_local_pose, uint32_t num_transforms) + { + ACL_ASSERT(out_local_pose != nullptr, "Cannot write to null output local pose"); + ACL_ASSERT(num_transforms > 0, "Cannot write to empty output local pose"); + ACL_ASSERT(num_transforms == m_num_transforms, "Expected the same number of pose transforms"); + (void)num_transforms; + + const segment_context* segment_context = m_mutable_bone_streams->segment; + + const uint32_t sample_key = get_uniform_sample_key(*segment_context, sample_time); + + sample_context context; + context.sample_key = sample_key; + context.sample_time = sample_time; + + for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index) + { + const every_track_query::transform_indices& indices = query.m_indices[transform_index]; + + context.track_index = transform_index; + context.bit_rates = query.m_bit_rates[transform_index]; + + const rtm::quatf rotation = sample_rotation(context, indices.rotation_cache_index); + const rtm::vector4f translation = sample_translation(context, indices.translation_cache_index); + const rtm::vector4f scale = sample_scale(context, indices.scale_cache_index); + + out_local_pose[transform_index] = rtm::qvv_set(rotation, translation, scale); + } + } + + inline size_t track_bit_rate_database::get_allocated_size() const + { + size_t cache_size = 0; + cache_size += sizeof(transform_cache_entry) * m_num_transforms; + cache_size += m_track_bitsets_size; + cache_size += m_data_size; + return cache_size; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_error.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_error.impl.h new file mode 100644 index 00000000..1dabb04f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_error.impl.h @@ -0,0 +1,816 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from track_error.h + +#include "acl/version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/debug_track_writer.h" +#include "acl/compression/track_array.h" +#include "acl/compression/transform_error_metrics.h" +#include "acl/compression/impl/track_list_context.h" +#include "acl/decompression/decompress.h" + +#include +#include + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline rtm::vector4f RTM_SIMD_CALL get_scalar_track_error(track_type8 track_type, uint32_t raw_track_index, uint32_t lossy_track_index, const debug_track_writer& raw_tracks_writer, const debug_track_writer& lossy_tracks_writer) + { + rtm::vector4f error; + switch (track_type) + { + case track_type8::float1f: + { + const float raw_value = raw_tracks_writer.read_float1(raw_track_index); + const float lossy_value = lossy_tracks_writer.read_float1(lossy_track_index); + error = rtm::vector_set(rtm::scalar_abs(raw_value - lossy_value)); + break; + } + case track_type8::float2f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_float2(raw_track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_float2(lossy_track_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + error = rtm::vector_mix(error, rtm::vector_zero()); + break; + } + case track_type8::float3f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_float3(raw_track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_float3(lossy_track_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + error = rtm::vector_mix(error, rtm::vector_zero()); + break; + } + case track_type8::float4f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_float4(raw_track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_float4(lossy_track_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + break; + } + case track_type8::vector4f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_vector4(raw_track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_vector4(lossy_track_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + break; + } + default: + ACL_ASSERT(false, "Unsupported track type"); + error = rtm::vector_zero(); + break; + } + + return error; + } + + struct ierror_calculation_adapter + { + virtual ~ierror_calculation_adapter() = default; + + // Scalar and transforms, mandatory + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) = 0; + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) = 0; + + // Scalar and transforms, optional + virtual uint32_t get_output_index(uint32_t track_index) { return track_index; } + + // Transforms only, mandatory + virtual uint32_t get_parent_index(uint32_t track_index) { (void)track_index; return k_invalid_track_index; } + virtual float get_shell_distance(uint32_t track_index) { (void)track_index; return 0.0F; } + + // Transforms only, optional + virtual void remap_output(debug_track_writer& track_writer0, debug_track_writer& track_writer1, debug_track_writer& track_writer_remapped) + { + (void)track_writer0; + std::memcpy(track_writer_remapped.tracks_typed.qvvf, track_writer1.tracks_typed.qvvf, sizeof(rtm::qvvf) * track_writer1.num_tracks); + } + + // Transform only, optional, additive base support + virtual bool has_additive_base() const { return false; } + virtual void sample_tracks_base(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) + { + (void)sample_time; + (void)rounding_policy; + (void)track_writer; + } + }; + + struct calculate_track_error_args + { + explicit calculate_track_error_args(ierror_calculation_adapter& adapter_) + : adapter(adapter_) + { + } + + // Scalar and transforms + ierror_calculation_adapter& adapter; + uint32_t num_samples = 0; + uint32_t num_tracks = 0; + float duration = 0.0F; + float sample_rate = 0.0F; + track_type8 track_type = track_type8::float1f; + sample_rounding_policy rounding_policy = sample_rounding_policy::nearest; + + // Transforms only + const itransform_error_metric* error_metric = nullptr; + + // Optional + uint32_t base_num_samples = 0; + float base_duration = 0.0F; + }; + + inline track_error calculate_scalar_track_error(iallocator& allocator, const calculate_track_error_args& args) + { + const uint32_t num_samples = args.num_samples; + if (num_samples == 0) + return track_error(); // Cannot measure any error + + const uint32_t num_tracks = args.num_tracks; + if (num_tracks == 0) + return track_error(); // Cannot measure any error + + track_error result; + result.error = -1.0F; // Can never have a negative error, use -1 so the first sample is used + + const float duration = args.duration; + const float sample_rate = args.sample_rate; + const track_type8 track_type = args.track_type; + const sample_rounding_policy rounding_policy = args.rounding_policy; + + debug_track_writer tracks_writer0(allocator, track_type, num_tracks); + debug_track_writer tracks_writer1(allocator, track_type, num_tracks); + + // Measure our error + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration); + + args.adapter.sample_tracks0(sample_time, rounding_policy, tracks_writer0); + args.adapter.sample_tracks1(sample_time, rounding_policy, tracks_writer1); + + // Validate decompress_tracks + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const uint32_t output_index = args.adapter.get_output_index(track_index); + if (output_index == k_invalid_track_index) + continue; // Track is being stripped, ignore it + + const rtm::vector4f error = get_scalar_track_error(track_type, track_index, output_index, tracks_writer0, tracks_writer1); + + const float max_error = rtm::vector_get_max_component(error); + if (max_error > result.error) + { + result.error = max_error; + result.index = track_index; + result.sample_time = sample_time; + } + } + } + + return result; + } + + inline track_error calculate_transform_track_error(iallocator& allocator, const calculate_track_error_args& args) + { + ACL_ASSERT(args.error_metric != nullptr, "Must have an error metric"); + + const uint32_t num_samples = args.num_samples; + if (num_samples == 0) + return track_error(); // Cannot measure any error + + const uint32_t num_tracks = args.num_tracks; + if (num_tracks == 0) + return track_error(); // Cannot measure any error + + const float clip_duration = args.duration; + const float sample_rate = args.sample_rate; + const itransform_error_metric& error_metric = *args.error_metric; + const uint32_t additive_num_samples = args.base_num_samples; + const float additive_duration = args.base_duration; + const sample_rounding_policy rounding_policy = args.rounding_policy; + const bool has_additive_base = args.adapter.has_additive_base(); + + // Always calculate the error with scale, slower but we don't need to know if we have scale or not + const bool has_scale = true; + + debug_track_writer tracks_writer0(allocator, track_type8::qvvf, num_tracks); + debug_track_writer tracks_writer1(allocator, track_type8::qvvf, num_tracks); + debug_track_writer tracks_writer1_remapped(allocator, track_type8::qvvf, num_tracks); + debug_track_writer tracks_writer_base(allocator, track_type8::qvvf, num_tracks); + + const size_t transform_size = error_metric.get_transform_size(has_scale); + const bool needs_conversion = error_metric.needs_conversion(has_scale); + uint8_t* raw_local_pose_converted = nullptr; + uint8_t* base_local_pose_converted = nullptr; + uint8_t* lossy_local_pose_converted = nullptr; + if (needs_conversion) + { + raw_local_pose_converted = allocate_type_array_aligned(allocator, num_tracks * transform_size, 64); + base_local_pose_converted = allocate_type_array_aligned(allocator, num_tracks * transform_size, 64); + lossy_local_pose_converted = allocate_type_array_aligned(allocator, num_tracks * transform_size, 64); + } + + uint8_t* raw_object_pose = allocate_type_array_aligned(allocator, num_tracks * transform_size, 64); + uint8_t* lossy_object_pose = allocate_type_array_aligned(allocator, num_tracks * transform_size, 64); + + uint32_t* parent_transform_indices = allocate_type_array(allocator, num_tracks); + uint32_t* self_transform_indices = allocate_type_array(allocator, num_tracks); + + for (uint32_t transform_index = 0; transform_index < num_tracks; ++transform_index) + { + const uint32_t parent_index = args.adapter.get_parent_index(transform_index); + parent_transform_indices[transform_index] = parent_index; + self_transform_indices[transform_index] = transform_index; + } + + void* raw_local_pose_ = needs_conversion ? (void*)raw_local_pose_converted : (void*)tracks_writer0.tracks_typed.qvvf; + const void* base_local_pose_ = needs_conversion ? (void*)base_local_pose_converted : (void*)tracks_writer_base.tracks_typed.qvvf; + void* lossy_local_pose_ = needs_conversion ? (void*)lossy_local_pose_converted : (void*)tracks_writer1_remapped.tracks_typed.qvvf; + + itransform_error_metric::convert_transforms_args convert_transforms_args_raw; + convert_transforms_args_raw.dirty_transform_indices = self_transform_indices; + convert_transforms_args_raw.num_dirty_transforms = num_tracks; + convert_transforms_args_raw.transforms = tracks_writer0.tracks_typed.qvvf; + convert_transforms_args_raw.num_transforms = num_tracks; + + itransform_error_metric::convert_transforms_args convert_transforms_args_base = convert_transforms_args_raw; + convert_transforms_args_base.transforms = tracks_writer_base.tracks_typed.qvvf; + + itransform_error_metric::convert_transforms_args convert_transforms_args_lossy = convert_transforms_args_raw; + convert_transforms_args_lossy.transforms = tracks_writer1_remapped.tracks_typed.qvvf; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_raw; + apply_additive_to_base_args_raw.dirty_transform_indices = self_transform_indices; + apply_additive_to_base_args_raw.num_dirty_transforms = num_tracks; + apply_additive_to_base_args_raw.local_transforms = raw_local_pose_; + apply_additive_to_base_args_raw.base_transforms = base_local_pose_; + apply_additive_to_base_args_raw.num_transforms = num_tracks; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy = apply_additive_to_base_args_raw; + apply_additive_to_base_args_lossy.local_transforms = lossy_local_pose_; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw; + local_to_object_space_args_raw.dirty_transform_indices = self_transform_indices; + local_to_object_space_args_raw.num_dirty_transforms = num_tracks; + local_to_object_space_args_raw.parent_transform_indices = parent_transform_indices; + local_to_object_space_args_raw.local_transforms = raw_local_pose_; + local_to_object_space_args_raw.num_transforms = num_tracks; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw; + local_to_object_space_args_lossy.local_transforms = lossy_local_pose_; + + track_error result; + result.error = -1.0F; // Can never have a negative error, use -1 so the first sample is used + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, clip_duration); + + // Sample our tracks + args.adapter.sample_tracks0(sample_time, rounding_policy, tracks_writer0); + args.adapter.sample_tracks1(sample_time, rounding_policy, tracks_writer1); + + // Maybe remap them + args.adapter.remap_output(tracks_writer0, tracks_writer1, tracks_writer1_remapped); + + if (needs_conversion) + { + error_metric.convert_transforms(convert_transforms_args_raw, raw_local_pose_converted); + error_metric.convert_transforms(convert_transforms_args_lossy, lossy_local_pose_converted); + } + + if (has_additive_base) + { + const float normalized_sample_time = additive_num_samples > 1 ? (sample_time / clip_duration) : 0.0F; + const float additive_sample_time = additive_num_samples > 1 ? (normalized_sample_time * additive_duration) : 0.0F; + args.adapter.sample_tracks_base(additive_sample_time, rounding_policy, tracks_writer_base); + + if (needs_conversion) + error_metric.convert_transforms(convert_transforms_args_base, base_local_pose_converted); + + error_metric.apply_additive_to_base(apply_additive_to_base_args_raw, raw_local_pose_); + error_metric.apply_additive_to_base(apply_additive_to_base_args_lossy, lossy_local_pose_); + } + + error_metric.local_to_object_space(local_to_object_space_args_raw, raw_object_pose); + error_metric.local_to_object_space(local_to_object_space_args_lossy, lossy_object_pose); + + for (uint32_t bone_index = 0; bone_index < num_tracks; ++bone_index) + { + const float shell_distance = args.adapter.get_shell_distance(bone_index); + + itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = raw_object_pose + (bone_index * transform_size); + calculate_error_args.transform1 = lossy_object_pose + (bone_index * transform_size); + calculate_error_args.construct_sphere_shell(shell_distance); + + const float error = rtm::scalar_cast(error_metric.calculate_error(calculate_error_args)); + + if (error > result.error) + { + result.error = error; + result.index = bone_index; + result.sample_time = sample_time; + } + } + } + + deallocate_type_array(allocator, raw_local_pose_converted, num_tracks * transform_size); + deallocate_type_array(allocator, base_local_pose_converted, num_tracks * transform_size); + deallocate_type_array(allocator, lossy_local_pose_converted, num_tracks * transform_size); + deallocate_type_array(allocator, raw_object_pose, num_tracks * transform_size); + deallocate_type_array(allocator, lossy_object_pose, num_tracks * transform_size); + deallocate_type_array(allocator, parent_transform_indices, num_tracks); + deallocate_type_array(allocator, self_transform_indices, num_tracks); + + return result; + } + + inline track_error invalid_track_error() + { + track_error result; + result.index = ~0U; + result.error = -1.0F; + result.sample_time = -1.0F; + return result; + } + } + + template> + inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks, decompression_context_type& context) + { + using namespace acl_impl; + + ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid"); + ACL_ASSERT(context.is_initialized(), "Context isn't initialized"); + + if (raw_tracks.get_track_type() == track_type8::qvvf) + return invalid_track_error(); // Only supports scalar tracks + + struct error_calculation_adapter final : public ierror_calculation_adapter + { + const track_array& raw_tracks_; + decompression_context_type& context_; + + error_calculation_adapter(const track_array& raw_tracks__, decompression_context_type& context__) + : raw_tracks_(raw_tracks__) + , context_(context__) + { + } + + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks_.sample_tracks(sample_time, rounding_policy, track_writer); + } + + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + context_.seek(sample_time, rounding_policy); + context_.decompress_tracks(track_writer); + } + + virtual uint32_t get_output_index(uint32_t track_index) override + { + const track& track_ = raw_tracks_[track_index]; + return track_.get_output_index(); + } + }; + + error_calculation_adapter adapter(raw_tracks, context); + + calculate_track_error_args args(adapter); + args.num_samples = raw_tracks.get_num_samples_per_track(); + args.num_tracks = raw_tracks.get_num_tracks(); + args.duration = raw_tracks.get_duration(); + args.sample_rate = raw_tracks.get_sample_rate(); + args.track_type = raw_tracks.get_track_type(); + + // We use the nearest sample to accurately measure the loss that happened, if any + args.rounding_policy = sample_rounding_policy::nearest; + + return calculate_scalar_track_error(allocator, args); + } + + template> + inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks, decompression_context_type& context, const itransform_error_metric& error_metric) + { + using namespace acl_impl; + + ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid"); + ACL_ASSERT(context.is_initialized(), "Context isn't initialized"); + + struct error_calculation_adapter final : public ierror_calculation_adapter + { + const track_array& raw_tracks_; + decompression_context_type& context_; + iallocator& allocator_; + + uint32_t* output_bone_mapping; + uint32_t num_output_bones; + + error_calculation_adapter(iallocator& allocator__, const track_array& raw_tracks__, decompression_context_type& context__) + : raw_tracks_(raw_tracks__) + , context_(context__) + , allocator_(allocator__) + { + output_bone_mapping = create_output_track_mapping(allocator__, raw_tracks__, num_output_bones); + } + + virtual ~error_calculation_adapter() override + { + deallocate_type_array(allocator_, output_bone_mapping, num_output_bones); + } + + error_calculation_adapter(const error_calculation_adapter&) = delete; + error_calculation_adapter& operator=(const error_calculation_adapter&) = delete; + + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks_.sample_tracks(sample_time, rounding_policy, track_writer); + } + + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + context_.seek(sample_time, rounding_policy); + context_.decompress_tracks(track_writer); + } + + virtual uint32_t get_output_index(uint32_t track_index) override + { + const track& track_ = raw_tracks_[track_index]; + return track_.get_output_index(); + } + + virtual uint32_t get_parent_index(uint32_t track_index) override + { + const track_qvvf& track_ = track_cast(raw_tracks_[track_index]); + return track_.get_description().parent_index; + } + + virtual float get_shell_distance(uint32_t track_index) override + { + const track_qvvf& track_ = track_cast(raw_tracks_[track_index]); + return track_.get_description().shell_distance; + } + + virtual void remap_output(debug_track_writer& track_writer0, debug_track_writer& track_writer1, debug_track_writer& track_writer_remapped) override + { + // Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if + // the data is available + std::memcpy(track_writer_remapped.tracks_typed.qvvf, track_writer0.tracks_typed.qvvf, sizeof(rtm::qvvf) * track_writer_remapped.num_tracks); + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + track_writer_remapped.tracks_typed.qvvf[bone_index] = track_writer1.tracks_typed.qvvf[output_index]; + } + } + }; + + error_calculation_adapter adapter(allocator, raw_tracks, context); + + calculate_track_error_args args(adapter); + args.num_samples = raw_tracks.get_num_samples_per_track(); + args.num_tracks = raw_tracks.get_num_tracks(); + args.duration = raw_tracks.get_duration(); + args.sample_rate = raw_tracks.get_sample_rate(); + args.track_type = raw_tracks.get_track_type(); + + // We use the nearest sample to accurately measure the loss that happened, if any but only if all data is loaded + // If we have a database with some data missing, we can't use the nearest samples, we have to interpolate + args.rounding_policy = sample_rounding_policy::nearest; + + const compressed_tracks& tracks = *context.get_compressed_tracks(); + if (tracks.has_database()) + { + // TODO: Check if all the data is loaded, always interpolate for now + args.rounding_policy = sample_rounding_policy::none; + } + + if (raw_tracks.get_track_type() != track_type8::qvvf) + return calculate_scalar_track_error(allocator, args); + + args.error_metric = &error_metric; + + return calculate_transform_track_error(allocator, args); + } + + template> + inline track_error calculate_compression_error(iallocator& allocator, const track_array_qvvf& raw_tracks, decompression_context_type& context, const itransform_error_metric& error_metric, const track_array_qvvf& additive_base_tracks) + { + using namespace acl_impl; + + ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid"); + ACL_ASSERT(context.is_initialized(), "Context isn't initialized"); + + struct error_calculation_adapter final : public ierror_calculation_adapter + { + const track_array& raw_tracks_; + decompression_context_type& context_; + const track_array_qvvf& additive_base_tracks_; + + iallocator& allocator_; + uint32_t* output_bone_mapping; + uint32_t num_output_bones; + + error_calculation_adapter(iallocator& allocator__, const track_array& raw_tracks__, decompression_context_type& context__, const track_array_qvvf& additive_base_tracks__) + : raw_tracks_(raw_tracks__) + , context_(context__) + , additive_base_tracks_(additive_base_tracks__) + , allocator_(allocator__) + { + output_bone_mapping = create_output_track_mapping(allocator__, raw_tracks__, num_output_bones); + } + + virtual ~error_calculation_adapter() override + { + deallocate_type_array(allocator_, output_bone_mapping, num_output_bones); + } + + error_calculation_adapter(const error_calculation_adapter&) = delete; + error_calculation_adapter& operator=(const error_calculation_adapter&) = delete; + + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks_.sample_tracks(sample_time, rounding_policy, track_writer); + } + + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + context_.seek(sample_time, rounding_policy); + context_.decompress_tracks(track_writer); + } + + virtual uint32_t get_output_index(uint32_t track_index) override + { + const track& track_ = raw_tracks_[track_index]; + return track_.get_output_index(); + } + + virtual uint32_t get_parent_index(uint32_t track_index) override + { + const track_qvvf& track_ = track_cast(raw_tracks_[track_index]); + return track_.get_description().parent_index; + } + + virtual float get_shell_distance(uint32_t track_index) override + { + const track_qvvf& track_ = track_cast(raw_tracks_[track_index]); + return track_.get_description().shell_distance; + } + + virtual void remap_output(debug_track_writer& track_writer0, debug_track_writer& track_writer1, debug_track_writer& track_writer_remapped) override + { + // Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if + // the data is available + std::memcpy(track_writer_remapped.tracks_typed.qvvf, track_writer0.tracks_typed.qvvf, sizeof(rtm::qvvf) * track_writer_remapped.num_tracks); + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + track_writer_remapped.tracks_typed.qvvf[bone_index] = track_writer1.tracks_typed.qvvf[output_index]; + } + } + + virtual bool has_additive_base() const override { return !additive_base_tracks_.is_empty(); } + virtual void sample_tracks_base(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + additive_base_tracks_.sample_tracks(sample_time, rounding_policy, track_writer); + } + }; + + error_calculation_adapter adapter(allocator, raw_tracks, context, additive_base_tracks); + + calculate_track_error_args args(adapter); + args.num_samples = raw_tracks.get_num_samples_per_track(); + args.num_tracks = raw_tracks.get_num_tracks(); + args.duration = raw_tracks.get_duration(); + args.sample_rate = raw_tracks.get_sample_rate(); + args.track_type = raw_tracks.get_track_type(); + + // We use the nearest sample to accurately measure the loss that happened, if any but only if all data is loaded + // If we have a database with some data missing, we can't use the nearest samples, we have to interpolate + args.rounding_policy = sample_rounding_policy::nearest; + + const compressed_tracks& tracks = *context.get_compressed_tracks(); + if (tracks.has_database()) + { + // TODO: Check if all the data is loaded, always interpolate for now + args.rounding_policy = sample_rounding_policy::none; + } + + args.error_metric = &error_metric; + + if (!additive_base_tracks.is_empty()) + { + args.base_num_samples = additive_base_tracks.get_num_samples_per_track(); + args.base_duration = additive_base_tracks.get_duration(); + } + + return calculate_transform_track_error(allocator, args); + } + + template, acl_impl::is_decompression_context> + inline track_error calculate_compression_error(iallocator& allocator, decompression_context_type0& context0, decompression_context_type1& context1) + { + using namespace acl_impl; + + ACL_ASSERT(context0.is_initialized(), "Context isn't initialized"); + ACL_ASSERT(context1.is_initialized(), "Context isn't initialized"); + + const compressed_tracks* tracks0 = context0.get_compressed_tracks(); + + if (tracks0->get_track_type() == track_type8::qvvf) + return invalid_track_error(); // Only supports scalar tracks + + struct error_calculation_adapter final : public ierror_calculation_adapter + { + decompression_context_type0& context0_; + decompression_context_type1& context1_; + + error_calculation_adapter(decompression_context_type0& context0__, decompression_context_type1& context1__) + : context0_(context0__) + , context1_(context1__) + { + } + + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + context0_.seek(sample_time, rounding_policy); + context0_.decompress_tracks(track_writer); + } + + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + context1_.seek(sample_time, rounding_policy); + context1_.decompress_tracks(track_writer); + } + }; + + error_calculation_adapter adapter(context0, context1); + + calculate_track_error_args args(adapter); + args.num_samples = tracks0->get_num_samples_per_track(); + args.num_tracks = tracks0->get_num_tracks(); + args.duration = tracks0->get_duration(); + args.sample_rate = tracks0->get_sample_rate(); + args.track_type = tracks0->get_track_type(); + + // We use the nearest sample to accurately measure the loss that happened, if any but only if all data is loaded + // If we have a database with some data missing, we can't use the nearest samples, we have to interpolate + args.rounding_policy = sample_rounding_policy::nearest; + + const compressed_tracks* tracks1 = context1.get_compressed_tracks(); + if (tracks0->has_database() || tracks1->has_database()) + { + // TODO: Check if all the data is loaded, always interpolate for now + args.rounding_policy = sample_rounding_policy::none; + } + + return calculate_scalar_track_error(allocator, args); + } + + inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1) + { + using namespace acl_impl; + + ACL_ASSERT(raw_tracks0.is_valid().empty(), "Raw tracks are invalid"); + ACL_ASSERT(raw_tracks1.is_valid().empty(), "Raw tracks are invalid"); + + if (raw_tracks0.get_track_type() == track_type8::qvvf) + return invalid_track_error(); // Only supports scalar tracks + + struct error_calculation_adapter final : public ierror_calculation_adapter + { + const track_array& raw_tracks0_; + const track_array& raw_tracks1_; + + error_calculation_adapter(const track_array& raw_tracks0__, const track_array& raw_tracks1__) + : raw_tracks0_(raw_tracks0__) + , raw_tracks1_(raw_tracks1__) + { + } + + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks0_.sample_tracks(sample_time, rounding_policy, track_writer); + } + + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks1_.sample_tracks(sample_time, rounding_policy, track_writer); + } + }; + + error_calculation_adapter adapter(raw_tracks0, raw_tracks1); + + calculate_track_error_args args(adapter); + args.num_samples = raw_tracks0.get_num_samples_per_track(); + args.num_tracks = raw_tracks0.get_num_tracks(); + args.duration = raw_tracks0.get_duration(); + args.sample_rate = raw_tracks0.get_sample_rate(); + args.track_type = raw_tracks0.get_track_type(); + + // We use the nearest sample to accurately measure the loss that happened, if any + args.rounding_policy = sample_rounding_policy::nearest; + + return calculate_scalar_track_error(allocator, args); + } + + inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1, const itransform_error_metric& error_metric) + { + using namespace acl_impl; + + ACL_ASSERT(raw_tracks0.is_valid().empty(), "Raw tracks are invalid"); + ACL_ASSERT(raw_tracks1.is_valid().empty(), "Raw tracks are invalid"); + + struct error_calculation_adapter final : public ierror_calculation_adapter + { + const track_array& raw_tracks0_; + const track_array& raw_tracks1_; + + error_calculation_adapter(const track_array& raw_tracks0__, const track_array& raw_tracks1__) + : raw_tracks0_(raw_tracks0__) + , raw_tracks1_(raw_tracks1__) + { + } + + virtual void sample_tracks0(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks0_.sample_tracks(sample_time, rounding_policy, track_writer); + } + + virtual void sample_tracks1(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer) override + { + raw_tracks1_.sample_tracks(sample_time, rounding_policy, track_writer); + } + + virtual uint32_t get_parent_index(uint32_t track_index) override + { + const track_qvvf& track_ = track_cast(raw_tracks0_[track_index]); + return track_.get_description().parent_index; + } + + virtual float get_shell_distance(uint32_t track_index) override + { + const track_qvvf& track_ = track_cast(raw_tracks0_[track_index]); + return track_.get_description().shell_distance; + } + }; + + error_calculation_adapter adapter(raw_tracks0, raw_tracks1); + + calculate_track_error_args args(adapter); + args.num_samples = raw_tracks0.get_num_samples_per_track(); + args.num_tracks = raw_tracks0.get_num_tracks(); + args.duration = raw_tracks0.get_duration(); + args.sample_rate = raw_tracks0.get_sample_rate(); + args.track_type = raw_tracks0.get_track_type(); + + // We use the nearest sample to accurately measure the loss that happened, if any + args.rounding_policy = sample_rounding_policy::nearest; + + if (raw_tracks0.get_track_type() != track_type8::qvvf) + return calculate_scalar_track_error(allocator, args); + + args.error_metric = &error_metric; + + return calculate_transform_track_error(allocator, args); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_list_context.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_list_context.h new file mode 100644 index 00000000..7a1f2966 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_list_context.h @@ -0,0 +1,271 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/bitset.h" +#include "acl/core/track_desc.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/compression/track_array.h" +#include "acl/compression/impl/track_range.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct scalar_bit_rate + { + uint8_t value; + }; + + struct qvv_bit_rate + { + uint8_t rotation; + uint8_t translation; + uint8_t scale; + }; + + union track_bit_rate + { + scalar_bit_rate scalar; + qvv_bit_rate qvv; + + track_bit_rate() : qvv{k_invalid_bit_rate, k_invalid_bit_rate, k_invalid_bit_rate} {} + }; + + struct track_list_context + { + iallocator* allocator; + const track_array* reference_list; + + track_array track_list; + + track_range* range_list; + uint32_t* constant_tracks_bitset; + track_bit_rate* bit_rate_list; + uint32_t* track_output_indices; + + uint32_t num_tracks; + uint32_t num_output_tracks; + uint32_t num_samples; + float sample_rate; + float duration; + + track_list_context() + : allocator(nullptr) + , reference_list(nullptr) + , track_list() + , range_list(nullptr) + , constant_tracks_bitset(nullptr) + , bit_rate_list(nullptr) + , track_output_indices(nullptr) + , num_tracks(0) + , num_output_tracks(0) + , num_samples(0) + , sample_rate(0.0F) + , duration(0.0F) + {} + + ~track_list_context() + { + if (allocator != nullptr) + { + deallocate_type_array(*allocator, range_list, num_tracks); + + const bitset_description bitset_desc = bitset_description::make_from_num_bits(num_tracks); + deallocate_type_array(*allocator, constant_tracks_bitset, bitset_desc.get_size()); + + deallocate_type_array(*allocator, bit_rate_list, num_tracks); + + deallocate_type_array(*allocator, track_output_indices, num_output_tracks); + } + } + + bool is_valid() const { return allocator != nullptr; } + bool is_constant(uint32_t track_index) const { return bitset_test(constant_tracks_bitset, bitset_description::make_from_num_bits(num_tracks), track_index); } + + track_list_context(const track_list_context&) = delete; + track_list_context(track_list_context&&) = delete; + track_list_context& operator=(const track_list_context&) = delete; + track_list_context& operator=(track_list_context&&) = delete; + }; + + // Promote scalar tracks to vector tracks for SIMD alignment and padding + inline track_array copy_and_promote_track_list(iallocator& allocator, const track_array& ref_track_list, bool& out_are_samples_valid) + { + using namespace rtm; + + const uint32_t num_tracks = ref_track_list.get_num_tracks(); + const uint32_t num_samples = ref_track_list.get_num_samples_per_track(); + const float sample_rate = ref_track_list.get_sample_rate(); + bool are_samples_valid = true; + + track_array out_track_list(allocator, num_tracks); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& ref_track = ref_track_list[track_index]; + track& out_track = out_track_list[track_index]; + + switch (ref_track.get_type()) + { + case track_type8::float1f: + { + const track_float1f& typed_ref_track = track_cast(ref_track); + track_vector4f track = track_vector4f::make_reserve(ref_track.get_description(), allocator, num_samples, sample_rate); + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const vector4f sample = vector_load1(&typed_ref_track[sample_index]); + are_samples_valid &= scalar_is_finite(scalarf(vector_get_x(sample))); + track[sample_index] = sample; + } + out_track = std::move(track); + break; + } + case track_type8::float2f: + { + const track_float2f& typed_ref_track = track_cast(ref_track); + track_vector4f track = track_vector4f::make_reserve(ref_track.get_description(), allocator, num_samples, sample_rate); + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const vector4f sample = vector_load2(&typed_ref_track[sample_index]); + are_samples_valid &= vector_is_finite2(sample); + track[sample_index] = sample; + } + out_track = std::move(track); + break; + } + case track_type8::float3f: + { + const track_float3f& typed_ref_track = track_cast(ref_track); + track_vector4f track = track_vector4f::make_reserve(ref_track.get_description(), allocator, num_samples, sample_rate); + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const vector4f sample = vector_load3(&typed_ref_track[sample_index]); + are_samples_valid &= vector_is_finite3(sample); + track[sample_index] = sample; + } + out_track = std::move(track); + break; + } + case track_type8::float4f: + { + const track_float4f& typed_ref_track = track_cast(ref_track); + track_vector4f track = track_vector4f::make_reserve(ref_track.get_description(), allocator, num_samples, sample_rate); + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const vector4f sample = vector_load(&typed_ref_track[sample_index]); + are_samples_valid &= vector_is_finite(sample); + track[sample_index] = sample; + } + out_track = std::move(track); + break; + } + case track_type8::vector4f: + { + const track_vector4f& typed_ref_track = track_cast(ref_track); + track_vector4f track = track_vector4f::make_reserve(ref_track.get_description(), allocator, num_samples, sample_rate); + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const vector4f sample = typed_ref_track[sample_index]; + are_samples_valid &= vector_is_finite(sample); + track[sample_index] = sample; + } + out_track = std::move(track); + break; + } + default: + ACL_ASSERT(false, "Unexpected track type"); + are_samples_valid = false; + break; + } + } + + out_are_samples_valid = are_samples_valid; + return out_track_list; + } + + inline uint32_t* create_output_track_mapping(iallocator& allocator, const track_array& track_list, uint32_t& out_num_output_tracks) + { + const uint32_t num_tracks = track_list.get_num_tracks(); + uint32_t num_output_tracks = num_tracks; + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const uint32_t output_index = track_list[track_index].get_output_index(); + if (output_index == k_invalid_track_index) + num_output_tracks--; // Stripped from the output + } + + uint32_t* output_indices = allocate_type_array(allocator, num_output_tracks); + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const uint32_t output_index = track_list[track_index].get_output_index(); + if (output_index != k_invalid_track_index) + output_indices[output_index] = track_index; + } + + out_num_output_tracks = num_output_tracks; + return output_indices; + } + + inline bool initialize_context(iallocator& allocator, const track_array& track_list, track_list_context& context) + { + ACL_ASSERT(track_list.is_valid().empty(), "Invalid track list"); + ACL_ASSERT(!context.is_valid(), "Context already initialized"); + + bool are_samples_valid = true; + + context.allocator = &allocator; + context.reference_list = &track_list; + context.track_list = copy_and_promote_track_list(allocator, track_list, are_samples_valid); + context.range_list = nullptr; + context.constant_tracks_bitset = nullptr; + context.track_output_indices = nullptr; + context.num_tracks = track_list.get_num_tracks(); + context.num_output_tracks = 0; + context.num_samples = track_list.get_num_samples_per_track(); + context.sample_rate = track_list.get_sample_rate(); + context.duration = track_list.get_duration(); + + context.track_output_indices = create_output_track_mapping(allocator, track_list, context.num_output_tracks); + + return are_samples_valid; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_range.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_range.h new file mode 100644 index 00000000..d3cf0152 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_range.h @@ -0,0 +1,114 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/track_types.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + class scalarf_range + { + public: + static constexpr track_category8 type = track_category8::scalarf; + + scalarf_range() : m_min(), m_extent() {} + scalarf_range(rtm::vector4f_arg0 min, rtm::vector4f_arg1 extent) : m_min(min), m_extent(extent) {} + + static scalarf_range RTM_SIMD_CALL from_min_max(rtm::vector4f_arg0 min, rtm::vector4f_arg1 max) { return scalarf_range(min, rtm::vector_sub(max, min)); } + static scalarf_range RTM_SIMD_CALL from_min_extent(rtm::vector4f_arg0 min, rtm::vector4f_arg1 extent) { return scalarf_range(min, extent); } + + rtm::vector4f RTM_SIMD_CALL get_min() const { return m_min; } + rtm::vector4f RTM_SIMD_CALL get_max() const { return rtm::vector_add(m_min, m_extent); } + + rtm::vector4f RTM_SIMD_CALL get_center() const { return rtm::vector_add(m_min, rtm::vector_mul(m_extent, 0.5F)); } + rtm::vector4f RTM_SIMD_CALL get_extent() const { return m_extent; } + + bool is_constant(float threshold) const { return rtm::vector_all_less_than(rtm::vector_abs(m_extent), rtm::vector_set(threshold)); } + + private: + rtm::vector4f m_min; + rtm::vector4f m_extent; + }; + + struct track_range + { + track_range() : range(), category(track_category8::scalarf) {} + explicit track_range(const scalarf_range& range_) : range(range_), category(track_category8::scalarf) {} + + bool is_constant(float threshold) const + { + switch (category) + { + case track_category8::scalarf: return range.scalarf.is_constant(threshold); + default: + ACL_ASSERT(false, "Invalid track category"); + return false; + } + } + + union range_union + { + scalarf_range scalarf; + // TODO: Add qvv range and scalard/i/q ranges + + range_union() : scalarf(scalarf_range::from_min_extent(rtm::vector_zero(), rtm::vector_zero())) {} + explicit range_union(const scalarf_range& range) : scalarf(range) {} + }; + + range_union range; + track_category8 category; + + uint8_t padding[15]; + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_range_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_range_impl.h new file mode 100644 index 00000000..190346e7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_range_impl.h @@ -0,0 +1,91 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/compression/impl/track_list_context.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline scalarf_range extract_scalarf_range(const track& track) + { + using namespace rtm; + + vector4f min = rtm::vector_set(1e10F); + vector4f max = rtm::vector_set(-1e10F); + + const uint32_t num_samples = track.get_num_samples(); + const track_vector4f& typed_track = track_cast(track); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const vector4f sample = typed_track[sample_index]; + + min = vector_min(min, sample); + max = vector_max(max, sample); + } + + return scalarf_range::from_min_max(min, max); + } + + inline void extract_track_ranges(track_list_context& context) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + context.range_list = allocate_type_array(*context.allocator, context.num_tracks); + + for (uint32_t track_index = 0; track_index < context.num_tracks; ++track_index) + { + const track& track = context.track_list[track_index]; + + switch (track.get_category()) + { + case track_category8::scalarf: + context.range_list[track_index] = track_range(extract_scalarf_range(track)); + break; + default: + ACL_ASSERT(false, "Invalid track category"); + break; + } + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_stream.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_stream.h new file mode 100644 index 00000000..427ed609 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/track_stream.h @@ -0,0 +1,366 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/core/utils.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/math/quat_packing.h" +#include "acl/math/vector4_packing.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + class track_stream + { + public: + uint8_t* get_raw_sample_ptr(uint32_t sample_index) + { + ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples); + uint32_t offset = sample_index * m_sample_size; + return m_samples + offset; + } + + const uint8_t* get_raw_sample_ptr(uint32_t sample_index) const + { + ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples); + uint32_t offset = sample_index * m_sample_size; + return m_samples + offset; + } + + template + sample_type RTM_SIMD_CALL get_raw_sample(uint32_t sample_index) const + { + const uint8_t* ptr = get_raw_sample_ptr(sample_index); + return *safe_ptr_cast(ptr); + } + +#if defined(RTM_NO_INTRINSICS) + template + void RTM_SIMD_CALL set_raw_sample(uint32_t sample_index, const sample_type& sample) +#else + template + void RTM_SIMD_CALL set_raw_sample(uint32_t sample_index, sample_type sample) +#endif + { + ACL_ASSERT(m_sample_size == sizeof(sample_type), "Unexpected sample size. %u != %zu", m_sample_size, sizeof(sample_type)); + uint8_t* ptr = get_raw_sample_ptr(sample_index); + *safe_ptr_cast(ptr) = sample; + } + + uint32_t get_num_samples() const { return m_num_samples; } + uint32_t get_sample_size() const { return m_sample_size; } + float get_sample_rate() const { return m_sample_rate; } + animation_track_type8 get_track_type() const { return m_type; } + uint8_t get_bit_rate() const { return m_bit_rate; } + bool is_bit_rate_variable() const { return m_bit_rate != k_invalid_bit_rate; } + float get_duration() const { return calculate_duration(m_num_samples, m_sample_rate); } + float get_finite_duration() const { return calculate_finite_duration(m_num_samples, m_sample_rate); } + + uint32_t get_packed_sample_size() const + { + if (m_type == animation_track_type8::rotation) + return get_packed_rotation_size(m_format.rotation); + else + return get_packed_vector_size(m_format.vector); + } + + protected: + track_stream(animation_track_type8 type, track_format8 format) noexcept : m_allocator(nullptr), m_samples(nullptr), m_num_samples(0), m_sample_size(0), m_sample_rate(0.0F), m_type(type), m_format(format), m_bit_rate(0) {} + + track_stream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, animation_track_type8 type, track_format8 format, uint8_t bit_rate) + : m_allocator(&allocator) + , m_samples(reinterpret_cast(allocator.allocate(sample_size * size_t(num_samples) + k_padding, 16))) + , m_num_samples(num_samples) + , m_sample_size(sample_size) + , m_sample_rate(sample_rate) + , m_type(type) + , m_format(format) + , m_bit_rate(bit_rate) + {} + + track_stream(const track_stream&) = delete; + track_stream(track_stream&& other) noexcept + : m_allocator(other.m_allocator) + , m_samples(other.m_samples) + , m_num_samples(other.m_num_samples) + , m_sample_size(other.m_sample_size) + , m_sample_rate(other.m_sample_rate) + , m_type(other.m_type) + , m_format(other.m_format) + , m_bit_rate(other.m_bit_rate) + { + new(&other) track_stream(other.m_type, other.m_format); + } + + ~track_stream() + { + if (m_allocator != nullptr) + m_allocator->deallocate(m_samples, m_sample_size * size_t(m_num_samples) + k_padding); + } + + track_stream& operator=(const track_stream&) = delete; + track_stream& operator=(track_stream&& rhs) noexcept + { + std::swap(m_allocator, rhs.m_allocator); + std::swap(m_samples, rhs.m_samples); + std::swap(m_num_samples, rhs.m_num_samples); + std::swap(m_sample_size, rhs.m_sample_size); + std::swap(m_sample_rate, rhs.m_sample_rate); + std::swap(m_type, rhs.m_type); + std::swap(m_format, rhs.m_format); + std::swap(m_bit_rate, rhs.m_bit_rate); + return *this; + } + + void duplicate_impl(track_stream& copy) const + { + ACL_ASSERT(copy.m_type == m_type, "Attempting to duplicate streams with incompatible types!"); + if (m_allocator != nullptr) + { + copy.m_allocator = m_allocator; + copy.m_samples = reinterpret_cast(m_allocator->allocate(m_sample_size * size_t(m_num_samples) + k_padding, 16)); + copy.m_num_samples = m_num_samples; + copy.m_sample_size = m_sample_size; + copy.m_sample_rate = m_sample_rate; + copy.m_format = m_format; + copy.m_bit_rate = m_bit_rate; + + std::memcpy(copy.m_samples, m_samples, (size_t)m_sample_size * m_num_samples); + } + } + + // In order to guarantee the safety of unaligned SIMD loads of every byte, we add some padding + static constexpr uint32_t k_padding = 15; + + iallocator* m_allocator; + uint8_t* m_samples; + uint32_t m_num_samples; + uint32_t m_sample_size; + float m_sample_rate; + + animation_track_type8 m_type; + track_format8 m_format; + uint8_t m_bit_rate; + }; + + class rotation_track_stream final : public track_stream + { + public: + rotation_track_stream() noexcept : track_stream(animation_track_type8::rotation, track_format8(rotation_format8::quatf_full)) {} + rotation_track_stream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, rotation_format8 format, uint8_t bit_rate = k_invalid_bit_rate) + : track_stream(allocator, num_samples, sample_size, sample_rate, animation_track_type8::rotation, track_format8(format), bit_rate) + {} + rotation_track_stream(const rotation_track_stream&) = delete; + rotation_track_stream(rotation_track_stream&& other) noexcept + : track_stream(static_cast(other)) + {} + ~rotation_track_stream() = default; + + rotation_track_stream& operator=(const rotation_track_stream&) = delete; + rotation_track_stream& operator=(rotation_track_stream&& rhs) noexcept + { + track_stream::operator=(static_cast(rhs)); + return *this; + } + + rotation_track_stream duplicate() const + { + rotation_track_stream copy; + duplicate_impl(copy); + return copy; + } + + rotation_format8 get_rotation_format() const { return m_format.rotation; } + }; + + class translation_track_stream final : public track_stream + { + public: + translation_track_stream() noexcept : track_stream(animation_track_type8::translation, track_format8(vector_format8::vector3f_full)) {} + translation_track_stream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, vector_format8 format, uint8_t bit_rate = k_invalid_bit_rate) + : track_stream(allocator, num_samples, sample_size, sample_rate, animation_track_type8::translation, track_format8(format), bit_rate) + {} + translation_track_stream(const translation_track_stream&) = delete; + translation_track_stream(translation_track_stream&& other) noexcept + : track_stream(static_cast(other)) + {} + ~translation_track_stream() = default; + + translation_track_stream& operator=(const translation_track_stream&) = delete; + translation_track_stream& operator=(translation_track_stream&& rhs) noexcept + { + track_stream::operator=(static_cast(rhs)); + return *this; + } + + translation_track_stream duplicate() const + { + translation_track_stream copy; + duplicate_impl(copy); + return copy; + } + + vector_format8 get_vector_format() const { return m_format.vector; } + }; + + class scale_track_stream final : public track_stream + { + public: + scale_track_stream() noexcept : track_stream(animation_track_type8::scale, track_format8(vector_format8::vector3f_full)) {} + scale_track_stream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, vector_format8 format, uint8_t bit_rate = k_invalid_bit_rate) + : track_stream(allocator, num_samples, sample_size, sample_rate, animation_track_type8::scale, track_format8(format), bit_rate) + {} + scale_track_stream(const scale_track_stream&) = delete; + scale_track_stream(scale_track_stream&& other) noexcept + : track_stream(static_cast(other)) + {} + ~scale_track_stream() = default; + + scale_track_stream& operator=(const scale_track_stream&) = delete; + scale_track_stream& operator=(scale_track_stream&& rhs) noexcept + { + track_stream::operator=(static_cast(rhs)); + return *this; + } + + scale_track_stream duplicate() const + { + scale_track_stream copy; + duplicate_impl(copy); + return copy; + } + + vector_format8 get_vector_format() const { return m_format.vector; } + }; + + // For a rotation track, the extent only tells us if the track is constant or not + // since the min/max we maintain aren't valid rotations. + // Similarly, the center isn't a valid rotation and is meaningless. + class track_stream_range + { + public: + static track_stream_range RTM_SIMD_CALL from_min_max(rtm::vector4f_arg0 min, rtm::vector4f_arg1 max) + { + return track_stream_range(min, rtm::vector_sub(max, min)); + } + + static track_stream_range RTM_SIMD_CALL from_min_extent(rtm::vector4f_arg0 min, rtm::vector4f_arg1 extent) + { + return track_stream_range(min, extent); + } + + track_stream_range() + : m_min(rtm::vector_zero()) + , m_extent(rtm::vector_zero()) + {} + + rtm::vector4f RTM_SIMD_CALL get_min() const { return m_min; } + rtm::vector4f RTM_SIMD_CALL get_max() const { return rtm::vector_add(m_min, m_extent); } + + rtm::vector4f RTM_SIMD_CALL get_center() const { return rtm::vector_add(m_min, rtm::vector_mul(m_extent, 0.5F)); } + rtm::vector4f RTM_SIMD_CALL get_extent() const { return m_extent; } + + bool is_constant(float threshold) const { return rtm::vector_all_less_than(rtm::vector_abs(m_extent), rtm::vector_set(threshold)); } + + private: + track_stream_range(rtm::vector4f_arg0 min, rtm::vector4f_arg1 extent) + : m_min(min) + , m_extent(extent) + {} + + rtm::vector4f m_min; + rtm::vector4f m_extent; + }; + + struct transform_range + { + track_stream_range rotation; + track_stream_range translation; + track_stream_range scale; + }; + + struct segment_context; + + struct transform_streams + { + segment_context* segment = nullptr; + uint32_t bone_index = k_invalid_track_index; + uint32_t parent_bone_index = k_invalid_track_index; + uint32_t output_index = k_invalid_track_index; + + rotation_track_stream rotations; + translation_track_stream translations; + scale_track_stream scales; + + bool is_rotation_constant = false; + bool is_rotation_default = false; + bool is_translation_constant = false; + bool is_translation_default = false; + bool is_scale_constant = false; + bool is_scale_default = false; + + bool is_stripped_from_output() const { return output_index == k_invalid_track_index; } + + transform_streams duplicate() const + { + transform_streams copy; + copy.segment = segment; + copy.bone_index = bone_index; + copy.parent_bone_index = parent_bone_index; + copy.output_index = output_index; + copy.rotations = rotations.duplicate(); + copy.translations = translations.duplicate(); + copy.scales = scales.duplicate(); + copy.is_rotation_constant = is_rotation_constant; + copy.is_rotation_default = is_rotation_default; + copy.is_translation_constant = is_translation_constant; + copy.is_translation_default = is_translation_default; + copy.is_scale_constant = is_scale_constant; + copy.is_scale_default = is_scale_default; + return copy; + } + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/transform_bit_rate_permutations.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/transform_bit_rate_permutations.h new file mode 100644 index 00000000..18d44042 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/transform_bit_rate_permutations.h @@ -0,0 +1,7274 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +// This file is generated with the python script located at: ./tools/calc_local_bit_rates.py + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + constexpr uint8_t k_local_bit_rate_permutations_no_scale[361][2] = + { + { 0, 0 }, // 0 bits per transform + { 0, 1 }, // 9 bits per transform + { 1, 0 }, // 9 bits per transform + { 0, 2 }, // 12 bits per transform + { 2, 0 }, // 12 bits per transform + { 0, 3 }, // 15 bits per transform + { 3, 0 }, // 15 bits per transform + { 0, 4 }, // 18 bits per transform + { 1, 1 }, // 18 bits per transform + { 4, 0 }, // 18 bits per transform + { 0, 5 }, // 21 bits per transform + { 1, 2 }, // 21 bits per transform + { 2, 1 }, // 21 bits per transform + { 5, 0 }, // 21 bits per transform + { 0, 6 }, // 24 bits per transform + { 1, 3 }, // 24 bits per transform + { 2, 2 }, // 24 bits per transform + { 3, 1 }, // 24 bits per transform + { 6, 0 }, // 24 bits per transform + { 0, 7 }, // 27 bits per transform + { 1, 4 }, // 27 bits per transform + { 2, 3 }, // 27 bits per transform + { 3, 2 }, // 27 bits per transform + { 4, 1 }, // 27 bits per transform + { 7, 0 }, // 27 bits per transform + { 0, 8 }, // 30 bits per transform + { 1, 5 }, // 30 bits per transform + { 2, 4 }, // 30 bits per transform + { 3, 3 }, // 30 bits per transform + { 4, 2 }, // 30 bits per transform + { 5, 1 }, // 30 bits per transform + { 8, 0 }, // 30 bits per transform + { 0, 9 }, // 33 bits per transform + { 1, 6 }, // 33 bits per transform + { 2, 5 }, // 33 bits per transform + { 3, 4 }, // 33 bits per transform + { 4, 3 }, // 33 bits per transform + { 5, 2 }, // 33 bits per transform + { 6, 1 }, // 33 bits per transform + { 9, 0 }, // 33 bits per transform + { 0, 10 }, // 36 bits per transform + { 1, 7 }, // 36 bits per transform + { 2, 6 }, // 36 bits per transform + { 3, 5 }, // 36 bits per transform + { 4, 4 }, // 36 bits per transform + { 5, 3 }, // 36 bits per transform + { 6, 2 }, // 36 bits per transform + { 7, 1 }, // 36 bits per transform + { 10, 0 }, // 36 bits per transform + { 0, 11 }, // 39 bits per transform + { 1, 8 }, // 39 bits per transform + { 2, 7 }, // 39 bits per transform + { 3, 6 }, // 39 bits per transform + { 4, 5 }, // 39 bits per transform + { 5, 4 }, // 39 bits per transform + { 6, 3 }, // 39 bits per transform + { 7, 2 }, // 39 bits per transform + { 8, 1 }, // 39 bits per transform + { 11, 0 }, // 39 bits per transform + { 0, 12 }, // 42 bits per transform + { 1, 9 }, // 42 bits per transform + { 2, 8 }, // 42 bits per transform + { 3, 7 }, // 42 bits per transform + { 4, 6 }, // 42 bits per transform + { 5, 5 }, // 42 bits per transform + { 6, 4 }, // 42 bits per transform + { 7, 3 }, // 42 bits per transform + { 8, 2 }, // 42 bits per transform + { 9, 1 }, // 42 bits per transform + { 12, 0 }, // 42 bits per transform + { 0, 13 }, // 45 bits per transform + { 1, 10 }, // 45 bits per transform + { 2, 9 }, // 45 bits per transform + { 3, 8 }, // 45 bits per transform + { 4, 7 }, // 45 bits per transform + { 5, 6 }, // 45 bits per transform + { 6, 5 }, // 45 bits per transform + { 7, 4 }, // 45 bits per transform + { 8, 3 }, // 45 bits per transform + { 9, 2 }, // 45 bits per transform + { 10, 1 }, // 45 bits per transform + { 13, 0 }, // 45 bits per transform + { 0, 14 }, // 48 bits per transform + { 1, 11 }, // 48 bits per transform + { 2, 10 }, // 48 bits per transform + { 3, 9 }, // 48 bits per transform + { 4, 8 }, // 48 bits per transform + { 5, 7 }, // 48 bits per transform + { 6, 6 }, // 48 bits per transform + { 7, 5 }, // 48 bits per transform + { 8, 4 }, // 48 bits per transform + { 9, 3 }, // 48 bits per transform + { 10, 2 }, // 48 bits per transform + { 11, 1 }, // 48 bits per transform + { 14, 0 }, // 48 bits per transform + { 0, 15 }, // 51 bits per transform + { 1, 12 }, // 51 bits per transform + { 2, 11 }, // 51 bits per transform + { 3, 10 }, // 51 bits per transform + { 4, 9 }, // 51 bits per transform + { 5, 8 }, // 51 bits per transform + { 6, 7 }, // 51 bits per transform + { 7, 6 }, // 51 bits per transform + { 8, 5 }, // 51 bits per transform + { 9, 4 }, // 51 bits per transform + { 10, 3 }, // 51 bits per transform + { 11, 2 }, // 51 bits per transform + { 12, 1 }, // 51 bits per transform + { 15, 0 }, // 51 bits per transform + { 0, 16 }, // 54 bits per transform + { 1, 13 }, // 54 bits per transform + { 2, 12 }, // 54 bits per transform + { 3, 11 }, // 54 bits per transform + { 4, 10 }, // 54 bits per transform + { 5, 9 }, // 54 bits per transform + { 6, 8 }, // 54 bits per transform + { 7, 7 }, // 54 bits per transform + { 8, 6 }, // 54 bits per transform + { 9, 5 }, // 54 bits per transform + { 10, 4 }, // 54 bits per transform + { 11, 3 }, // 54 bits per transform + { 12, 2 }, // 54 bits per transform + { 13, 1 }, // 54 bits per transform + { 16, 0 }, // 54 bits per transform + { 0, 17 }, // 57 bits per transform + { 1, 14 }, // 57 bits per transform + { 2, 13 }, // 57 bits per transform + { 3, 12 }, // 57 bits per transform + { 4, 11 }, // 57 bits per transform + { 5, 10 }, // 57 bits per transform + { 6, 9 }, // 57 bits per transform + { 7, 8 }, // 57 bits per transform + { 8, 7 }, // 57 bits per transform + { 9, 6 }, // 57 bits per transform + { 10, 5 }, // 57 bits per transform + { 11, 4 }, // 57 bits per transform + { 12, 3 }, // 57 bits per transform + { 13, 2 }, // 57 bits per transform + { 14, 1 }, // 57 bits per transform + { 17, 0 }, // 57 bits per transform + { 1, 15 }, // 60 bits per transform + { 2, 14 }, // 60 bits per transform + { 3, 13 }, // 60 bits per transform + { 4, 12 }, // 60 bits per transform + { 5, 11 }, // 60 bits per transform + { 6, 10 }, // 60 bits per transform + { 7, 9 }, // 60 bits per transform + { 8, 8 }, // 60 bits per transform + { 9, 7 }, // 60 bits per transform + { 10, 6 }, // 60 bits per transform + { 11, 5 }, // 60 bits per transform + { 12, 4 }, // 60 bits per transform + { 13, 3 }, // 60 bits per transform + { 14, 2 }, // 60 bits per transform + { 15, 1 }, // 60 bits per transform + { 1, 16 }, // 63 bits per transform + { 2, 15 }, // 63 bits per transform + { 3, 14 }, // 63 bits per transform + { 4, 13 }, // 63 bits per transform + { 5, 12 }, // 63 bits per transform + { 6, 11 }, // 63 bits per transform + { 7, 10 }, // 63 bits per transform + { 8, 9 }, // 63 bits per transform + { 9, 8 }, // 63 bits per transform + { 10, 7 }, // 63 bits per transform + { 11, 6 }, // 63 bits per transform + { 12, 5 }, // 63 bits per transform + { 13, 4 }, // 63 bits per transform + { 14, 3 }, // 63 bits per transform + { 15, 2 }, // 63 bits per transform + { 16, 1 }, // 63 bits per transform + { 1, 17 }, // 66 bits per transform + { 2, 16 }, // 66 bits per transform + { 3, 15 }, // 66 bits per transform + { 4, 14 }, // 66 bits per transform + { 5, 13 }, // 66 bits per transform + { 6, 12 }, // 66 bits per transform + { 7, 11 }, // 66 bits per transform + { 8, 10 }, // 66 bits per transform + { 9, 9 }, // 66 bits per transform + { 10, 8 }, // 66 bits per transform + { 11, 7 }, // 66 bits per transform + { 12, 6 }, // 66 bits per transform + { 13, 5 }, // 66 bits per transform + { 14, 4 }, // 66 bits per transform + { 15, 3 }, // 66 bits per transform + { 16, 2 }, // 66 bits per transform + { 17, 1 }, // 66 bits per transform + { 2, 17 }, // 69 bits per transform + { 3, 16 }, // 69 bits per transform + { 4, 15 }, // 69 bits per transform + { 5, 14 }, // 69 bits per transform + { 6, 13 }, // 69 bits per transform + { 7, 12 }, // 69 bits per transform + { 8, 11 }, // 69 bits per transform + { 9, 10 }, // 69 bits per transform + { 10, 9 }, // 69 bits per transform + { 11, 8 }, // 69 bits per transform + { 12, 7 }, // 69 bits per transform + { 13, 6 }, // 69 bits per transform + { 14, 5 }, // 69 bits per transform + { 15, 4 }, // 69 bits per transform + { 16, 3 }, // 69 bits per transform + { 17, 2 }, // 69 bits per transform + { 3, 17 }, // 72 bits per transform + { 4, 16 }, // 72 bits per transform + { 5, 15 }, // 72 bits per transform + { 6, 14 }, // 72 bits per transform + { 7, 13 }, // 72 bits per transform + { 8, 12 }, // 72 bits per transform + { 9, 11 }, // 72 bits per transform + { 10, 10 }, // 72 bits per transform + { 11, 9 }, // 72 bits per transform + { 12, 8 }, // 72 bits per transform + { 13, 7 }, // 72 bits per transform + { 14, 6 }, // 72 bits per transform + { 15, 5 }, // 72 bits per transform + { 16, 4 }, // 72 bits per transform + { 17, 3 }, // 72 bits per transform + { 4, 17 }, // 75 bits per transform + { 5, 16 }, // 75 bits per transform + { 6, 15 }, // 75 bits per transform + { 7, 14 }, // 75 bits per transform + { 8, 13 }, // 75 bits per transform + { 9, 12 }, // 75 bits per transform + { 10, 11 }, // 75 bits per transform + { 11, 10 }, // 75 bits per transform + { 12, 9 }, // 75 bits per transform + { 13, 8 }, // 75 bits per transform + { 14, 7 }, // 75 bits per transform + { 15, 6 }, // 75 bits per transform + { 16, 5 }, // 75 bits per transform + { 17, 4 }, // 75 bits per transform + { 5, 17 }, // 78 bits per transform + { 6, 16 }, // 78 bits per transform + { 7, 15 }, // 78 bits per transform + { 8, 14 }, // 78 bits per transform + { 9, 13 }, // 78 bits per transform + { 10, 12 }, // 78 bits per transform + { 11, 11 }, // 78 bits per transform + { 12, 10 }, // 78 bits per transform + { 13, 9 }, // 78 bits per transform + { 14, 8 }, // 78 bits per transform + { 15, 7 }, // 78 bits per transform + { 16, 6 }, // 78 bits per transform + { 17, 5 }, // 78 bits per transform + { 6, 17 }, // 81 bits per transform + { 7, 16 }, // 81 bits per transform + { 8, 15 }, // 81 bits per transform + { 9, 14 }, // 81 bits per transform + { 10, 13 }, // 81 bits per transform + { 11, 12 }, // 81 bits per transform + { 12, 11 }, // 81 bits per transform + { 13, 10 }, // 81 bits per transform + { 14, 9 }, // 81 bits per transform + { 15, 8 }, // 81 bits per transform + { 16, 7 }, // 81 bits per transform + { 17, 6 }, // 81 bits per transform + { 7, 17 }, // 84 bits per transform + { 8, 16 }, // 84 bits per transform + { 9, 15 }, // 84 bits per transform + { 10, 14 }, // 84 bits per transform + { 11, 13 }, // 84 bits per transform + { 12, 12 }, // 84 bits per transform + { 13, 11 }, // 84 bits per transform + { 14, 10 }, // 84 bits per transform + { 15, 9 }, // 84 bits per transform + { 16, 8 }, // 84 bits per transform + { 17, 7 }, // 84 bits per transform + { 8, 17 }, // 87 bits per transform + { 9, 16 }, // 87 bits per transform + { 10, 15 }, // 87 bits per transform + { 11, 14 }, // 87 bits per transform + { 12, 13 }, // 87 bits per transform + { 13, 12 }, // 87 bits per transform + { 14, 11 }, // 87 bits per transform + { 15, 10 }, // 87 bits per transform + { 16, 9 }, // 87 bits per transform + { 17, 8 }, // 87 bits per transform + { 9, 17 }, // 90 bits per transform + { 10, 16 }, // 90 bits per transform + { 11, 15 }, // 90 bits per transform + { 12, 14 }, // 90 bits per transform + { 13, 13 }, // 90 bits per transform + { 14, 12 }, // 90 bits per transform + { 15, 11 }, // 90 bits per transform + { 16, 10 }, // 90 bits per transform + { 17, 9 }, // 90 bits per transform + { 10, 17 }, // 93 bits per transform + { 11, 16 }, // 93 bits per transform + { 12, 15 }, // 93 bits per transform + { 13, 14 }, // 93 bits per transform + { 14, 13 }, // 93 bits per transform + { 15, 12 }, // 93 bits per transform + { 16, 11 }, // 93 bits per transform + { 17, 10 }, // 93 bits per transform + { 0, 18 }, // 96 bits per transform + { 11, 17 }, // 96 bits per transform + { 12, 16 }, // 96 bits per transform + { 13, 15 }, // 96 bits per transform + { 14, 14 }, // 96 bits per transform + { 15, 13 }, // 96 bits per transform + { 16, 12 }, // 96 bits per transform + { 17, 11 }, // 96 bits per transform + { 18, 0 }, // 96 bits per transform + { 12, 17 }, // 99 bits per transform + { 13, 16 }, // 99 bits per transform + { 14, 15 }, // 99 bits per transform + { 15, 14 }, // 99 bits per transform + { 16, 13 }, // 99 bits per transform + { 17, 12 }, // 99 bits per transform + { 13, 17 }, // 102 bits per transform + { 14, 16 }, // 102 bits per transform + { 15, 15 }, // 102 bits per transform + { 16, 14 }, // 102 bits per transform + { 17, 13 }, // 102 bits per transform + { 1, 18 }, // 105 bits per transform + { 14, 17 }, // 105 bits per transform + { 15, 16 }, // 105 bits per transform + { 16, 15 }, // 105 bits per transform + { 17, 14 }, // 105 bits per transform + { 18, 1 }, // 105 bits per transform + { 2, 18 }, // 108 bits per transform + { 15, 17 }, // 108 bits per transform + { 16, 16 }, // 108 bits per transform + { 17, 15 }, // 108 bits per transform + { 18, 2 }, // 108 bits per transform + { 3, 18 }, // 111 bits per transform + { 16, 17 }, // 111 bits per transform + { 17, 16 }, // 111 bits per transform + { 18, 3 }, // 111 bits per transform + { 4, 18 }, // 114 bits per transform + { 17, 17 }, // 114 bits per transform + { 18, 4 }, // 114 bits per transform + { 5, 18 }, // 117 bits per transform + { 18, 5 }, // 117 bits per transform + { 6, 18 }, // 120 bits per transform + { 18, 6 }, // 120 bits per transform + { 7, 18 }, // 123 bits per transform + { 18, 7 }, // 123 bits per transform + { 8, 18 }, // 126 bits per transform + { 18, 8 }, // 126 bits per transform + { 9, 18 }, // 129 bits per transform + { 18, 9 }, // 129 bits per transform + { 10, 18 }, // 132 bits per transform + { 18, 10 }, // 132 bits per transform + { 11, 18 }, // 135 bits per transform + { 18, 11 }, // 135 bits per transform + { 12, 18 }, // 138 bits per transform + { 18, 12 }, // 138 bits per transform + { 13, 18 }, // 141 bits per transform + { 18, 13 }, // 141 bits per transform + { 14, 18 }, // 144 bits per transform + { 18, 14 }, // 144 bits per transform + { 15, 18 }, // 147 bits per transform + { 18, 15 }, // 147 bits per transform + { 16, 18 }, // 150 bits per transform + { 18, 16 }, // 150 bits per transform + { 17, 18 }, // 153 bits per transform + { 18, 17 }, // 153 bits per transform + { 18, 18 }, // 192 bits per transform + }; + + constexpr uint8_t k_local_bit_rate_permutations[6859][3] = + { + { 0, 0, 0 }, // 0 bits per transform + { 0, 0, 1 }, // 9 bits per transform + { 0, 1, 0 }, // 9 bits per transform + { 1, 0, 0 }, // 9 bits per transform + { 0, 0, 2 }, // 12 bits per transform + { 0, 2, 0 }, // 12 bits per transform + { 2, 0, 0 }, // 12 bits per transform + { 0, 0, 3 }, // 15 bits per transform + { 0, 3, 0 }, // 15 bits per transform + { 3, 0, 0 }, // 15 bits per transform + { 0, 0, 4 }, // 18 bits per transform + { 0, 1, 1 }, // 18 bits per transform + { 0, 4, 0 }, // 18 bits per transform + { 1, 0, 1 }, // 18 bits per transform + { 1, 1, 0 }, // 18 bits per transform + { 4, 0, 0 }, // 18 bits per transform + { 0, 0, 5 }, // 21 bits per transform + { 0, 1, 2 }, // 21 bits per transform + { 0, 2, 1 }, // 21 bits per transform + { 0, 5, 0 }, // 21 bits per transform + { 1, 0, 2 }, // 21 bits per transform + { 1, 2, 0 }, // 21 bits per transform + { 2, 0, 1 }, // 21 bits per transform + { 2, 1, 0 }, // 21 bits per transform + { 5, 0, 0 }, // 21 bits per transform + { 0, 0, 6 }, // 24 bits per transform + { 0, 1, 3 }, // 24 bits per transform + { 0, 2, 2 }, // 24 bits per transform + { 0, 3, 1 }, // 24 bits per transform + { 0, 6, 0 }, // 24 bits per transform + { 1, 0, 3 }, // 24 bits per transform + { 1, 3, 0 }, // 24 bits per transform + { 2, 0, 2 }, // 24 bits per transform + { 2, 2, 0 }, // 24 bits per transform + { 3, 0, 1 }, // 24 bits per transform + { 3, 1, 0 }, // 24 bits per transform + { 6, 0, 0 }, // 24 bits per transform + { 0, 0, 7 }, // 27 bits per transform + { 0, 1, 4 }, // 27 bits per transform + { 0, 2, 3 }, // 27 bits per transform + { 0, 3, 2 }, // 27 bits per transform + { 0, 4, 1 }, // 27 bits per transform + { 0, 7, 0 }, // 27 bits per transform + { 1, 0, 4 }, // 27 bits per transform + { 1, 1, 1 }, // 27 bits per transform + { 1, 4, 0 }, // 27 bits per transform + { 2, 0, 3 }, // 27 bits per transform + { 2, 3, 0 }, // 27 bits per transform + { 3, 0, 2 }, // 27 bits per transform + { 3, 2, 0 }, // 27 bits per transform + { 4, 0, 1 }, // 27 bits per transform + { 4, 1, 0 }, // 27 bits per transform + { 7, 0, 0 }, // 27 bits per transform + { 0, 0, 8 }, // 30 bits per transform + { 0, 1, 5 }, // 30 bits per transform + { 0, 2, 4 }, // 30 bits per transform + { 0, 3, 3 }, // 30 bits per transform + { 0, 4, 2 }, // 30 bits per transform + { 0, 5, 1 }, // 30 bits per transform + { 0, 8, 0 }, // 30 bits per transform + { 1, 0, 5 }, // 30 bits per transform + { 1, 1, 2 }, // 30 bits per transform + { 1, 2, 1 }, // 30 bits per transform + { 1, 5, 0 }, // 30 bits per transform + { 2, 0, 4 }, // 30 bits per transform + { 2, 1, 1 }, // 30 bits per transform + { 2, 4, 0 }, // 30 bits per transform + { 3, 0, 3 }, // 30 bits per transform + { 3, 3, 0 }, // 30 bits per transform + { 4, 0, 2 }, // 30 bits per transform + { 4, 2, 0 }, // 30 bits per transform + { 5, 0, 1 }, // 30 bits per transform + { 5, 1, 0 }, // 30 bits per transform + { 8, 0, 0 }, // 30 bits per transform + { 0, 0, 9 }, // 33 bits per transform + { 0, 1, 6 }, // 33 bits per transform + { 0, 2, 5 }, // 33 bits per transform + { 0, 3, 4 }, // 33 bits per transform + { 0, 4, 3 }, // 33 bits per transform + { 0, 5, 2 }, // 33 bits per transform + { 0, 6, 1 }, // 33 bits per transform + { 0, 9, 0 }, // 33 bits per transform + { 1, 0, 6 }, // 33 bits per transform + { 1, 1, 3 }, // 33 bits per transform + { 1, 2, 2 }, // 33 bits per transform + { 1, 3, 1 }, // 33 bits per transform + { 1, 6, 0 }, // 33 bits per transform + { 2, 0, 5 }, // 33 bits per transform + { 2, 1, 2 }, // 33 bits per transform + { 2, 2, 1 }, // 33 bits per transform + { 2, 5, 0 }, // 33 bits per transform + { 3, 0, 4 }, // 33 bits per transform + { 3, 1, 1 }, // 33 bits per transform + { 3, 4, 0 }, // 33 bits per transform + { 4, 0, 3 }, // 33 bits per transform + { 4, 3, 0 }, // 33 bits per transform + { 5, 0, 2 }, // 33 bits per transform + { 5, 2, 0 }, // 33 bits per transform + { 6, 0, 1 }, // 33 bits per transform + { 6, 1, 0 }, // 33 bits per transform + { 9, 0, 0 }, // 33 bits per transform + { 0, 0, 10 }, // 36 bits per transform + { 0, 1, 7 }, // 36 bits per transform + { 0, 2, 6 }, // 36 bits per transform + { 0, 3, 5 }, // 36 bits per transform + { 0, 4, 4 }, // 36 bits per transform + { 0, 5, 3 }, // 36 bits per transform + { 0, 6, 2 }, // 36 bits per transform + { 0, 7, 1 }, // 36 bits per transform + { 0, 10, 0 }, // 36 bits per transform + { 1, 0, 7 }, // 36 bits per transform + { 1, 1, 4 }, // 36 bits per transform + { 1, 2, 3 }, // 36 bits per transform + { 1, 3, 2 }, // 36 bits per transform + { 1, 4, 1 }, // 36 bits per transform + { 1, 7, 0 }, // 36 bits per transform + { 2, 0, 6 }, // 36 bits per transform + { 2, 1, 3 }, // 36 bits per transform + { 2, 2, 2 }, // 36 bits per transform + { 2, 3, 1 }, // 36 bits per transform + { 2, 6, 0 }, // 36 bits per transform + { 3, 0, 5 }, // 36 bits per transform + { 3, 1, 2 }, // 36 bits per transform + { 3, 2, 1 }, // 36 bits per transform + { 3, 5, 0 }, // 36 bits per transform + { 4, 0, 4 }, // 36 bits per transform + { 4, 1, 1 }, // 36 bits per transform + { 4, 4, 0 }, // 36 bits per transform + { 5, 0, 3 }, // 36 bits per transform + { 5, 3, 0 }, // 36 bits per transform + { 6, 0, 2 }, // 36 bits per transform + { 6, 2, 0 }, // 36 bits per transform + { 7, 0, 1 }, // 36 bits per transform + { 7, 1, 0 }, // 36 bits per transform + { 10, 0, 0 }, // 36 bits per transform + { 0, 0, 11 }, // 39 bits per transform + { 0, 1, 8 }, // 39 bits per transform + { 0, 2, 7 }, // 39 bits per transform + { 0, 3, 6 }, // 39 bits per transform + { 0, 4, 5 }, // 39 bits per transform + { 0, 5, 4 }, // 39 bits per transform + { 0, 6, 3 }, // 39 bits per transform + { 0, 7, 2 }, // 39 bits per transform + { 0, 8, 1 }, // 39 bits per transform + { 0, 11, 0 }, // 39 bits per transform + { 1, 0, 8 }, // 39 bits per transform + { 1, 1, 5 }, // 39 bits per transform + { 1, 2, 4 }, // 39 bits per transform + { 1, 3, 3 }, // 39 bits per transform + { 1, 4, 2 }, // 39 bits per transform + { 1, 5, 1 }, // 39 bits per transform + { 1, 8, 0 }, // 39 bits per transform + { 2, 0, 7 }, // 39 bits per transform + { 2, 1, 4 }, // 39 bits per transform + { 2, 2, 3 }, // 39 bits per transform + { 2, 3, 2 }, // 39 bits per transform + { 2, 4, 1 }, // 39 bits per transform + { 2, 7, 0 }, // 39 bits per transform + { 3, 0, 6 }, // 39 bits per transform + { 3, 1, 3 }, // 39 bits per transform + { 3, 2, 2 }, // 39 bits per transform + { 3, 3, 1 }, // 39 bits per transform + { 3, 6, 0 }, // 39 bits per transform + { 4, 0, 5 }, // 39 bits per transform + { 4, 1, 2 }, // 39 bits per transform + { 4, 2, 1 }, // 39 bits per transform + { 4, 5, 0 }, // 39 bits per transform + { 5, 0, 4 }, // 39 bits per transform + { 5, 1, 1 }, // 39 bits per transform + { 5, 4, 0 }, // 39 bits per transform + { 6, 0, 3 }, // 39 bits per transform + { 6, 3, 0 }, // 39 bits per transform + { 7, 0, 2 }, // 39 bits per transform + { 7, 2, 0 }, // 39 bits per transform + { 8, 0, 1 }, // 39 bits per transform + { 8, 1, 0 }, // 39 bits per transform + { 11, 0, 0 }, // 39 bits per transform + { 0, 0, 12 }, // 42 bits per transform + { 0, 1, 9 }, // 42 bits per transform + { 0, 2, 8 }, // 42 bits per transform + { 0, 3, 7 }, // 42 bits per transform + { 0, 4, 6 }, // 42 bits per transform + { 0, 5, 5 }, // 42 bits per transform + { 0, 6, 4 }, // 42 bits per transform + { 0, 7, 3 }, // 42 bits per transform + { 0, 8, 2 }, // 42 bits per transform + { 0, 9, 1 }, // 42 bits per transform + { 0, 12, 0 }, // 42 bits per transform + { 1, 0, 9 }, // 42 bits per transform + { 1, 1, 6 }, // 42 bits per transform + { 1, 2, 5 }, // 42 bits per transform + { 1, 3, 4 }, // 42 bits per transform + { 1, 4, 3 }, // 42 bits per transform + { 1, 5, 2 }, // 42 bits per transform + { 1, 6, 1 }, // 42 bits per transform + { 1, 9, 0 }, // 42 bits per transform + { 2, 0, 8 }, // 42 bits per transform + { 2, 1, 5 }, // 42 bits per transform + { 2, 2, 4 }, // 42 bits per transform + { 2, 3, 3 }, // 42 bits per transform + { 2, 4, 2 }, // 42 bits per transform + { 2, 5, 1 }, // 42 bits per transform + { 2, 8, 0 }, // 42 bits per transform + { 3, 0, 7 }, // 42 bits per transform + { 3, 1, 4 }, // 42 bits per transform + { 3, 2, 3 }, // 42 bits per transform + { 3, 3, 2 }, // 42 bits per transform + { 3, 4, 1 }, // 42 bits per transform + { 3, 7, 0 }, // 42 bits per transform + { 4, 0, 6 }, // 42 bits per transform + { 4, 1, 3 }, // 42 bits per transform + { 4, 2, 2 }, // 42 bits per transform + { 4, 3, 1 }, // 42 bits per transform + { 4, 6, 0 }, // 42 bits per transform + { 5, 0, 5 }, // 42 bits per transform + { 5, 1, 2 }, // 42 bits per transform + { 5, 2, 1 }, // 42 bits per transform + { 5, 5, 0 }, // 42 bits per transform + { 6, 0, 4 }, // 42 bits per transform + { 6, 1, 1 }, // 42 bits per transform + { 6, 4, 0 }, // 42 bits per transform + { 7, 0, 3 }, // 42 bits per transform + { 7, 3, 0 }, // 42 bits per transform + { 8, 0, 2 }, // 42 bits per transform + { 8, 2, 0 }, // 42 bits per transform + { 9, 0, 1 }, // 42 bits per transform + { 9, 1, 0 }, // 42 bits per transform + { 12, 0, 0 }, // 42 bits per transform + { 0, 0, 13 }, // 45 bits per transform + { 0, 1, 10 }, // 45 bits per transform + { 0, 2, 9 }, // 45 bits per transform + { 0, 3, 8 }, // 45 bits per transform + { 0, 4, 7 }, // 45 bits per transform + { 0, 5, 6 }, // 45 bits per transform + { 0, 6, 5 }, // 45 bits per transform + { 0, 7, 4 }, // 45 bits per transform + { 0, 8, 3 }, // 45 bits per transform + { 0, 9, 2 }, // 45 bits per transform + { 0, 10, 1 }, // 45 bits per transform + { 0, 13, 0 }, // 45 bits per transform + { 1, 0, 10 }, // 45 bits per transform + { 1, 1, 7 }, // 45 bits per transform + { 1, 2, 6 }, // 45 bits per transform + { 1, 3, 5 }, // 45 bits per transform + { 1, 4, 4 }, // 45 bits per transform + { 1, 5, 3 }, // 45 bits per transform + { 1, 6, 2 }, // 45 bits per transform + { 1, 7, 1 }, // 45 bits per transform + { 1, 10, 0 }, // 45 bits per transform + { 2, 0, 9 }, // 45 bits per transform + { 2, 1, 6 }, // 45 bits per transform + { 2, 2, 5 }, // 45 bits per transform + { 2, 3, 4 }, // 45 bits per transform + { 2, 4, 3 }, // 45 bits per transform + { 2, 5, 2 }, // 45 bits per transform + { 2, 6, 1 }, // 45 bits per transform + { 2, 9, 0 }, // 45 bits per transform + { 3, 0, 8 }, // 45 bits per transform + { 3, 1, 5 }, // 45 bits per transform + { 3, 2, 4 }, // 45 bits per transform + { 3, 3, 3 }, // 45 bits per transform + { 3, 4, 2 }, // 45 bits per transform + { 3, 5, 1 }, // 45 bits per transform + { 3, 8, 0 }, // 45 bits per transform + { 4, 0, 7 }, // 45 bits per transform + { 4, 1, 4 }, // 45 bits per transform + { 4, 2, 3 }, // 45 bits per transform + { 4, 3, 2 }, // 45 bits per transform + { 4, 4, 1 }, // 45 bits per transform + { 4, 7, 0 }, // 45 bits per transform + { 5, 0, 6 }, // 45 bits per transform + { 5, 1, 3 }, // 45 bits per transform + { 5, 2, 2 }, // 45 bits per transform + { 5, 3, 1 }, // 45 bits per transform + { 5, 6, 0 }, // 45 bits per transform + { 6, 0, 5 }, // 45 bits per transform + { 6, 1, 2 }, // 45 bits per transform + { 6, 2, 1 }, // 45 bits per transform + { 6, 5, 0 }, // 45 bits per transform + { 7, 0, 4 }, // 45 bits per transform + { 7, 1, 1 }, // 45 bits per transform + { 7, 4, 0 }, // 45 bits per transform + { 8, 0, 3 }, // 45 bits per transform + { 8, 3, 0 }, // 45 bits per transform + { 9, 0, 2 }, // 45 bits per transform + { 9, 2, 0 }, // 45 bits per transform + { 10, 0, 1 }, // 45 bits per transform + { 10, 1, 0 }, // 45 bits per transform + { 13, 0, 0 }, // 45 bits per transform + { 0, 0, 14 }, // 48 bits per transform + { 0, 1, 11 }, // 48 bits per transform + { 0, 2, 10 }, // 48 bits per transform + { 0, 3, 9 }, // 48 bits per transform + { 0, 4, 8 }, // 48 bits per transform + { 0, 5, 7 }, // 48 bits per transform + { 0, 6, 6 }, // 48 bits per transform + { 0, 7, 5 }, // 48 bits per transform + { 0, 8, 4 }, // 48 bits per transform + { 0, 9, 3 }, // 48 bits per transform + { 0, 10, 2 }, // 48 bits per transform + { 0, 11, 1 }, // 48 bits per transform + { 0, 14, 0 }, // 48 bits per transform + { 1, 0, 11 }, // 48 bits per transform + { 1, 1, 8 }, // 48 bits per transform + { 1, 2, 7 }, // 48 bits per transform + { 1, 3, 6 }, // 48 bits per transform + { 1, 4, 5 }, // 48 bits per transform + { 1, 5, 4 }, // 48 bits per transform + { 1, 6, 3 }, // 48 bits per transform + { 1, 7, 2 }, // 48 bits per transform + { 1, 8, 1 }, // 48 bits per transform + { 1, 11, 0 }, // 48 bits per transform + { 2, 0, 10 }, // 48 bits per transform + { 2, 1, 7 }, // 48 bits per transform + { 2, 2, 6 }, // 48 bits per transform + { 2, 3, 5 }, // 48 bits per transform + { 2, 4, 4 }, // 48 bits per transform + { 2, 5, 3 }, // 48 bits per transform + { 2, 6, 2 }, // 48 bits per transform + { 2, 7, 1 }, // 48 bits per transform + { 2, 10, 0 }, // 48 bits per transform + { 3, 0, 9 }, // 48 bits per transform + { 3, 1, 6 }, // 48 bits per transform + { 3, 2, 5 }, // 48 bits per transform + { 3, 3, 4 }, // 48 bits per transform + { 3, 4, 3 }, // 48 bits per transform + { 3, 5, 2 }, // 48 bits per transform + { 3, 6, 1 }, // 48 bits per transform + { 3, 9, 0 }, // 48 bits per transform + { 4, 0, 8 }, // 48 bits per transform + { 4, 1, 5 }, // 48 bits per transform + { 4, 2, 4 }, // 48 bits per transform + { 4, 3, 3 }, // 48 bits per transform + { 4, 4, 2 }, // 48 bits per transform + { 4, 5, 1 }, // 48 bits per transform + { 4, 8, 0 }, // 48 bits per transform + { 5, 0, 7 }, // 48 bits per transform + { 5, 1, 4 }, // 48 bits per transform + { 5, 2, 3 }, // 48 bits per transform + { 5, 3, 2 }, // 48 bits per transform + { 5, 4, 1 }, // 48 bits per transform + { 5, 7, 0 }, // 48 bits per transform + { 6, 0, 6 }, // 48 bits per transform + { 6, 1, 3 }, // 48 bits per transform + { 6, 2, 2 }, // 48 bits per transform + { 6, 3, 1 }, // 48 bits per transform + { 6, 6, 0 }, // 48 bits per transform + { 7, 0, 5 }, // 48 bits per transform + { 7, 1, 2 }, // 48 bits per transform + { 7, 2, 1 }, // 48 bits per transform + { 7, 5, 0 }, // 48 bits per transform + { 8, 0, 4 }, // 48 bits per transform + { 8, 1, 1 }, // 48 bits per transform + { 8, 4, 0 }, // 48 bits per transform + { 9, 0, 3 }, // 48 bits per transform + { 9, 3, 0 }, // 48 bits per transform + { 10, 0, 2 }, // 48 bits per transform + { 10, 2, 0 }, // 48 bits per transform + { 11, 0, 1 }, // 48 bits per transform + { 11, 1, 0 }, // 48 bits per transform + { 14, 0, 0 }, // 48 bits per transform + { 0, 0, 15 }, // 51 bits per transform + { 0, 1, 12 }, // 51 bits per transform + { 0, 2, 11 }, // 51 bits per transform + { 0, 3, 10 }, // 51 bits per transform + { 0, 4, 9 }, // 51 bits per transform + { 0, 5, 8 }, // 51 bits per transform + { 0, 6, 7 }, // 51 bits per transform + { 0, 7, 6 }, // 51 bits per transform + { 0, 8, 5 }, // 51 bits per transform + { 0, 9, 4 }, // 51 bits per transform + { 0, 10, 3 }, // 51 bits per transform + { 0, 11, 2 }, // 51 bits per transform + { 0, 12, 1 }, // 51 bits per transform + { 0, 15, 0 }, // 51 bits per transform + { 1, 0, 12 }, // 51 bits per transform + { 1, 1, 9 }, // 51 bits per transform + { 1, 2, 8 }, // 51 bits per transform + { 1, 3, 7 }, // 51 bits per transform + { 1, 4, 6 }, // 51 bits per transform + { 1, 5, 5 }, // 51 bits per transform + { 1, 6, 4 }, // 51 bits per transform + { 1, 7, 3 }, // 51 bits per transform + { 1, 8, 2 }, // 51 bits per transform + { 1, 9, 1 }, // 51 bits per transform + { 1, 12, 0 }, // 51 bits per transform + { 2, 0, 11 }, // 51 bits per transform + { 2, 1, 8 }, // 51 bits per transform + { 2, 2, 7 }, // 51 bits per transform + { 2, 3, 6 }, // 51 bits per transform + { 2, 4, 5 }, // 51 bits per transform + { 2, 5, 4 }, // 51 bits per transform + { 2, 6, 3 }, // 51 bits per transform + { 2, 7, 2 }, // 51 bits per transform + { 2, 8, 1 }, // 51 bits per transform + { 2, 11, 0 }, // 51 bits per transform + { 3, 0, 10 }, // 51 bits per transform + { 3, 1, 7 }, // 51 bits per transform + { 3, 2, 6 }, // 51 bits per transform + { 3, 3, 5 }, // 51 bits per transform + { 3, 4, 4 }, // 51 bits per transform + { 3, 5, 3 }, // 51 bits per transform + { 3, 6, 2 }, // 51 bits per transform + { 3, 7, 1 }, // 51 bits per transform + { 3, 10, 0 }, // 51 bits per transform + { 4, 0, 9 }, // 51 bits per transform + { 4, 1, 6 }, // 51 bits per transform + { 4, 2, 5 }, // 51 bits per transform + { 4, 3, 4 }, // 51 bits per transform + { 4, 4, 3 }, // 51 bits per transform + { 4, 5, 2 }, // 51 bits per transform + { 4, 6, 1 }, // 51 bits per transform + { 4, 9, 0 }, // 51 bits per transform + { 5, 0, 8 }, // 51 bits per transform + { 5, 1, 5 }, // 51 bits per transform + { 5, 2, 4 }, // 51 bits per transform + { 5, 3, 3 }, // 51 bits per transform + { 5, 4, 2 }, // 51 bits per transform + { 5, 5, 1 }, // 51 bits per transform + { 5, 8, 0 }, // 51 bits per transform + { 6, 0, 7 }, // 51 bits per transform + { 6, 1, 4 }, // 51 bits per transform + { 6, 2, 3 }, // 51 bits per transform + { 6, 3, 2 }, // 51 bits per transform + { 6, 4, 1 }, // 51 bits per transform + { 6, 7, 0 }, // 51 bits per transform + { 7, 0, 6 }, // 51 bits per transform + { 7, 1, 3 }, // 51 bits per transform + { 7, 2, 2 }, // 51 bits per transform + { 7, 3, 1 }, // 51 bits per transform + { 7, 6, 0 }, // 51 bits per transform + { 8, 0, 5 }, // 51 bits per transform + { 8, 1, 2 }, // 51 bits per transform + { 8, 2, 1 }, // 51 bits per transform + { 8, 5, 0 }, // 51 bits per transform + { 9, 0, 4 }, // 51 bits per transform + { 9, 1, 1 }, // 51 bits per transform + { 9, 4, 0 }, // 51 bits per transform + { 10, 0, 3 }, // 51 bits per transform + { 10, 3, 0 }, // 51 bits per transform + { 11, 0, 2 }, // 51 bits per transform + { 11, 2, 0 }, // 51 bits per transform + { 12, 0, 1 }, // 51 bits per transform + { 12, 1, 0 }, // 51 bits per transform + { 15, 0, 0 }, // 51 bits per transform + { 0, 0, 16 }, // 54 bits per transform + { 0, 1, 13 }, // 54 bits per transform + { 0, 2, 12 }, // 54 bits per transform + { 0, 3, 11 }, // 54 bits per transform + { 0, 4, 10 }, // 54 bits per transform + { 0, 5, 9 }, // 54 bits per transform + { 0, 6, 8 }, // 54 bits per transform + { 0, 7, 7 }, // 54 bits per transform + { 0, 8, 6 }, // 54 bits per transform + { 0, 9, 5 }, // 54 bits per transform + { 0, 10, 4 }, // 54 bits per transform + { 0, 11, 3 }, // 54 bits per transform + { 0, 12, 2 }, // 54 bits per transform + { 0, 13, 1 }, // 54 bits per transform + { 0, 16, 0 }, // 54 bits per transform + { 1, 0, 13 }, // 54 bits per transform + { 1, 1, 10 }, // 54 bits per transform + { 1, 2, 9 }, // 54 bits per transform + { 1, 3, 8 }, // 54 bits per transform + { 1, 4, 7 }, // 54 bits per transform + { 1, 5, 6 }, // 54 bits per transform + { 1, 6, 5 }, // 54 bits per transform + { 1, 7, 4 }, // 54 bits per transform + { 1, 8, 3 }, // 54 bits per transform + { 1, 9, 2 }, // 54 bits per transform + { 1, 10, 1 }, // 54 bits per transform + { 1, 13, 0 }, // 54 bits per transform + { 2, 0, 12 }, // 54 bits per transform + { 2, 1, 9 }, // 54 bits per transform + { 2, 2, 8 }, // 54 bits per transform + { 2, 3, 7 }, // 54 bits per transform + { 2, 4, 6 }, // 54 bits per transform + { 2, 5, 5 }, // 54 bits per transform + { 2, 6, 4 }, // 54 bits per transform + { 2, 7, 3 }, // 54 bits per transform + { 2, 8, 2 }, // 54 bits per transform + { 2, 9, 1 }, // 54 bits per transform + { 2, 12, 0 }, // 54 bits per transform + { 3, 0, 11 }, // 54 bits per transform + { 3, 1, 8 }, // 54 bits per transform + { 3, 2, 7 }, // 54 bits per transform + { 3, 3, 6 }, // 54 bits per transform + { 3, 4, 5 }, // 54 bits per transform + { 3, 5, 4 }, // 54 bits per transform + { 3, 6, 3 }, // 54 bits per transform + { 3, 7, 2 }, // 54 bits per transform + { 3, 8, 1 }, // 54 bits per transform + { 3, 11, 0 }, // 54 bits per transform + { 4, 0, 10 }, // 54 bits per transform + { 4, 1, 7 }, // 54 bits per transform + { 4, 2, 6 }, // 54 bits per transform + { 4, 3, 5 }, // 54 bits per transform + { 4, 4, 4 }, // 54 bits per transform + { 4, 5, 3 }, // 54 bits per transform + { 4, 6, 2 }, // 54 bits per transform + { 4, 7, 1 }, // 54 bits per transform + { 4, 10, 0 }, // 54 bits per transform + { 5, 0, 9 }, // 54 bits per transform + { 5, 1, 6 }, // 54 bits per transform + { 5, 2, 5 }, // 54 bits per transform + { 5, 3, 4 }, // 54 bits per transform + { 5, 4, 3 }, // 54 bits per transform + { 5, 5, 2 }, // 54 bits per transform + { 5, 6, 1 }, // 54 bits per transform + { 5, 9, 0 }, // 54 bits per transform + { 6, 0, 8 }, // 54 bits per transform + { 6, 1, 5 }, // 54 bits per transform + { 6, 2, 4 }, // 54 bits per transform + { 6, 3, 3 }, // 54 bits per transform + { 6, 4, 2 }, // 54 bits per transform + { 6, 5, 1 }, // 54 bits per transform + { 6, 8, 0 }, // 54 bits per transform + { 7, 0, 7 }, // 54 bits per transform + { 7, 1, 4 }, // 54 bits per transform + { 7, 2, 3 }, // 54 bits per transform + { 7, 3, 2 }, // 54 bits per transform + { 7, 4, 1 }, // 54 bits per transform + { 7, 7, 0 }, // 54 bits per transform + { 8, 0, 6 }, // 54 bits per transform + { 8, 1, 3 }, // 54 bits per transform + { 8, 2, 2 }, // 54 bits per transform + { 8, 3, 1 }, // 54 bits per transform + { 8, 6, 0 }, // 54 bits per transform + { 9, 0, 5 }, // 54 bits per transform + { 9, 1, 2 }, // 54 bits per transform + { 9, 2, 1 }, // 54 bits per transform + { 9, 5, 0 }, // 54 bits per transform + { 10, 0, 4 }, // 54 bits per transform + { 10, 1, 1 }, // 54 bits per transform + { 10, 4, 0 }, // 54 bits per transform + { 11, 0, 3 }, // 54 bits per transform + { 11, 3, 0 }, // 54 bits per transform + { 12, 0, 2 }, // 54 bits per transform + { 12, 2, 0 }, // 54 bits per transform + { 13, 0, 1 }, // 54 bits per transform + { 13, 1, 0 }, // 54 bits per transform + { 16, 0, 0 }, // 54 bits per transform + { 0, 0, 17 }, // 57 bits per transform + { 0, 1, 14 }, // 57 bits per transform + { 0, 2, 13 }, // 57 bits per transform + { 0, 3, 12 }, // 57 bits per transform + { 0, 4, 11 }, // 57 bits per transform + { 0, 5, 10 }, // 57 bits per transform + { 0, 6, 9 }, // 57 bits per transform + { 0, 7, 8 }, // 57 bits per transform + { 0, 8, 7 }, // 57 bits per transform + { 0, 9, 6 }, // 57 bits per transform + { 0, 10, 5 }, // 57 bits per transform + { 0, 11, 4 }, // 57 bits per transform + { 0, 12, 3 }, // 57 bits per transform + { 0, 13, 2 }, // 57 bits per transform + { 0, 14, 1 }, // 57 bits per transform + { 0, 17, 0 }, // 57 bits per transform + { 1, 0, 14 }, // 57 bits per transform + { 1, 1, 11 }, // 57 bits per transform + { 1, 2, 10 }, // 57 bits per transform + { 1, 3, 9 }, // 57 bits per transform + { 1, 4, 8 }, // 57 bits per transform + { 1, 5, 7 }, // 57 bits per transform + { 1, 6, 6 }, // 57 bits per transform + { 1, 7, 5 }, // 57 bits per transform + { 1, 8, 4 }, // 57 bits per transform + { 1, 9, 3 }, // 57 bits per transform + { 1, 10, 2 }, // 57 bits per transform + { 1, 11, 1 }, // 57 bits per transform + { 1, 14, 0 }, // 57 bits per transform + { 2, 0, 13 }, // 57 bits per transform + { 2, 1, 10 }, // 57 bits per transform + { 2, 2, 9 }, // 57 bits per transform + { 2, 3, 8 }, // 57 bits per transform + { 2, 4, 7 }, // 57 bits per transform + { 2, 5, 6 }, // 57 bits per transform + { 2, 6, 5 }, // 57 bits per transform + { 2, 7, 4 }, // 57 bits per transform + { 2, 8, 3 }, // 57 bits per transform + { 2, 9, 2 }, // 57 bits per transform + { 2, 10, 1 }, // 57 bits per transform + { 2, 13, 0 }, // 57 bits per transform + { 3, 0, 12 }, // 57 bits per transform + { 3, 1, 9 }, // 57 bits per transform + { 3, 2, 8 }, // 57 bits per transform + { 3, 3, 7 }, // 57 bits per transform + { 3, 4, 6 }, // 57 bits per transform + { 3, 5, 5 }, // 57 bits per transform + { 3, 6, 4 }, // 57 bits per transform + { 3, 7, 3 }, // 57 bits per transform + { 3, 8, 2 }, // 57 bits per transform + { 3, 9, 1 }, // 57 bits per transform + { 3, 12, 0 }, // 57 bits per transform + { 4, 0, 11 }, // 57 bits per transform + { 4, 1, 8 }, // 57 bits per transform + { 4, 2, 7 }, // 57 bits per transform + { 4, 3, 6 }, // 57 bits per transform + { 4, 4, 5 }, // 57 bits per transform + { 4, 5, 4 }, // 57 bits per transform + { 4, 6, 3 }, // 57 bits per transform + { 4, 7, 2 }, // 57 bits per transform + { 4, 8, 1 }, // 57 bits per transform + { 4, 11, 0 }, // 57 bits per transform + { 5, 0, 10 }, // 57 bits per transform + { 5, 1, 7 }, // 57 bits per transform + { 5, 2, 6 }, // 57 bits per transform + { 5, 3, 5 }, // 57 bits per transform + { 5, 4, 4 }, // 57 bits per transform + { 5, 5, 3 }, // 57 bits per transform + { 5, 6, 2 }, // 57 bits per transform + { 5, 7, 1 }, // 57 bits per transform + { 5, 10, 0 }, // 57 bits per transform + { 6, 0, 9 }, // 57 bits per transform + { 6, 1, 6 }, // 57 bits per transform + { 6, 2, 5 }, // 57 bits per transform + { 6, 3, 4 }, // 57 bits per transform + { 6, 4, 3 }, // 57 bits per transform + { 6, 5, 2 }, // 57 bits per transform + { 6, 6, 1 }, // 57 bits per transform + { 6, 9, 0 }, // 57 bits per transform + { 7, 0, 8 }, // 57 bits per transform + { 7, 1, 5 }, // 57 bits per transform + { 7, 2, 4 }, // 57 bits per transform + { 7, 3, 3 }, // 57 bits per transform + { 7, 4, 2 }, // 57 bits per transform + { 7, 5, 1 }, // 57 bits per transform + { 7, 8, 0 }, // 57 bits per transform + { 8, 0, 7 }, // 57 bits per transform + { 8, 1, 4 }, // 57 bits per transform + { 8, 2, 3 }, // 57 bits per transform + { 8, 3, 2 }, // 57 bits per transform + { 8, 4, 1 }, // 57 bits per transform + { 8, 7, 0 }, // 57 bits per transform + { 9, 0, 6 }, // 57 bits per transform + { 9, 1, 3 }, // 57 bits per transform + { 9, 2, 2 }, // 57 bits per transform + { 9, 3, 1 }, // 57 bits per transform + { 9, 6, 0 }, // 57 bits per transform + { 10, 0, 5 }, // 57 bits per transform + { 10, 1, 2 }, // 57 bits per transform + { 10, 2, 1 }, // 57 bits per transform + { 10, 5, 0 }, // 57 bits per transform + { 11, 0, 4 }, // 57 bits per transform + { 11, 1, 1 }, // 57 bits per transform + { 11, 4, 0 }, // 57 bits per transform + { 12, 0, 3 }, // 57 bits per transform + { 12, 3, 0 }, // 57 bits per transform + { 13, 0, 2 }, // 57 bits per transform + { 13, 2, 0 }, // 57 bits per transform + { 14, 0, 1 }, // 57 bits per transform + { 14, 1, 0 }, // 57 bits per transform + { 17, 0, 0 }, // 57 bits per transform + { 0, 1, 15 }, // 60 bits per transform + { 0, 2, 14 }, // 60 bits per transform + { 0, 3, 13 }, // 60 bits per transform + { 0, 4, 12 }, // 60 bits per transform + { 0, 5, 11 }, // 60 bits per transform + { 0, 6, 10 }, // 60 bits per transform + { 0, 7, 9 }, // 60 bits per transform + { 0, 8, 8 }, // 60 bits per transform + { 0, 9, 7 }, // 60 bits per transform + { 0, 10, 6 }, // 60 bits per transform + { 0, 11, 5 }, // 60 bits per transform + { 0, 12, 4 }, // 60 bits per transform + { 0, 13, 3 }, // 60 bits per transform + { 0, 14, 2 }, // 60 bits per transform + { 0, 15, 1 }, // 60 bits per transform + { 1, 0, 15 }, // 60 bits per transform + { 1, 1, 12 }, // 60 bits per transform + { 1, 2, 11 }, // 60 bits per transform + { 1, 3, 10 }, // 60 bits per transform + { 1, 4, 9 }, // 60 bits per transform + { 1, 5, 8 }, // 60 bits per transform + { 1, 6, 7 }, // 60 bits per transform + { 1, 7, 6 }, // 60 bits per transform + { 1, 8, 5 }, // 60 bits per transform + { 1, 9, 4 }, // 60 bits per transform + { 1, 10, 3 }, // 60 bits per transform + { 1, 11, 2 }, // 60 bits per transform + { 1, 12, 1 }, // 60 bits per transform + { 1, 15, 0 }, // 60 bits per transform + { 2, 0, 14 }, // 60 bits per transform + { 2, 1, 11 }, // 60 bits per transform + { 2, 2, 10 }, // 60 bits per transform + { 2, 3, 9 }, // 60 bits per transform + { 2, 4, 8 }, // 60 bits per transform + { 2, 5, 7 }, // 60 bits per transform + { 2, 6, 6 }, // 60 bits per transform + { 2, 7, 5 }, // 60 bits per transform + { 2, 8, 4 }, // 60 bits per transform + { 2, 9, 3 }, // 60 bits per transform + { 2, 10, 2 }, // 60 bits per transform + { 2, 11, 1 }, // 60 bits per transform + { 2, 14, 0 }, // 60 bits per transform + { 3, 0, 13 }, // 60 bits per transform + { 3, 1, 10 }, // 60 bits per transform + { 3, 2, 9 }, // 60 bits per transform + { 3, 3, 8 }, // 60 bits per transform + { 3, 4, 7 }, // 60 bits per transform + { 3, 5, 6 }, // 60 bits per transform + { 3, 6, 5 }, // 60 bits per transform + { 3, 7, 4 }, // 60 bits per transform + { 3, 8, 3 }, // 60 bits per transform + { 3, 9, 2 }, // 60 bits per transform + { 3, 10, 1 }, // 60 bits per transform + { 3, 13, 0 }, // 60 bits per transform + { 4, 0, 12 }, // 60 bits per transform + { 4, 1, 9 }, // 60 bits per transform + { 4, 2, 8 }, // 60 bits per transform + { 4, 3, 7 }, // 60 bits per transform + { 4, 4, 6 }, // 60 bits per transform + { 4, 5, 5 }, // 60 bits per transform + { 4, 6, 4 }, // 60 bits per transform + { 4, 7, 3 }, // 60 bits per transform + { 4, 8, 2 }, // 60 bits per transform + { 4, 9, 1 }, // 60 bits per transform + { 4, 12, 0 }, // 60 bits per transform + { 5, 0, 11 }, // 60 bits per transform + { 5, 1, 8 }, // 60 bits per transform + { 5, 2, 7 }, // 60 bits per transform + { 5, 3, 6 }, // 60 bits per transform + { 5, 4, 5 }, // 60 bits per transform + { 5, 5, 4 }, // 60 bits per transform + { 5, 6, 3 }, // 60 bits per transform + { 5, 7, 2 }, // 60 bits per transform + { 5, 8, 1 }, // 60 bits per transform + { 5, 11, 0 }, // 60 bits per transform + { 6, 0, 10 }, // 60 bits per transform + { 6, 1, 7 }, // 60 bits per transform + { 6, 2, 6 }, // 60 bits per transform + { 6, 3, 5 }, // 60 bits per transform + { 6, 4, 4 }, // 60 bits per transform + { 6, 5, 3 }, // 60 bits per transform + { 6, 6, 2 }, // 60 bits per transform + { 6, 7, 1 }, // 60 bits per transform + { 6, 10, 0 }, // 60 bits per transform + { 7, 0, 9 }, // 60 bits per transform + { 7, 1, 6 }, // 60 bits per transform + { 7, 2, 5 }, // 60 bits per transform + { 7, 3, 4 }, // 60 bits per transform + { 7, 4, 3 }, // 60 bits per transform + { 7, 5, 2 }, // 60 bits per transform + { 7, 6, 1 }, // 60 bits per transform + { 7, 9, 0 }, // 60 bits per transform + { 8, 0, 8 }, // 60 bits per transform + { 8, 1, 5 }, // 60 bits per transform + { 8, 2, 4 }, // 60 bits per transform + { 8, 3, 3 }, // 60 bits per transform + { 8, 4, 2 }, // 60 bits per transform + { 8, 5, 1 }, // 60 bits per transform + { 8, 8, 0 }, // 60 bits per transform + { 9, 0, 7 }, // 60 bits per transform + { 9, 1, 4 }, // 60 bits per transform + { 9, 2, 3 }, // 60 bits per transform + { 9, 3, 2 }, // 60 bits per transform + { 9, 4, 1 }, // 60 bits per transform + { 9, 7, 0 }, // 60 bits per transform + { 10, 0, 6 }, // 60 bits per transform + { 10, 1, 3 }, // 60 bits per transform + { 10, 2, 2 }, // 60 bits per transform + { 10, 3, 1 }, // 60 bits per transform + { 10, 6, 0 }, // 60 bits per transform + { 11, 0, 5 }, // 60 bits per transform + { 11, 1, 2 }, // 60 bits per transform + { 11, 2, 1 }, // 60 bits per transform + { 11, 5, 0 }, // 60 bits per transform + { 12, 0, 4 }, // 60 bits per transform + { 12, 1, 1 }, // 60 bits per transform + { 12, 4, 0 }, // 60 bits per transform + { 13, 0, 3 }, // 60 bits per transform + { 13, 3, 0 }, // 60 bits per transform + { 14, 0, 2 }, // 60 bits per transform + { 14, 2, 0 }, // 60 bits per transform + { 15, 0, 1 }, // 60 bits per transform + { 15, 1, 0 }, // 60 bits per transform + { 0, 1, 16 }, // 63 bits per transform + { 0, 2, 15 }, // 63 bits per transform + { 0, 3, 14 }, // 63 bits per transform + { 0, 4, 13 }, // 63 bits per transform + { 0, 5, 12 }, // 63 bits per transform + { 0, 6, 11 }, // 63 bits per transform + { 0, 7, 10 }, // 63 bits per transform + { 0, 8, 9 }, // 63 bits per transform + { 0, 9, 8 }, // 63 bits per transform + { 0, 10, 7 }, // 63 bits per transform + { 0, 11, 6 }, // 63 bits per transform + { 0, 12, 5 }, // 63 bits per transform + { 0, 13, 4 }, // 63 bits per transform + { 0, 14, 3 }, // 63 bits per transform + { 0, 15, 2 }, // 63 bits per transform + { 0, 16, 1 }, // 63 bits per transform + { 1, 0, 16 }, // 63 bits per transform + { 1, 1, 13 }, // 63 bits per transform + { 1, 2, 12 }, // 63 bits per transform + { 1, 3, 11 }, // 63 bits per transform + { 1, 4, 10 }, // 63 bits per transform + { 1, 5, 9 }, // 63 bits per transform + { 1, 6, 8 }, // 63 bits per transform + { 1, 7, 7 }, // 63 bits per transform + { 1, 8, 6 }, // 63 bits per transform + { 1, 9, 5 }, // 63 bits per transform + { 1, 10, 4 }, // 63 bits per transform + { 1, 11, 3 }, // 63 bits per transform + { 1, 12, 2 }, // 63 bits per transform + { 1, 13, 1 }, // 63 bits per transform + { 1, 16, 0 }, // 63 bits per transform + { 2, 0, 15 }, // 63 bits per transform + { 2, 1, 12 }, // 63 bits per transform + { 2, 2, 11 }, // 63 bits per transform + { 2, 3, 10 }, // 63 bits per transform + { 2, 4, 9 }, // 63 bits per transform + { 2, 5, 8 }, // 63 bits per transform + { 2, 6, 7 }, // 63 bits per transform + { 2, 7, 6 }, // 63 bits per transform + { 2, 8, 5 }, // 63 bits per transform + { 2, 9, 4 }, // 63 bits per transform + { 2, 10, 3 }, // 63 bits per transform + { 2, 11, 2 }, // 63 bits per transform + { 2, 12, 1 }, // 63 bits per transform + { 2, 15, 0 }, // 63 bits per transform + { 3, 0, 14 }, // 63 bits per transform + { 3, 1, 11 }, // 63 bits per transform + { 3, 2, 10 }, // 63 bits per transform + { 3, 3, 9 }, // 63 bits per transform + { 3, 4, 8 }, // 63 bits per transform + { 3, 5, 7 }, // 63 bits per transform + { 3, 6, 6 }, // 63 bits per transform + { 3, 7, 5 }, // 63 bits per transform + { 3, 8, 4 }, // 63 bits per transform + { 3, 9, 3 }, // 63 bits per transform + { 3, 10, 2 }, // 63 bits per transform + { 3, 11, 1 }, // 63 bits per transform + { 3, 14, 0 }, // 63 bits per transform + { 4, 0, 13 }, // 63 bits per transform + { 4, 1, 10 }, // 63 bits per transform + { 4, 2, 9 }, // 63 bits per transform + { 4, 3, 8 }, // 63 bits per transform + { 4, 4, 7 }, // 63 bits per transform + { 4, 5, 6 }, // 63 bits per transform + { 4, 6, 5 }, // 63 bits per transform + { 4, 7, 4 }, // 63 bits per transform + { 4, 8, 3 }, // 63 bits per transform + { 4, 9, 2 }, // 63 bits per transform + { 4, 10, 1 }, // 63 bits per transform + { 4, 13, 0 }, // 63 bits per transform + { 5, 0, 12 }, // 63 bits per transform + { 5, 1, 9 }, // 63 bits per transform + { 5, 2, 8 }, // 63 bits per transform + { 5, 3, 7 }, // 63 bits per transform + { 5, 4, 6 }, // 63 bits per transform + { 5, 5, 5 }, // 63 bits per transform + { 5, 6, 4 }, // 63 bits per transform + { 5, 7, 3 }, // 63 bits per transform + { 5, 8, 2 }, // 63 bits per transform + { 5, 9, 1 }, // 63 bits per transform + { 5, 12, 0 }, // 63 bits per transform + { 6, 0, 11 }, // 63 bits per transform + { 6, 1, 8 }, // 63 bits per transform + { 6, 2, 7 }, // 63 bits per transform + { 6, 3, 6 }, // 63 bits per transform + { 6, 4, 5 }, // 63 bits per transform + { 6, 5, 4 }, // 63 bits per transform + { 6, 6, 3 }, // 63 bits per transform + { 6, 7, 2 }, // 63 bits per transform + { 6, 8, 1 }, // 63 bits per transform + { 6, 11, 0 }, // 63 bits per transform + { 7, 0, 10 }, // 63 bits per transform + { 7, 1, 7 }, // 63 bits per transform + { 7, 2, 6 }, // 63 bits per transform + { 7, 3, 5 }, // 63 bits per transform + { 7, 4, 4 }, // 63 bits per transform + { 7, 5, 3 }, // 63 bits per transform + { 7, 6, 2 }, // 63 bits per transform + { 7, 7, 1 }, // 63 bits per transform + { 7, 10, 0 }, // 63 bits per transform + { 8, 0, 9 }, // 63 bits per transform + { 8, 1, 6 }, // 63 bits per transform + { 8, 2, 5 }, // 63 bits per transform + { 8, 3, 4 }, // 63 bits per transform + { 8, 4, 3 }, // 63 bits per transform + { 8, 5, 2 }, // 63 bits per transform + { 8, 6, 1 }, // 63 bits per transform + { 8, 9, 0 }, // 63 bits per transform + { 9, 0, 8 }, // 63 bits per transform + { 9, 1, 5 }, // 63 bits per transform + { 9, 2, 4 }, // 63 bits per transform + { 9, 3, 3 }, // 63 bits per transform + { 9, 4, 2 }, // 63 bits per transform + { 9, 5, 1 }, // 63 bits per transform + { 9, 8, 0 }, // 63 bits per transform + { 10, 0, 7 }, // 63 bits per transform + { 10, 1, 4 }, // 63 bits per transform + { 10, 2, 3 }, // 63 bits per transform + { 10, 3, 2 }, // 63 bits per transform + { 10, 4, 1 }, // 63 bits per transform + { 10, 7, 0 }, // 63 bits per transform + { 11, 0, 6 }, // 63 bits per transform + { 11, 1, 3 }, // 63 bits per transform + { 11, 2, 2 }, // 63 bits per transform + { 11, 3, 1 }, // 63 bits per transform + { 11, 6, 0 }, // 63 bits per transform + { 12, 0, 5 }, // 63 bits per transform + { 12, 1, 2 }, // 63 bits per transform + { 12, 2, 1 }, // 63 bits per transform + { 12, 5, 0 }, // 63 bits per transform + { 13, 0, 4 }, // 63 bits per transform + { 13, 1, 1 }, // 63 bits per transform + { 13, 4, 0 }, // 63 bits per transform + { 14, 0, 3 }, // 63 bits per transform + { 14, 3, 0 }, // 63 bits per transform + { 15, 0, 2 }, // 63 bits per transform + { 15, 2, 0 }, // 63 bits per transform + { 16, 0, 1 }, // 63 bits per transform + { 16, 1, 0 }, // 63 bits per transform + { 0, 1, 17 }, // 66 bits per transform + { 0, 2, 16 }, // 66 bits per transform + { 0, 3, 15 }, // 66 bits per transform + { 0, 4, 14 }, // 66 bits per transform + { 0, 5, 13 }, // 66 bits per transform + { 0, 6, 12 }, // 66 bits per transform + { 0, 7, 11 }, // 66 bits per transform + { 0, 8, 10 }, // 66 bits per transform + { 0, 9, 9 }, // 66 bits per transform + { 0, 10, 8 }, // 66 bits per transform + { 0, 11, 7 }, // 66 bits per transform + { 0, 12, 6 }, // 66 bits per transform + { 0, 13, 5 }, // 66 bits per transform + { 0, 14, 4 }, // 66 bits per transform + { 0, 15, 3 }, // 66 bits per transform + { 0, 16, 2 }, // 66 bits per transform + { 0, 17, 1 }, // 66 bits per transform + { 1, 0, 17 }, // 66 bits per transform + { 1, 1, 14 }, // 66 bits per transform + { 1, 2, 13 }, // 66 bits per transform + { 1, 3, 12 }, // 66 bits per transform + { 1, 4, 11 }, // 66 bits per transform + { 1, 5, 10 }, // 66 bits per transform + { 1, 6, 9 }, // 66 bits per transform + { 1, 7, 8 }, // 66 bits per transform + { 1, 8, 7 }, // 66 bits per transform + { 1, 9, 6 }, // 66 bits per transform + { 1, 10, 5 }, // 66 bits per transform + { 1, 11, 4 }, // 66 bits per transform + { 1, 12, 3 }, // 66 bits per transform + { 1, 13, 2 }, // 66 bits per transform + { 1, 14, 1 }, // 66 bits per transform + { 1, 17, 0 }, // 66 bits per transform + { 2, 0, 16 }, // 66 bits per transform + { 2, 1, 13 }, // 66 bits per transform + { 2, 2, 12 }, // 66 bits per transform + { 2, 3, 11 }, // 66 bits per transform + { 2, 4, 10 }, // 66 bits per transform + { 2, 5, 9 }, // 66 bits per transform + { 2, 6, 8 }, // 66 bits per transform + { 2, 7, 7 }, // 66 bits per transform + { 2, 8, 6 }, // 66 bits per transform + { 2, 9, 5 }, // 66 bits per transform + { 2, 10, 4 }, // 66 bits per transform + { 2, 11, 3 }, // 66 bits per transform + { 2, 12, 2 }, // 66 bits per transform + { 2, 13, 1 }, // 66 bits per transform + { 2, 16, 0 }, // 66 bits per transform + { 3, 0, 15 }, // 66 bits per transform + { 3, 1, 12 }, // 66 bits per transform + { 3, 2, 11 }, // 66 bits per transform + { 3, 3, 10 }, // 66 bits per transform + { 3, 4, 9 }, // 66 bits per transform + { 3, 5, 8 }, // 66 bits per transform + { 3, 6, 7 }, // 66 bits per transform + { 3, 7, 6 }, // 66 bits per transform + { 3, 8, 5 }, // 66 bits per transform + { 3, 9, 4 }, // 66 bits per transform + { 3, 10, 3 }, // 66 bits per transform + { 3, 11, 2 }, // 66 bits per transform + { 3, 12, 1 }, // 66 bits per transform + { 3, 15, 0 }, // 66 bits per transform + { 4, 0, 14 }, // 66 bits per transform + { 4, 1, 11 }, // 66 bits per transform + { 4, 2, 10 }, // 66 bits per transform + { 4, 3, 9 }, // 66 bits per transform + { 4, 4, 8 }, // 66 bits per transform + { 4, 5, 7 }, // 66 bits per transform + { 4, 6, 6 }, // 66 bits per transform + { 4, 7, 5 }, // 66 bits per transform + { 4, 8, 4 }, // 66 bits per transform + { 4, 9, 3 }, // 66 bits per transform + { 4, 10, 2 }, // 66 bits per transform + { 4, 11, 1 }, // 66 bits per transform + { 4, 14, 0 }, // 66 bits per transform + { 5, 0, 13 }, // 66 bits per transform + { 5, 1, 10 }, // 66 bits per transform + { 5, 2, 9 }, // 66 bits per transform + { 5, 3, 8 }, // 66 bits per transform + { 5, 4, 7 }, // 66 bits per transform + { 5, 5, 6 }, // 66 bits per transform + { 5, 6, 5 }, // 66 bits per transform + { 5, 7, 4 }, // 66 bits per transform + { 5, 8, 3 }, // 66 bits per transform + { 5, 9, 2 }, // 66 bits per transform + { 5, 10, 1 }, // 66 bits per transform + { 5, 13, 0 }, // 66 bits per transform + { 6, 0, 12 }, // 66 bits per transform + { 6, 1, 9 }, // 66 bits per transform + { 6, 2, 8 }, // 66 bits per transform + { 6, 3, 7 }, // 66 bits per transform + { 6, 4, 6 }, // 66 bits per transform + { 6, 5, 5 }, // 66 bits per transform + { 6, 6, 4 }, // 66 bits per transform + { 6, 7, 3 }, // 66 bits per transform + { 6, 8, 2 }, // 66 bits per transform + { 6, 9, 1 }, // 66 bits per transform + { 6, 12, 0 }, // 66 bits per transform + { 7, 0, 11 }, // 66 bits per transform + { 7, 1, 8 }, // 66 bits per transform + { 7, 2, 7 }, // 66 bits per transform + { 7, 3, 6 }, // 66 bits per transform + { 7, 4, 5 }, // 66 bits per transform + { 7, 5, 4 }, // 66 bits per transform + { 7, 6, 3 }, // 66 bits per transform + { 7, 7, 2 }, // 66 bits per transform + { 7, 8, 1 }, // 66 bits per transform + { 7, 11, 0 }, // 66 bits per transform + { 8, 0, 10 }, // 66 bits per transform + { 8, 1, 7 }, // 66 bits per transform + { 8, 2, 6 }, // 66 bits per transform + { 8, 3, 5 }, // 66 bits per transform + { 8, 4, 4 }, // 66 bits per transform + { 8, 5, 3 }, // 66 bits per transform + { 8, 6, 2 }, // 66 bits per transform + { 8, 7, 1 }, // 66 bits per transform + { 8, 10, 0 }, // 66 bits per transform + { 9, 0, 9 }, // 66 bits per transform + { 9, 1, 6 }, // 66 bits per transform + { 9, 2, 5 }, // 66 bits per transform + { 9, 3, 4 }, // 66 bits per transform + { 9, 4, 3 }, // 66 bits per transform + { 9, 5, 2 }, // 66 bits per transform + { 9, 6, 1 }, // 66 bits per transform + { 9, 9, 0 }, // 66 bits per transform + { 10, 0, 8 }, // 66 bits per transform + { 10, 1, 5 }, // 66 bits per transform + { 10, 2, 4 }, // 66 bits per transform + { 10, 3, 3 }, // 66 bits per transform + { 10, 4, 2 }, // 66 bits per transform + { 10, 5, 1 }, // 66 bits per transform + { 10, 8, 0 }, // 66 bits per transform + { 11, 0, 7 }, // 66 bits per transform + { 11, 1, 4 }, // 66 bits per transform + { 11, 2, 3 }, // 66 bits per transform + { 11, 3, 2 }, // 66 bits per transform + { 11, 4, 1 }, // 66 bits per transform + { 11, 7, 0 }, // 66 bits per transform + { 12, 0, 6 }, // 66 bits per transform + { 12, 1, 3 }, // 66 bits per transform + { 12, 2, 2 }, // 66 bits per transform + { 12, 3, 1 }, // 66 bits per transform + { 12, 6, 0 }, // 66 bits per transform + { 13, 0, 5 }, // 66 bits per transform + { 13, 1, 2 }, // 66 bits per transform + { 13, 2, 1 }, // 66 bits per transform + { 13, 5, 0 }, // 66 bits per transform + { 14, 0, 4 }, // 66 bits per transform + { 14, 1, 1 }, // 66 bits per transform + { 14, 4, 0 }, // 66 bits per transform + { 15, 0, 3 }, // 66 bits per transform + { 15, 3, 0 }, // 66 bits per transform + { 16, 0, 2 }, // 66 bits per transform + { 16, 2, 0 }, // 66 bits per transform + { 17, 0, 1 }, // 66 bits per transform + { 17, 1, 0 }, // 66 bits per transform + { 0, 2, 17 }, // 69 bits per transform + { 0, 3, 16 }, // 69 bits per transform + { 0, 4, 15 }, // 69 bits per transform + { 0, 5, 14 }, // 69 bits per transform + { 0, 6, 13 }, // 69 bits per transform + { 0, 7, 12 }, // 69 bits per transform + { 0, 8, 11 }, // 69 bits per transform + { 0, 9, 10 }, // 69 bits per transform + { 0, 10, 9 }, // 69 bits per transform + { 0, 11, 8 }, // 69 bits per transform + { 0, 12, 7 }, // 69 bits per transform + { 0, 13, 6 }, // 69 bits per transform + { 0, 14, 5 }, // 69 bits per transform + { 0, 15, 4 }, // 69 bits per transform + { 0, 16, 3 }, // 69 bits per transform + { 0, 17, 2 }, // 69 bits per transform + { 1, 1, 15 }, // 69 bits per transform + { 1, 2, 14 }, // 69 bits per transform + { 1, 3, 13 }, // 69 bits per transform + { 1, 4, 12 }, // 69 bits per transform + { 1, 5, 11 }, // 69 bits per transform + { 1, 6, 10 }, // 69 bits per transform + { 1, 7, 9 }, // 69 bits per transform + { 1, 8, 8 }, // 69 bits per transform + { 1, 9, 7 }, // 69 bits per transform + { 1, 10, 6 }, // 69 bits per transform + { 1, 11, 5 }, // 69 bits per transform + { 1, 12, 4 }, // 69 bits per transform + { 1, 13, 3 }, // 69 bits per transform + { 1, 14, 2 }, // 69 bits per transform + { 1, 15, 1 }, // 69 bits per transform + { 2, 0, 17 }, // 69 bits per transform + { 2, 1, 14 }, // 69 bits per transform + { 2, 2, 13 }, // 69 bits per transform + { 2, 3, 12 }, // 69 bits per transform + { 2, 4, 11 }, // 69 bits per transform + { 2, 5, 10 }, // 69 bits per transform + { 2, 6, 9 }, // 69 bits per transform + { 2, 7, 8 }, // 69 bits per transform + { 2, 8, 7 }, // 69 bits per transform + { 2, 9, 6 }, // 69 bits per transform + { 2, 10, 5 }, // 69 bits per transform + { 2, 11, 4 }, // 69 bits per transform + { 2, 12, 3 }, // 69 bits per transform + { 2, 13, 2 }, // 69 bits per transform + { 2, 14, 1 }, // 69 bits per transform + { 2, 17, 0 }, // 69 bits per transform + { 3, 0, 16 }, // 69 bits per transform + { 3, 1, 13 }, // 69 bits per transform + { 3, 2, 12 }, // 69 bits per transform + { 3, 3, 11 }, // 69 bits per transform + { 3, 4, 10 }, // 69 bits per transform + { 3, 5, 9 }, // 69 bits per transform + { 3, 6, 8 }, // 69 bits per transform + { 3, 7, 7 }, // 69 bits per transform + { 3, 8, 6 }, // 69 bits per transform + { 3, 9, 5 }, // 69 bits per transform + { 3, 10, 4 }, // 69 bits per transform + { 3, 11, 3 }, // 69 bits per transform + { 3, 12, 2 }, // 69 bits per transform + { 3, 13, 1 }, // 69 bits per transform + { 3, 16, 0 }, // 69 bits per transform + { 4, 0, 15 }, // 69 bits per transform + { 4, 1, 12 }, // 69 bits per transform + { 4, 2, 11 }, // 69 bits per transform + { 4, 3, 10 }, // 69 bits per transform + { 4, 4, 9 }, // 69 bits per transform + { 4, 5, 8 }, // 69 bits per transform + { 4, 6, 7 }, // 69 bits per transform + { 4, 7, 6 }, // 69 bits per transform + { 4, 8, 5 }, // 69 bits per transform + { 4, 9, 4 }, // 69 bits per transform + { 4, 10, 3 }, // 69 bits per transform + { 4, 11, 2 }, // 69 bits per transform + { 4, 12, 1 }, // 69 bits per transform + { 4, 15, 0 }, // 69 bits per transform + { 5, 0, 14 }, // 69 bits per transform + { 5, 1, 11 }, // 69 bits per transform + { 5, 2, 10 }, // 69 bits per transform + { 5, 3, 9 }, // 69 bits per transform + { 5, 4, 8 }, // 69 bits per transform + { 5, 5, 7 }, // 69 bits per transform + { 5, 6, 6 }, // 69 bits per transform + { 5, 7, 5 }, // 69 bits per transform + { 5, 8, 4 }, // 69 bits per transform + { 5, 9, 3 }, // 69 bits per transform + { 5, 10, 2 }, // 69 bits per transform + { 5, 11, 1 }, // 69 bits per transform + { 5, 14, 0 }, // 69 bits per transform + { 6, 0, 13 }, // 69 bits per transform + { 6, 1, 10 }, // 69 bits per transform + { 6, 2, 9 }, // 69 bits per transform + { 6, 3, 8 }, // 69 bits per transform + { 6, 4, 7 }, // 69 bits per transform + { 6, 5, 6 }, // 69 bits per transform + { 6, 6, 5 }, // 69 bits per transform + { 6, 7, 4 }, // 69 bits per transform + { 6, 8, 3 }, // 69 bits per transform + { 6, 9, 2 }, // 69 bits per transform + { 6, 10, 1 }, // 69 bits per transform + { 6, 13, 0 }, // 69 bits per transform + { 7, 0, 12 }, // 69 bits per transform + { 7, 1, 9 }, // 69 bits per transform + { 7, 2, 8 }, // 69 bits per transform + { 7, 3, 7 }, // 69 bits per transform + { 7, 4, 6 }, // 69 bits per transform + { 7, 5, 5 }, // 69 bits per transform + { 7, 6, 4 }, // 69 bits per transform + { 7, 7, 3 }, // 69 bits per transform + { 7, 8, 2 }, // 69 bits per transform + { 7, 9, 1 }, // 69 bits per transform + { 7, 12, 0 }, // 69 bits per transform + { 8, 0, 11 }, // 69 bits per transform + { 8, 1, 8 }, // 69 bits per transform + { 8, 2, 7 }, // 69 bits per transform + { 8, 3, 6 }, // 69 bits per transform + { 8, 4, 5 }, // 69 bits per transform + { 8, 5, 4 }, // 69 bits per transform + { 8, 6, 3 }, // 69 bits per transform + { 8, 7, 2 }, // 69 bits per transform + { 8, 8, 1 }, // 69 bits per transform + { 8, 11, 0 }, // 69 bits per transform + { 9, 0, 10 }, // 69 bits per transform + { 9, 1, 7 }, // 69 bits per transform + { 9, 2, 6 }, // 69 bits per transform + { 9, 3, 5 }, // 69 bits per transform + { 9, 4, 4 }, // 69 bits per transform + { 9, 5, 3 }, // 69 bits per transform + { 9, 6, 2 }, // 69 bits per transform + { 9, 7, 1 }, // 69 bits per transform + { 9, 10, 0 }, // 69 bits per transform + { 10, 0, 9 }, // 69 bits per transform + { 10, 1, 6 }, // 69 bits per transform + { 10, 2, 5 }, // 69 bits per transform + { 10, 3, 4 }, // 69 bits per transform + { 10, 4, 3 }, // 69 bits per transform + { 10, 5, 2 }, // 69 bits per transform + { 10, 6, 1 }, // 69 bits per transform + { 10, 9, 0 }, // 69 bits per transform + { 11, 0, 8 }, // 69 bits per transform + { 11, 1, 5 }, // 69 bits per transform + { 11, 2, 4 }, // 69 bits per transform + { 11, 3, 3 }, // 69 bits per transform + { 11, 4, 2 }, // 69 bits per transform + { 11, 5, 1 }, // 69 bits per transform + { 11, 8, 0 }, // 69 bits per transform + { 12, 0, 7 }, // 69 bits per transform + { 12, 1, 4 }, // 69 bits per transform + { 12, 2, 3 }, // 69 bits per transform + { 12, 3, 2 }, // 69 bits per transform + { 12, 4, 1 }, // 69 bits per transform + { 12, 7, 0 }, // 69 bits per transform + { 13, 0, 6 }, // 69 bits per transform + { 13, 1, 3 }, // 69 bits per transform + { 13, 2, 2 }, // 69 bits per transform + { 13, 3, 1 }, // 69 bits per transform + { 13, 6, 0 }, // 69 bits per transform + { 14, 0, 5 }, // 69 bits per transform + { 14, 1, 2 }, // 69 bits per transform + { 14, 2, 1 }, // 69 bits per transform + { 14, 5, 0 }, // 69 bits per transform + { 15, 0, 4 }, // 69 bits per transform + { 15, 1, 1 }, // 69 bits per transform + { 15, 4, 0 }, // 69 bits per transform + { 16, 0, 3 }, // 69 bits per transform + { 16, 3, 0 }, // 69 bits per transform + { 17, 0, 2 }, // 69 bits per transform + { 17, 2, 0 }, // 69 bits per transform + { 0, 3, 17 }, // 72 bits per transform + { 0, 4, 16 }, // 72 bits per transform + { 0, 5, 15 }, // 72 bits per transform + { 0, 6, 14 }, // 72 bits per transform + { 0, 7, 13 }, // 72 bits per transform + { 0, 8, 12 }, // 72 bits per transform + { 0, 9, 11 }, // 72 bits per transform + { 0, 10, 10 }, // 72 bits per transform + { 0, 11, 9 }, // 72 bits per transform + { 0, 12, 8 }, // 72 bits per transform + { 0, 13, 7 }, // 72 bits per transform + { 0, 14, 6 }, // 72 bits per transform + { 0, 15, 5 }, // 72 bits per transform + { 0, 16, 4 }, // 72 bits per transform + { 0, 17, 3 }, // 72 bits per transform + { 1, 1, 16 }, // 72 bits per transform + { 1, 2, 15 }, // 72 bits per transform + { 1, 3, 14 }, // 72 bits per transform + { 1, 4, 13 }, // 72 bits per transform + { 1, 5, 12 }, // 72 bits per transform + { 1, 6, 11 }, // 72 bits per transform + { 1, 7, 10 }, // 72 bits per transform + { 1, 8, 9 }, // 72 bits per transform + { 1, 9, 8 }, // 72 bits per transform + { 1, 10, 7 }, // 72 bits per transform + { 1, 11, 6 }, // 72 bits per transform + { 1, 12, 5 }, // 72 bits per transform + { 1, 13, 4 }, // 72 bits per transform + { 1, 14, 3 }, // 72 bits per transform + { 1, 15, 2 }, // 72 bits per transform + { 1, 16, 1 }, // 72 bits per transform + { 2, 1, 15 }, // 72 bits per transform + { 2, 2, 14 }, // 72 bits per transform + { 2, 3, 13 }, // 72 bits per transform + { 2, 4, 12 }, // 72 bits per transform + { 2, 5, 11 }, // 72 bits per transform + { 2, 6, 10 }, // 72 bits per transform + { 2, 7, 9 }, // 72 bits per transform + { 2, 8, 8 }, // 72 bits per transform + { 2, 9, 7 }, // 72 bits per transform + { 2, 10, 6 }, // 72 bits per transform + { 2, 11, 5 }, // 72 bits per transform + { 2, 12, 4 }, // 72 bits per transform + { 2, 13, 3 }, // 72 bits per transform + { 2, 14, 2 }, // 72 bits per transform + { 2, 15, 1 }, // 72 bits per transform + { 3, 0, 17 }, // 72 bits per transform + { 3, 1, 14 }, // 72 bits per transform + { 3, 2, 13 }, // 72 bits per transform + { 3, 3, 12 }, // 72 bits per transform + { 3, 4, 11 }, // 72 bits per transform + { 3, 5, 10 }, // 72 bits per transform + { 3, 6, 9 }, // 72 bits per transform + { 3, 7, 8 }, // 72 bits per transform + { 3, 8, 7 }, // 72 bits per transform + { 3, 9, 6 }, // 72 bits per transform + { 3, 10, 5 }, // 72 bits per transform + { 3, 11, 4 }, // 72 bits per transform + { 3, 12, 3 }, // 72 bits per transform + { 3, 13, 2 }, // 72 bits per transform + { 3, 14, 1 }, // 72 bits per transform + { 3, 17, 0 }, // 72 bits per transform + { 4, 0, 16 }, // 72 bits per transform + { 4, 1, 13 }, // 72 bits per transform + { 4, 2, 12 }, // 72 bits per transform + { 4, 3, 11 }, // 72 bits per transform + { 4, 4, 10 }, // 72 bits per transform + { 4, 5, 9 }, // 72 bits per transform + { 4, 6, 8 }, // 72 bits per transform + { 4, 7, 7 }, // 72 bits per transform + { 4, 8, 6 }, // 72 bits per transform + { 4, 9, 5 }, // 72 bits per transform + { 4, 10, 4 }, // 72 bits per transform + { 4, 11, 3 }, // 72 bits per transform + { 4, 12, 2 }, // 72 bits per transform + { 4, 13, 1 }, // 72 bits per transform + { 4, 16, 0 }, // 72 bits per transform + { 5, 0, 15 }, // 72 bits per transform + { 5, 1, 12 }, // 72 bits per transform + { 5, 2, 11 }, // 72 bits per transform + { 5, 3, 10 }, // 72 bits per transform + { 5, 4, 9 }, // 72 bits per transform + { 5, 5, 8 }, // 72 bits per transform + { 5, 6, 7 }, // 72 bits per transform + { 5, 7, 6 }, // 72 bits per transform + { 5, 8, 5 }, // 72 bits per transform + { 5, 9, 4 }, // 72 bits per transform + { 5, 10, 3 }, // 72 bits per transform + { 5, 11, 2 }, // 72 bits per transform + { 5, 12, 1 }, // 72 bits per transform + { 5, 15, 0 }, // 72 bits per transform + { 6, 0, 14 }, // 72 bits per transform + { 6, 1, 11 }, // 72 bits per transform + { 6, 2, 10 }, // 72 bits per transform + { 6, 3, 9 }, // 72 bits per transform + { 6, 4, 8 }, // 72 bits per transform + { 6, 5, 7 }, // 72 bits per transform + { 6, 6, 6 }, // 72 bits per transform + { 6, 7, 5 }, // 72 bits per transform + { 6, 8, 4 }, // 72 bits per transform + { 6, 9, 3 }, // 72 bits per transform + { 6, 10, 2 }, // 72 bits per transform + { 6, 11, 1 }, // 72 bits per transform + { 6, 14, 0 }, // 72 bits per transform + { 7, 0, 13 }, // 72 bits per transform + { 7, 1, 10 }, // 72 bits per transform + { 7, 2, 9 }, // 72 bits per transform + { 7, 3, 8 }, // 72 bits per transform + { 7, 4, 7 }, // 72 bits per transform + { 7, 5, 6 }, // 72 bits per transform + { 7, 6, 5 }, // 72 bits per transform + { 7, 7, 4 }, // 72 bits per transform + { 7, 8, 3 }, // 72 bits per transform + { 7, 9, 2 }, // 72 bits per transform + { 7, 10, 1 }, // 72 bits per transform + { 7, 13, 0 }, // 72 bits per transform + { 8, 0, 12 }, // 72 bits per transform + { 8, 1, 9 }, // 72 bits per transform + { 8, 2, 8 }, // 72 bits per transform + { 8, 3, 7 }, // 72 bits per transform + { 8, 4, 6 }, // 72 bits per transform + { 8, 5, 5 }, // 72 bits per transform + { 8, 6, 4 }, // 72 bits per transform + { 8, 7, 3 }, // 72 bits per transform + { 8, 8, 2 }, // 72 bits per transform + { 8, 9, 1 }, // 72 bits per transform + { 8, 12, 0 }, // 72 bits per transform + { 9, 0, 11 }, // 72 bits per transform + { 9, 1, 8 }, // 72 bits per transform + { 9, 2, 7 }, // 72 bits per transform + { 9, 3, 6 }, // 72 bits per transform + { 9, 4, 5 }, // 72 bits per transform + { 9, 5, 4 }, // 72 bits per transform + { 9, 6, 3 }, // 72 bits per transform + { 9, 7, 2 }, // 72 bits per transform + { 9, 8, 1 }, // 72 bits per transform + { 9, 11, 0 }, // 72 bits per transform + { 10, 0, 10 }, // 72 bits per transform + { 10, 1, 7 }, // 72 bits per transform + { 10, 2, 6 }, // 72 bits per transform + { 10, 3, 5 }, // 72 bits per transform + { 10, 4, 4 }, // 72 bits per transform + { 10, 5, 3 }, // 72 bits per transform + { 10, 6, 2 }, // 72 bits per transform + { 10, 7, 1 }, // 72 bits per transform + { 10, 10, 0 }, // 72 bits per transform + { 11, 0, 9 }, // 72 bits per transform + { 11, 1, 6 }, // 72 bits per transform + { 11, 2, 5 }, // 72 bits per transform + { 11, 3, 4 }, // 72 bits per transform + { 11, 4, 3 }, // 72 bits per transform + { 11, 5, 2 }, // 72 bits per transform + { 11, 6, 1 }, // 72 bits per transform + { 11, 9, 0 }, // 72 bits per transform + { 12, 0, 8 }, // 72 bits per transform + { 12, 1, 5 }, // 72 bits per transform + { 12, 2, 4 }, // 72 bits per transform + { 12, 3, 3 }, // 72 bits per transform + { 12, 4, 2 }, // 72 bits per transform + { 12, 5, 1 }, // 72 bits per transform + { 12, 8, 0 }, // 72 bits per transform + { 13, 0, 7 }, // 72 bits per transform + { 13, 1, 4 }, // 72 bits per transform + { 13, 2, 3 }, // 72 bits per transform + { 13, 3, 2 }, // 72 bits per transform + { 13, 4, 1 }, // 72 bits per transform + { 13, 7, 0 }, // 72 bits per transform + { 14, 0, 6 }, // 72 bits per transform + { 14, 1, 3 }, // 72 bits per transform + { 14, 2, 2 }, // 72 bits per transform + { 14, 3, 1 }, // 72 bits per transform + { 14, 6, 0 }, // 72 bits per transform + { 15, 0, 5 }, // 72 bits per transform + { 15, 1, 2 }, // 72 bits per transform + { 15, 2, 1 }, // 72 bits per transform + { 15, 5, 0 }, // 72 bits per transform + { 16, 0, 4 }, // 72 bits per transform + { 16, 1, 1 }, // 72 bits per transform + { 16, 4, 0 }, // 72 bits per transform + { 17, 0, 3 }, // 72 bits per transform + { 17, 3, 0 }, // 72 bits per transform + { 0, 4, 17 }, // 75 bits per transform + { 0, 5, 16 }, // 75 bits per transform + { 0, 6, 15 }, // 75 bits per transform + { 0, 7, 14 }, // 75 bits per transform + { 0, 8, 13 }, // 75 bits per transform + { 0, 9, 12 }, // 75 bits per transform + { 0, 10, 11 }, // 75 bits per transform + { 0, 11, 10 }, // 75 bits per transform + { 0, 12, 9 }, // 75 bits per transform + { 0, 13, 8 }, // 75 bits per transform + { 0, 14, 7 }, // 75 bits per transform + { 0, 15, 6 }, // 75 bits per transform + { 0, 16, 5 }, // 75 bits per transform + { 0, 17, 4 }, // 75 bits per transform + { 1, 1, 17 }, // 75 bits per transform + { 1, 2, 16 }, // 75 bits per transform + { 1, 3, 15 }, // 75 bits per transform + { 1, 4, 14 }, // 75 bits per transform + { 1, 5, 13 }, // 75 bits per transform + { 1, 6, 12 }, // 75 bits per transform + { 1, 7, 11 }, // 75 bits per transform + { 1, 8, 10 }, // 75 bits per transform + { 1, 9, 9 }, // 75 bits per transform + { 1, 10, 8 }, // 75 bits per transform + { 1, 11, 7 }, // 75 bits per transform + { 1, 12, 6 }, // 75 bits per transform + { 1, 13, 5 }, // 75 bits per transform + { 1, 14, 4 }, // 75 bits per transform + { 1, 15, 3 }, // 75 bits per transform + { 1, 16, 2 }, // 75 bits per transform + { 1, 17, 1 }, // 75 bits per transform + { 2, 1, 16 }, // 75 bits per transform + { 2, 2, 15 }, // 75 bits per transform + { 2, 3, 14 }, // 75 bits per transform + { 2, 4, 13 }, // 75 bits per transform + { 2, 5, 12 }, // 75 bits per transform + { 2, 6, 11 }, // 75 bits per transform + { 2, 7, 10 }, // 75 bits per transform + { 2, 8, 9 }, // 75 bits per transform + { 2, 9, 8 }, // 75 bits per transform + { 2, 10, 7 }, // 75 bits per transform + { 2, 11, 6 }, // 75 bits per transform + { 2, 12, 5 }, // 75 bits per transform + { 2, 13, 4 }, // 75 bits per transform + { 2, 14, 3 }, // 75 bits per transform + { 2, 15, 2 }, // 75 bits per transform + { 2, 16, 1 }, // 75 bits per transform + { 3, 1, 15 }, // 75 bits per transform + { 3, 2, 14 }, // 75 bits per transform + { 3, 3, 13 }, // 75 bits per transform + { 3, 4, 12 }, // 75 bits per transform + { 3, 5, 11 }, // 75 bits per transform + { 3, 6, 10 }, // 75 bits per transform + { 3, 7, 9 }, // 75 bits per transform + { 3, 8, 8 }, // 75 bits per transform + { 3, 9, 7 }, // 75 bits per transform + { 3, 10, 6 }, // 75 bits per transform + { 3, 11, 5 }, // 75 bits per transform + { 3, 12, 4 }, // 75 bits per transform + { 3, 13, 3 }, // 75 bits per transform + { 3, 14, 2 }, // 75 bits per transform + { 3, 15, 1 }, // 75 bits per transform + { 4, 0, 17 }, // 75 bits per transform + { 4, 1, 14 }, // 75 bits per transform + { 4, 2, 13 }, // 75 bits per transform + { 4, 3, 12 }, // 75 bits per transform + { 4, 4, 11 }, // 75 bits per transform + { 4, 5, 10 }, // 75 bits per transform + { 4, 6, 9 }, // 75 bits per transform + { 4, 7, 8 }, // 75 bits per transform + { 4, 8, 7 }, // 75 bits per transform + { 4, 9, 6 }, // 75 bits per transform + { 4, 10, 5 }, // 75 bits per transform + { 4, 11, 4 }, // 75 bits per transform + { 4, 12, 3 }, // 75 bits per transform + { 4, 13, 2 }, // 75 bits per transform + { 4, 14, 1 }, // 75 bits per transform + { 4, 17, 0 }, // 75 bits per transform + { 5, 0, 16 }, // 75 bits per transform + { 5, 1, 13 }, // 75 bits per transform + { 5, 2, 12 }, // 75 bits per transform + { 5, 3, 11 }, // 75 bits per transform + { 5, 4, 10 }, // 75 bits per transform + { 5, 5, 9 }, // 75 bits per transform + { 5, 6, 8 }, // 75 bits per transform + { 5, 7, 7 }, // 75 bits per transform + { 5, 8, 6 }, // 75 bits per transform + { 5, 9, 5 }, // 75 bits per transform + { 5, 10, 4 }, // 75 bits per transform + { 5, 11, 3 }, // 75 bits per transform + { 5, 12, 2 }, // 75 bits per transform + { 5, 13, 1 }, // 75 bits per transform + { 5, 16, 0 }, // 75 bits per transform + { 6, 0, 15 }, // 75 bits per transform + { 6, 1, 12 }, // 75 bits per transform + { 6, 2, 11 }, // 75 bits per transform + { 6, 3, 10 }, // 75 bits per transform + { 6, 4, 9 }, // 75 bits per transform + { 6, 5, 8 }, // 75 bits per transform + { 6, 6, 7 }, // 75 bits per transform + { 6, 7, 6 }, // 75 bits per transform + { 6, 8, 5 }, // 75 bits per transform + { 6, 9, 4 }, // 75 bits per transform + { 6, 10, 3 }, // 75 bits per transform + { 6, 11, 2 }, // 75 bits per transform + { 6, 12, 1 }, // 75 bits per transform + { 6, 15, 0 }, // 75 bits per transform + { 7, 0, 14 }, // 75 bits per transform + { 7, 1, 11 }, // 75 bits per transform + { 7, 2, 10 }, // 75 bits per transform + { 7, 3, 9 }, // 75 bits per transform + { 7, 4, 8 }, // 75 bits per transform + { 7, 5, 7 }, // 75 bits per transform + { 7, 6, 6 }, // 75 bits per transform + { 7, 7, 5 }, // 75 bits per transform + { 7, 8, 4 }, // 75 bits per transform + { 7, 9, 3 }, // 75 bits per transform + { 7, 10, 2 }, // 75 bits per transform + { 7, 11, 1 }, // 75 bits per transform + { 7, 14, 0 }, // 75 bits per transform + { 8, 0, 13 }, // 75 bits per transform + { 8, 1, 10 }, // 75 bits per transform + { 8, 2, 9 }, // 75 bits per transform + { 8, 3, 8 }, // 75 bits per transform + { 8, 4, 7 }, // 75 bits per transform + { 8, 5, 6 }, // 75 bits per transform + { 8, 6, 5 }, // 75 bits per transform + { 8, 7, 4 }, // 75 bits per transform + { 8, 8, 3 }, // 75 bits per transform + { 8, 9, 2 }, // 75 bits per transform + { 8, 10, 1 }, // 75 bits per transform + { 8, 13, 0 }, // 75 bits per transform + { 9, 0, 12 }, // 75 bits per transform + { 9, 1, 9 }, // 75 bits per transform + { 9, 2, 8 }, // 75 bits per transform + { 9, 3, 7 }, // 75 bits per transform + { 9, 4, 6 }, // 75 bits per transform + { 9, 5, 5 }, // 75 bits per transform + { 9, 6, 4 }, // 75 bits per transform + { 9, 7, 3 }, // 75 bits per transform + { 9, 8, 2 }, // 75 bits per transform + { 9, 9, 1 }, // 75 bits per transform + { 9, 12, 0 }, // 75 bits per transform + { 10, 0, 11 }, // 75 bits per transform + { 10, 1, 8 }, // 75 bits per transform + { 10, 2, 7 }, // 75 bits per transform + { 10, 3, 6 }, // 75 bits per transform + { 10, 4, 5 }, // 75 bits per transform + { 10, 5, 4 }, // 75 bits per transform + { 10, 6, 3 }, // 75 bits per transform + { 10, 7, 2 }, // 75 bits per transform + { 10, 8, 1 }, // 75 bits per transform + { 10, 11, 0 }, // 75 bits per transform + { 11, 0, 10 }, // 75 bits per transform + { 11, 1, 7 }, // 75 bits per transform + { 11, 2, 6 }, // 75 bits per transform + { 11, 3, 5 }, // 75 bits per transform + { 11, 4, 4 }, // 75 bits per transform + { 11, 5, 3 }, // 75 bits per transform + { 11, 6, 2 }, // 75 bits per transform + { 11, 7, 1 }, // 75 bits per transform + { 11, 10, 0 }, // 75 bits per transform + { 12, 0, 9 }, // 75 bits per transform + { 12, 1, 6 }, // 75 bits per transform + { 12, 2, 5 }, // 75 bits per transform + { 12, 3, 4 }, // 75 bits per transform + { 12, 4, 3 }, // 75 bits per transform + { 12, 5, 2 }, // 75 bits per transform + { 12, 6, 1 }, // 75 bits per transform + { 12, 9, 0 }, // 75 bits per transform + { 13, 0, 8 }, // 75 bits per transform + { 13, 1, 5 }, // 75 bits per transform + { 13, 2, 4 }, // 75 bits per transform + { 13, 3, 3 }, // 75 bits per transform + { 13, 4, 2 }, // 75 bits per transform + { 13, 5, 1 }, // 75 bits per transform + { 13, 8, 0 }, // 75 bits per transform + { 14, 0, 7 }, // 75 bits per transform + { 14, 1, 4 }, // 75 bits per transform + { 14, 2, 3 }, // 75 bits per transform + { 14, 3, 2 }, // 75 bits per transform + { 14, 4, 1 }, // 75 bits per transform + { 14, 7, 0 }, // 75 bits per transform + { 15, 0, 6 }, // 75 bits per transform + { 15, 1, 3 }, // 75 bits per transform + { 15, 2, 2 }, // 75 bits per transform + { 15, 3, 1 }, // 75 bits per transform + { 15, 6, 0 }, // 75 bits per transform + { 16, 0, 5 }, // 75 bits per transform + { 16, 1, 2 }, // 75 bits per transform + { 16, 2, 1 }, // 75 bits per transform + { 16, 5, 0 }, // 75 bits per transform + { 17, 0, 4 }, // 75 bits per transform + { 17, 1, 1 }, // 75 bits per transform + { 17, 4, 0 }, // 75 bits per transform + { 0, 5, 17 }, // 78 bits per transform + { 0, 6, 16 }, // 78 bits per transform + { 0, 7, 15 }, // 78 bits per transform + { 0, 8, 14 }, // 78 bits per transform + { 0, 9, 13 }, // 78 bits per transform + { 0, 10, 12 }, // 78 bits per transform + { 0, 11, 11 }, // 78 bits per transform + { 0, 12, 10 }, // 78 bits per transform + { 0, 13, 9 }, // 78 bits per transform + { 0, 14, 8 }, // 78 bits per transform + { 0, 15, 7 }, // 78 bits per transform + { 0, 16, 6 }, // 78 bits per transform + { 0, 17, 5 }, // 78 bits per transform + { 1, 2, 17 }, // 78 bits per transform + { 1, 3, 16 }, // 78 bits per transform + { 1, 4, 15 }, // 78 bits per transform + { 1, 5, 14 }, // 78 bits per transform + { 1, 6, 13 }, // 78 bits per transform + { 1, 7, 12 }, // 78 bits per transform + { 1, 8, 11 }, // 78 bits per transform + { 1, 9, 10 }, // 78 bits per transform + { 1, 10, 9 }, // 78 bits per transform + { 1, 11, 8 }, // 78 bits per transform + { 1, 12, 7 }, // 78 bits per transform + { 1, 13, 6 }, // 78 bits per transform + { 1, 14, 5 }, // 78 bits per transform + { 1, 15, 4 }, // 78 bits per transform + { 1, 16, 3 }, // 78 bits per transform + { 1, 17, 2 }, // 78 bits per transform + { 2, 1, 17 }, // 78 bits per transform + { 2, 2, 16 }, // 78 bits per transform + { 2, 3, 15 }, // 78 bits per transform + { 2, 4, 14 }, // 78 bits per transform + { 2, 5, 13 }, // 78 bits per transform + { 2, 6, 12 }, // 78 bits per transform + { 2, 7, 11 }, // 78 bits per transform + { 2, 8, 10 }, // 78 bits per transform + { 2, 9, 9 }, // 78 bits per transform + { 2, 10, 8 }, // 78 bits per transform + { 2, 11, 7 }, // 78 bits per transform + { 2, 12, 6 }, // 78 bits per transform + { 2, 13, 5 }, // 78 bits per transform + { 2, 14, 4 }, // 78 bits per transform + { 2, 15, 3 }, // 78 bits per transform + { 2, 16, 2 }, // 78 bits per transform + { 2, 17, 1 }, // 78 bits per transform + { 3, 1, 16 }, // 78 bits per transform + { 3, 2, 15 }, // 78 bits per transform + { 3, 3, 14 }, // 78 bits per transform + { 3, 4, 13 }, // 78 bits per transform + { 3, 5, 12 }, // 78 bits per transform + { 3, 6, 11 }, // 78 bits per transform + { 3, 7, 10 }, // 78 bits per transform + { 3, 8, 9 }, // 78 bits per transform + { 3, 9, 8 }, // 78 bits per transform + { 3, 10, 7 }, // 78 bits per transform + { 3, 11, 6 }, // 78 bits per transform + { 3, 12, 5 }, // 78 bits per transform + { 3, 13, 4 }, // 78 bits per transform + { 3, 14, 3 }, // 78 bits per transform + { 3, 15, 2 }, // 78 bits per transform + { 3, 16, 1 }, // 78 bits per transform + { 4, 1, 15 }, // 78 bits per transform + { 4, 2, 14 }, // 78 bits per transform + { 4, 3, 13 }, // 78 bits per transform + { 4, 4, 12 }, // 78 bits per transform + { 4, 5, 11 }, // 78 bits per transform + { 4, 6, 10 }, // 78 bits per transform + { 4, 7, 9 }, // 78 bits per transform + { 4, 8, 8 }, // 78 bits per transform + { 4, 9, 7 }, // 78 bits per transform + { 4, 10, 6 }, // 78 bits per transform + { 4, 11, 5 }, // 78 bits per transform + { 4, 12, 4 }, // 78 bits per transform + { 4, 13, 3 }, // 78 bits per transform + { 4, 14, 2 }, // 78 bits per transform + { 4, 15, 1 }, // 78 bits per transform + { 5, 0, 17 }, // 78 bits per transform + { 5, 1, 14 }, // 78 bits per transform + { 5, 2, 13 }, // 78 bits per transform + { 5, 3, 12 }, // 78 bits per transform + { 5, 4, 11 }, // 78 bits per transform + { 5, 5, 10 }, // 78 bits per transform + { 5, 6, 9 }, // 78 bits per transform + { 5, 7, 8 }, // 78 bits per transform + { 5, 8, 7 }, // 78 bits per transform + { 5, 9, 6 }, // 78 bits per transform + { 5, 10, 5 }, // 78 bits per transform + { 5, 11, 4 }, // 78 bits per transform + { 5, 12, 3 }, // 78 bits per transform + { 5, 13, 2 }, // 78 bits per transform + { 5, 14, 1 }, // 78 bits per transform + { 5, 17, 0 }, // 78 bits per transform + { 6, 0, 16 }, // 78 bits per transform + { 6, 1, 13 }, // 78 bits per transform + { 6, 2, 12 }, // 78 bits per transform + { 6, 3, 11 }, // 78 bits per transform + { 6, 4, 10 }, // 78 bits per transform + { 6, 5, 9 }, // 78 bits per transform + { 6, 6, 8 }, // 78 bits per transform + { 6, 7, 7 }, // 78 bits per transform + { 6, 8, 6 }, // 78 bits per transform + { 6, 9, 5 }, // 78 bits per transform + { 6, 10, 4 }, // 78 bits per transform + { 6, 11, 3 }, // 78 bits per transform + { 6, 12, 2 }, // 78 bits per transform + { 6, 13, 1 }, // 78 bits per transform + { 6, 16, 0 }, // 78 bits per transform + { 7, 0, 15 }, // 78 bits per transform + { 7, 1, 12 }, // 78 bits per transform + { 7, 2, 11 }, // 78 bits per transform + { 7, 3, 10 }, // 78 bits per transform + { 7, 4, 9 }, // 78 bits per transform + { 7, 5, 8 }, // 78 bits per transform + { 7, 6, 7 }, // 78 bits per transform + { 7, 7, 6 }, // 78 bits per transform + { 7, 8, 5 }, // 78 bits per transform + { 7, 9, 4 }, // 78 bits per transform + { 7, 10, 3 }, // 78 bits per transform + { 7, 11, 2 }, // 78 bits per transform + { 7, 12, 1 }, // 78 bits per transform + { 7, 15, 0 }, // 78 bits per transform + { 8, 0, 14 }, // 78 bits per transform + { 8, 1, 11 }, // 78 bits per transform + { 8, 2, 10 }, // 78 bits per transform + { 8, 3, 9 }, // 78 bits per transform + { 8, 4, 8 }, // 78 bits per transform + { 8, 5, 7 }, // 78 bits per transform + { 8, 6, 6 }, // 78 bits per transform + { 8, 7, 5 }, // 78 bits per transform + { 8, 8, 4 }, // 78 bits per transform + { 8, 9, 3 }, // 78 bits per transform + { 8, 10, 2 }, // 78 bits per transform + { 8, 11, 1 }, // 78 bits per transform + { 8, 14, 0 }, // 78 bits per transform + { 9, 0, 13 }, // 78 bits per transform + { 9, 1, 10 }, // 78 bits per transform + { 9, 2, 9 }, // 78 bits per transform + { 9, 3, 8 }, // 78 bits per transform + { 9, 4, 7 }, // 78 bits per transform + { 9, 5, 6 }, // 78 bits per transform + { 9, 6, 5 }, // 78 bits per transform + { 9, 7, 4 }, // 78 bits per transform + { 9, 8, 3 }, // 78 bits per transform + { 9, 9, 2 }, // 78 bits per transform + { 9, 10, 1 }, // 78 bits per transform + { 9, 13, 0 }, // 78 bits per transform + { 10, 0, 12 }, // 78 bits per transform + { 10, 1, 9 }, // 78 bits per transform + { 10, 2, 8 }, // 78 bits per transform + { 10, 3, 7 }, // 78 bits per transform + { 10, 4, 6 }, // 78 bits per transform + { 10, 5, 5 }, // 78 bits per transform + { 10, 6, 4 }, // 78 bits per transform + { 10, 7, 3 }, // 78 bits per transform + { 10, 8, 2 }, // 78 bits per transform + { 10, 9, 1 }, // 78 bits per transform + { 10, 12, 0 }, // 78 bits per transform + { 11, 0, 11 }, // 78 bits per transform + { 11, 1, 8 }, // 78 bits per transform + { 11, 2, 7 }, // 78 bits per transform + { 11, 3, 6 }, // 78 bits per transform + { 11, 4, 5 }, // 78 bits per transform + { 11, 5, 4 }, // 78 bits per transform + { 11, 6, 3 }, // 78 bits per transform + { 11, 7, 2 }, // 78 bits per transform + { 11, 8, 1 }, // 78 bits per transform + { 11, 11, 0 }, // 78 bits per transform + { 12, 0, 10 }, // 78 bits per transform + { 12, 1, 7 }, // 78 bits per transform + { 12, 2, 6 }, // 78 bits per transform + { 12, 3, 5 }, // 78 bits per transform + { 12, 4, 4 }, // 78 bits per transform + { 12, 5, 3 }, // 78 bits per transform + { 12, 6, 2 }, // 78 bits per transform + { 12, 7, 1 }, // 78 bits per transform + { 12, 10, 0 }, // 78 bits per transform + { 13, 0, 9 }, // 78 bits per transform + { 13, 1, 6 }, // 78 bits per transform + { 13, 2, 5 }, // 78 bits per transform + { 13, 3, 4 }, // 78 bits per transform + { 13, 4, 3 }, // 78 bits per transform + { 13, 5, 2 }, // 78 bits per transform + { 13, 6, 1 }, // 78 bits per transform + { 13, 9, 0 }, // 78 bits per transform + { 14, 0, 8 }, // 78 bits per transform + { 14, 1, 5 }, // 78 bits per transform + { 14, 2, 4 }, // 78 bits per transform + { 14, 3, 3 }, // 78 bits per transform + { 14, 4, 2 }, // 78 bits per transform + { 14, 5, 1 }, // 78 bits per transform + { 14, 8, 0 }, // 78 bits per transform + { 15, 0, 7 }, // 78 bits per transform + { 15, 1, 4 }, // 78 bits per transform + { 15, 2, 3 }, // 78 bits per transform + { 15, 3, 2 }, // 78 bits per transform + { 15, 4, 1 }, // 78 bits per transform + { 15, 7, 0 }, // 78 bits per transform + { 16, 0, 6 }, // 78 bits per transform + { 16, 1, 3 }, // 78 bits per transform + { 16, 2, 2 }, // 78 bits per transform + { 16, 3, 1 }, // 78 bits per transform + { 16, 6, 0 }, // 78 bits per transform + { 17, 0, 5 }, // 78 bits per transform + { 17, 1, 2 }, // 78 bits per transform + { 17, 2, 1 }, // 78 bits per transform + { 17, 5, 0 }, // 78 bits per transform + { 0, 6, 17 }, // 81 bits per transform + { 0, 7, 16 }, // 81 bits per transform + { 0, 8, 15 }, // 81 bits per transform + { 0, 9, 14 }, // 81 bits per transform + { 0, 10, 13 }, // 81 bits per transform + { 0, 11, 12 }, // 81 bits per transform + { 0, 12, 11 }, // 81 bits per transform + { 0, 13, 10 }, // 81 bits per transform + { 0, 14, 9 }, // 81 bits per transform + { 0, 15, 8 }, // 81 bits per transform + { 0, 16, 7 }, // 81 bits per transform + { 0, 17, 6 }, // 81 bits per transform + { 1, 3, 17 }, // 81 bits per transform + { 1, 4, 16 }, // 81 bits per transform + { 1, 5, 15 }, // 81 bits per transform + { 1, 6, 14 }, // 81 bits per transform + { 1, 7, 13 }, // 81 bits per transform + { 1, 8, 12 }, // 81 bits per transform + { 1, 9, 11 }, // 81 bits per transform + { 1, 10, 10 }, // 81 bits per transform + { 1, 11, 9 }, // 81 bits per transform + { 1, 12, 8 }, // 81 bits per transform + { 1, 13, 7 }, // 81 bits per transform + { 1, 14, 6 }, // 81 bits per transform + { 1, 15, 5 }, // 81 bits per transform + { 1, 16, 4 }, // 81 bits per transform + { 1, 17, 3 }, // 81 bits per transform + { 2, 2, 17 }, // 81 bits per transform + { 2, 3, 16 }, // 81 bits per transform + { 2, 4, 15 }, // 81 bits per transform + { 2, 5, 14 }, // 81 bits per transform + { 2, 6, 13 }, // 81 bits per transform + { 2, 7, 12 }, // 81 bits per transform + { 2, 8, 11 }, // 81 bits per transform + { 2, 9, 10 }, // 81 bits per transform + { 2, 10, 9 }, // 81 bits per transform + { 2, 11, 8 }, // 81 bits per transform + { 2, 12, 7 }, // 81 bits per transform + { 2, 13, 6 }, // 81 bits per transform + { 2, 14, 5 }, // 81 bits per transform + { 2, 15, 4 }, // 81 bits per transform + { 2, 16, 3 }, // 81 bits per transform + { 2, 17, 2 }, // 81 bits per transform + { 3, 1, 17 }, // 81 bits per transform + { 3, 2, 16 }, // 81 bits per transform + { 3, 3, 15 }, // 81 bits per transform + { 3, 4, 14 }, // 81 bits per transform + { 3, 5, 13 }, // 81 bits per transform + { 3, 6, 12 }, // 81 bits per transform + { 3, 7, 11 }, // 81 bits per transform + { 3, 8, 10 }, // 81 bits per transform + { 3, 9, 9 }, // 81 bits per transform + { 3, 10, 8 }, // 81 bits per transform + { 3, 11, 7 }, // 81 bits per transform + { 3, 12, 6 }, // 81 bits per transform + { 3, 13, 5 }, // 81 bits per transform + { 3, 14, 4 }, // 81 bits per transform + { 3, 15, 3 }, // 81 bits per transform + { 3, 16, 2 }, // 81 bits per transform + { 3, 17, 1 }, // 81 bits per transform + { 4, 1, 16 }, // 81 bits per transform + { 4, 2, 15 }, // 81 bits per transform + { 4, 3, 14 }, // 81 bits per transform + { 4, 4, 13 }, // 81 bits per transform + { 4, 5, 12 }, // 81 bits per transform + { 4, 6, 11 }, // 81 bits per transform + { 4, 7, 10 }, // 81 bits per transform + { 4, 8, 9 }, // 81 bits per transform + { 4, 9, 8 }, // 81 bits per transform + { 4, 10, 7 }, // 81 bits per transform + { 4, 11, 6 }, // 81 bits per transform + { 4, 12, 5 }, // 81 bits per transform + { 4, 13, 4 }, // 81 bits per transform + { 4, 14, 3 }, // 81 bits per transform + { 4, 15, 2 }, // 81 bits per transform + { 4, 16, 1 }, // 81 bits per transform + { 5, 1, 15 }, // 81 bits per transform + { 5, 2, 14 }, // 81 bits per transform + { 5, 3, 13 }, // 81 bits per transform + { 5, 4, 12 }, // 81 bits per transform + { 5, 5, 11 }, // 81 bits per transform + { 5, 6, 10 }, // 81 bits per transform + { 5, 7, 9 }, // 81 bits per transform + { 5, 8, 8 }, // 81 bits per transform + { 5, 9, 7 }, // 81 bits per transform + { 5, 10, 6 }, // 81 bits per transform + { 5, 11, 5 }, // 81 bits per transform + { 5, 12, 4 }, // 81 bits per transform + { 5, 13, 3 }, // 81 bits per transform + { 5, 14, 2 }, // 81 bits per transform + { 5, 15, 1 }, // 81 bits per transform + { 6, 0, 17 }, // 81 bits per transform + { 6, 1, 14 }, // 81 bits per transform + { 6, 2, 13 }, // 81 bits per transform + { 6, 3, 12 }, // 81 bits per transform + { 6, 4, 11 }, // 81 bits per transform + { 6, 5, 10 }, // 81 bits per transform + { 6, 6, 9 }, // 81 bits per transform + { 6, 7, 8 }, // 81 bits per transform + { 6, 8, 7 }, // 81 bits per transform + { 6, 9, 6 }, // 81 bits per transform + { 6, 10, 5 }, // 81 bits per transform + { 6, 11, 4 }, // 81 bits per transform + { 6, 12, 3 }, // 81 bits per transform + { 6, 13, 2 }, // 81 bits per transform + { 6, 14, 1 }, // 81 bits per transform + { 6, 17, 0 }, // 81 bits per transform + { 7, 0, 16 }, // 81 bits per transform + { 7, 1, 13 }, // 81 bits per transform + { 7, 2, 12 }, // 81 bits per transform + { 7, 3, 11 }, // 81 bits per transform + { 7, 4, 10 }, // 81 bits per transform + { 7, 5, 9 }, // 81 bits per transform + { 7, 6, 8 }, // 81 bits per transform + { 7, 7, 7 }, // 81 bits per transform + { 7, 8, 6 }, // 81 bits per transform + { 7, 9, 5 }, // 81 bits per transform + { 7, 10, 4 }, // 81 bits per transform + { 7, 11, 3 }, // 81 bits per transform + { 7, 12, 2 }, // 81 bits per transform + { 7, 13, 1 }, // 81 bits per transform + { 7, 16, 0 }, // 81 bits per transform + { 8, 0, 15 }, // 81 bits per transform + { 8, 1, 12 }, // 81 bits per transform + { 8, 2, 11 }, // 81 bits per transform + { 8, 3, 10 }, // 81 bits per transform + { 8, 4, 9 }, // 81 bits per transform + { 8, 5, 8 }, // 81 bits per transform + { 8, 6, 7 }, // 81 bits per transform + { 8, 7, 6 }, // 81 bits per transform + { 8, 8, 5 }, // 81 bits per transform + { 8, 9, 4 }, // 81 bits per transform + { 8, 10, 3 }, // 81 bits per transform + { 8, 11, 2 }, // 81 bits per transform + { 8, 12, 1 }, // 81 bits per transform + { 8, 15, 0 }, // 81 bits per transform + { 9, 0, 14 }, // 81 bits per transform + { 9, 1, 11 }, // 81 bits per transform + { 9, 2, 10 }, // 81 bits per transform + { 9, 3, 9 }, // 81 bits per transform + { 9, 4, 8 }, // 81 bits per transform + { 9, 5, 7 }, // 81 bits per transform + { 9, 6, 6 }, // 81 bits per transform + { 9, 7, 5 }, // 81 bits per transform + { 9, 8, 4 }, // 81 bits per transform + { 9, 9, 3 }, // 81 bits per transform + { 9, 10, 2 }, // 81 bits per transform + { 9, 11, 1 }, // 81 bits per transform + { 9, 14, 0 }, // 81 bits per transform + { 10, 0, 13 }, // 81 bits per transform + { 10, 1, 10 }, // 81 bits per transform + { 10, 2, 9 }, // 81 bits per transform + { 10, 3, 8 }, // 81 bits per transform + { 10, 4, 7 }, // 81 bits per transform + { 10, 5, 6 }, // 81 bits per transform + { 10, 6, 5 }, // 81 bits per transform + { 10, 7, 4 }, // 81 bits per transform + { 10, 8, 3 }, // 81 bits per transform + { 10, 9, 2 }, // 81 bits per transform + { 10, 10, 1 }, // 81 bits per transform + { 10, 13, 0 }, // 81 bits per transform + { 11, 0, 12 }, // 81 bits per transform + { 11, 1, 9 }, // 81 bits per transform + { 11, 2, 8 }, // 81 bits per transform + { 11, 3, 7 }, // 81 bits per transform + { 11, 4, 6 }, // 81 bits per transform + { 11, 5, 5 }, // 81 bits per transform + { 11, 6, 4 }, // 81 bits per transform + { 11, 7, 3 }, // 81 bits per transform + { 11, 8, 2 }, // 81 bits per transform + { 11, 9, 1 }, // 81 bits per transform + { 11, 12, 0 }, // 81 bits per transform + { 12, 0, 11 }, // 81 bits per transform + { 12, 1, 8 }, // 81 bits per transform + { 12, 2, 7 }, // 81 bits per transform + { 12, 3, 6 }, // 81 bits per transform + { 12, 4, 5 }, // 81 bits per transform + { 12, 5, 4 }, // 81 bits per transform + { 12, 6, 3 }, // 81 bits per transform + { 12, 7, 2 }, // 81 bits per transform + { 12, 8, 1 }, // 81 bits per transform + { 12, 11, 0 }, // 81 bits per transform + { 13, 0, 10 }, // 81 bits per transform + { 13, 1, 7 }, // 81 bits per transform + { 13, 2, 6 }, // 81 bits per transform + { 13, 3, 5 }, // 81 bits per transform + { 13, 4, 4 }, // 81 bits per transform + { 13, 5, 3 }, // 81 bits per transform + { 13, 6, 2 }, // 81 bits per transform + { 13, 7, 1 }, // 81 bits per transform + { 13, 10, 0 }, // 81 bits per transform + { 14, 0, 9 }, // 81 bits per transform + { 14, 1, 6 }, // 81 bits per transform + { 14, 2, 5 }, // 81 bits per transform + { 14, 3, 4 }, // 81 bits per transform + { 14, 4, 3 }, // 81 bits per transform + { 14, 5, 2 }, // 81 bits per transform + { 14, 6, 1 }, // 81 bits per transform + { 14, 9, 0 }, // 81 bits per transform + { 15, 0, 8 }, // 81 bits per transform + { 15, 1, 5 }, // 81 bits per transform + { 15, 2, 4 }, // 81 bits per transform + { 15, 3, 3 }, // 81 bits per transform + { 15, 4, 2 }, // 81 bits per transform + { 15, 5, 1 }, // 81 bits per transform + { 15, 8, 0 }, // 81 bits per transform + { 16, 0, 7 }, // 81 bits per transform + { 16, 1, 4 }, // 81 bits per transform + { 16, 2, 3 }, // 81 bits per transform + { 16, 3, 2 }, // 81 bits per transform + { 16, 4, 1 }, // 81 bits per transform + { 16, 7, 0 }, // 81 bits per transform + { 17, 0, 6 }, // 81 bits per transform + { 17, 1, 3 }, // 81 bits per transform + { 17, 2, 2 }, // 81 bits per transform + { 17, 3, 1 }, // 81 bits per transform + { 17, 6, 0 }, // 81 bits per transform + { 0, 7, 17 }, // 84 bits per transform + { 0, 8, 16 }, // 84 bits per transform + { 0, 9, 15 }, // 84 bits per transform + { 0, 10, 14 }, // 84 bits per transform + { 0, 11, 13 }, // 84 bits per transform + { 0, 12, 12 }, // 84 bits per transform + { 0, 13, 11 }, // 84 bits per transform + { 0, 14, 10 }, // 84 bits per transform + { 0, 15, 9 }, // 84 bits per transform + { 0, 16, 8 }, // 84 bits per transform + { 0, 17, 7 }, // 84 bits per transform + { 1, 4, 17 }, // 84 bits per transform + { 1, 5, 16 }, // 84 bits per transform + { 1, 6, 15 }, // 84 bits per transform + { 1, 7, 14 }, // 84 bits per transform + { 1, 8, 13 }, // 84 bits per transform + { 1, 9, 12 }, // 84 bits per transform + { 1, 10, 11 }, // 84 bits per transform + { 1, 11, 10 }, // 84 bits per transform + { 1, 12, 9 }, // 84 bits per transform + { 1, 13, 8 }, // 84 bits per transform + { 1, 14, 7 }, // 84 bits per transform + { 1, 15, 6 }, // 84 bits per transform + { 1, 16, 5 }, // 84 bits per transform + { 1, 17, 4 }, // 84 bits per transform + { 2, 3, 17 }, // 84 bits per transform + { 2, 4, 16 }, // 84 bits per transform + { 2, 5, 15 }, // 84 bits per transform + { 2, 6, 14 }, // 84 bits per transform + { 2, 7, 13 }, // 84 bits per transform + { 2, 8, 12 }, // 84 bits per transform + { 2, 9, 11 }, // 84 bits per transform + { 2, 10, 10 }, // 84 bits per transform + { 2, 11, 9 }, // 84 bits per transform + { 2, 12, 8 }, // 84 bits per transform + { 2, 13, 7 }, // 84 bits per transform + { 2, 14, 6 }, // 84 bits per transform + { 2, 15, 5 }, // 84 bits per transform + { 2, 16, 4 }, // 84 bits per transform + { 2, 17, 3 }, // 84 bits per transform + { 3, 2, 17 }, // 84 bits per transform + { 3, 3, 16 }, // 84 bits per transform + { 3, 4, 15 }, // 84 bits per transform + { 3, 5, 14 }, // 84 bits per transform + { 3, 6, 13 }, // 84 bits per transform + { 3, 7, 12 }, // 84 bits per transform + { 3, 8, 11 }, // 84 bits per transform + { 3, 9, 10 }, // 84 bits per transform + { 3, 10, 9 }, // 84 bits per transform + { 3, 11, 8 }, // 84 bits per transform + { 3, 12, 7 }, // 84 bits per transform + { 3, 13, 6 }, // 84 bits per transform + { 3, 14, 5 }, // 84 bits per transform + { 3, 15, 4 }, // 84 bits per transform + { 3, 16, 3 }, // 84 bits per transform + { 3, 17, 2 }, // 84 bits per transform + { 4, 1, 17 }, // 84 bits per transform + { 4, 2, 16 }, // 84 bits per transform + { 4, 3, 15 }, // 84 bits per transform + { 4, 4, 14 }, // 84 bits per transform + { 4, 5, 13 }, // 84 bits per transform + { 4, 6, 12 }, // 84 bits per transform + { 4, 7, 11 }, // 84 bits per transform + { 4, 8, 10 }, // 84 bits per transform + { 4, 9, 9 }, // 84 bits per transform + { 4, 10, 8 }, // 84 bits per transform + { 4, 11, 7 }, // 84 bits per transform + { 4, 12, 6 }, // 84 bits per transform + { 4, 13, 5 }, // 84 bits per transform + { 4, 14, 4 }, // 84 bits per transform + { 4, 15, 3 }, // 84 bits per transform + { 4, 16, 2 }, // 84 bits per transform + { 4, 17, 1 }, // 84 bits per transform + { 5, 1, 16 }, // 84 bits per transform + { 5, 2, 15 }, // 84 bits per transform + { 5, 3, 14 }, // 84 bits per transform + { 5, 4, 13 }, // 84 bits per transform + { 5, 5, 12 }, // 84 bits per transform + { 5, 6, 11 }, // 84 bits per transform + { 5, 7, 10 }, // 84 bits per transform + { 5, 8, 9 }, // 84 bits per transform + { 5, 9, 8 }, // 84 bits per transform + { 5, 10, 7 }, // 84 bits per transform + { 5, 11, 6 }, // 84 bits per transform + { 5, 12, 5 }, // 84 bits per transform + { 5, 13, 4 }, // 84 bits per transform + { 5, 14, 3 }, // 84 bits per transform + { 5, 15, 2 }, // 84 bits per transform + { 5, 16, 1 }, // 84 bits per transform + { 6, 1, 15 }, // 84 bits per transform + { 6, 2, 14 }, // 84 bits per transform + { 6, 3, 13 }, // 84 bits per transform + { 6, 4, 12 }, // 84 bits per transform + { 6, 5, 11 }, // 84 bits per transform + { 6, 6, 10 }, // 84 bits per transform + { 6, 7, 9 }, // 84 bits per transform + { 6, 8, 8 }, // 84 bits per transform + { 6, 9, 7 }, // 84 bits per transform + { 6, 10, 6 }, // 84 bits per transform + { 6, 11, 5 }, // 84 bits per transform + { 6, 12, 4 }, // 84 bits per transform + { 6, 13, 3 }, // 84 bits per transform + { 6, 14, 2 }, // 84 bits per transform + { 6, 15, 1 }, // 84 bits per transform + { 7, 0, 17 }, // 84 bits per transform + { 7, 1, 14 }, // 84 bits per transform + { 7, 2, 13 }, // 84 bits per transform + { 7, 3, 12 }, // 84 bits per transform + { 7, 4, 11 }, // 84 bits per transform + { 7, 5, 10 }, // 84 bits per transform + { 7, 6, 9 }, // 84 bits per transform + { 7, 7, 8 }, // 84 bits per transform + { 7, 8, 7 }, // 84 bits per transform + { 7, 9, 6 }, // 84 bits per transform + { 7, 10, 5 }, // 84 bits per transform + { 7, 11, 4 }, // 84 bits per transform + { 7, 12, 3 }, // 84 bits per transform + { 7, 13, 2 }, // 84 bits per transform + { 7, 14, 1 }, // 84 bits per transform + { 7, 17, 0 }, // 84 bits per transform + { 8, 0, 16 }, // 84 bits per transform + { 8, 1, 13 }, // 84 bits per transform + { 8, 2, 12 }, // 84 bits per transform + { 8, 3, 11 }, // 84 bits per transform + { 8, 4, 10 }, // 84 bits per transform + { 8, 5, 9 }, // 84 bits per transform + { 8, 6, 8 }, // 84 bits per transform + { 8, 7, 7 }, // 84 bits per transform + { 8, 8, 6 }, // 84 bits per transform + { 8, 9, 5 }, // 84 bits per transform + { 8, 10, 4 }, // 84 bits per transform + { 8, 11, 3 }, // 84 bits per transform + { 8, 12, 2 }, // 84 bits per transform + { 8, 13, 1 }, // 84 bits per transform + { 8, 16, 0 }, // 84 bits per transform + { 9, 0, 15 }, // 84 bits per transform + { 9, 1, 12 }, // 84 bits per transform + { 9, 2, 11 }, // 84 bits per transform + { 9, 3, 10 }, // 84 bits per transform + { 9, 4, 9 }, // 84 bits per transform + { 9, 5, 8 }, // 84 bits per transform + { 9, 6, 7 }, // 84 bits per transform + { 9, 7, 6 }, // 84 bits per transform + { 9, 8, 5 }, // 84 bits per transform + { 9, 9, 4 }, // 84 bits per transform + { 9, 10, 3 }, // 84 bits per transform + { 9, 11, 2 }, // 84 bits per transform + { 9, 12, 1 }, // 84 bits per transform + { 9, 15, 0 }, // 84 bits per transform + { 10, 0, 14 }, // 84 bits per transform + { 10, 1, 11 }, // 84 bits per transform + { 10, 2, 10 }, // 84 bits per transform + { 10, 3, 9 }, // 84 bits per transform + { 10, 4, 8 }, // 84 bits per transform + { 10, 5, 7 }, // 84 bits per transform + { 10, 6, 6 }, // 84 bits per transform + { 10, 7, 5 }, // 84 bits per transform + { 10, 8, 4 }, // 84 bits per transform + { 10, 9, 3 }, // 84 bits per transform + { 10, 10, 2 }, // 84 bits per transform + { 10, 11, 1 }, // 84 bits per transform + { 10, 14, 0 }, // 84 bits per transform + { 11, 0, 13 }, // 84 bits per transform + { 11, 1, 10 }, // 84 bits per transform + { 11, 2, 9 }, // 84 bits per transform + { 11, 3, 8 }, // 84 bits per transform + { 11, 4, 7 }, // 84 bits per transform + { 11, 5, 6 }, // 84 bits per transform + { 11, 6, 5 }, // 84 bits per transform + { 11, 7, 4 }, // 84 bits per transform + { 11, 8, 3 }, // 84 bits per transform + { 11, 9, 2 }, // 84 bits per transform + { 11, 10, 1 }, // 84 bits per transform + { 11, 13, 0 }, // 84 bits per transform + { 12, 0, 12 }, // 84 bits per transform + { 12, 1, 9 }, // 84 bits per transform + { 12, 2, 8 }, // 84 bits per transform + { 12, 3, 7 }, // 84 bits per transform + { 12, 4, 6 }, // 84 bits per transform + { 12, 5, 5 }, // 84 bits per transform + { 12, 6, 4 }, // 84 bits per transform + { 12, 7, 3 }, // 84 bits per transform + { 12, 8, 2 }, // 84 bits per transform + { 12, 9, 1 }, // 84 bits per transform + { 12, 12, 0 }, // 84 bits per transform + { 13, 0, 11 }, // 84 bits per transform + { 13, 1, 8 }, // 84 bits per transform + { 13, 2, 7 }, // 84 bits per transform + { 13, 3, 6 }, // 84 bits per transform + { 13, 4, 5 }, // 84 bits per transform + { 13, 5, 4 }, // 84 bits per transform + { 13, 6, 3 }, // 84 bits per transform + { 13, 7, 2 }, // 84 bits per transform + { 13, 8, 1 }, // 84 bits per transform + { 13, 11, 0 }, // 84 bits per transform + { 14, 0, 10 }, // 84 bits per transform + { 14, 1, 7 }, // 84 bits per transform + { 14, 2, 6 }, // 84 bits per transform + { 14, 3, 5 }, // 84 bits per transform + { 14, 4, 4 }, // 84 bits per transform + { 14, 5, 3 }, // 84 bits per transform + { 14, 6, 2 }, // 84 bits per transform + { 14, 7, 1 }, // 84 bits per transform + { 14, 10, 0 }, // 84 bits per transform + { 15, 0, 9 }, // 84 bits per transform + { 15, 1, 6 }, // 84 bits per transform + { 15, 2, 5 }, // 84 bits per transform + { 15, 3, 4 }, // 84 bits per transform + { 15, 4, 3 }, // 84 bits per transform + { 15, 5, 2 }, // 84 bits per transform + { 15, 6, 1 }, // 84 bits per transform + { 15, 9, 0 }, // 84 bits per transform + { 16, 0, 8 }, // 84 bits per transform + { 16, 1, 5 }, // 84 bits per transform + { 16, 2, 4 }, // 84 bits per transform + { 16, 3, 3 }, // 84 bits per transform + { 16, 4, 2 }, // 84 bits per transform + { 16, 5, 1 }, // 84 bits per transform + { 16, 8, 0 }, // 84 bits per transform + { 17, 0, 7 }, // 84 bits per transform + { 17, 1, 4 }, // 84 bits per transform + { 17, 2, 3 }, // 84 bits per transform + { 17, 3, 2 }, // 84 bits per transform + { 17, 4, 1 }, // 84 bits per transform + { 17, 7, 0 }, // 84 bits per transform + { 0, 8, 17 }, // 87 bits per transform + { 0, 9, 16 }, // 87 bits per transform + { 0, 10, 15 }, // 87 bits per transform + { 0, 11, 14 }, // 87 bits per transform + { 0, 12, 13 }, // 87 bits per transform + { 0, 13, 12 }, // 87 bits per transform + { 0, 14, 11 }, // 87 bits per transform + { 0, 15, 10 }, // 87 bits per transform + { 0, 16, 9 }, // 87 bits per transform + { 0, 17, 8 }, // 87 bits per transform + { 1, 5, 17 }, // 87 bits per transform + { 1, 6, 16 }, // 87 bits per transform + { 1, 7, 15 }, // 87 bits per transform + { 1, 8, 14 }, // 87 bits per transform + { 1, 9, 13 }, // 87 bits per transform + { 1, 10, 12 }, // 87 bits per transform + { 1, 11, 11 }, // 87 bits per transform + { 1, 12, 10 }, // 87 bits per transform + { 1, 13, 9 }, // 87 bits per transform + { 1, 14, 8 }, // 87 bits per transform + { 1, 15, 7 }, // 87 bits per transform + { 1, 16, 6 }, // 87 bits per transform + { 1, 17, 5 }, // 87 bits per transform + { 2, 4, 17 }, // 87 bits per transform + { 2, 5, 16 }, // 87 bits per transform + { 2, 6, 15 }, // 87 bits per transform + { 2, 7, 14 }, // 87 bits per transform + { 2, 8, 13 }, // 87 bits per transform + { 2, 9, 12 }, // 87 bits per transform + { 2, 10, 11 }, // 87 bits per transform + { 2, 11, 10 }, // 87 bits per transform + { 2, 12, 9 }, // 87 bits per transform + { 2, 13, 8 }, // 87 bits per transform + { 2, 14, 7 }, // 87 bits per transform + { 2, 15, 6 }, // 87 bits per transform + { 2, 16, 5 }, // 87 bits per transform + { 2, 17, 4 }, // 87 bits per transform + { 3, 3, 17 }, // 87 bits per transform + { 3, 4, 16 }, // 87 bits per transform + { 3, 5, 15 }, // 87 bits per transform + { 3, 6, 14 }, // 87 bits per transform + { 3, 7, 13 }, // 87 bits per transform + { 3, 8, 12 }, // 87 bits per transform + { 3, 9, 11 }, // 87 bits per transform + { 3, 10, 10 }, // 87 bits per transform + { 3, 11, 9 }, // 87 bits per transform + { 3, 12, 8 }, // 87 bits per transform + { 3, 13, 7 }, // 87 bits per transform + { 3, 14, 6 }, // 87 bits per transform + { 3, 15, 5 }, // 87 bits per transform + { 3, 16, 4 }, // 87 bits per transform + { 3, 17, 3 }, // 87 bits per transform + { 4, 2, 17 }, // 87 bits per transform + { 4, 3, 16 }, // 87 bits per transform + { 4, 4, 15 }, // 87 bits per transform + { 4, 5, 14 }, // 87 bits per transform + { 4, 6, 13 }, // 87 bits per transform + { 4, 7, 12 }, // 87 bits per transform + { 4, 8, 11 }, // 87 bits per transform + { 4, 9, 10 }, // 87 bits per transform + { 4, 10, 9 }, // 87 bits per transform + { 4, 11, 8 }, // 87 bits per transform + { 4, 12, 7 }, // 87 bits per transform + { 4, 13, 6 }, // 87 bits per transform + { 4, 14, 5 }, // 87 bits per transform + { 4, 15, 4 }, // 87 bits per transform + { 4, 16, 3 }, // 87 bits per transform + { 4, 17, 2 }, // 87 bits per transform + { 5, 1, 17 }, // 87 bits per transform + { 5, 2, 16 }, // 87 bits per transform + { 5, 3, 15 }, // 87 bits per transform + { 5, 4, 14 }, // 87 bits per transform + { 5, 5, 13 }, // 87 bits per transform + { 5, 6, 12 }, // 87 bits per transform + { 5, 7, 11 }, // 87 bits per transform + { 5, 8, 10 }, // 87 bits per transform + { 5, 9, 9 }, // 87 bits per transform + { 5, 10, 8 }, // 87 bits per transform + { 5, 11, 7 }, // 87 bits per transform + { 5, 12, 6 }, // 87 bits per transform + { 5, 13, 5 }, // 87 bits per transform + { 5, 14, 4 }, // 87 bits per transform + { 5, 15, 3 }, // 87 bits per transform + { 5, 16, 2 }, // 87 bits per transform + { 5, 17, 1 }, // 87 bits per transform + { 6, 1, 16 }, // 87 bits per transform + { 6, 2, 15 }, // 87 bits per transform + { 6, 3, 14 }, // 87 bits per transform + { 6, 4, 13 }, // 87 bits per transform + { 6, 5, 12 }, // 87 bits per transform + { 6, 6, 11 }, // 87 bits per transform + { 6, 7, 10 }, // 87 bits per transform + { 6, 8, 9 }, // 87 bits per transform + { 6, 9, 8 }, // 87 bits per transform + { 6, 10, 7 }, // 87 bits per transform + { 6, 11, 6 }, // 87 bits per transform + { 6, 12, 5 }, // 87 bits per transform + { 6, 13, 4 }, // 87 bits per transform + { 6, 14, 3 }, // 87 bits per transform + { 6, 15, 2 }, // 87 bits per transform + { 6, 16, 1 }, // 87 bits per transform + { 7, 1, 15 }, // 87 bits per transform + { 7, 2, 14 }, // 87 bits per transform + { 7, 3, 13 }, // 87 bits per transform + { 7, 4, 12 }, // 87 bits per transform + { 7, 5, 11 }, // 87 bits per transform + { 7, 6, 10 }, // 87 bits per transform + { 7, 7, 9 }, // 87 bits per transform + { 7, 8, 8 }, // 87 bits per transform + { 7, 9, 7 }, // 87 bits per transform + { 7, 10, 6 }, // 87 bits per transform + { 7, 11, 5 }, // 87 bits per transform + { 7, 12, 4 }, // 87 bits per transform + { 7, 13, 3 }, // 87 bits per transform + { 7, 14, 2 }, // 87 bits per transform + { 7, 15, 1 }, // 87 bits per transform + { 8, 0, 17 }, // 87 bits per transform + { 8, 1, 14 }, // 87 bits per transform + { 8, 2, 13 }, // 87 bits per transform + { 8, 3, 12 }, // 87 bits per transform + { 8, 4, 11 }, // 87 bits per transform + { 8, 5, 10 }, // 87 bits per transform + { 8, 6, 9 }, // 87 bits per transform + { 8, 7, 8 }, // 87 bits per transform + { 8, 8, 7 }, // 87 bits per transform + { 8, 9, 6 }, // 87 bits per transform + { 8, 10, 5 }, // 87 bits per transform + { 8, 11, 4 }, // 87 bits per transform + { 8, 12, 3 }, // 87 bits per transform + { 8, 13, 2 }, // 87 bits per transform + { 8, 14, 1 }, // 87 bits per transform + { 8, 17, 0 }, // 87 bits per transform + { 9, 0, 16 }, // 87 bits per transform + { 9, 1, 13 }, // 87 bits per transform + { 9, 2, 12 }, // 87 bits per transform + { 9, 3, 11 }, // 87 bits per transform + { 9, 4, 10 }, // 87 bits per transform + { 9, 5, 9 }, // 87 bits per transform + { 9, 6, 8 }, // 87 bits per transform + { 9, 7, 7 }, // 87 bits per transform + { 9, 8, 6 }, // 87 bits per transform + { 9, 9, 5 }, // 87 bits per transform + { 9, 10, 4 }, // 87 bits per transform + { 9, 11, 3 }, // 87 bits per transform + { 9, 12, 2 }, // 87 bits per transform + { 9, 13, 1 }, // 87 bits per transform + { 9, 16, 0 }, // 87 bits per transform + { 10, 0, 15 }, // 87 bits per transform + { 10, 1, 12 }, // 87 bits per transform + { 10, 2, 11 }, // 87 bits per transform + { 10, 3, 10 }, // 87 bits per transform + { 10, 4, 9 }, // 87 bits per transform + { 10, 5, 8 }, // 87 bits per transform + { 10, 6, 7 }, // 87 bits per transform + { 10, 7, 6 }, // 87 bits per transform + { 10, 8, 5 }, // 87 bits per transform + { 10, 9, 4 }, // 87 bits per transform + { 10, 10, 3 }, // 87 bits per transform + { 10, 11, 2 }, // 87 bits per transform + { 10, 12, 1 }, // 87 bits per transform + { 10, 15, 0 }, // 87 bits per transform + { 11, 0, 14 }, // 87 bits per transform + { 11, 1, 11 }, // 87 bits per transform + { 11, 2, 10 }, // 87 bits per transform + { 11, 3, 9 }, // 87 bits per transform + { 11, 4, 8 }, // 87 bits per transform + { 11, 5, 7 }, // 87 bits per transform + { 11, 6, 6 }, // 87 bits per transform + { 11, 7, 5 }, // 87 bits per transform + { 11, 8, 4 }, // 87 bits per transform + { 11, 9, 3 }, // 87 bits per transform + { 11, 10, 2 }, // 87 bits per transform + { 11, 11, 1 }, // 87 bits per transform + { 11, 14, 0 }, // 87 bits per transform + { 12, 0, 13 }, // 87 bits per transform + { 12, 1, 10 }, // 87 bits per transform + { 12, 2, 9 }, // 87 bits per transform + { 12, 3, 8 }, // 87 bits per transform + { 12, 4, 7 }, // 87 bits per transform + { 12, 5, 6 }, // 87 bits per transform + { 12, 6, 5 }, // 87 bits per transform + { 12, 7, 4 }, // 87 bits per transform + { 12, 8, 3 }, // 87 bits per transform + { 12, 9, 2 }, // 87 bits per transform + { 12, 10, 1 }, // 87 bits per transform + { 12, 13, 0 }, // 87 bits per transform + { 13, 0, 12 }, // 87 bits per transform + { 13, 1, 9 }, // 87 bits per transform + { 13, 2, 8 }, // 87 bits per transform + { 13, 3, 7 }, // 87 bits per transform + { 13, 4, 6 }, // 87 bits per transform + { 13, 5, 5 }, // 87 bits per transform + { 13, 6, 4 }, // 87 bits per transform + { 13, 7, 3 }, // 87 bits per transform + { 13, 8, 2 }, // 87 bits per transform + { 13, 9, 1 }, // 87 bits per transform + { 13, 12, 0 }, // 87 bits per transform + { 14, 0, 11 }, // 87 bits per transform + { 14, 1, 8 }, // 87 bits per transform + { 14, 2, 7 }, // 87 bits per transform + { 14, 3, 6 }, // 87 bits per transform + { 14, 4, 5 }, // 87 bits per transform + { 14, 5, 4 }, // 87 bits per transform + { 14, 6, 3 }, // 87 bits per transform + { 14, 7, 2 }, // 87 bits per transform + { 14, 8, 1 }, // 87 bits per transform + { 14, 11, 0 }, // 87 bits per transform + { 15, 0, 10 }, // 87 bits per transform + { 15, 1, 7 }, // 87 bits per transform + { 15, 2, 6 }, // 87 bits per transform + { 15, 3, 5 }, // 87 bits per transform + { 15, 4, 4 }, // 87 bits per transform + { 15, 5, 3 }, // 87 bits per transform + { 15, 6, 2 }, // 87 bits per transform + { 15, 7, 1 }, // 87 bits per transform + { 15, 10, 0 }, // 87 bits per transform + { 16, 0, 9 }, // 87 bits per transform + { 16, 1, 6 }, // 87 bits per transform + { 16, 2, 5 }, // 87 bits per transform + { 16, 3, 4 }, // 87 bits per transform + { 16, 4, 3 }, // 87 bits per transform + { 16, 5, 2 }, // 87 bits per transform + { 16, 6, 1 }, // 87 bits per transform + { 16, 9, 0 }, // 87 bits per transform + { 17, 0, 8 }, // 87 bits per transform + { 17, 1, 5 }, // 87 bits per transform + { 17, 2, 4 }, // 87 bits per transform + { 17, 3, 3 }, // 87 bits per transform + { 17, 4, 2 }, // 87 bits per transform + { 17, 5, 1 }, // 87 bits per transform + { 17, 8, 0 }, // 87 bits per transform + { 0, 9, 17 }, // 90 bits per transform + { 0, 10, 16 }, // 90 bits per transform + { 0, 11, 15 }, // 90 bits per transform + { 0, 12, 14 }, // 90 bits per transform + { 0, 13, 13 }, // 90 bits per transform + { 0, 14, 12 }, // 90 bits per transform + { 0, 15, 11 }, // 90 bits per transform + { 0, 16, 10 }, // 90 bits per transform + { 0, 17, 9 }, // 90 bits per transform + { 1, 6, 17 }, // 90 bits per transform + { 1, 7, 16 }, // 90 bits per transform + { 1, 8, 15 }, // 90 bits per transform + { 1, 9, 14 }, // 90 bits per transform + { 1, 10, 13 }, // 90 bits per transform + { 1, 11, 12 }, // 90 bits per transform + { 1, 12, 11 }, // 90 bits per transform + { 1, 13, 10 }, // 90 bits per transform + { 1, 14, 9 }, // 90 bits per transform + { 1, 15, 8 }, // 90 bits per transform + { 1, 16, 7 }, // 90 bits per transform + { 1, 17, 6 }, // 90 bits per transform + { 2, 5, 17 }, // 90 bits per transform + { 2, 6, 16 }, // 90 bits per transform + { 2, 7, 15 }, // 90 bits per transform + { 2, 8, 14 }, // 90 bits per transform + { 2, 9, 13 }, // 90 bits per transform + { 2, 10, 12 }, // 90 bits per transform + { 2, 11, 11 }, // 90 bits per transform + { 2, 12, 10 }, // 90 bits per transform + { 2, 13, 9 }, // 90 bits per transform + { 2, 14, 8 }, // 90 bits per transform + { 2, 15, 7 }, // 90 bits per transform + { 2, 16, 6 }, // 90 bits per transform + { 2, 17, 5 }, // 90 bits per transform + { 3, 4, 17 }, // 90 bits per transform + { 3, 5, 16 }, // 90 bits per transform + { 3, 6, 15 }, // 90 bits per transform + { 3, 7, 14 }, // 90 bits per transform + { 3, 8, 13 }, // 90 bits per transform + { 3, 9, 12 }, // 90 bits per transform + { 3, 10, 11 }, // 90 bits per transform + { 3, 11, 10 }, // 90 bits per transform + { 3, 12, 9 }, // 90 bits per transform + { 3, 13, 8 }, // 90 bits per transform + { 3, 14, 7 }, // 90 bits per transform + { 3, 15, 6 }, // 90 bits per transform + { 3, 16, 5 }, // 90 bits per transform + { 3, 17, 4 }, // 90 bits per transform + { 4, 3, 17 }, // 90 bits per transform + { 4, 4, 16 }, // 90 bits per transform + { 4, 5, 15 }, // 90 bits per transform + { 4, 6, 14 }, // 90 bits per transform + { 4, 7, 13 }, // 90 bits per transform + { 4, 8, 12 }, // 90 bits per transform + { 4, 9, 11 }, // 90 bits per transform + { 4, 10, 10 }, // 90 bits per transform + { 4, 11, 9 }, // 90 bits per transform + { 4, 12, 8 }, // 90 bits per transform + { 4, 13, 7 }, // 90 bits per transform + { 4, 14, 6 }, // 90 bits per transform + { 4, 15, 5 }, // 90 bits per transform + { 4, 16, 4 }, // 90 bits per transform + { 4, 17, 3 }, // 90 bits per transform + { 5, 2, 17 }, // 90 bits per transform + { 5, 3, 16 }, // 90 bits per transform + { 5, 4, 15 }, // 90 bits per transform + { 5, 5, 14 }, // 90 bits per transform + { 5, 6, 13 }, // 90 bits per transform + { 5, 7, 12 }, // 90 bits per transform + { 5, 8, 11 }, // 90 bits per transform + { 5, 9, 10 }, // 90 bits per transform + { 5, 10, 9 }, // 90 bits per transform + { 5, 11, 8 }, // 90 bits per transform + { 5, 12, 7 }, // 90 bits per transform + { 5, 13, 6 }, // 90 bits per transform + { 5, 14, 5 }, // 90 bits per transform + { 5, 15, 4 }, // 90 bits per transform + { 5, 16, 3 }, // 90 bits per transform + { 5, 17, 2 }, // 90 bits per transform + { 6, 1, 17 }, // 90 bits per transform + { 6, 2, 16 }, // 90 bits per transform + { 6, 3, 15 }, // 90 bits per transform + { 6, 4, 14 }, // 90 bits per transform + { 6, 5, 13 }, // 90 bits per transform + { 6, 6, 12 }, // 90 bits per transform + { 6, 7, 11 }, // 90 bits per transform + { 6, 8, 10 }, // 90 bits per transform + { 6, 9, 9 }, // 90 bits per transform + { 6, 10, 8 }, // 90 bits per transform + { 6, 11, 7 }, // 90 bits per transform + { 6, 12, 6 }, // 90 bits per transform + { 6, 13, 5 }, // 90 bits per transform + { 6, 14, 4 }, // 90 bits per transform + { 6, 15, 3 }, // 90 bits per transform + { 6, 16, 2 }, // 90 bits per transform + { 6, 17, 1 }, // 90 bits per transform + { 7, 1, 16 }, // 90 bits per transform + { 7, 2, 15 }, // 90 bits per transform + { 7, 3, 14 }, // 90 bits per transform + { 7, 4, 13 }, // 90 bits per transform + { 7, 5, 12 }, // 90 bits per transform + { 7, 6, 11 }, // 90 bits per transform + { 7, 7, 10 }, // 90 bits per transform + { 7, 8, 9 }, // 90 bits per transform + { 7, 9, 8 }, // 90 bits per transform + { 7, 10, 7 }, // 90 bits per transform + { 7, 11, 6 }, // 90 bits per transform + { 7, 12, 5 }, // 90 bits per transform + { 7, 13, 4 }, // 90 bits per transform + { 7, 14, 3 }, // 90 bits per transform + { 7, 15, 2 }, // 90 bits per transform + { 7, 16, 1 }, // 90 bits per transform + { 8, 1, 15 }, // 90 bits per transform + { 8, 2, 14 }, // 90 bits per transform + { 8, 3, 13 }, // 90 bits per transform + { 8, 4, 12 }, // 90 bits per transform + { 8, 5, 11 }, // 90 bits per transform + { 8, 6, 10 }, // 90 bits per transform + { 8, 7, 9 }, // 90 bits per transform + { 8, 8, 8 }, // 90 bits per transform + { 8, 9, 7 }, // 90 bits per transform + { 8, 10, 6 }, // 90 bits per transform + { 8, 11, 5 }, // 90 bits per transform + { 8, 12, 4 }, // 90 bits per transform + { 8, 13, 3 }, // 90 bits per transform + { 8, 14, 2 }, // 90 bits per transform + { 8, 15, 1 }, // 90 bits per transform + { 9, 0, 17 }, // 90 bits per transform + { 9, 1, 14 }, // 90 bits per transform + { 9, 2, 13 }, // 90 bits per transform + { 9, 3, 12 }, // 90 bits per transform + { 9, 4, 11 }, // 90 bits per transform + { 9, 5, 10 }, // 90 bits per transform + { 9, 6, 9 }, // 90 bits per transform + { 9, 7, 8 }, // 90 bits per transform + { 9, 8, 7 }, // 90 bits per transform + { 9, 9, 6 }, // 90 bits per transform + { 9, 10, 5 }, // 90 bits per transform + { 9, 11, 4 }, // 90 bits per transform + { 9, 12, 3 }, // 90 bits per transform + { 9, 13, 2 }, // 90 bits per transform + { 9, 14, 1 }, // 90 bits per transform + { 9, 17, 0 }, // 90 bits per transform + { 10, 0, 16 }, // 90 bits per transform + { 10, 1, 13 }, // 90 bits per transform + { 10, 2, 12 }, // 90 bits per transform + { 10, 3, 11 }, // 90 bits per transform + { 10, 4, 10 }, // 90 bits per transform + { 10, 5, 9 }, // 90 bits per transform + { 10, 6, 8 }, // 90 bits per transform + { 10, 7, 7 }, // 90 bits per transform + { 10, 8, 6 }, // 90 bits per transform + { 10, 9, 5 }, // 90 bits per transform + { 10, 10, 4 }, // 90 bits per transform + { 10, 11, 3 }, // 90 bits per transform + { 10, 12, 2 }, // 90 bits per transform + { 10, 13, 1 }, // 90 bits per transform + { 10, 16, 0 }, // 90 bits per transform + { 11, 0, 15 }, // 90 bits per transform + { 11, 1, 12 }, // 90 bits per transform + { 11, 2, 11 }, // 90 bits per transform + { 11, 3, 10 }, // 90 bits per transform + { 11, 4, 9 }, // 90 bits per transform + { 11, 5, 8 }, // 90 bits per transform + { 11, 6, 7 }, // 90 bits per transform + { 11, 7, 6 }, // 90 bits per transform + { 11, 8, 5 }, // 90 bits per transform + { 11, 9, 4 }, // 90 bits per transform + { 11, 10, 3 }, // 90 bits per transform + { 11, 11, 2 }, // 90 bits per transform + { 11, 12, 1 }, // 90 bits per transform + { 11, 15, 0 }, // 90 bits per transform + { 12, 0, 14 }, // 90 bits per transform + { 12, 1, 11 }, // 90 bits per transform + { 12, 2, 10 }, // 90 bits per transform + { 12, 3, 9 }, // 90 bits per transform + { 12, 4, 8 }, // 90 bits per transform + { 12, 5, 7 }, // 90 bits per transform + { 12, 6, 6 }, // 90 bits per transform + { 12, 7, 5 }, // 90 bits per transform + { 12, 8, 4 }, // 90 bits per transform + { 12, 9, 3 }, // 90 bits per transform + { 12, 10, 2 }, // 90 bits per transform + { 12, 11, 1 }, // 90 bits per transform + { 12, 14, 0 }, // 90 bits per transform + { 13, 0, 13 }, // 90 bits per transform + { 13, 1, 10 }, // 90 bits per transform + { 13, 2, 9 }, // 90 bits per transform + { 13, 3, 8 }, // 90 bits per transform + { 13, 4, 7 }, // 90 bits per transform + { 13, 5, 6 }, // 90 bits per transform + { 13, 6, 5 }, // 90 bits per transform + { 13, 7, 4 }, // 90 bits per transform + { 13, 8, 3 }, // 90 bits per transform + { 13, 9, 2 }, // 90 bits per transform + { 13, 10, 1 }, // 90 bits per transform + { 13, 13, 0 }, // 90 bits per transform + { 14, 0, 12 }, // 90 bits per transform + { 14, 1, 9 }, // 90 bits per transform + { 14, 2, 8 }, // 90 bits per transform + { 14, 3, 7 }, // 90 bits per transform + { 14, 4, 6 }, // 90 bits per transform + { 14, 5, 5 }, // 90 bits per transform + { 14, 6, 4 }, // 90 bits per transform + { 14, 7, 3 }, // 90 bits per transform + { 14, 8, 2 }, // 90 bits per transform + { 14, 9, 1 }, // 90 bits per transform + { 14, 12, 0 }, // 90 bits per transform + { 15, 0, 11 }, // 90 bits per transform + { 15, 1, 8 }, // 90 bits per transform + { 15, 2, 7 }, // 90 bits per transform + { 15, 3, 6 }, // 90 bits per transform + { 15, 4, 5 }, // 90 bits per transform + { 15, 5, 4 }, // 90 bits per transform + { 15, 6, 3 }, // 90 bits per transform + { 15, 7, 2 }, // 90 bits per transform + { 15, 8, 1 }, // 90 bits per transform + { 15, 11, 0 }, // 90 bits per transform + { 16, 0, 10 }, // 90 bits per transform + { 16, 1, 7 }, // 90 bits per transform + { 16, 2, 6 }, // 90 bits per transform + { 16, 3, 5 }, // 90 bits per transform + { 16, 4, 4 }, // 90 bits per transform + { 16, 5, 3 }, // 90 bits per transform + { 16, 6, 2 }, // 90 bits per transform + { 16, 7, 1 }, // 90 bits per transform + { 16, 10, 0 }, // 90 bits per transform + { 17, 0, 9 }, // 90 bits per transform + { 17, 1, 6 }, // 90 bits per transform + { 17, 2, 5 }, // 90 bits per transform + { 17, 3, 4 }, // 90 bits per transform + { 17, 4, 3 }, // 90 bits per transform + { 17, 5, 2 }, // 90 bits per transform + { 17, 6, 1 }, // 90 bits per transform + { 17, 9, 0 }, // 90 bits per transform + { 0, 10, 17 }, // 93 bits per transform + { 0, 11, 16 }, // 93 bits per transform + { 0, 12, 15 }, // 93 bits per transform + { 0, 13, 14 }, // 93 bits per transform + { 0, 14, 13 }, // 93 bits per transform + { 0, 15, 12 }, // 93 bits per transform + { 0, 16, 11 }, // 93 bits per transform + { 0, 17, 10 }, // 93 bits per transform + { 1, 7, 17 }, // 93 bits per transform + { 1, 8, 16 }, // 93 bits per transform + { 1, 9, 15 }, // 93 bits per transform + { 1, 10, 14 }, // 93 bits per transform + { 1, 11, 13 }, // 93 bits per transform + { 1, 12, 12 }, // 93 bits per transform + { 1, 13, 11 }, // 93 bits per transform + { 1, 14, 10 }, // 93 bits per transform + { 1, 15, 9 }, // 93 bits per transform + { 1, 16, 8 }, // 93 bits per transform + { 1, 17, 7 }, // 93 bits per transform + { 2, 6, 17 }, // 93 bits per transform + { 2, 7, 16 }, // 93 bits per transform + { 2, 8, 15 }, // 93 bits per transform + { 2, 9, 14 }, // 93 bits per transform + { 2, 10, 13 }, // 93 bits per transform + { 2, 11, 12 }, // 93 bits per transform + { 2, 12, 11 }, // 93 bits per transform + { 2, 13, 10 }, // 93 bits per transform + { 2, 14, 9 }, // 93 bits per transform + { 2, 15, 8 }, // 93 bits per transform + { 2, 16, 7 }, // 93 bits per transform + { 2, 17, 6 }, // 93 bits per transform + { 3, 5, 17 }, // 93 bits per transform + { 3, 6, 16 }, // 93 bits per transform + { 3, 7, 15 }, // 93 bits per transform + { 3, 8, 14 }, // 93 bits per transform + { 3, 9, 13 }, // 93 bits per transform + { 3, 10, 12 }, // 93 bits per transform + { 3, 11, 11 }, // 93 bits per transform + { 3, 12, 10 }, // 93 bits per transform + { 3, 13, 9 }, // 93 bits per transform + { 3, 14, 8 }, // 93 bits per transform + { 3, 15, 7 }, // 93 bits per transform + { 3, 16, 6 }, // 93 bits per transform + { 3, 17, 5 }, // 93 bits per transform + { 4, 4, 17 }, // 93 bits per transform + { 4, 5, 16 }, // 93 bits per transform + { 4, 6, 15 }, // 93 bits per transform + { 4, 7, 14 }, // 93 bits per transform + { 4, 8, 13 }, // 93 bits per transform + { 4, 9, 12 }, // 93 bits per transform + { 4, 10, 11 }, // 93 bits per transform + { 4, 11, 10 }, // 93 bits per transform + { 4, 12, 9 }, // 93 bits per transform + { 4, 13, 8 }, // 93 bits per transform + { 4, 14, 7 }, // 93 bits per transform + { 4, 15, 6 }, // 93 bits per transform + { 4, 16, 5 }, // 93 bits per transform + { 4, 17, 4 }, // 93 bits per transform + { 5, 3, 17 }, // 93 bits per transform + { 5, 4, 16 }, // 93 bits per transform + { 5, 5, 15 }, // 93 bits per transform + { 5, 6, 14 }, // 93 bits per transform + { 5, 7, 13 }, // 93 bits per transform + { 5, 8, 12 }, // 93 bits per transform + { 5, 9, 11 }, // 93 bits per transform + { 5, 10, 10 }, // 93 bits per transform + { 5, 11, 9 }, // 93 bits per transform + { 5, 12, 8 }, // 93 bits per transform + { 5, 13, 7 }, // 93 bits per transform + { 5, 14, 6 }, // 93 bits per transform + { 5, 15, 5 }, // 93 bits per transform + { 5, 16, 4 }, // 93 bits per transform + { 5, 17, 3 }, // 93 bits per transform + { 6, 2, 17 }, // 93 bits per transform + { 6, 3, 16 }, // 93 bits per transform + { 6, 4, 15 }, // 93 bits per transform + { 6, 5, 14 }, // 93 bits per transform + { 6, 6, 13 }, // 93 bits per transform + { 6, 7, 12 }, // 93 bits per transform + { 6, 8, 11 }, // 93 bits per transform + { 6, 9, 10 }, // 93 bits per transform + { 6, 10, 9 }, // 93 bits per transform + { 6, 11, 8 }, // 93 bits per transform + { 6, 12, 7 }, // 93 bits per transform + { 6, 13, 6 }, // 93 bits per transform + { 6, 14, 5 }, // 93 bits per transform + { 6, 15, 4 }, // 93 bits per transform + { 6, 16, 3 }, // 93 bits per transform + { 6, 17, 2 }, // 93 bits per transform + { 7, 1, 17 }, // 93 bits per transform + { 7, 2, 16 }, // 93 bits per transform + { 7, 3, 15 }, // 93 bits per transform + { 7, 4, 14 }, // 93 bits per transform + { 7, 5, 13 }, // 93 bits per transform + { 7, 6, 12 }, // 93 bits per transform + { 7, 7, 11 }, // 93 bits per transform + { 7, 8, 10 }, // 93 bits per transform + { 7, 9, 9 }, // 93 bits per transform + { 7, 10, 8 }, // 93 bits per transform + { 7, 11, 7 }, // 93 bits per transform + { 7, 12, 6 }, // 93 bits per transform + { 7, 13, 5 }, // 93 bits per transform + { 7, 14, 4 }, // 93 bits per transform + { 7, 15, 3 }, // 93 bits per transform + { 7, 16, 2 }, // 93 bits per transform + { 7, 17, 1 }, // 93 bits per transform + { 8, 1, 16 }, // 93 bits per transform + { 8, 2, 15 }, // 93 bits per transform + { 8, 3, 14 }, // 93 bits per transform + { 8, 4, 13 }, // 93 bits per transform + { 8, 5, 12 }, // 93 bits per transform + { 8, 6, 11 }, // 93 bits per transform + { 8, 7, 10 }, // 93 bits per transform + { 8, 8, 9 }, // 93 bits per transform + { 8, 9, 8 }, // 93 bits per transform + { 8, 10, 7 }, // 93 bits per transform + { 8, 11, 6 }, // 93 bits per transform + { 8, 12, 5 }, // 93 bits per transform + { 8, 13, 4 }, // 93 bits per transform + { 8, 14, 3 }, // 93 bits per transform + { 8, 15, 2 }, // 93 bits per transform + { 8, 16, 1 }, // 93 bits per transform + { 9, 1, 15 }, // 93 bits per transform + { 9, 2, 14 }, // 93 bits per transform + { 9, 3, 13 }, // 93 bits per transform + { 9, 4, 12 }, // 93 bits per transform + { 9, 5, 11 }, // 93 bits per transform + { 9, 6, 10 }, // 93 bits per transform + { 9, 7, 9 }, // 93 bits per transform + { 9, 8, 8 }, // 93 bits per transform + { 9, 9, 7 }, // 93 bits per transform + { 9, 10, 6 }, // 93 bits per transform + { 9, 11, 5 }, // 93 bits per transform + { 9, 12, 4 }, // 93 bits per transform + { 9, 13, 3 }, // 93 bits per transform + { 9, 14, 2 }, // 93 bits per transform + { 9, 15, 1 }, // 93 bits per transform + { 10, 0, 17 }, // 93 bits per transform + { 10, 1, 14 }, // 93 bits per transform + { 10, 2, 13 }, // 93 bits per transform + { 10, 3, 12 }, // 93 bits per transform + { 10, 4, 11 }, // 93 bits per transform + { 10, 5, 10 }, // 93 bits per transform + { 10, 6, 9 }, // 93 bits per transform + { 10, 7, 8 }, // 93 bits per transform + { 10, 8, 7 }, // 93 bits per transform + { 10, 9, 6 }, // 93 bits per transform + { 10, 10, 5 }, // 93 bits per transform + { 10, 11, 4 }, // 93 bits per transform + { 10, 12, 3 }, // 93 bits per transform + { 10, 13, 2 }, // 93 bits per transform + { 10, 14, 1 }, // 93 bits per transform + { 10, 17, 0 }, // 93 bits per transform + { 11, 0, 16 }, // 93 bits per transform + { 11, 1, 13 }, // 93 bits per transform + { 11, 2, 12 }, // 93 bits per transform + { 11, 3, 11 }, // 93 bits per transform + { 11, 4, 10 }, // 93 bits per transform + { 11, 5, 9 }, // 93 bits per transform + { 11, 6, 8 }, // 93 bits per transform + { 11, 7, 7 }, // 93 bits per transform + { 11, 8, 6 }, // 93 bits per transform + { 11, 9, 5 }, // 93 bits per transform + { 11, 10, 4 }, // 93 bits per transform + { 11, 11, 3 }, // 93 bits per transform + { 11, 12, 2 }, // 93 bits per transform + { 11, 13, 1 }, // 93 bits per transform + { 11, 16, 0 }, // 93 bits per transform + { 12, 0, 15 }, // 93 bits per transform + { 12, 1, 12 }, // 93 bits per transform + { 12, 2, 11 }, // 93 bits per transform + { 12, 3, 10 }, // 93 bits per transform + { 12, 4, 9 }, // 93 bits per transform + { 12, 5, 8 }, // 93 bits per transform + { 12, 6, 7 }, // 93 bits per transform + { 12, 7, 6 }, // 93 bits per transform + { 12, 8, 5 }, // 93 bits per transform + { 12, 9, 4 }, // 93 bits per transform + { 12, 10, 3 }, // 93 bits per transform + { 12, 11, 2 }, // 93 bits per transform + { 12, 12, 1 }, // 93 bits per transform + { 12, 15, 0 }, // 93 bits per transform + { 13, 0, 14 }, // 93 bits per transform + { 13, 1, 11 }, // 93 bits per transform + { 13, 2, 10 }, // 93 bits per transform + { 13, 3, 9 }, // 93 bits per transform + { 13, 4, 8 }, // 93 bits per transform + { 13, 5, 7 }, // 93 bits per transform + { 13, 6, 6 }, // 93 bits per transform + { 13, 7, 5 }, // 93 bits per transform + { 13, 8, 4 }, // 93 bits per transform + { 13, 9, 3 }, // 93 bits per transform + { 13, 10, 2 }, // 93 bits per transform + { 13, 11, 1 }, // 93 bits per transform + { 13, 14, 0 }, // 93 bits per transform + { 14, 0, 13 }, // 93 bits per transform + { 14, 1, 10 }, // 93 bits per transform + { 14, 2, 9 }, // 93 bits per transform + { 14, 3, 8 }, // 93 bits per transform + { 14, 4, 7 }, // 93 bits per transform + { 14, 5, 6 }, // 93 bits per transform + { 14, 6, 5 }, // 93 bits per transform + { 14, 7, 4 }, // 93 bits per transform + { 14, 8, 3 }, // 93 bits per transform + { 14, 9, 2 }, // 93 bits per transform + { 14, 10, 1 }, // 93 bits per transform + { 14, 13, 0 }, // 93 bits per transform + { 15, 0, 12 }, // 93 bits per transform + { 15, 1, 9 }, // 93 bits per transform + { 15, 2, 8 }, // 93 bits per transform + { 15, 3, 7 }, // 93 bits per transform + { 15, 4, 6 }, // 93 bits per transform + { 15, 5, 5 }, // 93 bits per transform + { 15, 6, 4 }, // 93 bits per transform + { 15, 7, 3 }, // 93 bits per transform + { 15, 8, 2 }, // 93 bits per transform + { 15, 9, 1 }, // 93 bits per transform + { 15, 12, 0 }, // 93 bits per transform + { 16, 0, 11 }, // 93 bits per transform + { 16, 1, 8 }, // 93 bits per transform + { 16, 2, 7 }, // 93 bits per transform + { 16, 3, 6 }, // 93 bits per transform + { 16, 4, 5 }, // 93 bits per transform + { 16, 5, 4 }, // 93 bits per transform + { 16, 6, 3 }, // 93 bits per transform + { 16, 7, 2 }, // 93 bits per transform + { 16, 8, 1 }, // 93 bits per transform + { 16, 11, 0 }, // 93 bits per transform + { 17, 0, 10 }, // 93 bits per transform + { 17, 1, 7 }, // 93 bits per transform + { 17, 2, 6 }, // 93 bits per transform + { 17, 3, 5 }, // 93 bits per transform + { 17, 4, 4 }, // 93 bits per transform + { 17, 5, 3 }, // 93 bits per transform + { 17, 6, 2 }, // 93 bits per transform + { 17, 7, 1 }, // 93 bits per transform + { 17, 10, 0 }, // 93 bits per transform + { 0, 0, 18 }, // 96 bits per transform + { 0, 11, 17 }, // 96 bits per transform + { 0, 12, 16 }, // 96 bits per transform + { 0, 13, 15 }, // 96 bits per transform + { 0, 14, 14 }, // 96 bits per transform + { 0, 15, 13 }, // 96 bits per transform + { 0, 16, 12 }, // 96 bits per transform + { 0, 17, 11 }, // 96 bits per transform + { 0, 18, 0 }, // 96 bits per transform + { 1, 8, 17 }, // 96 bits per transform + { 1, 9, 16 }, // 96 bits per transform + { 1, 10, 15 }, // 96 bits per transform + { 1, 11, 14 }, // 96 bits per transform + { 1, 12, 13 }, // 96 bits per transform + { 1, 13, 12 }, // 96 bits per transform + { 1, 14, 11 }, // 96 bits per transform + { 1, 15, 10 }, // 96 bits per transform + { 1, 16, 9 }, // 96 bits per transform + { 1, 17, 8 }, // 96 bits per transform + { 2, 7, 17 }, // 96 bits per transform + { 2, 8, 16 }, // 96 bits per transform + { 2, 9, 15 }, // 96 bits per transform + { 2, 10, 14 }, // 96 bits per transform + { 2, 11, 13 }, // 96 bits per transform + { 2, 12, 12 }, // 96 bits per transform + { 2, 13, 11 }, // 96 bits per transform + { 2, 14, 10 }, // 96 bits per transform + { 2, 15, 9 }, // 96 bits per transform + { 2, 16, 8 }, // 96 bits per transform + { 2, 17, 7 }, // 96 bits per transform + { 3, 6, 17 }, // 96 bits per transform + { 3, 7, 16 }, // 96 bits per transform + { 3, 8, 15 }, // 96 bits per transform + { 3, 9, 14 }, // 96 bits per transform + { 3, 10, 13 }, // 96 bits per transform + { 3, 11, 12 }, // 96 bits per transform + { 3, 12, 11 }, // 96 bits per transform + { 3, 13, 10 }, // 96 bits per transform + { 3, 14, 9 }, // 96 bits per transform + { 3, 15, 8 }, // 96 bits per transform + { 3, 16, 7 }, // 96 bits per transform + { 3, 17, 6 }, // 96 bits per transform + { 4, 5, 17 }, // 96 bits per transform + { 4, 6, 16 }, // 96 bits per transform + { 4, 7, 15 }, // 96 bits per transform + { 4, 8, 14 }, // 96 bits per transform + { 4, 9, 13 }, // 96 bits per transform + { 4, 10, 12 }, // 96 bits per transform + { 4, 11, 11 }, // 96 bits per transform + { 4, 12, 10 }, // 96 bits per transform + { 4, 13, 9 }, // 96 bits per transform + { 4, 14, 8 }, // 96 bits per transform + { 4, 15, 7 }, // 96 bits per transform + { 4, 16, 6 }, // 96 bits per transform + { 4, 17, 5 }, // 96 bits per transform + { 5, 4, 17 }, // 96 bits per transform + { 5, 5, 16 }, // 96 bits per transform + { 5, 6, 15 }, // 96 bits per transform + { 5, 7, 14 }, // 96 bits per transform + { 5, 8, 13 }, // 96 bits per transform + { 5, 9, 12 }, // 96 bits per transform + { 5, 10, 11 }, // 96 bits per transform + { 5, 11, 10 }, // 96 bits per transform + { 5, 12, 9 }, // 96 bits per transform + { 5, 13, 8 }, // 96 bits per transform + { 5, 14, 7 }, // 96 bits per transform + { 5, 15, 6 }, // 96 bits per transform + { 5, 16, 5 }, // 96 bits per transform + { 5, 17, 4 }, // 96 bits per transform + { 6, 3, 17 }, // 96 bits per transform + { 6, 4, 16 }, // 96 bits per transform + { 6, 5, 15 }, // 96 bits per transform + { 6, 6, 14 }, // 96 bits per transform + { 6, 7, 13 }, // 96 bits per transform + { 6, 8, 12 }, // 96 bits per transform + { 6, 9, 11 }, // 96 bits per transform + { 6, 10, 10 }, // 96 bits per transform + { 6, 11, 9 }, // 96 bits per transform + { 6, 12, 8 }, // 96 bits per transform + { 6, 13, 7 }, // 96 bits per transform + { 6, 14, 6 }, // 96 bits per transform + { 6, 15, 5 }, // 96 bits per transform + { 6, 16, 4 }, // 96 bits per transform + { 6, 17, 3 }, // 96 bits per transform + { 7, 2, 17 }, // 96 bits per transform + { 7, 3, 16 }, // 96 bits per transform + { 7, 4, 15 }, // 96 bits per transform + { 7, 5, 14 }, // 96 bits per transform + { 7, 6, 13 }, // 96 bits per transform + { 7, 7, 12 }, // 96 bits per transform + { 7, 8, 11 }, // 96 bits per transform + { 7, 9, 10 }, // 96 bits per transform + { 7, 10, 9 }, // 96 bits per transform + { 7, 11, 8 }, // 96 bits per transform + { 7, 12, 7 }, // 96 bits per transform + { 7, 13, 6 }, // 96 bits per transform + { 7, 14, 5 }, // 96 bits per transform + { 7, 15, 4 }, // 96 bits per transform + { 7, 16, 3 }, // 96 bits per transform + { 7, 17, 2 }, // 96 bits per transform + { 8, 1, 17 }, // 96 bits per transform + { 8, 2, 16 }, // 96 bits per transform + { 8, 3, 15 }, // 96 bits per transform + { 8, 4, 14 }, // 96 bits per transform + { 8, 5, 13 }, // 96 bits per transform + { 8, 6, 12 }, // 96 bits per transform + { 8, 7, 11 }, // 96 bits per transform + { 8, 8, 10 }, // 96 bits per transform + { 8, 9, 9 }, // 96 bits per transform + { 8, 10, 8 }, // 96 bits per transform + { 8, 11, 7 }, // 96 bits per transform + { 8, 12, 6 }, // 96 bits per transform + { 8, 13, 5 }, // 96 bits per transform + { 8, 14, 4 }, // 96 bits per transform + { 8, 15, 3 }, // 96 bits per transform + { 8, 16, 2 }, // 96 bits per transform + { 8, 17, 1 }, // 96 bits per transform + { 9, 1, 16 }, // 96 bits per transform + { 9, 2, 15 }, // 96 bits per transform + { 9, 3, 14 }, // 96 bits per transform + { 9, 4, 13 }, // 96 bits per transform + { 9, 5, 12 }, // 96 bits per transform + { 9, 6, 11 }, // 96 bits per transform + { 9, 7, 10 }, // 96 bits per transform + { 9, 8, 9 }, // 96 bits per transform + { 9, 9, 8 }, // 96 bits per transform + { 9, 10, 7 }, // 96 bits per transform + { 9, 11, 6 }, // 96 bits per transform + { 9, 12, 5 }, // 96 bits per transform + { 9, 13, 4 }, // 96 bits per transform + { 9, 14, 3 }, // 96 bits per transform + { 9, 15, 2 }, // 96 bits per transform + { 9, 16, 1 }, // 96 bits per transform + { 10, 1, 15 }, // 96 bits per transform + { 10, 2, 14 }, // 96 bits per transform + { 10, 3, 13 }, // 96 bits per transform + { 10, 4, 12 }, // 96 bits per transform + { 10, 5, 11 }, // 96 bits per transform + { 10, 6, 10 }, // 96 bits per transform + { 10, 7, 9 }, // 96 bits per transform + { 10, 8, 8 }, // 96 bits per transform + { 10, 9, 7 }, // 96 bits per transform + { 10, 10, 6 }, // 96 bits per transform + { 10, 11, 5 }, // 96 bits per transform + { 10, 12, 4 }, // 96 bits per transform + { 10, 13, 3 }, // 96 bits per transform + { 10, 14, 2 }, // 96 bits per transform + { 10, 15, 1 }, // 96 bits per transform + { 11, 0, 17 }, // 96 bits per transform + { 11, 1, 14 }, // 96 bits per transform + { 11, 2, 13 }, // 96 bits per transform + { 11, 3, 12 }, // 96 bits per transform + { 11, 4, 11 }, // 96 bits per transform + { 11, 5, 10 }, // 96 bits per transform + { 11, 6, 9 }, // 96 bits per transform + { 11, 7, 8 }, // 96 bits per transform + { 11, 8, 7 }, // 96 bits per transform + { 11, 9, 6 }, // 96 bits per transform + { 11, 10, 5 }, // 96 bits per transform + { 11, 11, 4 }, // 96 bits per transform + { 11, 12, 3 }, // 96 bits per transform + { 11, 13, 2 }, // 96 bits per transform + { 11, 14, 1 }, // 96 bits per transform + { 11, 17, 0 }, // 96 bits per transform + { 12, 0, 16 }, // 96 bits per transform + { 12, 1, 13 }, // 96 bits per transform + { 12, 2, 12 }, // 96 bits per transform + { 12, 3, 11 }, // 96 bits per transform + { 12, 4, 10 }, // 96 bits per transform + { 12, 5, 9 }, // 96 bits per transform + { 12, 6, 8 }, // 96 bits per transform + { 12, 7, 7 }, // 96 bits per transform + { 12, 8, 6 }, // 96 bits per transform + { 12, 9, 5 }, // 96 bits per transform + { 12, 10, 4 }, // 96 bits per transform + { 12, 11, 3 }, // 96 bits per transform + { 12, 12, 2 }, // 96 bits per transform + { 12, 13, 1 }, // 96 bits per transform + { 12, 16, 0 }, // 96 bits per transform + { 13, 0, 15 }, // 96 bits per transform + { 13, 1, 12 }, // 96 bits per transform + { 13, 2, 11 }, // 96 bits per transform + { 13, 3, 10 }, // 96 bits per transform + { 13, 4, 9 }, // 96 bits per transform + { 13, 5, 8 }, // 96 bits per transform + { 13, 6, 7 }, // 96 bits per transform + { 13, 7, 6 }, // 96 bits per transform + { 13, 8, 5 }, // 96 bits per transform + { 13, 9, 4 }, // 96 bits per transform + { 13, 10, 3 }, // 96 bits per transform + { 13, 11, 2 }, // 96 bits per transform + { 13, 12, 1 }, // 96 bits per transform + { 13, 15, 0 }, // 96 bits per transform + { 14, 0, 14 }, // 96 bits per transform + { 14, 1, 11 }, // 96 bits per transform + { 14, 2, 10 }, // 96 bits per transform + { 14, 3, 9 }, // 96 bits per transform + { 14, 4, 8 }, // 96 bits per transform + { 14, 5, 7 }, // 96 bits per transform + { 14, 6, 6 }, // 96 bits per transform + { 14, 7, 5 }, // 96 bits per transform + { 14, 8, 4 }, // 96 bits per transform + { 14, 9, 3 }, // 96 bits per transform + { 14, 10, 2 }, // 96 bits per transform + { 14, 11, 1 }, // 96 bits per transform + { 14, 14, 0 }, // 96 bits per transform + { 15, 0, 13 }, // 96 bits per transform + { 15, 1, 10 }, // 96 bits per transform + { 15, 2, 9 }, // 96 bits per transform + { 15, 3, 8 }, // 96 bits per transform + { 15, 4, 7 }, // 96 bits per transform + { 15, 5, 6 }, // 96 bits per transform + { 15, 6, 5 }, // 96 bits per transform + { 15, 7, 4 }, // 96 bits per transform + { 15, 8, 3 }, // 96 bits per transform + { 15, 9, 2 }, // 96 bits per transform + { 15, 10, 1 }, // 96 bits per transform + { 15, 13, 0 }, // 96 bits per transform + { 16, 0, 12 }, // 96 bits per transform + { 16, 1, 9 }, // 96 bits per transform + { 16, 2, 8 }, // 96 bits per transform + { 16, 3, 7 }, // 96 bits per transform + { 16, 4, 6 }, // 96 bits per transform + { 16, 5, 5 }, // 96 bits per transform + { 16, 6, 4 }, // 96 bits per transform + { 16, 7, 3 }, // 96 bits per transform + { 16, 8, 2 }, // 96 bits per transform + { 16, 9, 1 }, // 96 bits per transform + { 16, 12, 0 }, // 96 bits per transform + { 17, 0, 11 }, // 96 bits per transform + { 17, 1, 8 }, // 96 bits per transform + { 17, 2, 7 }, // 96 bits per transform + { 17, 3, 6 }, // 96 bits per transform + { 17, 4, 5 }, // 96 bits per transform + { 17, 5, 4 }, // 96 bits per transform + { 17, 6, 3 }, // 96 bits per transform + { 17, 7, 2 }, // 96 bits per transform + { 17, 8, 1 }, // 96 bits per transform + { 17, 11, 0 }, // 96 bits per transform + { 18, 0, 0 }, // 96 bits per transform + { 0, 12, 17 }, // 99 bits per transform + { 0, 13, 16 }, // 99 bits per transform + { 0, 14, 15 }, // 99 bits per transform + { 0, 15, 14 }, // 99 bits per transform + { 0, 16, 13 }, // 99 bits per transform + { 0, 17, 12 }, // 99 bits per transform + { 1, 9, 17 }, // 99 bits per transform + { 1, 10, 16 }, // 99 bits per transform + { 1, 11, 15 }, // 99 bits per transform + { 1, 12, 14 }, // 99 bits per transform + { 1, 13, 13 }, // 99 bits per transform + { 1, 14, 12 }, // 99 bits per transform + { 1, 15, 11 }, // 99 bits per transform + { 1, 16, 10 }, // 99 bits per transform + { 1, 17, 9 }, // 99 bits per transform + { 2, 8, 17 }, // 99 bits per transform + { 2, 9, 16 }, // 99 bits per transform + { 2, 10, 15 }, // 99 bits per transform + { 2, 11, 14 }, // 99 bits per transform + { 2, 12, 13 }, // 99 bits per transform + { 2, 13, 12 }, // 99 bits per transform + { 2, 14, 11 }, // 99 bits per transform + { 2, 15, 10 }, // 99 bits per transform + { 2, 16, 9 }, // 99 bits per transform + { 2, 17, 8 }, // 99 bits per transform + { 3, 7, 17 }, // 99 bits per transform + { 3, 8, 16 }, // 99 bits per transform + { 3, 9, 15 }, // 99 bits per transform + { 3, 10, 14 }, // 99 bits per transform + { 3, 11, 13 }, // 99 bits per transform + { 3, 12, 12 }, // 99 bits per transform + { 3, 13, 11 }, // 99 bits per transform + { 3, 14, 10 }, // 99 bits per transform + { 3, 15, 9 }, // 99 bits per transform + { 3, 16, 8 }, // 99 bits per transform + { 3, 17, 7 }, // 99 bits per transform + { 4, 6, 17 }, // 99 bits per transform + { 4, 7, 16 }, // 99 bits per transform + { 4, 8, 15 }, // 99 bits per transform + { 4, 9, 14 }, // 99 bits per transform + { 4, 10, 13 }, // 99 bits per transform + { 4, 11, 12 }, // 99 bits per transform + { 4, 12, 11 }, // 99 bits per transform + { 4, 13, 10 }, // 99 bits per transform + { 4, 14, 9 }, // 99 bits per transform + { 4, 15, 8 }, // 99 bits per transform + { 4, 16, 7 }, // 99 bits per transform + { 4, 17, 6 }, // 99 bits per transform + { 5, 5, 17 }, // 99 bits per transform + { 5, 6, 16 }, // 99 bits per transform + { 5, 7, 15 }, // 99 bits per transform + { 5, 8, 14 }, // 99 bits per transform + { 5, 9, 13 }, // 99 bits per transform + { 5, 10, 12 }, // 99 bits per transform + { 5, 11, 11 }, // 99 bits per transform + { 5, 12, 10 }, // 99 bits per transform + { 5, 13, 9 }, // 99 bits per transform + { 5, 14, 8 }, // 99 bits per transform + { 5, 15, 7 }, // 99 bits per transform + { 5, 16, 6 }, // 99 bits per transform + { 5, 17, 5 }, // 99 bits per transform + { 6, 4, 17 }, // 99 bits per transform + { 6, 5, 16 }, // 99 bits per transform + { 6, 6, 15 }, // 99 bits per transform + { 6, 7, 14 }, // 99 bits per transform + { 6, 8, 13 }, // 99 bits per transform + { 6, 9, 12 }, // 99 bits per transform + { 6, 10, 11 }, // 99 bits per transform + { 6, 11, 10 }, // 99 bits per transform + { 6, 12, 9 }, // 99 bits per transform + { 6, 13, 8 }, // 99 bits per transform + { 6, 14, 7 }, // 99 bits per transform + { 6, 15, 6 }, // 99 bits per transform + { 6, 16, 5 }, // 99 bits per transform + { 6, 17, 4 }, // 99 bits per transform + { 7, 3, 17 }, // 99 bits per transform + { 7, 4, 16 }, // 99 bits per transform + { 7, 5, 15 }, // 99 bits per transform + { 7, 6, 14 }, // 99 bits per transform + { 7, 7, 13 }, // 99 bits per transform + { 7, 8, 12 }, // 99 bits per transform + { 7, 9, 11 }, // 99 bits per transform + { 7, 10, 10 }, // 99 bits per transform + { 7, 11, 9 }, // 99 bits per transform + { 7, 12, 8 }, // 99 bits per transform + { 7, 13, 7 }, // 99 bits per transform + { 7, 14, 6 }, // 99 bits per transform + { 7, 15, 5 }, // 99 bits per transform + { 7, 16, 4 }, // 99 bits per transform + { 7, 17, 3 }, // 99 bits per transform + { 8, 2, 17 }, // 99 bits per transform + { 8, 3, 16 }, // 99 bits per transform + { 8, 4, 15 }, // 99 bits per transform + { 8, 5, 14 }, // 99 bits per transform + { 8, 6, 13 }, // 99 bits per transform + { 8, 7, 12 }, // 99 bits per transform + { 8, 8, 11 }, // 99 bits per transform + { 8, 9, 10 }, // 99 bits per transform + { 8, 10, 9 }, // 99 bits per transform + { 8, 11, 8 }, // 99 bits per transform + { 8, 12, 7 }, // 99 bits per transform + { 8, 13, 6 }, // 99 bits per transform + { 8, 14, 5 }, // 99 bits per transform + { 8, 15, 4 }, // 99 bits per transform + { 8, 16, 3 }, // 99 bits per transform + { 8, 17, 2 }, // 99 bits per transform + { 9, 1, 17 }, // 99 bits per transform + { 9, 2, 16 }, // 99 bits per transform + { 9, 3, 15 }, // 99 bits per transform + { 9, 4, 14 }, // 99 bits per transform + { 9, 5, 13 }, // 99 bits per transform + { 9, 6, 12 }, // 99 bits per transform + { 9, 7, 11 }, // 99 bits per transform + { 9, 8, 10 }, // 99 bits per transform + { 9, 9, 9 }, // 99 bits per transform + { 9, 10, 8 }, // 99 bits per transform + { 9, 11, 7 }, // 99 bits per transform + { 9, 12, 6 }, // 99 bits per transform + { 9, 13, 5 }, // 99 bits per transform + { 9, 14, 4 }, // 99 bits per transform + { 9, 15, 3 }, // 99 bits per transform + { 9, 16, 2 }, // 99 bits per transform + { 9, 17, 1 }, // 99 bits per transform + { 10, 1, 16 }, // 99 bits per transform + { 10, 2, 15 }, // 99 bits per transform + { 10, 3, 14 }, // 99 bits per transform + { 10, 4, 13 }, // 99 bits per transform + { 10, 5, 12 }, // 99 bits per transform + { 10, 6, 11 }, // 99 bits per transform + { 10, 7, 10 }, // 99 bits per transform + { 10, 8, 9 }, // 99 bits per transform + { 10, 9, 8 }, // 99 bits per transform + { 10, 10, 7 }, // 99 bits per transform + { 10, 11, 6 }, // 99 bits per transform + { 10, 12, 5 }, // 99 bits per transform + { 10, 13, 4 }, // 99 bits per transform + { 10, 14, 3 }, // 99 bits per transform + { 10, 15, 2 }, // 99 bits per transform + { 10, 16, 1 }, // 99 bits per transform + { 11, 1, 15 }, // 99 bits per transform + { 11, 2, 14 }, // 99 bits per transform + { 11, 3, 13 }, // 99 bits per transform + { 11, 4, 12 }, // 99 bits per transform + { 11, 5, 11 }, // 99 bits per transform + { 11, 6, 10 }, // 99 bits per transform + { 11, 7, 9 }, // 99 bits per transform + { 11, 8, 8 }, // 99 bits per transform + { 11, 9, 7 }, // 99 bits per transform + { 11, 10, 6 }, // 99 bits per transform + { 11, 11, 5 }, // 99 bits per transform + { 11, 12, 4 }, // 99 bits per transform + { 11, 13, 3 }, // 99 bits per transform + { 11, 14, 2 }, // 99 bits per transform + { 11, 15, 1 }, // 99 bits per transform + { 12, 0, 17 }, // 99 bits per transform + { 12, 1, 14 }, // 99 bits per transform + { 12, 2, 13 }, // 99 bits per transform + { 12, 3, 12 }, // 99 bits per transform + { 12, 4, 11 }, // 99 bits per transform + { 12, 5, 10 }, // 99 bits per transform + { 12, 6, 9 }, // 99 bits per transform + { 12, 7, 8 }, // 99 bits per transform + { 12, 8, 7 }, // 99 bits per transform + { 12, 9, 6 }, // 99 bits per transform + { 12, 10, 5 }, // 99 bits per transform + { 12, 11, 4 }, // 99 bits per transform + { 12, 12, 3 }, // 99 bits per transform + { 12, 13, 2 }, // 99 bits per transform + { 12, 14, 1 }, // 99 bits per transform + { 12, 17, 0 }, // 99 bits per transform + { 13, 0, 16 }, // 99 bits per transform + { 13, 1, 13 }, // 99 bits per transform + { 13, 2, 12 }, // 99 bits per transform + { 13, 3, 11 }, // 99 bits per transform + { 13, 4, 10 }, // 99 bits per transform + { 13, 5, 9 }, // 99 bits per transform + { 13, 6, 8 }, // 99 bits per transform + { 13, 7, 7 }, // 99 bits per transform + { 13, 8, 6 }, // 99 bits per transform + { 13, 9, 5 }, // 99 bits per transform + { 13, 10, 4 }, // 99 bits per transform + { 13, 11, 3 }, // 99 bits per transform + { 13, 12, 2 }, // 99 bits per transform + { 13, 13, 1 }, // 99 bits per transform + { 13, 16, 0 }, // 99 bits per transform + { 14, 0, 15 }, // 99 bits per transform + { 14, 1, 12 }, // 99 bits per transform + { 14, 2, 11 }, // 99 bits per transform + { 14, 3, 10 }, // 99 bits per transform + { 14, 4, 9 }, // 99 bits per transform + { 14, 5, 8 }, // 99 bits per transform + { 14, 6, 7 }, // 99 bits per transform + { 14, 7, 6 }, // 99 bits per transform + { 14, 8, 5 }, // 99 bits per transform + { 14, 9, 4 }, // 99 bits per transform + { 14, 10, 3 }, // 99 bits per transform + { 14, 11, 2 }, // 99 bits per transform + { 14, 12, 1 }, // 99 bits per transform + { 14, 15, 0 }, // 99 bits per transform + { 15, 0, 14 }, // 99 bits per transform + { 15, 1, 11 }, // 99 bits per transform + { 15, 2, 10 }, // 99 bits per transform + { 15, 3, 9 }, // 99 bits per transform + { 15, 4, 8 }, // 99 bits per transform + { 15, 5, 7 }, // 99 bits per transform + { 15, 6, 6 }, // 99 bits per transform + { 15, 7, 5 }, // 99 bits per transform + { 15, 8, 4 }, // 99 bits per transform + { 15, 9, 3 }, // 99 bits per transform + { 15, 10, 2 }, // 99 bits per transform + { 15, 11, 1 }, // 99 bits per transform + { 15, 14, 0 }, // 99 bits per transform + { 16, 0, 13 }, // 99 bits per transform + { 16, 1, 10 }, // 99 bits per transform + { 16, 2, 9 }, // 99 bits per transform + { 16, 3, 8 }, // 99 bits per transform + { 16, 4, 7 }, // 99 bits per transform + { 16, 5, 6 }, // 99 bits per transform + { 16, 6, 5 }, // 99 bits per transform + { 16, 7, 4 }, // 99 bits per transform + { 16, 8, 3 }, // 99 bits per transform + { 16, 9, 2 }, // 99 bits per transform + { 16, 10, 1 }, // 99 bits per transform + { 16, 13, 0 }, // 99 bits per transform + { 17, 0, 12 }, // 99 bits per transform + { 17, 1, 9 }, // 99 bits per transform + { 17, 2, 8 }, // 99 bits per transform + { 17, 3, 7 }, // 99 bits per transform + { 17, 4, 6 }, // 99 bits per transform + { 17, 5, 5 }, // 99 bits per transform + { 17, 6, 4 }, // 99 bits per transform + { 17, 7, 3 }, // 99 bits per transform + { 17, 8, 2 }, // 99 bits per transform + { 17, 9, 1 }, // 99 bits per transform + { 17, 12, 0 }, // 99 bits per transform + { 0, 13, 17 }, // 102 bits per transform + { 0, 14, 16 }, // 102 bits per transform + { 0, 15, 15 }, // 102 bits per transform + { 0, 16, 14 }, // 102 bits per transform + { 0, 17, 13 }, // 102 bits per transform + { 1, 10, 17 }, // 102 bits per transform + { 1, 11, 16 }, // 102 bits per transform + { 1, 12, 15 }, // 102 bits per transform + { 1, 13, 14 }, // 102 bits per transform + { 1, 14, 13 }, // 102 bits per transform + { 1, 15, 12 }, // 102 bits per transform + { 1, 16, 11 }, // 102 bits per transform + { 1, 17, 10 }, // 102 bits per transform + { 2, 9, 17 }, // 102 bits per transform + { 2, 10, 16 }, // 102 bits per transform + { 2, 11, 15 }, // 102 bits per transform + { 2, 12, 14 }, // 102 bits per transform + { 2, 13, 13 }, // 102 bits per transform + { 2, 14, 12 }, // 102 bits per transform + { 2, 15, 11 }, // 102 bits per transform + { 2, 16, 10 }, // 102 bits per transform + { 2, 17, 9 }, // 102 bits per transform + { 3, 8, 17 }, // 102 bits per transform + { 3, 9, 16 }, // 102 bits per transform + { 3, 10, 15 }, // 102 bits per transform + { 3, 11, 14 }, // 102 bits per transform + { 3, 12, 13 }, // 102 bits per transform + { 3, 13, 12 }, // 102 bits per transform + { 3, 14, 11 }, // 102 bits per transform + { 3, 15, 10 }, // 102 bits per transform + { 3, 16, 9 }, // 102 bits per transform + { 3, 17, 8 }, // 102 bits per transform + { 4, 7, 17 }, // 102 bits per transform + { 4, 8, 16 }, // 102 bits per transform + { 4, 9, 15 }, // 102 bits per transform + { 4, 10, 14 }, // 102 bits per transform + { 4, 11, 13 }, // 102 bits per transform + { 4, 12, 12 }, // 102 bits per transform + { 4, 13, 11 }, // 102 bits per transform + { 4, 14, 10 }, // 102 bits per transform + { 4, 15, 9 }, // 102 bits per transform + { 4, 16, 8 }, // 102 bits per transform + { 4, 17, 7 }, // 102 bits per transform + { 5, 6, 17 }, // 102 bits per transform + { 5, 7, 16 }, // 102 bits per transform + { 5, 8, 15 }, // 102 bits per transform + { 5, 9, 14 }, // 102 bits per transform + { 5, 10, 13 }, // 102 bits per transform + { 5, 11, 12 }, // 102 bits per transform + { 5, 12, 11 }, // 102 bits per transform + { 5, 13, 10 }, // 102 bits per transform + { 5, 14, 9 }, // 102 bits per transform + { 5, 15, 8 }, // 102 bits per transform + { 5, 16, 7 }, // 102 bits per transform + { 5, 17, 6 }, // 102 bits per transform + { 6, 5, 17 }, // 102 bits per transform + { 6, 6, 16 }, // 102 bits per transform + { 6, 7, 15 }, // 102 bits per transform + { 6, 8, 14 }, // 102 bits per transform + { 6, 9, 13 }, // 102 bits per transform + { 6, 10, 12 }, // 102 bits per transform + { 6, 11, 11 }, // 102 bits per transform + { 6, 12, 10 }, // 102 bits per transform + { 6, 13, 9 }, // 102 bits per transform + { 6, 14, 8 }, // 102 bits per transform + { 6, 15, 7 }, // 102 bits per transform + { 6, 16, 6 }, // 102 bits per transform + { 6, 17, 5 }, // 102 bits per transform + { 7, 4, 17 }, // 102 bits per transform + { 7, 5, 16 }, // 102 bits per transform + { 7, 6, 15 }, // 102 bits per transform + { 7, 7, 14 }, // 102 bits per transform + { 7, 8, 13 }, // 102 bits per transform + { 7, 9, 12 }, // 102 bits per transform + { 7, 10, 11 }, // 102 bits per transform + { 7, 11, 10 }, // 102 bits per transform + { 7, 12, 9 }, // 102 bits per transform + { 7, 13, 8 }, // 102 bits per transform + { 7, 14, 7 }, // 102 bits per transform + { 7, 15, 6 }, // 102 bits per transform + { 7, 16, 5 }, // 102 bits per transform + { 7, 17, 4 }, // 102 bits per transform + { 8, 3, 17 }, // 102 bits per transform + { 8, 4, 16 }, // 102 bits per transform + { 8, 5, 15 }, // 102 bits per transform + { 8, 6, 14 }, // 102 bits per transform + { 8, 7, 13 }, // 102 bits per transform + { 8, 8, 12 }, // 102 bits per transform + { 8, 9, 11 }, // 102 bits per transform + { 8, 10, 10 }, // 102 bits per transform + { 8, 11, 9 }, // 102 bits per transform + { 8, 12, 8 }, // 102 bits per transform + { 8, 13, 7 }, // 102 bits per transform + { 8, 14, 6 }, // 102 bits per transform + { 8, 15, 5 }, // 102 bits per transform + { 8, 16, 4 }, // 102 bits per transform + { 8, 17, 3 }, // 102 bits per transform + { 9, 2, 17 }, // 102 bits per transform + { 9, 3, 16 }, // 102 bits per transform + { 9, 4, 15 }, // 102 bits per transform + { 9, 5, 14 }, // 102 bits per transform + { 9, 6, 13 }, // 102 bits per transform + { 9, 7, 12 }, // 102 bits per transform + { 9, 8, 11 }, // 102 bits per transform + { 9, 9, 10 }, // 102 bits per transform + { 9, 10, 9 }, // 102 bits per transform + { 9, 11, 8 }, // 102 bits per transform + { 9, 12, 7 }, // 102 bits per transform + { 9, 13, 6 }, // 102 bits per transform + { 9, 14, 5 }, // 102 bits per transform + { 9, 15, 4 }, // 102 bits per transform + { 9, 16, 3 }, // 102 bits per transform + { 9, 17, 2 }, // 102 bits per transform + { 10, 1, 17 }, // 102 bits per transform + { 10, 2, 16 }, // 102 bits per transform + { 10, 3, 15 }, // 102 bits per transform + { 10, 4, 14 }, // 102 bits per transform + { 10, 5, 13 }, // 102 bits per transform + { 10, 6, 12 }, // 102 bits per transform + { 10, 7, 11 }, // 102 bits per transform + { 10, 8, 10 }, // 102 bits per transform + { 10, 9, 9 }, // 102 bits per transform + { 10, 10, 8 }, // 102 bits per transform + { 10, 11, 7 }, // 102 bits per transform + { 10, 12, 6 }, // 102 bits per transform + { 10, 13, 5 }, // 102 bits per transform + { 10, 14, 4 }, // 102 bits per transform + { 10, 15, 3 }, // 102 bits per transform + { 10, 16, 2 }, // 102 bits per transform + { 10, 17, 1 }, // 102 bits per transform + { 11, 1, 16 }, // 102 bits per transform + { 11, 2, 15 }, // 102 bits per transform + { 11, 3, 14 }, // 102 bits per transform + { 11, 4, 13 }, // 102 bits per transform + { 11, 5, 12 }, // 102 bits per transform + { 11, 6, 11 }, // 102 bits per transform + { 11, 7, 10 }, // 102 bits per transform + { 11, 8, 9 }, // 102 bits per transform + { 11, 9, 8 }, // 102 bits per transform + { 11, 10, 7 }, // 102 bits per transform + { 11, 11, 6 }, // 102 bits per transform + { 11, 12, 5 }, // 102 bits per transform + { 11, 13, 4 }, // 102 bits per transform + { 11, 14, 3 }, // 102 bits per transform + { 11, 15, 2 }, // 102 bits per transform + { 11, 16, 1 }, // 102 bits per transform + { 12, 1, 15 }, // 102 bits per transform + { 12, 2, 14 }, // 102 bits per transform + { 12, 3, 13 }, // 102 bits per transform + { 12, 4, 12 }, // 102 bits per transform + { 12, 5, 11 }, // 102 bits per transform + { 12, 6, 10 }, // 102 bits per transform + { 12, 7, 9 }, // 102 bits per transform + { 12, 8, 8 }, // 102 bits per transform + { 12, 9, 7 }, // 102 bits per transform + { 12, 10, 6 }, // 102 bits per transform + { 12, 11, 5 }, // 102 bits per transform + { 12, 12, 4 }, // 102 bits per transform + { 12, 13, 3 }, // 102 bits per transform + { 12, 14, 2 }, // 102 bits per transform + { 12, 15, 1 }, // 102 bits per transform + { 13, 0, 17 }, // 102 bits per transform + { 13, 1, 14 }, // 102 bits per transform + { 13, 2, 13 }, // 102 bits per transform + { 13, 3, 12 }, // 102 bits per transform + { 13, 4, 11 }, // 102 bits per transform + { 13, 5, 10 }, // 102 bits per transform + { 13, 6, 9 }, // 102 bits per transform + { 13, 7, 8 }, // 102 bits per transform + { 13, 8, 7 }, // 102 bits per transform + { 13, 9, 6 }, // 102 bits per transform + { 13, 10, 5 }, // 102 bits per transform + { 13, 11, 4 }, // 102 bits per transform + { 13, 12, 3 }, // 102 bits per transform + { 13, 13, 2 }, // 102 bits per transform + { 13, 14, 1 }, // 102 bits per transform + { 13, 17, 0 }, // 102 bits per transform + { 14, 0, 16 }, // 102 bits per transform + { 14, 1, 13 }, // 102 bits per transform + { 14, 2, 12 }, // 102 bits per transform + { 14, 3, 11 }, // 102 bits per transform + { 14, 4, 10 }, // 102 bits per transform + { 14, 5, 9 }, // 102 bits per transform + { 14, 6, 8 }, // 102 bits per transform + { 14, 7, 7 }, // 102 bits per transform + { 14, 8, 6 }, // 102 bits per transform + { 14, 9, 5 }, // 102 bits per transform + { 14, 10, 4 }, // 102 bits per transform + { 14, 11, 3 }, // 102 bits per transform + { 14, 12, 2 }, // 102 bits per transform + { 14, 13, 1 }, // 102 bits per transform + { 14, 16, 0 }, // 102 bits per transform + { 15, 0, 15 }, // 102 bits per transform + { 15, 1, 12 }, // 102 bits per transform + { 15, 2, 11 }, // 102 bits per transform + { 15, 3, 10 }, // 102 bits per transform + { 15, 4, 9 }, // 102 bits per transform + { 15, 5, 8 }, // 102 bits per transform + { 15, 6, 7 }, // 102 bits per transform + { 15, 7, 6 }, // 102 bits per transform + { 15, 8, 5 }, // 102 bits per transform + { 15, 9, 4 }, // 102 bits per transform + { 15, 10, 3 }, // 102 bits per transform + { 15, 11, 2 }, // 102 bits per transform + { 15, 12, 1 }, // 102 bits per transform + { 15, 15, 0 }, // 102 bits per transform + { 16, 0, 14 }, // 102 bits per transform + { 16, 1, 11 }, // 102 bits per transform + { 16, 2, 10 }, // 102 bits per transform + { 16, 3, 9 }, // 102 bits per transform + { 16, 4, 8 }, // 102 bits per transform + { 16, 5, 7 }, // 102 bits per transform + { 16, 6, 6 }, // 102 bits per transform + { 16, 7, 5 }, // 102 bits per transform + { 16, 8, 4 }, // 102 bits per transform + { 16, 9, 3 }, // 102 bits per transform + { 16, 10, 2 }, // 102 bits per transform + { 16, 11, 1 }, // 102 bits per transform + { 16, 14, 0 }, // 102 bits per transform + { 17, 0, 13 }, // 102 bits per transform + { 17, 1, 10 }, // 102 bits per transform + { 17, 2, 9 }, // 102 bits per transform + { 17, 3, 8 }, // 102 bits per transform + { 17, 4, 7 }, // 102 bits per transform + { 17, 5, 6 }, // 102 bits per transform + { 17, 6, 5 }, // 102 bits per transform + { 17, 7, 4 }, // 102 bits per transform + { 17, 8, 3 }, // 102 bits per transform + { 17, 9, 2 }, // 102 bits per transform + { 17, 10, 1 }, // 102 bits per transform + { 17, 13, 0 }, // 102 bits per transform + { 0, 1, 18 }, // 105 bits per transform + { 0, 14, 17 }, // 105 bits per transform + { 0, 15, 16 }, // 105 bits per transform + { 0, 16, 15 }, // 105 bits per transform + { 0, 17, 14 }, // 105 bits per transform + { 0, 18, 1 }, // 105 bits per transform + { 1, 0, 18 }, // 105 bits per transform + { 1, 11, 17 }, // 105 bits per transform + { 1, 12, 16 }, // 105 bits per transform + { 1, 13, 15 }, // 105 bits per transform + { 1, 14, 14 }, // 105 bits per transform + { 1, 15, 13 }, // 105 bits per transform + { 1, 16, 12 }, // 105 bits per transform + { 1, 17, 11 }, // 105 bits per transform + { 1, 18, 0 }, // 105 bits per transform + { 2, 10, 17 }, // 105 bits per transform + { 2, 11, 16 }, // 105 bits per transform + { 2, 12, 15 }, // 105 bits per transform + { 2, 13, 14 }, // 105 bits per transform + { 2, 14, 13 }, // 105 bits per transform + { 2, 15, 12 }, // 105 bits per transform + { 2, 16, 11 }, // 105 bits per transform + { 2, 17, 10 }, // 105 bits per transform + { 3, 9, 17 }, // 105 bits per transform + { 3, 10, 16 }, // 105 bits per transform + { 3, 11, 15 }, // 105 bits per transform + { 3, 12, 14 }, // 105 bits per transform + { 3, 13, 13 }, // 105 bits per transform + { 3, 14, 12 }, // 105 bits per transform + { 3, 15, 11 }, // 105 bits per transform + { 3, 16, 10 }, // 105 bits per transform + { 3, 17, 9 }, // 105 bits per transform + { 4, 8, 17 }, // 105 bits per transform + { 4, 9, 16 }, // 105 bits per transform + { 4, 10, 15 }, // 105 bits per transform + { 4, 11, 14 }, // 105 bits per transform + { 4, 12, 13 }, // 105 bits per transform + { 4, 13, 12 }, // 105 bits per transform + { 4, 14, 11 }, // 105 bits per transform + { 4, 15, 10 }, // 105 bits per transform + { 4, 16, 9 }, // 105 bits per transform + { 4, 17, 8 }, // 105 bits per transform + { 5, 7, 17 }, // 105 bits per transform + { 5, 8, 16 }, // 105 bits per transform + { 5, 9, 15 }, // 105 bits per transform + { 5, 10, 14 }, // 105 bits per transform + { 5, 11, 13 }, // 105 bits per transform + { 5, 12, 12 }, // 105 bits per transform + { 5, 13, 11 }, // 105 bits per transform + { 5, 14, 10 }, // 105 bits per transform + { 5, 15, 9 }, // 105 bits per transform + { 5, 16, 8 }, // 105 bits per transform + { 5, 17, 7 }, // 105 bits per transform + { 6, 6, 17 }, // 105 bits per transform + { 6, 7, 16 }, // 105 bits per transform + { 6, 8, 15 }, // 105 bits per transform + { 6, 9, 14 }, // 105 bits per transform + { 6, 10, 13 }, // 105 bits per transform + { 6, 11, 12 }, // 105 bits per transform + { 6, 12, 11 }, // 105 bits per transform + { 6, 13, 10 }, // 105 bits per transform + { 6, 14, 9 }, // 105 bits per transform + { 6, 15, 8 }, // 105 bits per transform + { 6, 16, 7 }, // 105 bits per transform + { 6, 17, 6 }, // 105 bits per transform + { 7, 5, 17 }, // 105 bits per transform + { 7, 6, 16 }, // 105 bits per transform + { 7, 7, 15 }, // 105 bits per transform + { 7, 8, 14 }, // 105 bits per transform + { 7, 9, 13 }, // 105 bits per transform + { 7, 10, 12 }, // 105 bits per transform + { 7, 11, 11 }, // 105 bits per transform + { 7, 12, 10 }, // 105 bits per transform + { 7, 13, 9 }, // 105 bits per transform + { 7, 14, 8 }, // 105 bits per transform + { 7, 15, 7 }, // 105 bits per transform + { 7, 16, 6 }, // 105 bits per transform + { 7, 17, 5 }, // 105 bits per transform + { 8, 4, 17 }, // 105 bits per transform + { 8, 5, 16 }, // 105 bits per transform + { 8, 6, 15 }, // 105 bits per transform + { 8, 7, 14 }, // 105 bits per transform + { 8, 8, 13 }, // 105 bits per transform + { 8, 9, 12 }, // 105 bits per transform + { 8, 10, 11 }, // 105 bits per transform + { 8, 11, 10 }, // 105 bits per transform + { 8, 12, 9 }, // 105 bits per transform + { 8, 13, 8 }, // 105 bits per transform + { 8, 14, 7 }, // 105 bits per transform + { 8, 15, 6 }, // 105 bits per transform + { 8, 16, 5 }, // 105 bits per transform + { 8, 17, 4 }, // 105 bits per transform + { 9, 3, 17 }, // 105 bits per transform + { 9, 4, 16 }, // 105 bits per transform + { 9, 5, 15 }, // 105 bits per transform + { 9, 6, 14 }, // 105 bits per transform + { 9, 7, 13 }, // 105 bits per transform + { 9, 8, 12 }, // 105 bits per transform + { 9, 9, 11 }, // 105 bits per transform + { 9, 10, 10 }, // 105 bits per transform + { 9, 11, 9 }, // 105 bits per transform + { 9, 12, 8 }, // 105 bits per transform + { 9, 13, 7 }, // 105 bits per transform + { 9, 14, 6 }, // 105 bits per transform + { 9, 15, 5 }, // 105 bits per transform + { 9, 16, 4 }, // 105 bits per transform + { 9, 17, 3 }, // 105 bits per transform + { 10, 2, 17 }, // 105 bits per transform + { 10, 3, 16 }, // 105 bits per transform + { 10, 4, 15 }, // 105 bits per transform + { 10, 5, 14 }, // 105 bits per transform + { 10, 6, 13 }, // 105 bits per transform + { 10, 7, 12 }, // 105 bits per transform + { 10, 8, 11 }, // 105 bits per transform + { 10, 9, 10 }, // 105 bits per transform + { 10, 10, 9 }, // 105 bits per transform + { 10, 11, 8 }, // 105 bits per transform + { 10, 12, 7 }, // 105 bits per transform + { 10, 13, 6 }, // 105 bits per transform + { 10, 14, 5 }, // 105 bits per transform + { 10, 15, 4 }, // 105 bits per transform + { 10, 16, 3 }, // 105 bits per transform + { 10, 17, 2 }, // 105 bits per transform + { 11, 1, 17 }, // 105 bits per transform + { 11, 2, 16 }, // 105 bits per transform + { 11, 3, 15 }, // 105 bits per transform + { 11, 4, 14 }, // 105 bits per transform + { 11, 5, 13 }, // 105 bits per transform + { 11, 6, 12 }, // 105 bits per transform + { 11, 7, 11 }, // 105 bits per transform + { 11, 8, 10 }, // 105 bits per transform + { 11, 9, 9 }, // 105 bits per transform + { 11, 10, 8 }, // 105 bits per transform + { 11, 11, 7 }, // 105 bits per transform + { 11, 12, 6 }, // 105 bits per transform + { 11, 13, 5 }, // 105 bits per transform + { 11, 14, 4 }, // 105 bits per transform + { 11, 15, 3 }, // 105 bits per transform + { 11, 16, 2 }, // 105 bits per transform + { 11, 17, 1 }, // 105 bits per transform + { 12, 1, 16 }, // 105 bits per transform + { 12, 2, 15 }, // 105 bits per transform + { 12, 3, 14 }, // 105 bits per transform + { 12, 4, 13 }, // 105 bits per transform + { 12, 5, 12 }, // 105 bits per transform + { 12, 6, 11 }, // 105 bits per transform + { 12, 7, 10 }, // 105 bits per transform + { 12, 8, 9 }, // 105 bits per transform + { 12, 9, 8 }, // 105 bits per transform + { 12, 10, 7 }, // 105 bits per transform + { 12, 11, 6 }, // 105 bits per transform + { 12, 12, 5 }, // 105 bits per transform + { 12, 13, 4 }, // 105 bits per transform + { 12, 14, 3 }, // 105 bits per transform + { 12, 15, 2 }, // 105 bits per transform + { 12, 16, 1 }, // 105 bits per transform + { 13, 1, 15 }, // 105 bits per transform + { 13, 2, 14 }, // 105 bits per transform + { 13, 3, 13 }, // 105 bits per transform + { 13, 4, 12 }, // 105 bits per transform + { 13, 5, 11 }, // 105 bits per transform + { 13, 6, 10 }, // 105 bits per transform + { 13, 7, 9 }, // 105 bits per transform + { 13, 8, 8 }, // 105 bits per transform + { 13, 9, 7 }, // 105 bits per transform + { 13, 10, 6 }, // 105 bits per transform + { 13, 11, 5 }, // 105 bits per transform + { 13, 12, 4 }, // 105 bits per transform + { 13, 13, 3 }, // 105 bits per transform + { 13, 14, 2 }, // 105 bits per transform + { 13, 15, 1 }, // 105 bits per transform + { 14, 0, 17 }, // 105 bits per transform + { 14, 1, 14 }, // 105 bits per transform + { 14, 2, 13 }, // 105 bits per transform + { 14, 3, 12 }, // 105 bits per transform + { 14, 4, 11 }, // 105 bits per transform + { 14, 5, 10 }, // 105 bits per transform + { 14, 6, 9 }, // 105 bits per transform + { 14, 7, 8 }, // 105 bits per transform + { 14, 8, 7 }, // 105 bits per transform + { 14, 9, 6 }, // 105 bits per transform + { 14, 10, 5 }, // 105 bits per transform + { 14, 11, 4 }, // 105 bits per transform + { 14, 12, 3 }, // 105 bits per transform + { 14, 13, 2 }, // 105 bits per transform + { 14, 14, 1 }, // 105 bits per transform + { 14, 17, 0 }, // 105 bits per transform + { 15, 0, 16 }, // 105 bits per transform + { 15, 1, 13 }, // 105 bits per transform + { 15, 2, 12 }, // 105 bits per transform + { 15, 3, 11 }, // 105 bits per transform + { 15, 4, 10 }, // 105 bits per transform + { 15, 5, 9 }, // 105 bits per transform + { 15, 6, 8 }, // 105 bits per transform + { 15, 7, 7 }, // 105 bits per transform + { 15, 8, 6 }, // 105 bits per transform + { 15, 9, 5 }, // 105 bits per transform + { 15, 10, 4 }, // 105 bits per transform + { 15, 11, 3 }, // 105 bits per transform + { 15, 12, 2 }, // 105 bits per transform + { 15, 13, 1 }, // 105 bits per transform + { 15, 16, 0 }, // 105 bits per transform + { 16, 0, 15 }, // 105 bits per transform + { 16, 1, 12 }, // 105 bits per transform + { 16, 2, 11 }, // 105 bits per transform + { 16, 3, 10 }, // 105 bits per transform + { 16, 4, 9 }, // 105 bits per transform + { 16, 5, 8 }, // 105 bits per transform + { 16, 6, 7 }, // 105 bits per transform + { 16, 7, 6 }, // 105 bits per transform + { 16, 8, 5 }, // 105 bits per transform + { 16, 9, 4 }, // 105 bits per transform + { 16, 10, 3 }, // 105 bits per transform + { 16, 11, 2 }, // 105 bits per transform + { 16, 12, 1 }, // 105 bits per transform + { 16, 15, 0 }, // 105 bits per transform + { 17, 0, 14 }, // 105 bits per transform + { 17, 1, 11 }, // 105 bits per transform + { 17, 2, 10 }, // 105 bits per transform + { 17, 3, 9 }, // 105 bits per transform + { 17, 4, 8 }, // 105 bits per transform + { 17, 5, 7 }, // 105 bits per transform + { 17, 6, 6 }, // 105 bits per transform + { 17, 7, 5 }, // 105 bits per transform + { 17, 8, 4 }, // 105 bits per transform + { 17, 9, 3 }, // 105 bits per transform + { 17, 10, 2 }, // 105 bits per transform + { 17, 11, 1 }, // 105 bits per transform + { 17, 14, 0 }, // 105 bits per transform + { 18, 0, 1 }, // 105 bits per transform + { 18, 1, 0 }, // 105 bits per transform + { 0, 2, 18 }, // 108 bits per transform + { 0, 15, 17 }, // 108 bits per transform + { 0, 16, 16 }, // 108 bits per transform + { 0, 17, 15 }, // 108 bits per transform + { 0, 18, 2 }, // 108 bits per transform + { 1, 12, 17 }, // 108 bits per transform + { 1, 13, 16 }, // 108 bits per transform + { 1, 14, 15 }, // 108 bits per transform + { 1, 15, 14 }, // 108 bits per transform + { 1, 16, 13 }, // 108 bits per transform + { 1, 17, 12 }, // 108 bits per transform + { 2, 0, 18 }, // 108 bits per transform + { 2, 11, 17 }, // 108 bits per transform + { 2, 12, 16 }, // 108 bits per transform + { 2, 13, 15 }, // 108 bits per transform + { 2, 14, 14 }, // 108 bits per transform + { 2, 15, 13 }, // 108 bits per transform + { 2, 16, 12 }, // 108 bits per transform + { 2, 17, 11 }, // 108 bits per transform + { 2, 18, 0 }, // 108 bits per transform + { 3, 10, 17 }, // 108 bits per transform + { 3, 11, 16 }, // 108 bits per transform + { 3, 12, 15 }, // 108 bits per transform + { 3, 13, 14 }, // 108 bits per transform + { 3, 14, 13 }, // 108 bits per transform + { 3, 15, 12 }, // 108 bits per transform + { 3, 16, 11 }, // 108 bits per transform + { 3, 17, 10 }, // 108 bits per transform + { 4, 9, 17 }, // 108 bits per transform + { 4, 10, 16 }, // 108 bits per transform + { 4, 11, 15 }, // 108 bits per transform + { 4, 12, 14 }, // 108 bits per transform + { 4, 13, 13 }, // 108 bits per transform + { 4, 14, 12 }, // 108 bits per transform + { 4, 15, 11 }, // 108 bits per transform + { 4, 16, 10 }, // 108 bits per transform + { 4, 17, 9 }, // 108 bits per transform + { 5, 8, 17 }, // 108 bits per transform + { 5, 9, 16 }, // 108 bits per transform + { 5, 10, 15 }, // 108 bits per transform + { 5, 11, 14 }, // 108 bits per transform + { 5, 12, 13 }, // 108 bits per transform + { 5, 13, 12 }, // 108 bits per transform + { 5, 14, 11 }, // 108 bits per transform + { 5, 15, 10 }, // 108 bits per transform + { 5, 16, 9 }, // 108 bits per transform + { 5, 17, 8 }, // 108 bits per transform + { 6, 7, 17 }, // 108 bits per transform + { 6, 8, 16 }, // 108 bits per transform + { 6, 9, 15 }, // 108 bits per transform + { 6, 10, 14 }, // 108 bits per transform + { 6, 11, 13 }, // 108 bits per transform + { 6, 12, 12 }, // 108 bits per transform + { 6, 13, 11 }, // 108 bits per transform + { 6, 14, 10 }, // 108 bits per transform + { 6, 15, 9 }, // 108 bits per transform + { 6, 16, 8 }, // 108 bits per transform + { 6, 17, 7 }, // 108 bits per transform + { 7, 6, 17 }, // 108 bits per transform + { 7, 7, 16 }, // 108 bits per transform + { 7, 8, 15 }, // 108 bits per transform + { 7, 9, 14 }, // 108 bits per transform + { 7, 10, 13 }, // 108 bits per transform + { 7, 11, 12 }, // 108 bits per transform + { 7, 12, 11 }, // 108 bits per transform + { 7, 13, 10 }, // 108 bits per transform + { 7, 14, 9 }, // 108 bits per transform + { 7, 15, 8 }, // 108 bits per transform + { 7, 16, 7 }, // 108 bits per transform + { 7, 17, 6 }, // 108 bits per transform + { 8, 5, 17 }, // 108 bits per transform + { 8, 6, 16 }, // 108 bits per transform + { 8, 7, 15 }, // 108 bits per transform + { 8, 8, 14 }, // 108 bits per transform + { 8, 9, 13 }, // 108 bits per transform + { 8, 10, 12 }, // 108 bits per transform + { 8, 11, 11 }, // 108 bits per transform + { 8, 12, 10 }, // 108 bits per transform + { 8, 13, 9 }, // 108 bits per transform + { 8, 14, 8 }, // 108 bits per transform + { 8, 15, 7 }, // 108 bits per transform + { 8, 16, 6 }, // 108 bits per transform + { 8, 17, 5 }, // 108 bits per transform + { 9, 4, 17 }, // 108 bits per transform + { 9, 5, 16 }, // 108 bits per transform + { 9, 6, 15 }, // 108 bits per transform + { 9, 7, 14 }, // 108 bits per transform + { 9, 8, 13 }, // 108 bits per transform + { 9, 9, 12 }, // 108 bits per transform + { 9, 10, 11 }, // 108 bits per transform + { 9, 11, 10 }, // 108 bits per transform + { 9, 12, 9 }, // 108 bits per transform + { 9, 13, 8 }, // 108 bits per transform + { 9, 14, 7 }, // 108 bits per transform + { 9, 15, 6 }, // 108 bits per transform + { 9, 16, 5 }, // 108 bits per transform + { 9, 17, 4 }, // 108 bits per transform + { 10, 3, 17 }, // 108 bits per transform + { 10, 4, 16 }, // 108 bits per transform + { 10, 5, 15 }, // 108 bits per transform + { 10, 6, 14 }, // 108 bits per transform + { 10, 7, 13 }, // 108 bits per transform + { 10, 8, 12 }, // 108 bits per transform + { 10, 9, 11 }, // 108 bits per transform + { 10, 10, 10 }, // 108 bits per transform + { 10, 11, 9 }, // 108 bits per transform + { 10, 12, 8 }, // 108 bits per transform + { 10, 13, 7 }, // 108 bits per transform + { 10, 14, 6 }, // 108 bits per transform + { 10, 15, 5 }, // 108 bits per transform + { 10, 16, 4 }, // 108 bits per transform + { 10, 17, 3 }, // 108 bits per transform + { 11, 2, 17 }, // 108 bits per transform + { 11, 3, 16 }, // 108 bits per transform + { 11, 4, 15 }, // 108 bits per transform + { 11, 5, 14 }, // 108 bits per transform + { 11, 6, 13 }, // 108 bits per transform + { 11, 7, 12 }, // 108 bits per transform + { 11, 8, 11 }, // 108 bits per transform + { 11, 9, 10 }, // 108 bits per transform + { 11, 10, 9 }, // 108 bits per transform + { 11, 11, 8 }, // 108 bits per transform + { 11, 12, 7 }, // 108 bits per transform + { 11, 13, 6 }, // 108 bits per transform + { 11, 14, 5 }, // 108 bits per transform + { 11, 15, 4 }, // 108 bits per transform + { 11, 16, 3 }, // 108 bits per transform + { 11, 17, 2 }, // 108 bits per transform + { 12, 1, 17 }, // 108 bits per transform + { 12, 2, 16 }, // 108 bits per transform + { 12, 3, 15 }, // 108 bits per transform + { 12, 4, 14 }, // 108 bits per transform + { 12, 5, 13 }, // 108 bits per transform + { 12, 6, 12 }, // 108 bits per transform + { 12, 7, 11 }, // 108 bits per transform + { 12, 8, 10 }, // 108 bits per transform + { 12, 9, 9 }, // 108 bits per transform + { 12, 10, 8 }, // 108 bits per transform + { 12, 11, 7 }, // 108 bits per transform + { 12, 12, 6 }, // 108 bits per transform + { 12, 13, 5 }, // 108 bits per transform + { 12, 14, 4 }, // 108 bits per transform + { 12, 15, 3 }, // 108 bits per transform + { 12, 16, 2 }, // 108 bits per transform + { 12, 17, 1 }, // 108 bits per transform + { 13, 1, 16 }, // 108 bits per transform + { 13, 2, 15 }, // 108 bits per transform + { 13, 3, 14 }, // 108 bits per transform + { 13, 4, 13 }, // 108 bits per transform + { 13, 5, 12 }, // 108 bits per transform + { 13, 6, 11 }, // 108 bits per transform + { 13, 7, 10 }, // 108 bits per transform + { 13, 8, 9 }, // 108 bits per transform + { 13, 9, 8 }, // 108 bits per transform + { 13, 10, 7 }, // 108 bits per transform + { 13, 11, 6 }, // 108 bits per transform + { 13, 12, 5 }, // 108 bits per transform + { 13, 13, 4 }, // 108 bits per transform + { 13, 14, 3 }, // 108 bits per transform + { 13, 15, 2 }, // 108 bits per transform + { 13, 16, 1 }, // 108 bits per transform + { 14, 1, 15 }, // 108 bits per transform + { 14, 2, 14 }, // 108 bits per transform + { 14, 3, 13 }, // 108 bits per transform + { 14, 4, 12 }, // 108 bits per transform + { 14, 5, 11 }, // 108 bits per transform + { 14, 6, 10 }, // 108 bits per transform + { 14, 7, 9 }, // 108 bits per transform + { 14, 8, 8 }, // 108 bits per transform + { 14, 9, 7 }, // 108 bits per transform + { 14, 10, 6 }, // 108 bits per transform + { 14, 11, 5 }, // 108 bits per transform + { 14, 12, 4 }, // 108 bits per transform + { 14, 13, 3 }, // 108 bits per transform + { 14, 14, 2 }, // 108 bits per transform + { 14, 15, 1 }, // 108 bits per transform + { 15, 0, 17 }, // 108 bits per transform + { 15, 1, 14 }, // 108 bits per transform + { 15, 2, 13 }, // 108 bits per transform + { 15, 3, 12 }, // 108 bits per transform + { 15, 4, 11 }, // 108 bits per transform + { 15, 5, 10 }, // 108 bits per transform + { 15, 6, 9 }, // 108 bits per transform + { 15, 7, 8 }, // 108 bits per transform + { 15, 8, 7 }, // 108 bits per transform + { 15, 9, 6 }, // 108 bits per transform + { 15, 10, 5 }, // 108 bits per transform + { 15, 11, 4 }, // 108 bits per transform + { 15, 12, 3 }, // 108 bits per transform + { 15, 13, 2 }, // 108 bits per transform + { 15, 14, 1 }, // 108 bits per transform + { 15, 17, 0 }, // 108 bits per transform + { 16, 0, 16 }, // 108 bits per transform + { 16, 1, 13 }, // 108 bits per transform + { 16, 2, 12 }, // 108 bits per transform + { 16, 3, 11 }, // 108 bits per transform + { 16, 4, 10 }, // 108 bits per transform + { 16, 5, 9 }, // 108 bits per transform + { 16, 6, 8 }, // 108 bits per transform + { 16, 7, 7 }, // 108 bits per transform + { 16, 8, 6 }, // 108 bits per transform + { 16, 9, 5 }, // 108 bits per transform + { 16, 10, 4 }, // 108 bits per transform + { 16, 11, 3 }, // 108 bits per transform + { 16, 12, 2 }, // 108 bits per transform + { 16, 13, 1 }, // 108 bits per transform + { 16, 16, 0 }, // 108 bits per transform + { 17, 0, 15 }, // 108 bits per transform + { 17, 1, 12 }, // 108 bits per transform + { 17, 2, 11 }, // 108 bits per transform + { 17, 3, 10 }, // 108 bits per transform + { 17, 4, 9 }, // 108 bits per transform + { 17, 5, 8 }, // 108 bits per transform + { 17, 6, 7 }, // 108 bits per transform + { 17, 7, 6 }, // 108 bits per transform + { 17, 8, 5 }, // 108 bits per transform + { 17, 9, 4 }, // 108 bits per transform + { 17, 10, 3 }, // 108 bits per transform + { 17, 11, 2 }, // 108 bits per transform + { 17, 12, 1 }, // 108 bits per transform + { 17, 15, 0 }, // 108 bits per transform + { 18, 0, 2 }, // 108 bits per transform + { 18, 2, 0 }, // 108 bits per transform + { 0, 3, 18 }, // 111 bits per transform + { 0, 16, 17 }, // 111 bits per transform + { 0, 17, 16 }, // 111 bits per transform + { 0, 18, 3 }, // 111 bits per transform + { 1, 13, 17 }, // 111 bits per transform + { 1, 14, 16 }, // 111 bits per transform + { 1, 15, 15 }, // 111 bits per transform + { 1, 16, 14 }, // 111 bits per transform + { 1, 17, 13 }, // 111 bits per transform + { 2, 12, 17 }, // 111 bits per transform + { 2, 13, 16 }, // 111 bits per transform + { 2, 14, 15 }, // 111 bits per transform + { 2, 15, 14 }, // 111 bits per transform + { 2, 16, 13 }, // 111 bits per transform + { 2, 17, 12 }, // 111 bits per transform + { 3, 0, 18 }, // 111 bits per transform + { 3, 11, 17 }, // 111 bits per transform + { 3, 12, 16 }, // 111 bits per transform + { 3, 13, 15 }, // 111 bits per transform + { 3, 14, 14 }, // 111 bits per transform + { 3, 15, 13 }, // 111 bits per transform + { 3, 16, 12 }, // 111 bits per transform + { 3, 17, 11 }, // 111 bits per transform + { 3, 18, 0 }, // 111 bits per transform + { 4, 10, 17 }, // 111 bits per transform + { 4, 11, 16 }, // 111 bits per transform + { 4, 12, 15 }, // 111 bits per transform + { 4, 13, 14 }, // 111 bits per transform + { 4, 14, 13 }, // 111 bits per transform + { 4, 15, 12 }, // 111 bits per transform + { 4, 16, 11 }, // 111 bits per transform + { 4, 17, 10 }, // 111 bits per transform + { 5, 9, 17 }, // 111 bits per transform + { 5, 10, 16 }, // 111 bits per transform + { 5, 11, 15 }, // 111 bits per transform + { 5, 12, 14 }, // 111 bits per transform + { 5, 13, 13 }, // 111 bits per transform + { 5, 14, 12 }, // 111 bits per transform + { 5, 15, 11 }, // 111 bits per transform + { 5, 16, 10 }, // 111 bits per transform + { 5, 17, 9 }, // 111 bits per transform + { 6, 8, 17 }, // 111 bits per transform + { 6, 9, 16 }, // 111 bits per transform + { 6, 10, 15 }, // 111 bits per transform + { 6, 11, 14 }, // 111 bits per transform + { 6, 12, 13 }, // 111 bits per transform + { 6, 13, 12 }, // 111 bits per transform + { 6, 14, 11 }, // 111 bits per transform + { 6, 15, 10 }, // 111 bits per transform + { 6, 16, 9 }, // 111 bits per transform + { 6, 17, 8 }, // 111 bits per transform + { 7, 7, 17 }, // 111 bits per transform + { 7, 8, 16 }, // 111 bits per transform + { 7, 9, 15 }, // 111 bits per transform + { 7, 10, 14 }, // 111 bits per transform + { 7, 11, 13 }, // 111 bits per transform + { 7, 12, 12 }, // 111 bits per transform + { 7, 13, 11 }, // 111 bits per transform + { 7, 14, 10 }, // 111 bits per transform + { 7, 15, 9 }, // 111 bits per transform + { 7, 16, 8 }, // 111 bits per transform + { 7, 17, 7 }, // 111 bits per transform + { 8, 6, 17 }, // 111 bits per transform + { 8, 7, 16 }, // 111 bits per transform + { 8, 8, 15 }, // 111 bits per transform + { 8, 9, 14 }, // 111 bits per transform + { 8, 10, 13 }, // 111 bits per transform + { 8, 11, 12 }, // 111 bits per transform + { 8, 12, 11 }, // 111 bits per transform + { 8, 13, 10 }, // 111 bits per transform + { 8, 14, 9 }, // 111 bits per transform + { 8, 15, 8 }, // 111 bits per transform + { 8, 16, 7 }, // 111 bits per transform + { 8, 17, 6 }, // 111 bits per transform + { 9, 5, 17 }, // 111 bits per transform + { 9, 6, 16 }, // 111 bits per transform + { 9, 7, 15 }, // 111 bits per transform + { 9, 8, 14 }, // 111 bits per transform + { 9, 9, 13 }, // 111 bits per transform + { 9, 10, 12 }, // 111 bits per transform + { 9, 11, 11 }, // 111 bits per transform + { 9, 12, 10 }, // 111 bits per transform + { 9, 13, 9 }, // 111 bits per transform + { 9, 14, 8 }, // 111 bits per transform + { 9, 15, 7 }, // 111 bits per transform + { 9, 16, 6 }, // 111 bits per transform + { 9, 17, 5 }, // 111 bits per transform + { 10, 4, 17 }, // 111 bits per transform + { 10, 5, 16 }, // 111 bits per transform + { 10, 6, 15 }, // 111 bits per transform + { 10, 7, 14 }, // 111 bits per transform + { 10, 8, 13 }, // 111 bits per transform + { 10, 9, 12 }, // 111 bits per transform + { 10, 10, 11 }, // 111 bits per transform + { 10, 11, 10 }, // 111 bits per transform + { 10, 12, 9 }, // 111 bits per transform + { 10, 13, 8 }, // 111 bits per transform + { 10, 14, 7 }, // 111 bits per transform + { 10, 15, 6 }, // 111 bits per transform + { 10, 16, 5 }, // 111 bits per transform + { 10, 17, 4 }, // 111 bits per transform + { 11, 3, 17 }, // 111 bits per transform + { 11, 4, 16 }, // 111 bits per transform + { 11, 5, 15 }, // 111 bits per transform + { 11, 6, 14 }, // 111 bits per transform + { 11, 7, 13 }, // 111 bits per transform + { 11, 8, 12 }, // 111 bits per transform + { 11, 9, 11 }, // 111 bits per transform + { 11, 10, 10 }, // 111 bits per transform + { 11, 11, 9 }, // 111 bits per transform + { 11, 12, 8 }, // 111 bits per transform + { 11, 13, 7 }, // 111 bits per transform + { 11, 14, 6 }, // 111 bits per transform + { 11, 15, 5 }, // 111 bits per transform + { 11, 16, 4 }, // 111 bits per transform + { 11, 17, 3 }, // 111 bits per transform + { 12, 2, 17 }, // 111 bits per transform + { 12, 3, 16 }, // 111 bits per transform + { 12, 4, 15 }, // 111 bits per transform + { 12, 5, 14 }, // 111 bits per transform + { 12, 6, 13 }, // 111 bits per transform + { 12, 7, 12 }, // 111 bits per transform + { 12, 8, 11 }, // 111 bits per transform + { 12, 9, 10 }, // 111 bits per transform + { 12, 10, 9 }, // 111 bits per transform + { 12, 11, 8 }, // 111 bits per transform + { 12, 12, 7 }, // 111 bits per transform + { 12, 13, 6 }, // 111 bits per transform + { 12, 14, 5 }, // 111 bits per transform + { 12, 15, 4 }, // 111 bits per transform + { 12, 16, 3 }, // 111 bits per transform + { 12, 17, 2 }, // 111 bits per transform + { 13, 1, 17 }, // 111 bits per transform + { 13, 2, 16 }, // 111 bits per transform + { 13, 3, 15 }, // 111 bits per transform + { 13, 4, 14 }, // 111 bits per transform + { 13, 5, 13 }, // 111 bits per transform + { 13, 6, 12 }, // 111 bits per transform + { 13, 7, 11 }, // 111 bits per transform + { 13, 8, 10 }, // 111 bits per transform + { 13, 9, 9 }, // 111 bits per transform + { 13, 10, 8 }, // 111 bits per transform + { 13, 11, 7 }, // 111 bits per transform + { 13, 12, 6 }, // 111 bits per transform + { 13, 13, 5 }, // 111 bits per transform + { 13, 14, 4 }, // 111 bits per transform + { 13, 15, 3 }, // 111 bits per transform + { 13, 16, 2 }, // 111 bits per transform + { 13, 17, 1 }, // 111 bits per transform + { 14, 1, 16 }, // 111 bits per transform + { 14, 2, 15 }, // 111 bits per transform + { 14, 3, 14 }, // 111 bits per transform + { 14, 4, 13 }, // 111 bits per transform + { 14, 5, 12 }, // 111 bits per transform + { 14, 6, 11 }, // 111 bits per transform + { 14, 7, 10 }, // 111 bits per transform + { 14, 8, 9 }, // 111 bits per transform + { 14, 9, 8 }, // 111 bits per transform + { 14, 10, 7 }, // 111 bits per transform + { 14, 11, 6 }, // 111 bits per transform + { 14, 12, 5 }, // 111 bits per transform + { 14, 13, 4 }, // 111 bits per transform + { 14, 14, 3 }, // 111 bits per transform + { 14, 15, 2 }, // 111 bits per transform + { 14, 16, 1 }, // 111 bits per transform + { 15, 1, 15 }, // 111 bits per transform + { 15, 2, 14 }, // 111 bits per transform + { 15, 3, 13 }, // 111 bits per transform + { 15, 4, 12 }, // 111 bits per transform + { 15, 5, 11 }, // 111 bits per transform + { 15, 6, 10 }, // 111 bits per transform + { 15, 7, 9 }, // 111 bits per transform + { 15, 8, 8 }, // 111 bits per transform + { 15, 9, 7 }, // 111 bits per transform + { 15, 10, 6 }, // 111 bits per transform + { 15, 11, 5 }, // 111 bits per transform + { 15, 12, 4 }, // 111 bits per transform + { 15, 13, 3 }, // 111 bits per transform + { 15, 14, 2 }, // 111 bits per transform + { 15, 15, 1 }, // 111 bits per transform + { 16, 0, 17 }, // 111 bits per transform + { 16, 1, 14 }, // 111 bits per transform + { 16, 2, 13 }, // 111 bits per transform + { 16, 3, 12 }, // 111 bits per transform + { 16, 4, 11 }, // 111 bits per transform + { 16, 5, 10 }, // 111 bits per transform + { 16, 6, 9 }, // 111 bits per transform + { 16, 7, 8 }, // 111 bits per transform + { 16, 8, 7 }, // 111 bits per transform + { 16, 9, 6 }, // 111 bits per transform + { 16, 10, 5 }, // 111 bits per transform + { 16, 11, 4 }, // 111 bits per transform + { 16, 12, 3 }, // 111 bits per transform + { 16, 13, 2 }, // 111 bits per transform + { 16, 14, 1 }, // 111 bits per transform + { 16, 17, 0 }, // 111 bits per transform + { 17, 0, 16 }, // 111 bits per transform + { 17, 1, 13 }, // 111 bits per transform + { 17, 2, 12 }, // 111 bits per transform + { 17, 3, 11 }, // 111 bits per transform + { 17, 4, 10 }, // 111 bits per transform + { 17, 5, 9 }, // 111 bits per transform + { 17, 6, 8 }, // 111 bits per transform + { 17, 7, 7 }, // 111 bits per transform + { 17, 8, 6 }, // 111 bits per transform + { 17, 9, 5 }, // 111 bits per transform + { 17, 10, 4 }, // 111 bits per transform + { 17, 11, 3 }, // 111 bits per transform + { 17, 12, 2 }, // 111 bits per transform + { 17, 13, 1 }, // 111 bits per transform + { 17, 16, 0 }, // 111 bits per transform + { 18, 0, 3 }, // 111 bits per transform + { 18, 3, 0 }, // 111 bits per transform + { 0, 4, 18 }, // 114 bits per transform + { 0, 17, 17 }, // 114 bits per transform + { 0, 18, 4 }, // 114 bits per transform + { 1, 1, 18 }, // 114 bits per transform + { 1, 14, 17 }, // 114 bits per transform + { 1, 15, 16 }, // 114 bits per transform + { 1, 16, 15 }, // 114 bits per transform + { 1, 17, 14 }, // 114 bits per transform + { 1, 18, 1 }, // 114 bits per transform + { 2, 13, 17 }, // 114 bits per transform + { 2, 14, 16 }, // 114 bits per transform + { 2, 15, 15 }, // 114 bits per transform + { 2, 16, 14 }, // 114 bits per transform + { 2, 17, 13 }, // 114 bits per transform + { 3, 12, 17 }, // 114 bits per transform + { 3, 13, 16 }, // 114 bits per transform + { 3, 14, 15 }, // 114 bits per transform + { 3, 15, 14 }, // 114 bits per transform + { 3, 16, 13 }, // 114 bits per transform + { 3, 17, 12 }, // 114 bits per transform + { 4, 0, 18 }, // 114 bits per transform + { 4, 11, 17 }, // 114 bits per transform + { 4, 12, 16 }, // 114 bits per transform + { 4, 13, 15 }, // 114 bits per transform + { 4, 14, 14 }, // 114 bits per transform + { 4, 15, 13 }, // 114 bits per transform + { 4, 16, 12 }, // 114 bits per transform + { 4, 17, 11 }, // 114 bits per transform + { 4, 18, 0 }, // 114 bits per transform + { 5, 10, 17 }, // 114 bits per transform + { 5, 11, 16 }, // 114 bits per transform + { 5, 12, 15 }, // 114 bits per transform + { 5, 13, 14 }, // 114 bits per transform + { 5, 14, 13 }, // 114 bits per transform + { 5, 15, 12 }, // 114 bits per transform + { 5, 16, 11 }, // 114 bits per transform + { 5, 17, 10 }, // 114 bits per transform + { 6, 9, 17 }, // 114 bits per transform + { 6, 10, 16 }, // 114 bits per transform + { 6, 11, 15 }, // 114 bits per transform + { 6, 12, 14 }, // 114 bits per transform + { 6, 13, 13 }, // 114 bits per transform + { 6, 14, 12 }, // 114 bits per transform + { 6, 15, 11 }, // 114 bits per transform + { 6, 16, 10 }, // 114 bits per transform + { 6, 17, 9 }, // 114 bits per transform + { 7, 8, 17 }, // 114 bits per transform + { 7, 9, 16 }, // 114 bits per transform + { 7, 10, 15 }, // 114 bits per transform + { 7, 11, 14 }, // 114 bits per transform + { 7, 12, 13 }, // 114 bits per transform + { 7, 13, 12 }, // 114 bits per transform + { 7, 14, 11 }, // 114 bits per transform + { 7, 15, 10 }, // 114 bits per transform + { 7, 16, 9 }, // 114 bits per transform + { 7, 17, 8 }, // 114 bits per transform + { 8, 7, 17 }, // 114 bits per transform + { 8, 8, 16 }, // 114 bits per transform + { 8, 9, 15 }, // 114 bits per transform + { 8, 10, 14 }, // 114 bits per transform + { 8, 11, 13 }, // 114 bits per transform + { 8, 12, 12 }, // 114 bits per transform + { 8, 13, 11 }, // 114 bits per transform + { 8, 14, 10 }, // 114 bits per transform + { 8, 15, 9 }, // 114 bits per transform + { 8, 16, 8 }, // 114 bits per transform + { 8, 17, 7 }, // 114 bits per transform + { 9, 6, 17 }, // 114 bits per transform + { 9, 7, 16 }, // 114 bits per transform + { 9, 8, 15 }, // 114 bits per transform + { 9, 9, 14 }, // 114 bits per transform + { 9, 10, 13 }, // 114 bits per transform + { 9, 11, 12 }, // 114 bits per transform + { 9, 12, 11 }, // 114 bits per transform + { 9, 13, 10 }, // 114 bits per transform + { 9, 14, 9 }, // 114 bits per transform + { 9, 15, 8 }, // 114 bits per transform + { 9, 16, 7 }, // 114 bits per transform + { 9, 17, 6 }, // 114 bits per transform + { 10, 5, 17 }, // 114 bits per transform + { 10, 6, 16 }, // 114 bits per transform + { 10, 7, 15 }, // 114 bits per transform + { 10, 8, 14 }, // 114 bits per transform + { 10, 9, 13 }, // 114 bits per transform + { 10, 10, 12 }, // 114 bits per transform + { 10, 11, 11 }, // 114 bits per transform + { 10, 12, 10 }, // 114 bits per transform + { 10, 13, 9 }, // 114 bits per transform + { 10, 14, 8 }, // 114 bits per transform + { 10, 15, 7 }, // 114 bits per transform + { 10, 16, 6 }, // 114 bits per transform + { 10, 17, 5 }, // 114 bits per transform + { 11, 4, 17 }, // 114 bits per transform + { 11, 5, 16 }, // 114 bits per transform + { 11, 6, 15 }, // 114 bits per transform + { 11, 7, 14 }, // 114 bits per transform + { 11, 8, 13 }, // 114 bits per transform + { 11, 9, 12 }, // 114 bits per transform + { 11, 10, 11 }, // 114 bits per transform + { 11, 11, 10 }, // 114 bits per transform + { 11, 12, 9 }, // 114 bits per transform + { 11, 13, 8 }, // 114 bits per transform + { 11, 14, 7 }, // 114 bits per transform + { 11, 15, 6 }, // 114 bits per transform + { 11, 16, 5 }, // 114 bits per transform + { 11, 17, 4 }, // 114 bits per transform + { 12, 3, 17 }, // 114 bits per transform + { 12, 4, 16 }, // 114 bits per transform + { 12, 5, 15 }, // 114 bits per transform + { 12, 6, 14 }, // 114 bits per transform + { 12, 7, 13 }, // 114 bits per transform + { 12, 8, 12 }, // 114 bits per transform + { 12, 9, 11 }, // 114 bits per transform + { 12, 10, 10 }, // 114 bits per transform + { 12, 11, 9 }, // 114 bits per transform + { 12, 12, 8 }, // 114 bits per transform + { 12, 13, 7 }, // 114 bits per transform + { 12, 14, 6 }, // 114 bits per transform + { 12, 15, 5 }, // 114 bits per transform + { 12, 16, 4 }, // 114 bits per transform + { 12, 17, 3 }, // 114 bits per transform + { 13, 2, 17 }, // 114 bits per transform + { 13, 3, 16 }, // 114 bits per transform + { 13, 4, 15 }, // 114 bits per transform + { 13, 5, 14 }, // 114 bits per transform + { 13, 6, 13 }, // 114 bits per transform + { 13, 7, 12 }, // 114 bits per transform + { 13, 8, 11 }, // 114 bits per transform + { 13, 9, 10 }, // 114 bits per transform + { 13, 10, 9 }, // 114 bits per transform + { 13, 11, 8 }, // 114 bits per transform + { 13, 12, 7 }, // 114 bits per transform + { 13, 13, 6 }, // 114 bits per transform + { 13, 14, 5 }, // 114 bits per transform + { 13, 15, 4 }, // 114 bits per transform + { 13, 16, 3 }, // 114 bits per transform + { 13, 17, 2 }, // 114 bits per transform + { 14, 1, 17 }, // 114 bits per transform + { 14, 2, 16 }, // 114 bits per transform + { 14, 3, 15 }, // 114 bits per transform + { 14, 4, 14 }, // 114 bits per transform + { 14, 5, 13 }, // 114 bits per transform + { 14, 6, 12 }, // 114 bits per transform + { 14, 7, 11 }, // 114 bits per transform + { 14, 8, 10 }, // 114 bits per transform + { 14, 9, 9 }, // 114 bits per transform + { 14, 10, 8 }, // 114 bits per transform + { 14, 11, 7 }, // 114 bits per transform + { 14, 12, 6 }, // 114 bits per transform + { 14, 13, 5 }, // 114 bits per transform + { 14, 14, 4 }, // 114 bits per transform + { 14, 15, 3 }, // 114 bits per transform + { 14, 16, 2 }, // 114 bits per transform + { 14, 17, 1 }, // 114 bits per transform + { 15, 1, 16 }, // 114 bits per transform + { 15, 2, 15 }, // 114 bits per transform + { 15, 3, 14 }, // 114 bits per transform + { 15, 4, 13 }, // 114 bits per transform + { 15, 5, 12 }, // 114 bits per transform + { 15, 6, 11 }, // 114 bits per transform + { 15, 7, 10 }, // 114 bits per transform + { 15, 8, 9 }, // 114 bits per transform + { 15, 9, 8 }, // 114 bits per transform + { 15, 10, 7 }, // 114 bits per transform + { 15, 11, 6 }, // 114 bits per transform + { 15, 12, 5 }, // 114 bits per transform + { 15, 13, 4 }, // 114 bits per transform + { 15, 14, 3 }, // 114 bits per transform + { 15, 15, 2 }, // 114 bits per transform + { 15, 16, 1 }, // 114 bits per transform + { 16, 1, 15 }, // 114 bits per transform + { 16, 2, 14 }, // 114 bits per transform + { 16, 3, 13 }, // 114 bits per transform + { 16, 4, 12 }, // 114 bits per transform + { 16, 5, 11 }, // 114 bits per transform + { 16, 6, 10 }, // 114 bits per transform + { 16, 7, 9 }, // 114 bits per transform + { 16, 8, 8 }, // 114 bits per transform + { 16, 9, 7 }, // 114 bits per transform + { 16, 10, 6 }, // 114 bits per transform + { 16, 11, 5 }, // 114 bits per transform + { 16, 12, 4 }, // 114 bits per transform + { 16, 13, 3 }, // 114 bits per transform + { 16, 14, 2 }, // 114 bits per transform + { 16, 15, 1 }, // 114 bits per transform + { 17, 0, 17 }, // 114 bits per transform + { 17, 1, 14 }, // 114 bits per transform + { 17, 2, 13 }, // 114 bits per transform + { 17, 3, 12 }, // 114 bits per transform + { 17, 4, 11 }, // 114 bits per transform + { 17, 5, 10 }, // 114 bits per transform + { 17, 6, 9 }, // 114 bits per transform + { 17, 7, 8 }, // 114 bits per transform + { 17, 8, 7 }, // 114 bits per transform + { 17, 9, 6 }, // 114 bits per transform + { 17, 10, 5 }, // 114 bits per transform + { 17, 11, 4 }, // 114 bits per transform + { 17, 12, 3 }, // 114 bits per transform + { 17, 13, 2 }, // 114 bits per transform + { 17, 14, 1 }, // 114 bits per transform + { 17, 17, 0 }, // 114 bits per transform + { 18, 0, 4 }, // 114 bits per transform + { 18, 1, 1 }, // 114 bits per transform + { 18, 4, 0 }, // 114 bits per transform + { 0, 5, 18 }, // 117 bits per transform + { 0, 18, 5 }, // 117 bits per transform + { 1, 2, 18 }, // 117 bits per transform + { 1, 15, 17 }, // 117 bits per transform + { 1, 16, 16 }, // 117 bits per transform + { 1, 17, 15 }, // 117 bits per transform + { 1, 18, 2 }, // 117 bits per transform + { 2, 1, 18 }, // 117 bits per transform + { 2, 14, 17 }, // 117 bits per transform + { 2, 15, 16 }, // 117 bits per transform + { 2, 16, 15 }, // 117 bits per transform + { 2, 17, 14 }, // 117 bits per transform + { 2, 18, 1 }, // 117 bits per transform + { 3, 13, 17 }, // 117 bits per transform + { 3, 14, 16 }, // 117 bits per transform + { 3, 15, 15 }, // 117 bits per transform + { 3, 16, 14 }, // 117 bits per transform + { 3, 17, 13 }, // 117 bits per transform + { 4, 12, 17 }, // 117 bits per transform + { 4, 13, 16 }, // 117 bits per transform + { 4, 14, 15 }, // 117 bits per transform + { 4, 15, 14 }, // 117 bits per transform + { 4, 16, 13 }, // 117 bits per transform + { 4, 17, 12 }, // 117 bits per transform + { 5, 0, 18 }, // 117 bits per transform + { 5, 11, 17 }, // 117 bits per transform + { 5, 12, 16 }, // 117 bits per transform + { 5, 13, 15 }, // 117 bits per transform + { 5, 14, 14 }, // 117 bits per transform + { 5, 15, 13 }, // 117 bits per transform + { 5, 16, 12 }, // 117 bits per transform + { 5, 17, 11 }, // 117 bits per transform + { 5, 18, 0 }, // 117 bits per transform + { 6, 10, 17 }, // 117 bits per transform + { 6, 11, 16 }, // 117 bits per transform + { 6, 12, 15 }, // 117 bits per transform + { 6, 13, 14 }, // 117 bits per transform + { 6, 14, 13 }, // 117 bits per transform + { 6, 15, 12 }, // 117 bits per transform + { 6, 16, 11 }, // 117 bits per transform + { 6, 17, 10 }, // 117 bits per transform + { 7, 9, 17 }, // 117 bits per transform + { 7, 10, 16 }, // 117 bits per transform + { 7, 11, 15 }, // 117 bits per transform + { 7, 12, 14 }, // 117 bits per transform + { 7, 13, 13 }, // 117 bits per transform + { 7, 14, 12 }, // 117 bits per transform + { 7, 15, 11 }, // 117 bits per transform + { 7, 16, 10 }, // 117 bits per transform + { 7, 17, 9 }, // 117 bits per transform + { 8, 8, 17 }, // 117 bits per transform + { 8, 9, 16 }, // 117 bits per transform + { 8, 10, 15 }, // 117 bits per transform + { 8, 11, 14 }, // 117 bits per transform + { 8, 12, 13 }, // 117 bits per transform + { 8, 13, 12 }, // 117 bits per transform + { 8, 14, 11 }, // 117 bits per transform + { 8, 15, 10 }, // 117 bits per transform + { 8, 16, 9 }, // 117 bits per transform + { 8, 17, 8 }, // 117 bits per transform + { 9, 7, 17 }, // 117 bits per transform + { 9, 8, 16 }, // 117 bits per transform + { 9, 9, 15 }, // 117 bits per transform + { 9, 10, 14 }, // 117 bits per transform + { 9, 11, 13 }, // 117 bits per transform + { 9, 12, 12 }, // 117 bits per transform + { 9, 13, 11 }, // 117 bits per transform + { 9, 14, 10 }, // 117 bits per transform + { 9, 15, 9 }, // 117 bits per transform + { 9, 16, 8 }, // 117 bits per transform + { 9, 17, 7 }, // 117 bits per transform + { 10, 6, 17 }, // 117 bits per transform + { 10, 7, 16 }, // 117 bits per transform + { 10, 8, 15 }, // 117 bits per transform + { 10, 9, 14 }, // 117 bits per transform + { 10, 10, 13 }, // 117 bits per transform + { 10, 11, 12 }, // 117 bits per transform + { 10, 12, 11 }, // 117 bits per transform + { 10, 13, 10 }, // 117 bits per transform + { 10, 14, 9 }, // 117 bits per transform + { 10, 15, 8 }, // 117 bits per transform + { 10, 16, 7 }, // 117 bits per transform + { 10, 17, 6 }, // 117 bits per transform + { 11, 5, 17 }, // 117 bits per transform + { 11, 6, 16 }, // 117 bits per transform + { 11, 7, 15 }, // 117 bits per transform + { 11, 8, 14 }, // 117 bits per transform + { 11, 9, 13 }, // 117 bits per transform + { 11, 10, 12 }, // 117 bits per transform + { 11, 11, 11 }, // 117 bits per transform + { 11, 12, 10 }, // 117 bits per transform + { 11, 13, 9 }, // 117 bits per transform + { 11, 14, 8 }, // 117 bits per transform + { 11, 15, 7 }, // 117 bits per transform + { 11, 16, 6 }, // 117 bits per transform + { 11, 17, 5 }, // 117 bits per transform + { 12, 4, 17 }, // 117 bits per transform + { 12, 5, 16 }, // 117 bits per transform + { 12, 6, 15 }, // 117 bits per transform + { 12, 7, 14 }, // 117 bits per transform + { 12, 8, 13 }, // 117 bits per transform + { 12, 9, 12 }, // 117 bits per transform + { 12, 10, 11 }, // 117 bits per transform + { 12, 11, 10 }, // 117 bits per transform + { 12, 12, 9 }, // 117 bits per transform + { 12, 13, 8 }, // 117 bits per transform + { 12, 14, 7 }, // 117 bits per transform + { 12, 15, 6 }, // 117 bits per transform + { 12, 16, 5 }, // 117 bits per transform + { 12, 17, 4 }, // 117 bits per transform + { 13, 3, 17 }, // 117 bits per transform + { 13, 4, 16 }, // 117 bits per transform + { 13, 5, 15 }, // 117 bits per transform + { 13, 6, 14 }, // 117 bits per transform + { 13, 7, 13 }, // 117 bits per transform + { 13, 8, 12 }, // 117 bits per transform + { 13, 9, 11 }, // 117 bits per transform + { 13, 10, 10 }, // 117 bits per transform + { 13, 11, 9 }, // 117 bits per transform + { 13, 12, 8 }, // 117 bits per transform + { 13, 13, 7 }, // 117 bits per transform + { 13, 14, 6 }, // 117 bits per transform + { 13, 15, 5 }, // 117 bits per transform + { 13, 16, 4 }, // 117 bits per transform + { 13, 17, 3 }, // 117 bits per transform + { 14, 2, 17 }, // 117 bits per transform + { 14, 3, 16 }, // 117 bits per transform + { 14, 4, 15 }, // 117 bits per transform + { 14, 5, 14 }, // 117 bits per transform + { 14, 6, 13 }, // 117 bits per transform + { 14, 7, 12 }, // 117 bits per transform + { 14, 8, 11 }, // 117 bits per transform + { 14, 9, 10 }, // 117 bits per transform + { 14, 10, 9 }, // 117 bits per transform + { 14, 11, 8 }, // 117 bits per transform + { 14, 12, 7 }, // 117 bits per transform + { 14, 13, 6 }, // 117 bits per transform + { 14, 14, 5 }, // 117 bits per transform + { 14, 15, 4 }, // 117 bits per transform + { 14, 16, 3 }, // 117 bits per transform + { 14, 17, 2 }, // 117 bits per transform + { 15, 1, 17 }, // 117 bits per transform + { 15, 2, 16 }, // 117 bits per transform + { 15, 3, 15 }, // 117 bits per transform + { 15, 4, 14 }, // 117 bits per transform + { 15, 5, 13 }, // 117 bits per transform + { 15, 6, 12 }, // 117 bits per transform + { 15, 7, 11 }, // 117 bits per transform + { 15, 8, 10 }, // 117 bits per transform + { 15, 9, 9 }, // 117 bits per transform + { 15, 10, 8 }, // 117 bits per transform + { 15, 11, 7 }, // 117 bits per transform + { 15, 12, 6 }, // 117 bits per transform + { 15, 13, 5 }, // 117 bits per transform + { 15, 14, 4 }, // 117 bits per transform + { 15, 15, 3 }, // 117 bits per transform + { 15, 16, 2 }, // 117 bits per transform + { 15, 17, 1 }, // 117 bits per transform + { 16, 1, 16 }, // 117 bits per transform + { 16, 2, 15 }, // 117 bits per transform + { 16, 3, 14 }, // 117 bits per transform + { 16, 4, 13 }, // 117 bits per transform + { 16, 5, 12 }, // 117 bits per transform + { 16, 6, 11 }, // 117 bits per transform + { 16, 7, 10 }, // 117 bits per transform + { 16, 8, 9 }, // 117 bits per transform + { 16, 9, 8 }, // 117 bits per transform + { 16, 10, 7 }, // 117 bits per transform + { 16, 11, 6 }, // 117 bits per transform + { 16, 12, 5 }, // 117 bits per transform + { 16, 13, 4 }, // 117 bits per transform + { 16, 14, 3 }, // 117 bits per transform + { 16, 15, 2 }, // 117 bits per transform + { 16, 16, 1 }, // 117 bits per transform + { 17, 1, 15 }, // 117 bits per transform + { 17, 2, 14 }, // 117 bits per transform + { 17, 3, 13 }, // 117 bits per transform + { 17, 4, 12 }, // 117 bits per transform + { 17, 5, 11 }, // 117 bits per transform + { 17, 6, 10 }, // 117 bits per transform + { 17, 7, 9 }, // 117 bits per transform + { 17, 8, 8 }, // 117 bits per transform + { 17, 9, 7 }, // 117 bits per transform + { 17, 10, 6 }, // 117 bits per transform + { 17, 11, 5 }, // 117 bits per transform + { 17, 12, 4 }, // 117 bits per transform + { 17, 13, 3 }, // 117 bits per transform + { 17, 14, 2 }, // 117 bits per transform + { 17, 15, 1 }, // 117 bits per transform + { 18, 0, 5 }, // 117 bits per transform + { 18, 1, 2 }, // 117 bits per transform + { 18, 2, 1 }, // 117 bits per transform + { 18, 5, 0 }, // 117 bits per transform + { 0, 6, 18 }, // 120 bits per transform + { 0, 18, 6 }, // 120 bits per transform + { 1, 3, 18 }, // 120 bits per transform + { 1, 16, 17 }, // 120 bits per transform + { 1, 17, 16 }, // 120 bits per transform + { 1, 18, 3 }, // 120 bits per transform + { 2, 2, 18 }, // 120 bits per transform + { 2, 15, 17 }, // 120 bits per transform + { 2, 16, 16 }, // 120 bits per transform + { 2, 17, 15 }, // 120 bits per transform + { 2, 18, 2 }, // 120 bits per transform + { 3, 1, 18 }, // 120 bits per transform + { 3, 14, 17 }, // 120 bits per transform + { 3, 15, 16 }, // 120 bits per transform + { 3, 16, 15 }, // 120 bits per transform + { 3, 17, 14 }, // 120 bits per transform + { 3, 18, 1 }, // 120 bits per transform + { 4, 13, 17 }, // 120 bits per transform + { 4, 14, 16 }, // 120 bits per transform + { 4, 15, 15 }, // 120 bits per transform + { 4, 16, 14 }, // 120 bits per transform + { 4, 17, 13 }, // 120 bits per transform + { 5, 12, 17 }, // 120 bits per transform + { 5, 13, 16 }, // 120 bits per transform + { 5, 14, 15 }, // 120 bits per transform + { 5, 15, 14 }, // 120 bits per transform + { 5, 16, 13 }, // 120 bits per transform + { 5, 17, 12 }, // 120 bits per transform + { 6, 0, 18 }, // 120 bits per transform + { 6, 11, 17 }, // 120 bits per transform + { 6, 12, 16 }, // 120 bits per transform + { 6, 13, 15 }, // 120 bits per transform + { 6, 14, 14 }, // 120 bits per transform + { 6, 15, 13 }, // 120 bits per transform + { 6, 16, 12 }, // 120 bits per transform + { 6, 17, 11 }, // 120 bits per transform + { 6, 18, 0 }, // 120 bits per transform + { 7, 10, 17 }, // 120 bits per transform + { 7, 11, 16 }, // 120 bits per transform + { 7, 12, 15 }, // 120 bits per transform + { 7, 13, 14 }, // 120 bits per transform + { 7, 14, 13 }, // 120 bits per transform + { 7, 15, 12 }, // 120 bits per transform + { 7, 16, 11 }, // 120 bits per transform + { 7, 17, 10 }, // 120 bits per transform + { 8, 9, 17 }, // 120 bits per transform + { 8, 10, 16 }, // 120 bits per transform + { 8, 11, 15 }, // 120 bits per transform + { 8, 12, 14 }, // 120 bits per transform + { 8, 13, 13 }, // 120 bits per transform + { 8, 14, 12 }, // 120 bits per transform + { 8, 15, 11 }, // 120 bits per transform + { 8, 16, 10 }, // 120 bits per transform + { 8, 17, 9 }, // 120 bits per transform + { 9, 8, 17 }, // 120 bits per transform + { 9, 9, 16 }, // 120 bits per transform + { 9, 10, 15 }, // 120 bits per transform + { 9, 11, 14 }, // 120 bits per transform + { 9, 12, 13 }, // 120 bits per transform + { 9, 13, 12 }, // 120 bits per transform + { 9, 14, 11 }, // 120 bits per transform + { 9, 15, 10 }, // 120 bits per transform + { 9, 16, 9 }, // 120 bits per transform + { 9, 17, 8 }, // 120 bits per transform + { 10, 7, 17 }, // 120 bits per transform + { 10, 8, 16 }, // 120 bits per transform + { 10, 9, 15 }, // 120 bits per transform + { 10, 10, 14 }, // 120 bits per transform + { 10, 11, 13 }, // 120 bits per transform + { 10, 12, 12 }, // 120 bits per transform + { 10, 13, 11 }, // 120 bits per transform + { 10, 14, 10 }, // 120 bits per transform + { 10, 15, 9 }, // 120 bits per transform + { 10, 16, 8 }, // 120 bits per transform + { 10, 17, 7 }, // 120 bits per transform + { 11, 6, 17 }, // 120 bits per transform + { 11, 7, 16 }, // 120 bits per transform + { 11, 8, 15 }, // 120 bits per transform + { 11, 9, 14 }, // 120 bits per transform + { 11, 10, 13 }, // 120 bits per transform + { 11, 11, 12 }, // 120 bits per transform + { 11, 12, 11 }, // 120 bits per transform + { 11, 13, 10 }, // 120 bits per transform + { 11, 14, 9 }, // 120 bits per transform + { 11, 15, 8 }, // 120 bits per transform + { 11, 16, 7 }, // 120 bits per transform + { 11, 17, 6 }, // 120 bits per transform + { 12, 5, 17 }, // 120 bits per transform + { 12, 6, 16 }, // 120 bits per transform + { 12, 7, 15 }, // 120 bits per transform + { 12, 8, 14 }, // 120 bits per transform + { 12, 9, 13 }, // 120 bits per transform + { 12, 10, 12 }, // 120 bits per transform + { 12, 11, 11 }, // 120 bits per transform + { 12, 12, 10 }, // 120 bits per transform + { 12, 13, 9 }, // 120 bits per transform + { 12, 14, 8 }, // 120 bits per transform + { 12, 15, 7 }, // 120 bits per transform + { 12, 16, 6 }, // 120 bits per transform + { 12, 17, 5 }, // 120 bits per transform + { 13, 4, 17 }, // 120 bits per transform + { 13, 5, 16 }, // 120 bits per transform + { 13, 6, 15 }, // 120 bits per transform + { 13, 7, 14 }, // 120 bits per transform + { 13, 8, 13 }, // 120 bits per transform + { 13, 9, 12 }, // 120 bits per transform + { 13, 10, 11 }, // 120 bits per transform + { 13, 11, 10 }, // 120 bits per transform + { 13, 12, 9 }, // 120 bits per transform + { 13, 13, 8 }, // 120 bits per transform + { 13, 14, 7 }, // 120 bits per transform + { 13, 15, 6 }, // 120 bits per transform + { 13, 16, 5 }, // 120 bits per transform + { 13, 17, 4 }, // 120 bits per transform + { 14, 3, 17 }, // 120 bits per transform + { 14, 4, 16 }, // 120 bits per transform + { 14, 5, 15 }, // 120 bits per transform + { 14, 6, 14 }, // 120 bits per transform + { 14, 7, 13 }, // 120 bits per transform + { 14, 8, 12 }, // 120 bits per transform + { 14, 9, 11 }, // 120 bits per transform + { 14, 10, 10 }, // 120 bits per transform + { 14, 11, 9 }, // 120 bits per transform + { 14, 12, 8 }, // 120 bits per transform + { 14, 13, 7 }, // 120 bits per transform + { 14, 14, 6 }, // 120 bits per transform + { 14, 15, 5 }, // 120 bits per transform + { 14, 16, 4 }, // 120 bits per transform + { 14, 17, 3 }, // 120 bits per transform + { 15, 2, 17 }, // 120 bits per transform + { 15, 3, 16 }, // 120 bits per transform + { 15, 4, 15 }, // 120 bits per transform + { 15, 5, 14 }, // 120 bits per transform + { 15, 6, 13 }, // 120 bits per transform + { 15, 7, 12 }, // 120 bits per transform + { 15, 8, 11 }, // 120 bits per transform + { 15, 9, 10 }, // 120 bits per transform + { 15, 10, 9 }, // 120 bits per transform + { 15, 11, 8 }, // 120 bits per transform + { 15, 12, 7 }, // 120 bits per transform + { 15, 13, 6 }, // 120 bits per transform + { 15, 14, 5 }, // 120 bits per transform + { 15, 15, 4 }, // 120 bits per transform + { 15, 16, 3 }, // 120 bits per transform + { 15, 17, 2 }, // 120 bits per transform + { 16, 1, 17 }, // 120 bits per transform + { 16, 2, 16 }, // 120 bits per transform + { 16, 3, 15 }, // 120 bits per transform + { 16, 4, 14 }, // 120 bits per transform + { 16, 5, 13 }, // 120 bits per transform + { 16, 6, 12 }, // 120 bits per transform + { 16, 7, 11 }, // 120 bits per transform + { 16, 8, 10 }, // 120 bits per transform + { 16, 9, 9 }, // 120 bits per transform + { 16, 10, 8 }, // 120 bits per transform + { 16, 11, 7 }, // 120 bits per transform + { 16, 12, 6 }, // 120 bits per transform + { 16, 13, 5 }, // 120 bits per transform + { 16, 14, 4 }, // 120 bits per transform + { 16, 15, 3 }, // 120 bits per transform + { 16, 16, 2 }, // 120 bits per transform + { 16, 17, 1 }, // 120 bits per transform + { 17, 1, 16 }, // 120 bits per transform + { 17, 2, 15 }, // 120 bits per transform + { 17, 3, 14 }, // 120 bits per transform + { 17, 4, 13 }, // 120 bits per transform + { 17, 5, 12 }, // 120 bits per transform + { 17, 6, 11 }, // 120 bits per transform + { 17, 7, 10 }, // 120 bits per transform + { 17, 8, 9 }, // 120 bits per transform + { 17, 9, 8 }, // 120 bits per transform + { 17, 10, 7 }, // 120 bits per transform + { 17, 11, 6 }, // 120 bits per transform + { 17, 12, 5 }, // 120 bits per transform + { 17, 13, 4 }, // 120 bits per transform + { 17, 14, 3 }, // 120 bits per transform + { 17, 15, 2 }, // 120 bits per transform + { 17, 16, 1 }, // 120 bits per transform + { 18, 0, 6 }, // 120 bits per transform + { 18, 1, 3 }, // 120 bits per transform + { 18, 2, 2 }, // 120 bits per transform + { 18, 3, 1 }, // 120 bits per transform + { 18, 6, 0 }, // 120 bits per transform + { 0, 7, 18 }, // 123 bits per transform + { 0, 18, 7 }, // 123 bits per transform + { 1, 4, 18 }, // 123 bits per transform + { 1, 17, 17 }, // 123 bits per transform + { 1, 18, 4 }, // 123 bits per transform + { 2, 3, 18 }, // 123 bits per transform + { 2, 16, 17 }, // 123 bits per transform + { 2, 17, 16 }, // 123 bits per transform + { 2, 18, 3 }, // 123 bits per transform + { 3, 2, 18 }, // 123 bits per transform + { 3, 15, 17 }, // 123 bits per transform + { 3, 16, 16 }, // 123 bits per transform + { 3, 17, 15 }, // 123 bits per transform + { 3, 18, 2 }, // 123 bits per transform + { 4, 1, 18 }, // 123 bits per transform + { 4, 14, 17 }, // 123 bits per transform + { 4, 15, 16 }, // 123 bits per transform + { 4, 16, 15 }, // 123 bits per transform + { 4, 17, 14 }, // 123 bits per transform + { 4, 18, 1 }, // 123 bits per transform + { 5, 13, 17 }, // 123 bits per transform + { 5, 14, 16 }, // 123 bits per transform + { 5, 15, 15 }, // 123 bits per transform + { 5, 16, 14 }, // 123 bits per transform + { 5, 17, 13 }, // 123 bits per transform + { 6, 12, 17 }, // 123 bits per transform + { 6, 13, 16 }, // 123 bits per transform + { 6, 14, 15 }, // 123 bits per transform + { 6, 15, 14 }, // 123 bits per transform + { 6, 16, 13 }, // 123 bits per transform + { 6, 17, 12 }, // 123 bits per transform + { 7, 0, 18 }, // 123 bits per transform + { 7, 11, 17 }, // 123 bits per transform + { 7, 12, 16 }, // 123 bits per transform + { 7, 13, 15 }, // 123 bits per transform + { 7, 14, 14 }, // 123 bits per transform + { 7, 15, 13 }, // 123 bits per transform + { 7, 16, 12 }, // 123 bits per transform + { 7, 17, 11 }, // 123 bits per transform + { 7, 18, 0 }, // 123 bits per transform + { 8, 10, 17 }, // 123 bits per transform + { 8, 11, 16 }, // 123 bits per transform + { 8, 12, 15 }, // 123 bits per transform + { 8, 13, 14 }, // 123 bits per transform + { 8, 14, 13 }, // 123 bits per transform + { 8, 15, 12 }, // 123 bits per transform + { 8, 16, 11 }, // 123 bits per transform + { 8, 17, 10 }, // 123 bits per transform + { 9, 9, 17 }, // 123 bits per transform + { 9, 10, 16 }, // 123 bits per transform + { 9, 11, 15 }, // 123 bits per transform + { 9, 12, 14 }, // 123 bits per transform + { 9, 13, 13 }, // 123 bits per transform + { 9, 14, 12 }, // 123 bits per transform + { 9, 15, 11 }, // 123 bits per transform + { 9, 16, 10 }, // 123 bits per transform + { 9, 17, 9 }, // 123 bits per transform + { 10, 8, 17 }, // 123 bits per transform + { 10, 9, 16 }, // 123 bits per transform + { 10, 10, 15 }, // 123 bits per transform + { 10, 11, 14 }, // 123 bits per transform + { 10, 12, 13 }, // 123 bits per transform + { 10, 13, 12 }, // 123 bits per transform + { 10, 14, 11 }, // 123 bits per transform + { 10, 15, 10 }, // 123 bits per transform + { 10, 16, 9 }, // 123 bits per transform + { 10, 17, 8 }, // 123 bits per transform + { 11, 7, 17 }, // 123 bits per transform + { 11, 8, 16 }, // 123 bits per transform + { 11, 9, 15 }, // 123 bits per transform + { 11, 10, 14 }, // 123 bits per transform + { 11, 11, 13 }, // 123 bits per transform + { 11, 12, 12 }, // 123 bits per transform + { 11, 13, 11 }, // 123 bits per transform + { 11, 14, 10 }, // 123 bits per transform + { 11, 15, 9 }, // 123 bits per transform + { 11, 16, 8 }, // 123 bits per transform + { 11, 17, 7 }, // 123 bits per transform + { 12, 6, 17 }, // 123 bits per transform + { 12, 7, 16 }, // 123 bits per transform + { 12, 8, 15 }, // 123 bits per transform + { 12, 9, 14 }, // 123 bits per transform + { 12, 10, 13 }, // 123 bits per transform + { 12, 11, 12 }, // 123 bits per transform + { 12, 12, 11 }, // 123 bits per transform + { 12, 13, 10 }, // 123 bits per transform + { 12, 14, 9 }, // 123 bits per transform + { 12, 15, 8 }, // 123 bits per transform + { 12, 16, 7 }, // 123 bits per transform + { 12, 17, 6 }, // 123 bits per transform + { 13, 5, 17 }, // 123 bits per transform + { 13, 6, 16 }, // 123 bits per transform + { 13, 7, 15 }, // 123 bits per transform + { 13, 8, 14 }, // 123 bits per transform + { 13, 9, 13 }, // 123 bits per transform + { 13, 10, 12 }, // 123 bits per transform + { 13, 11, 11 }, // 123 bits per transform + { 13, 12, 10 }, // 123 bits per transform + { 13, 13, 9 }, // 123 bits per transform + { 13, 14, 8 }, // 123 bits per transform + { 13, 15, 7 }, // 123 bits per transform + { 13, 16, 6 }, // 123 bits per transform + { 13, 17, 5 }, // 123 bits per transform + { 14, 4, 17 }, // 123 bits per transform + { 14, 5, 16 }, // 123 bits per transform + { 14, 6, 15 }, // 123 bits per transform + { 14, 7, 14 }, // 123 bits per transform + { 14, 8, 13 }, // 123 bits per transform + { 14, 9, 12 }, // 123 bits per transform + { 14, 10, 11 }, // 123 bits per transform + { 14, 11, 10 }, // 123 bits per transform + { 14, 12, 9 }, // 123 bits per transform + { 14, 13, 8 }, // 123 bits per transform + { 14, 14, 7 }, // 123 bits per transform + { 14, 15, 6 }, // 123 bits per transform + { 14, 16, 5 }, // 123 bits per transform + { 14, 17, 4 }, // 123 bits per transform + { 15, 3, 17 }, // 123 bits per transform + { 15, 4, 16 }, // 123 bits per transform + { 15, 5, 15 }, // 123 bits per transform + { 15, 6, 14 }, // 123 bits per transform + { 15, 7, 13 }, // 123 bits per transform + { 15, 8, 12 }, // 123 bits per transform + { 15, 9, 11 }, // 123 bits per transform + { 15, 10, 10 }, // 123 bits per transform + { 15, 11, 9 }, // 123 bits per transform + { 15, 12, 8 }, // 123 bits per transform + { 15, 13, 7 }, // 123 bits per transform + { 15, 14, 6 }, // 123 bits per transform + { 15, 15, 5 }, // 123 bits per transform + { 15, 16, 4 }, // 123 bits per transform + { 15, 17, 3 }, // 123 bits per transform + { 16, 2, 17 }, // 123 bits per transform + { 16, 3, 16 }, // 123 bits per transform + { 16, 4, 15 }, // 123 bits per transform + { 16, 5, 14 }, // 123 bits per transform + { 16, 6, 13 }, // 123 bits per transform + { 16, 7, 12 }, // 123 bits per transform + { 16, 8, 11 }, // 123 bits per transform + { 16, 9, 10 }, // 123 bits per transform + { 16, 10, 9 }, // 123 bits per transform + { 16, 11, 8 }, // 123 bits per transform + { 16, 12, 7 }, // 123 bits per transform + { 16, 13, 6 }, // 123 bits per transform + { 16, 14, 5 }, // 123 bits per transform + { 16, 15, 4 }, // 123 bits per transform + { 16, 16, 3 }, // 123 bits per transform + { 16, 17, 2 }, // 123 bits per transform + { 17, 1, 17 }, // 123 bits per transform + { 17, 2, 16 }, // 123 bits per transform + { 17, 3, 15 }, // 123 bits per transform + { 17, 4, 14 }, // 123 bits per transform + { 17, 5, 13 }, // 123 bits per transform + { 17, 6, 12 }, // 123 bits per transform + { 17, 7, 11 }, // 123 bits per transform + { 17, 8, 10 }, // 123 bits per transform + { 17, 9, 9 }, // 123 bits per transform + { 17, 10, 8 }, // 123 bits per transform + { 17, 11, 7 }, // 123 bits per transform + { 17, 12, 6 }, // 123 bits per transform + { 17, 13, 5 }, // 123 bits per transform + { 17, 14, 4 }, // 123 bits per transform + { 17, 15, 3 }, // 123 bits per transform + { 17, 16, 2 }, // 123 bits per transform + { 17, 17, 1 }, // 123 bits per transform + { 18, 0, 7 }, // 123 bits per transform + { 18, 1, 4 }, // 123 bits per transform + { 18, 2, 3 }, // 123 bits per transform + { 18, 3, 2 }, // 123 bits per transform + { 18, 4, 1 }, // 123 bits per transform + { 18, 7, 0 }, // 123 bits per transform + { 0, 8, 18 }, // 126 bits per transform + { 0, 18, 8 }, // 126 bits per transform + { 1, 5, 18 }, // 126 bits per transform + { 1, 18, 5 }, // 126 bits per transform + { 2, 4, 18 }, // 126 bits per transform + { 2, 17, 17 }, // 126 bits per transform + { 2, 18, 4 }, // 126 bits per transform + { 3, 3, 18 }, // 126 bits per transform + { 3, 16, 17 }, // 126 bits per transform + { 3, 17, 16 }, // 126 bits per transform + { 3, 18, 3 }, // 126 bits per transform + { 4, 2, 18 }, // 126 bits per transform + { 4, 15, 17 }, // 126 bits per transform + { 4, 16, 16 }, // 126 bits per transform + { 4, 17, 15 }, // 126 bits per transform + { 4, 18, 2 }, // 126 bits per transform + { 5, 1, 18 }, // 126 bits per transform + { 5, 14, 17 }, // 126 bits per transform + { 5, 15, 16 }, // 126 bits per transform + { 5, 16, 15 }, // 126 bits per transform + { 5, 17, 14 }, // 126 bits per transform + { 5, 18, 1 }, // 126 bits per transform + { 6, 13, 17 }, // 126 bits per transform + { 6, 14, 16 }, // 126 bits per transform + { 6, 15, 15 }, // 126 bits per transform + { 6, 16, 14 }, // 126 bits per transform + { 6, 17, 13 }, // 126 bits per transform + { 7, 12, 17 }, // 126 bits per transform + { 7, 13, 16 }, // 126 bits per transform + { 7, 14, 15 }, // 126 bits per transform + { 7, 15, 14 }, // 126 bits per transform + { 7, 16, 13 }, // 126 bits per transform + { 7, 17, 12 }, // 126 bits per transform + { 8, 0, 18 }, // 126 bits per transform + { 8, 11, 17 }, // 126 bits per transform + { 8, 12, 16 }, // 126 bits per transform + { 8, 13, 15 }, // 126 bits per transform + { 8, 14, 14 }, // 126 bits per transform + { 8, 15, 13 }, // 126 bits per transform + { 8, 16, 12 }, // 126 bits per transform + { 8, 17, 11 }, // 126 bits per transform + { 8, 18, 0 }, // 126 bits per transform + { 9, 10, 17 }, // 126 bits per transform + { 9, 11, 16 }, // 126 bits per transform + { 9, 12, 15 }, // 126 bits per transform + { 9, 13, 14 }, // 126 bits per transform + { 9, 14, 13 }, // 126 bits per transform + { 9, 15, 12 }, // 126 bits per transform + { 9, 16, 11 }, // 126 bits per transform + { 9, 17, 10 }, // 126 bits per transform + { 10, 9, 17 }, // 126 bits per transform + { 10, 10, 16 }, // 126 bits per transform + { 10, 11, 15 }, // 126 bits per transform + { 10, 12, 14 }, // 126 bits per transform + { 10, 13, 13 }, // 126 bits per transform + { 10, 14, 12 }, // 126 bits per transform + { 10, 15, 11 }, // 126 bits per transform + { 10, 16, 10 }, // 126 bits per transform + { 10, 17, 9 }, // 126 bits per transform + { 11, 8, 17 }, // 126 bits per transform + { 11, 9, 16 }, // 126 bits per transform + { 11, 10, 15 }, // 126 bits per transform + { 11, 11, 14 }, // 126 bits per transform + { 11, 12, 13 }, // 126 bits per transform + { 11, 13, 12 }, // 126 bits per transform + { 11, 14, 11 }, // 126 bits per transform + { 11, 15, 10 }, // 126 bits per transform + { 11, 16, 9 }, // 126 bits per transform + { 11, 17, 8 }, // 126 bits per transform + { 12, 7, 17 }, // 126 bits per transform + { 12, 8, 16 }, // 126 bits per transform + { 12, 9, 15 }, // 126 bits per transform + { 12, 10, 14 }, // 126 bits per transform + { 12, 11, 13 }, // 126 bits per transform + { 12, 12, 12 }, // 126 bits per transform + { 12, 13, 11 }, // 126 bits per transform + { 12, 14, 10 }, // 126 bits per transform + { 12, 15, 9 }, // 126 bits per transform + { 12, 16, 8 }, // 126 bits per transform + { 12, 17, 7 }, // 126 bits per transform + { 13, 6, 17 }, // 126 bits per transform + { 13, 7, 16 }, // 126 bits per transform + { 13, 8, 15 }, // 126 bits per transform + { 13, 9, 14 }, // 126 bits per transform + { 13, 10, 13 }, // 126 bits per transform + { 13, 11, 12 }, // 126 bits per transform + { 13, 12, 11 }, // 126 bits per transform + { 13, 13, 10 }, // 126 bits per transform + { 13, 14, 9 }, // 126 bits per transform + { 13, 15, 8 }, // 126 bits per transform + { 13, 16, 7 }, // 126 bits per transform + { 13, 17, 6 }, // 126 bits per transform + { 14, 5, 17 }, // 126 bits per transform + { 14, 6, 16 }, // 126 bits per transform + { 14, 7, 15 }, // 126 bits per transform + { 14, 8, 14 }, // 126 bits per transform + { 14, 9, 13 }, // 126 bits per transform + { 14, 10, 12 }, // 126 bits per transform + { 14, 11, 11 }, // 126 bits per transform + { 14, 12, 10 }, // 126 bits per transform + { 14, 13, 9 }, // 126 bits per transform + { 14, 14, 8 }, // 126 bits per transform + { 14, 15, 7 }, // 126 bits per transform + { 14, 16, 6 }, // 126 bits per transform + { 14, 17, 5 }, // 126 bits per transform + { 15, 4, 17 }, // 126 bits per transform + { 15, 5, 16 }, // 126 bits per transform + { 15, 6, 15 }, // 126 bits per transform + { 15, 7, 14 }, // 126 bits per transform + { 15, 8, 13 }, // 126 bits per transform + { 15, 9, 12 }, // 126 bits per transform + { 15, 10, 11 }, // 126 bits per transform + { 15, 11, 10 }, // 126 bits per transform + { 15, 12, 9 }, // 126 bits per transform + { 15, 13, 8 }, // 126 bits per transform + { 15, 14, 7 }, // 126 bits per transform + { 15, 15, 6 }, // 126 bits per transform + { 15, 16, 5 }, // 126 bits per transform + { 15, 17, 4 }, // 126 bits per transform + { 16, 3, 17 }, // 126 bits per transform + { 16, 4, 16 }, // 126 bits per transform + { 16, 5, 15 }, // 126 bits per transform + { 16, 6, 14 }, // 126 bits per transform + { 16, 7, 13 }, // 126 bits per transform + { 16, 8, 12 }, // 126 bits per transform + { 16, 9, 11 }, // 126 bits per transform + { 16, 10, 10 }, // 126 bits per transform + { 16, 11, 9 }, // 126 bits per transform + { 16, 12, 8 }, // 126 bits per transform + { 16, 13, 7 }, // 126 bits per transform + { 16, 14, 6 }, // 126 bits per transform + { 16, 15, 5 }, // 126 bits per transform + { 16, 16, 4 }, // 126 bits per transform + { 16, 17, 3 }, // 126 bits per transform + { 17, 2, 17 }, // 126 bits per transform + { 17, 3, 16 }, // 126 bits per transform + { 17, 4, 15 }, // 126 bits per transform + { 17, 5, 14 }, // 126 bits per transform + { 17, 6, 13 }, // 126 bits per transform + { 17, 7, 12 }, // 126 bits per transform + { 17, 8, 11 }, // 126 bits per transform + { 17, 9, 10 }, // 126 bits per transform + { 17, 10, 9 }, // 126 bits per transform + { 17, 11, 8 }, // 126 bits per transform + { 17, 12, 7 }, // 126 bits per transform + { 17, 13, 6 }, // 126 bits per transform + { 17, 14, 5 }, // 126 bits per transform + { 17, 15, 4 }, // 126 bits per transform + { 17, 16, 3 }, // 126 bits per transform + { 17, 17, 2 }, // 126 bits per transform + { 18, 0, 8 }, // 126 bits per transform + { 18, 1, 5 }, // 126 bits per transform + { 18, 2, 4 }, // 126 bits per transform + { 18, 3, 3 }, // 126 bits per transform + { 18, 4, 2 }, // 126 bits per transform + { 18, 5, 1 }, // 126 bits per transform + { 18, 8, 0 }, // 126 bits per transform + { 0, 9, 18 }, // 129 bits per transform + { 0, 18, 9 }, // 129 bits per transform + { 1, 6, 18 }, // 129 bits per transform + { 1, 18, 6 }, // 129 bits per transform + { 2, 5, 18 }, // 129 bits per transform + { 2, 18, 5 }, // 129 bits per transform + { 3, 4, 18 }, // 129 bits per transform + { 3, 17, 17 }, // 129 bits per transform + { 3, 18, 4 }, // 129 bits per transform + { 4, 3, 18 }, // 129 bits per transform + { 4, 16, 17 }, // 129 bits per transform + { 4, 17, 16 }, // 129 bits per transform + { 4, 18, 3 }, // 129 bits per transform + { 5, 2, 18 }, // 129 bits per transform + { 5, 15, 17 }, // 129 bits per transform + { 5, 16, 16 }, // 129 bits per transform + { 5, 17, 15 }, // 129 bits per transform + { 5, 18, 2 }, // 129 bits per transform + { 6, 1, 18 }, // 129 bits per transform + { 6, 14, 17 }, // 129 bits per transform + { 6, 15, 16 }, // 129 bits per transform + { 6, 16, 15 }, // 129 bits per transform + { 6, 17, 14 }, // 129 bits per transform + { 6, 18, 1 }, // 129 bits per transform + { 7, 13, 17 }, // 129 bits per transform + { 7, 14, 16 }, // 129 bits per transform + { 7, 15, 15 }, // 129 bits per transform + { 7, 16, 14 }, // 129 bits per transform + { 7, 17, 13 }, // 129 bits per transform + { 8, 12, 17 }, // 129 bits per transform + { 8, 13, 16 }, // 129 bits per transform + { 8, 14, 15 }, // 129 bits per transform + { 8, 15, 14 }, // 129 bits per transform + { 8, 16, 13 }, // 129 bits per transform + { 8, 17, 12 }, // 129 bits per transform + { 9, 0, 18 }, // 129 bits per transform + { 9, 11, 17 }, // 129 bits per transform + { 9, 12, 16 }, // 129 bits per transform + { 9, 13, 15 }, // 129 bits per transform + { 9, 14, 14 }, // 129 bits per transform + { 9, 15, 13 }, // 129 bits per transform + { 9, 16, 12 }, // 129 bits per transform + { 9, 17, 11 }, // 129 bits per transform + { 9, 18, 0 }, // 129 bits per transform + { 10, 10, 17 }, // 129 bits per transform + { 10, 11, 16 }, // 129 bits per transform + { 10, 12, 15 }, // 129 bits per transform + { 10, 13, 14 }, // 129 bits per transform + { 10, 14, 13 }, // 129 bits per transform + { 10, 15, 12 }, // 129 bits per transform + { 10, 16, 11 }, // 129 bits per transform + { 10, 17, 10 }, // 129 bits per transform + { 11, 9, 17 }, // 129 bits per transform + { 11, 10, 16 }, // 129 bits per transform + { 11, 11, 15 }, // 129 bits per transform + { 11, 12, 14 }, // 129 bits per transform + { 11, 13, 13 }, // 129 bits per transform + { 11, 14, 12 }, // 129 bits per transform + { 11, 15, 11 }, // 129 bits per transform + { 11, 16, 10 }, // 129 bits per transform + { 11, 17, 9 }, // 129 bits per transform + { 12, 8, 17 }, // 129 bits per transform + { 12, 9, 16 }, // 129 bits per transform + { 12, 10, 15 }, // 129 bits per transform + { 12, 11, 14 }, // 129 bits per transform + { 12, 12, 13 }, // 129 bits per transform + { 12, 13, 12 }, // 129 bits per transform + { 12, 14, 11 }, // 129 bits per transform + { 12, 15, 10 }, // 129 bits per transform + { 12, 16, 9 }, // 129 bits per transform + { 12, 17, 8 }, // 129 bits per transform + { 13, 7, 17 }, // 129 bits per transform + { 13, 8, 16 }, // 129 bits per transform + { 13, 9, 15 }, // 129 bits per transform + { 13, 10, 14 }, // 129 bits per transform + { 13, 11, 13 }, // 129 bits per transform + { 13, 12, 12 }, // 129 bits per transform + { 13, 13, 11 }, // 129 bits per transform + { 13, 14, 10 }, // 129 bits per transform + { 13, 15, 9 }, // 129 bits per transform + { 13, 16, 8 }, // 129 bits per transform + { 13, 17, 7 }, // 129 bits per transform + { 14, 6, 17 }, // 129 bits per transform + { 14, 7, 16 }, // 129 bits per transform + { 14, 8, 15 }, // 129 bits per transform + { 14, 9, 14 }, // 129 bits per transform + { 14, 10, 13 }, // 129 bits per transform + { 14, 11, 12 }, // 129 bits per transform + { 14, 12, 11 }, // 129 bits per transform + { 14, 13, 10 }, // 129 bits per transform + { 14, 14, 9 }, // 129 bits per transform + { 14, 15, 8 }, // 129 bits per transform + { 14, 16, 7 }, // 129 bits per transform + { 14, 17, 6 }, // 129 bits per transform + { 15, 5, 17 }, // 129 bits per transform + { 15, 6, 16 }, // 129 bits per transform + { 15, 7, 15 }, // 129 bits per transform + { 15, 8, 14 }, // 129 bits per transform + { 15, 9, 13 }, // 129 bits per transform + { 15, 10, 12 }, // 129 bits per transform + { 15, 11, 11 }, // 129 bits per transform + { 15, 12, 10 }, // 129 bits per transform + { 15, 13, 9 }, // 129 bits per transform + { 15, 14, 8 }, // 129 bits per transform + { 15, 15, 7 }, // 129 bits per transform + { 15, 16, 6 }, // 129 bits per transform + { 15, 17, 5 }, // 129 bits per transform + { 16, 4, 17 }, // 129 bits per transform + { 16, 5, 16 }, // 129 bits per transform + { 16, 6, 15 }, // 129 bits per transform + { 16, 7, 14 }, // 129 bits per transform + { 16, 8, 13 }, // 129 bits per transform + { 16, 9, 12 }, // 129 bits per transform + { 16, 10, 11 }, // 129 bits per transform + { 16, 11, 10 }, // 129 bits per transform + { 16, 12, 9 }, // 129 bits per transform + { 16, 13, 8 }, // 129 bits per transform + { 16, 14, 7 }, // 129 bits per transform + { 16, 15, 6 }, // 129 bits per transform + { 16, 16, 5 }, // 129 bits per transform + { 16, 17, 4 }, // 129 bits per transform + { 17, 3, 17 }, // 129 bits per transform + { 17, 4, 16 }, // 129 bits per transform + { 17, 5, 15 }, // 129 bits per transform + { 17, 6, 14 }, // 129 bits per transform + { 17, 7, 13 }, // 129 bits per transform + { 17, 8, 12 }, // 129 bits per transform + { 17, 9, 11 }, // 129 bits per transform + { 17, 10, 10 }, // 129 bits per transform + { 17, 11, 9 }, // 129 bits per transform + { 17, 12, 8 }, // 129 bits per transform + { 17, 13, 7 }, // 129 bits per transform + { 17, 14, 6 }, // 129 bits per transform + { 17, 15, 5 }, // 129 bits per transform + { 17, 16, 4 }, // 129 bits per transform + { 17, 17, 3 }, // 129 bits per transform + { 18, 0, 9 }, // 129 bits per transform + { 18, 1, 6 }, // 129 bits per transform + { 18, 2, 5 }, // 129 bits per transform + { 18, 3, 4 }, // 129 bits per transform + { 18, 4, 3 }, // 129 bits per transform + { 18, 5, 2 }, // 129 bits per transform + { 18, 6, 1 }, // 129 bits per transform + { 18, 9, 0 }, // 129 bits per transform + { 0, 10, 18 }, // 132 bits per transform + { 0, 18, 10 }, // 132 bits per transform + { 1, 7, 18 }, // 132 bits per transform + { 1, 18, 7 }, // 132 bits per transform + { 2, 6, 18 }, // 132 bits per transform + { 2, 18, 6 }, // 132 bits per transform + { 3, 5, 18 }, // 132 bits per transform + { 3, 18, 5 }, // 132 bits per transform + { 4, 4, 18 }, // 132 bits per transform + { 4, 17, 17 }, // 132 bits per transform + { 4, 18, 4 }, // 132 bits per transform + { 5, 3, 18 }, // 132 bits per transform + { 5, 16, 17 }, // 132 bits per transform + { 5, 17, 16 }, // 132 bits per transform + { 5, 18, 3 }, // 132 bits per transform + { 6, 2, 18 }, // 132 bits per transform + { 6, 15, 17 }, // 132 bits per transform + { 6, 16, 16 }, // 132 bits per transform + { 6, 17, 15 }, // 132 bits per transform + { 6, 18, 2 }, // 132 bits per transform + { 7, 1, 18 }, // 132 bits per transform + { 7, 14, 17 }, // 132 bits per transform + { 7, 15, 16 }, // 132 bits per transform + { 7, 16, 15 }, // 132 bits per transform + { 7, 17, 14 }, // 132 bits per transform + { 7, 18, 1 }, // 132 bits per transform + { 8, 13, 17 }, // 132 bits per transform + { 8, 14, 16 }, // 132 bits per transform + { 8, 15, 15 }, // 132 bits per transform + { 8, 16, 14 }, // 132 bits per transform + { 8, 17, 13 }, // 132 bits per transform + { 9, 12, 17 }, // 132 bits per transform + { 9, 13, 16 }, // 132 bits per transform + { 9, 14, 15 }, // 132 bits per transform + { 9, 15, 14 }, // 132 bits per transform + { 9, 16, 13 }, // 132 bits per transform + { 9, 17, 12 }, // 132 bits per transform + { 10, 0, 18 }, // 132 bits per transform + { 10, 11, 17 }, // 132 bits per transform + { 10, 12, 16 }, // 132 bits per transform + { 10, 13, 15 }, // 132 bits per transform + { 10, 14, 14 }, // 132 bits per transform + { 10, 15, 13 }, // 132 bits per transform + { 10, 16, 12 }, // 132 bits per transform + { 10, 17, 11 }, // 132 bits per transform + { 10, 18, 0 }, // 132 bits per transform + { 11, 10, 17 }, // 132 bits per transform + { 11, 11, 16 }, // 132 bits per transform + { 11, 12, 15 }, // 132 bits per transform + { 11, 13, 14 }, // 132 bits per transform + { 11, 14, 13 }, // 132 bits per transform + { 11, 15, 12 }, // 132 bits per transform + { 11, 16, 11 }, // 132 bits per transform + { 11, 17, 10 }, // 132 bits per transform + { 12, 9, 17 }, // 132 bits per transform + { 12, 10, 16 }, // 132 bits per transform + { 12, 11, 15 }, // 132 bits per transform + { 12, 12, 14 }, // 132 bits per transform + { 12, 13, 13 }, // 132 bits per transform + { 12, 14, 12 }, // 132 bits per transform + { 12, 15, 11 }, // 132 bits per transform + { 12, 16, 10 }, // 132 bits per transform + { 12, 17, 9 }, // 132 bits per transform + { 13, 8, 17 }, // 132 bits per transform + { 13, 9, 16 }, // 132 bits per transform + { 13, 10, 15 }, // 132 bits per transform + { 13, 11, 14 }, // 132 bits per transform + { 13, 12, 13 }, // 132 bits per transform + { 13, 13, 12 }, // 132 bits per transform + { 13, 14, 11 }, // 132 bits per transform + { 13, 15, 10 }, // 132 bits per transform + { 13, 16, 9 }, // 132 bits per transform + { 13, 17, 8 }, // 132 bits per transform + { 14, 7, 17 }, // 132 bits per transform + { 14, 8, 16 }, // 132 bits per transform + { 14, 9, 15 }, // 132 bits per transform + { 14, 10, 14 }, // 132 bits per transform + { 14, 11, 13 }, // 132 bits per transform + { 14, 12, 12 }, // 132 bits per transform + { 14, 13, 11 }, // 132 bits per transform + { 14, 14, 10 }, // 132 bits per transform + { 14, 15, 9 }, // 132 bits per transform + { 14, 16, 8 }, // 132 bits per transform + { 14, 17, 7 }, // 132 bits per transform + { 15, 6, 17 }, // 132 bits per transform + { 15, 7, 16 }, // 132 bits per transform + { 15, 8, 15 }, // 132 bits per transform + { 15, 9, 14 }, // 132 bits per transform + { 15, 10, 13 }, // 132 bits per transform + { 15, 11, 12 }, // 132 bits per transform + { 15, 12, 11 }, // 132 bits per transform + { 15, 13, 10 }, // 132 bits per transform + { 15, 14, 9 }, // 132 bits per transform + { 15, 15, 8 }, // 132 bits per transform + { 15, 16, 7 }, // 132 bits per transform + { 15, 17, 6 }, // 132 bits per transform + { 16, 5, 17 }, // 132 bits per transform + { 16, 6, 16 }, // 132 bits per transform + { 16, 7, 15 }, // 132 bits per transform + { 16, 8, 14 }, // 132 bits per transform + { 16, 9, 13 }, // 132 bits per transform + { 16, 10, 12 }, // 132 bits per transform + { 16, 11, 11 }, // 132 bits per transform + { 16, 12, 10 }, // 132 bits per transform + { 16, 13, 9 }, // 132 bits per transform + { 16, 14, 8 }, // 132 bits per transform + { 16, 15, 7 }, // 132 bits per transform + { 16, 16, 6 }, // 132 bits per transform + { 16, 17, 5 }, // 132 bits per transform + { 17, 4, 17 }, // 132 bits per transform + { 17, 5, 16 }, // 132 bits per transform + { 17, 6, 15 }, // 132 bits per transform + { 17, 7, 14 }, // 132 bits per transform + { 17, 8, 13 }, // 132 bits per transform + { 17, 9, 12 }, // 132 bits per transform + { 17, 10, 11 }, // 132 bits per transform + { 17, 11, 10 }, // 132 bits per transform + { 17, 12, 9 }, // 132 bits per transform + { 17, 13, 8 }, // 132 bits per transform + { 17, 14, 7 }, // 132 bits per transform + { 17, 15, 6 }, // 132 bits per transform + { 17, 16, 5 }, // 132 bits per transform + { 17, 17, 4 }, // 132 bits per transform + { 18, 0, 10 }, // 132 bits per transform + { 18, 1, 7 }, // 132 bits per transform + { 18, 2, 6 }, // 132 bits per transform + { 18, 3, 5 }, // 132 bits per transform + { 18, 4, 4 }, // 132 bits per transform + { 18, 5, 3 }, // 132 bits per transform + { 18, 6, 2 }, // 132 bits per transform + { 18, 7, 1 }, // 132 bits per transform + { 18, 10, 0 }, // 132 bits per transform + { 0, 11, 18 }, // 135 bits per transform + { 0, 18, 11 }, // 135 bits per transform + { 1, 8, 18 }, // 135 bits per transform + { 1, 18, 8 }, // 135 bits per transform + { 2, 7, 18 }, // 135 bits per transform + { 2, 18, 7 }, // 135 bits per transform + { 3, 6, 18 }, // 135 bits per transform + { 3, 18, 6 }, // 135 bits per transform + { 4, 5, 18 }, // 135 bits per transform + { 4, 18, 5 }, // 135 bits per transform + { 5, 4, 18 }, // 135 bits per transform + { 5, 17, 17 }, // 135 bits per transform + { 5, 18, 4 }, // 135 bits per transform + { 6, 3, 18 }, // 135 bits per transform + { 6, 16, 17 }, // 135 bits per transform + { 6, 17, 16 }, // 135 bits per transform + { 6, 18, 3 }, // 135 bits per transform + { 7, 2, 18 }, // 135 bits per transform + { 7, 15, 17 }, // 135 bits per transform + { 7, 16, 16 }, // 135 bits per transform + { 7, 17, 15 }, // 135 bits per transform + { 7, 18, 2 }, // 135 bits per transform + { 8, 1, 18 }, // 135 bits per transform + { 8, 14, 17 }, // 135 bits per transform + { 8, 15, 16 }, // 135 bits per transform + { 8, 16, 15 }, // 135 bits per transform + { 8, 17, 14 }, // 135 bits per transform + { 8, 18, 1 }, // 135 bits per transform + { 9, 13, 17 }, // 135 bits per transform + { 9, 14, 16 }, // 135 bits per transform + { 9, 15, 15 }, // 135 bits per transform + { 9, 16, 14 }, // 135 bits per transform + { 9, 17, 13 }, // 135 bits per transform + { 10, 12, 17 }, // 135 bits per transform + { 10, 13, 16 }, // 135 bits per transform + { 10, 14, 15 }, // 135 bits per transform + { 10, 15, 14 }, // 135 bits per transform + { 10, 16, 13 }, // 135 bits per transform + { 10, 17, 12 }, // 135 bits per transform + { 11, 0, 18 }, // 135 bits per transform + { 11, 11, 17 }, // 135 bits per transform + { 11, 12, 16 }, // 135 bits per transform + { 11, 13, 15 }, // 135 bits per transform + { 11, 14, 14 }, // 135 bits per transform + { 11, 15, 13 }, // 135 bits per transform + { 11, 16, 12 }, // 135 bits per transform + { 11, 17, 11 }, // 135 bits per transform + { 11, 18, 0 }, // 135 bits per transform + { 12, 10, 17 }, // 135 bits per transform + { 12, 11, 16 }, // 135 bits per transform + { 12, 12, 15 }, // 135 bits per transform + { 12, 13, 14 }, // 135 bits per transform + { 12, 14, 13 }, // 135 bits per transform + { 12, 15, 12 }, // 135 bits per transform + { 12, 16, 11 }, // 135 bits per transform + { 12, 17, 10 }, // 135 bits per transform + { 13, 9, 17 }, // 135 bits per transform + { 13, 10, 16 }, // 135 bits per transform + { 13, 11, 15 }, // 135 bits per transform + { 13, 12, 14 }, // 135 bits per transform + { 13, 13, 13 }, // 135 bits per transform + { 13, 14, 12 }, // 135 bits per transform + { 13, 15, 11 }, // 135 bits per transform + { 13, 16, 10 }, // 135 bits per transform + { 13, 17, 9 }, // 135 bits per transform + { 14, 8, 17 }, // 135 bits per transform + { 14, 9, 16 }, // 135 bits per transform + { 14, 10, 15 }, // 135 bits per transform + { 14, 11, 14 }, // 135 bits per transform + { 14, 12, 13 }, // 135 bits per transform + { 14, 13, 12 }, // 135 bits per transform + { 14, 14, 11 }, // 135 bits per transform + { 14, 15, 10 }, // 135 bits per transform + { 14, 16, 9 }, // 135 bits per transform + { 14, 17, 8 }, // 135 bits per transform + { 15, 7, 17 }, // 135 bits per transform + { 15, 8, 16 }, // 135 bits per transform + { 15, 9, 15 }, // 135 bits per transform + { 15, 10, 14 }, // 135 bits per transform + { 15, 11, 13 }, // 135 bits per transform + { 15, 12, 12 }, // 135 bits per transform + { 15, 13, 11 }, // 135 bits per transform + { 15, 14, 10 }, // 135 bits per transform + { 15, 15, 9 }, // 135 bits per transform + { 15, 16, 8 }, // 135 bits per transform + { 15, 17, 7 }, // 135 bits per transform + { 16, 6, 17 }, // 135 bits per transform + { 16, 7, 16 }, // 135 bits per transform + { 16, 8, 15 }, // 135 bits per transform + { 16, 9, 14 }, // 135 bits per transform + { 16, 10, 13 }, // 135 bits per transform + { 16, 11, 12 }, // 135 bits per transform + { 16, 12, 11 }, // 135 bits per transform + { 16, 13, 10 }, // 135 bits per transform + { 16, 14, 9 }, // 135 bits per transform + { 16, 15, 8 }, // 135 bits per transform + { 16, 16, 7 }, // 135 bits per transform + { 16, 17, 6 }, // 135 bits per transform + { 17, 5, 17 }, // 135 bits per transform + { 17, 6, 16 }, // 135 bits per transform + { 17, 7, 15 }, // 135 bits per transform + { 17, 8, 14 }, // 135 bits per transform + { 17, 9, 13 }, // 135 bits per transform + { 17, 10, 12 }, // 135 bits per transform + { 17, 11, 11 }, // 135 bits per transform + { 17, 12, 10 }, // 135 bits per transform + { 17, 13, 9 }, // 135 bits per transform + { 17, 14, 8 }, // 135 bits per transform + { 17, 15, 7 }, // 135 bits per transform + { 17, 16, 6 }, // 135 bits per transform + { 17, 17, 5 }, // 135 bits per transform + { 18, 0, 11 }, // 135 bits per transform + { 18, 1, 8 }, // 135 bits per transform + { 18, 2, 7 }, // 135 bits per transform + { 18, 3, 6 }, // 135 bits per transform + { 18, 4, 5 }, // 135 bits per transform + { 18, 5, 4 }, // 135 bits per transform + { 18, 6, 3 }, // 135 bits per transform + { 18, 7, 2 }, // 135 bits per transform + { 18, 8, 1 }, // 135 bits per transform + { 18, 11, 0 }, // 135 bits per transform + { 0, 12, 18 }, // 138 bits per transform + { 0, 18, 12 }, // 138 bits per transform + { 1, 9, 18 }, // 138 bits per transform + { 1, 18, 9 }, // 138 bits per transform + { 2, 8, 18 }, // 138 bits per transform + { 2, 18, 8 }, // 138 bits per transform + { 3, 7, 18 }, // 138 bits per transform + { 3, 18, 7 }, // 138 bits per transform + { 4, 6, 18 }, // 138 bits per transform + { 4, 18, 6 }, // 138 bits per transform + { 5, 5, 18 }, // 138 bits per transform + { 5, 18, 5 }, // 138 bits per transform + { 6, 4, 18 }, // 138 bits per transform + { 6, 17, 17 }, // 138 bits per transform + { 6, 18, 4 }, // 138 bits per transform + { 7, 3, 18 }, // 138 bits per transform + { 7, 16, 17 }, // 138 bits per transform + { 7, 17, 16 }, // 138 bits per transform + { 7, 18, 3 }, // 138 bits per transform + { 8, 2, 18 }, // 138 bits per transform + { 8, 15, 17 }, // 138 bits per transform + { 8, 16, 16 }, // 138 bits per transform + { 8, 17, 15 }, // 138 bits per transform + { 8, 18, 2 }, // 138 bits per transform + { 9, 1, 18 }, // 138 bits per transform + { 9, 14, 17 }, // 138 bits per transform + { 9, 15, 16 }, // 138 bits per transform + { 9, 16, 15 }, // 138 bits per transform + { 9, 17, 14 }, // 138 bits per transform + { 9, 18, 1 }, // 138 bits per transform + { 10, 13, 17 }, // 138 bits per transform + { 10, 14, 16 }, // 138 bits per transform + { 10, 15, 15 }, // 138 bits per transform + { 10, 16, 14 }, // 138 bits per transform + { 10, 17, 13 }, // 138 bits per transform + { 11, 12, 17 }, // 138 bits per transform + { 11, 13, 16 }, // 138 bits per transform + { 11, 14, 15 }, // 138 bits per transform + { 11, 15, 14 }, // 138 bits per transform + { 11, 16, 13 }, // 138 bits per transform + { 11, 17, 12 }, // 138 bits per transform + { 12, 0, 18 }, // 138 bits per transform + { 12, 11, 17 }, // 138 bits per transform + { 12, 12, 16 }, // 138 bits per transform + { 12, 13, 15 }, // 138 bits per transform + { 12, 14, 14 }, // 138 bits per transform + { 12, 15, 13 }, // 138 bits per transform + { 12, 16, 12 }, // 138 bits per transform + { 12, 17, 11 }, // 138 bits per transform + { 12, 18, 0 }, // 138 bits per transform + { 13, 10, 17 }, // 138 bits per transform + { 13, 11, 16 }, // 138 bits per transform + { 13, 12, 15 }, // 138 bits per transform + { 13, 13, 14 }, // 138 bits per transform + { 13, 14, 13 }, // 138 bits per transform + { 13, 15, 12 }, // 138 bits per transform + { 13, 16, 11 }, // 138 bits per transform + { 13, 17, 10 }, // 138 bits per transform + { 14, 9, 17 }, // 138 bits per transform + { 14, 10, 16 }, // 138 bits per transform + { 14, 11, 15 }, // 138 bits per transform + { 14, 12, 14 }, // 138 bits per transform + { 14, 13, 13 }, // 138 bits per transform + { 14, 14, 12 }, // 138 bits per transform + { 14, 15, 11 }, // 138 bits per transform + { 14, 16, 10 }, // 138 bits per transform + { 14, 17, 9 }, // 138 bits per transform + { 15, 8, 17 }, // 138 bits per transform + { 15, 9, 16 }, // 138 bits per transform + { 15, 10, 15 }, // 138 bits per transform + { 15, 11, 14 }, // 138 bits per transform + { 15, 12, 13 }, // 138 bits per transform + { 15, 13, 12 }, // 138 bits per transform + { 15, 14, 11 }, // 138 bits per transform + { 15, 15, 10 }, // 138 bits per transform + { 15, 16, 9 }, // 138 bits per transform + { 15, 17, 8 }, // 138 bits per transform + { 16, 7, 17 }, // 138 bits per transform + { 16, 8, 16 }, // 138 bits per transform + { 16, 9, 15 }, // 138 bits per transform + { 16, 10, 14 }, // 138 bits per transform + { 16, 11, 13 }, // 138 bits per transform + { 16, 12, 12 }, // 138 bits per transform + { 16, 13, 11 }, // 138 bits per transform + { 16, 14, 10 }, // 138 bits per transform + { 16, 15, 9 }, // 138 bits per transform + { 16, 16, 8 }, // 138 bits per transform + { 16, 17, 7 }, // 138 bits per transform + { 17, 6, 17 }, // 138 bits per transform + { 17, 7, 16 }, // 138 bits per transform + { 17, 8, 15 }, // 138 bits per transform + { 17, 9, 14 }, // 138 bits per transform + { 17, 10, 13 }, // 138 bits per transform + { 17, 11, 12 }, // 138 bits per transform + { 17, 12, 11 }, // 138 bits per transform + { 17, 13, 10 }, // 138 bits per transform + { 17, 14, 9 }, // 138 bits per transform + { 17, 15, 8 }, // 138 bits per transform + { 17, 16, 7 }, // 138 bits per transform + { 17, 17, 6 }, // 138 bits per transform + { 18, 0, 12 }, // 138 bits per transform + { 18, 1, 9 }, // 138 bits per transform + { 18, 2, 8 }, // 138 bits per transform + { 18, 3, 7 }, // 138 bits per transform + { 18, 4, 6 }, // 138 bits per transform + { 18, 5, 5 }, // 138 bits per transform + { 18, 6, 4 }, // 138 bits per transform + { 18, 7, 3 }, // 138 bits per transform + { 18, 8, 2 }, // 138 bits per transform + { 18, 9, 1 }, // 138 bits per transform + { 18, 12, 0 }, // 138 bits per transform + { 0, 13, 18 }, // 141 bits per transform + { 0, 18, 13 }, // 141 bits per transform + { 1, 10, 18 }, // 141 bits per transform + { 1, 18, 10 }, // 141 bits per transform + { 2, 9, 18 }, // 141 bits per transform + { 2, 18, 9 }, // 141 bits per transform + { 3, 8, 18 }, // 141 bits per transform + { 3, 18, 8 }, // 141 bits per transform + { 4, 7, 18 }, // 141 bits per transform + { 4, 18, 7 }, // 141 bits per transform + { 5, 6, 18 }, // 141 bits per transform + { 5, 18, 6 }, // 141 bits per transform + { 6, 5, 18 }, // 141 bits per transform + { 6, 18, 5 }, // 141 bits per transform + { 7, 4, 18 }, // 141 bits per transform + { 7, 17, 17 }, // 141 bits per transform + { 7, 18, 4 }, // 141 bits per transform + { 8, 3, 18 }, // 141 bits per transform + { 8, 16, 17 }, // 141 bits per transform + { 8, 17, 16 }, // 141 bits per transform + { 8, 18, 3 }, // 141 bits per transform + { 9, 2, 18 }, // 141 bits per transform + { 9, 15, 17 }, // 141 bits per transform + { 9, 16, 16 }, // 141 bits per transform + { 9, 17, 15 }, // 141 bits per transform + { 9, 18, 2 }, // 141 bits per transform + { 10, 1, 18 }, // 141 bits per transform + { 10, 14, 17 }, // 141 bits per transform + { 10, 15, 16 }, // 141 bits per transform + { 10, 16, 15 }, // 141 bits per transform + { 10, 17, 14 }, // 141 bits per transform + { 10, 18, 1 }, // 141 bits per transform + { 11, 13, 17 }, // 141 bits per transform + { 11, 14, 16 }, // 141 bits per transform + { 11, 15, 15 }, // 141 bits per transform + { 11, 16, 14 }, // 141 bits per transform + { 11, 17, 13 }, // 141 bits per transform + { 12, 12, 17 }, // 141 bits per transform + { 12, 13, 16 }, // 141 bits per transform + { 12, 14, 15 }, // 141 bits per transform + { 12, 15, 14 }, // 141 bits per transform + { 12, 16, 13 }, // 141 bits per transform + { 12, 17, 12 }, // 141 bits per transform + { 13, 0, 18 }, // 141 bits per transform + { 13, 11, 17 }, // 141 bits per transform + { 13, 12, 16 }, // 141 bits per transform + { 13, 13, 15 }, // 141 bits per transform + { 13, 14, 14 }, // 141 bits per transform + { 13, 15, 13 }, // 141 bits per transform + { 13, 16, 12 }, // 141 bits per transform + { 13, 17, 11 }, // 141 bits per transform + { 13, 18, 0 }, // 141 bits per transform + { 14, 10, 17 }, // 141 bits per transform + { 14, 11, 16 }, // 141 bits per transform + { 14, 12, 15 }, // 141 bits per transform + { 14, 13, 14 }, // 141 bits per transform + { 14, 14, 13 }, // 141 bits per transform + { 14, 15, 12 }, // 141 bits per transform + { 14, 16, 11 }, // 141 bits per transform + { 14, 17, 10 }, // 141 bits per transform + { 15, 9, 17 }, // 141 bits per transform + { 15, 10, 16 }, // 141 bits per transform + { 15, 11, 15 }, // 141 bits per transform + { 15, 12, 14 }, // 141 bits per transform + { 15, 13, 13 }, // 141 bits per transform + { 15, 14, 12 }, // 141 bits per transform + { 15, 15, 11 }, // 141 bits per transform + { 15, 16, 10 }, // 141 bits per transform + { 15, 17, 9 }, // 141 bits per transform + { 16, 8, 17 }, // 141 bits per transform + { 16, 9, 16 }, // 141 bits per transform + { 16, 10, 15 }, // 141 bits per transform + { 16, 11, 14 }, // 141 bits per transform + { 16, 12, 13 }, // 141 bits per transform + { 16, 13, 12 }, // 141 bits per transform + { 16, 14, 11 }, // 141 bits per transform + { 16, 15, 10 }, // 141 bits per transform + { 16, 16, 9 }, // 141 bits per transform + { 16, 17, 8 }, // 141 bits per transform + { 17, 7, 17 }, // 141 bits per transform + { 17, 8, 16 }, // 141 bits per transform + { 17, 9, 15 }, // 141 bits per transform + { 17, 10, 14 }, // 141 bits per transform + { 17, 11, 13 }, // 141 bits per transform + { 17, 12, 12 }, // 141 bits per transform + { 17, 13, 11 }, // 141 bits per transform + { 17, 14, 10 }, // 141 bits per transform + { 17, 15, 9 }, // 141 bits per transform + { 17, 16, 8 }, // 141 bits per transform + { 17, 17, 7 }, // 141 bits per transform + { 18, 0, 13 }, // 141 bits per transform + { 18, 1, 10 }, // 141 bits per transform + { 18, 2, 9 }, // 141 bits per transform + { 18, 3, 8 }, // 141 bits per transform + { 18, 4, 7 }, // 141 bits per transform + { 18, 5, 6 }, // 141 bits per transform + { 18, 6, 5 }, // 141 bits per transform + { 18, 7, 4 }, // 141 bits per transform + { 18, 8, 3 }, // 141 bits per transform + { 18, 9, 2 }, // 141 bits per transform + { 18, 10, 1 }, // 141 bits per transform + { 18, 13, 0 }, // 141 bits per transform + { 0, 14, 18 }, // 144 bits per transform + { 0, 18, 14 }, // 144 bits per transform + { 1, 11, 18 }, // 144 bits per transform + { 1, 18, 11 }, // 144 bits per transform + { 2, 10, 18 }, // 144 bits per transform + { 2, 18, 10 }, // 144 bits per transform + { 3, 9, 18 }, // 144 bits per transform + { 3, 18, 9 }, // 144 bits per transform + { 4, 8, 18 }, // 144 bits per transform + { 4, 18, 8 }, // 144 bits per transform + { 5, 7, 18 }, // 144 bits per transform + { 5, 18, 7 }, // 144 bits per transform + { 6, 6, 18 }, // 144 bits per transform + { 6, 18, 6 }, // 144 bits per transform + { 7, 5, 18 }, // 144 bits per transform + { 7, 18, 5 }, // 144 bits per transform + { 8, 4, 18 }, // 144 bits per transform + { 8, 17, 17 }, // 144 bits per transform + { 8, 18, 4 }, // 144 bits per transform + { 9, 3, 18 }, // 144 bits per transform + { 9, 16, 17 }, // 144 bits per transform + { 9, 17, 16 }, // 144 bits per transform + { 9, 18, 3 }, // 144 bits per transform + { 10, 2, 18 }, // 144 bits per transform + { 10, 15, 17 }, // 144 bits per transform + { 10, 16, 16 }, // 144 bits per transform + { 10, 17, 15 }, // 144 bits per transform + { 10, 18, 2 }, // 144 bits per transform + { 11, 1, 18 }, // 144 bits per transform + { 11, 14, 17 }, // 144 bits per transform + { 11, 15, 16 }, // 144 bits per transform + { 11, 16, 15 }, // 144 bits per transform + { 11, 17, 14 }, // 144 bits per transform + { 11, 18, 1 }, // 144 bits per transform + { 12, 13, 17 }, // 144 bits per transform + { 12, 14, 16 }, // 144 bits per transform + { 12, 15, 15 }, // 144 bits per transform + { 12, 16, 14 }, // 144 bits per transform + { 12, 17, 13 }, // 144 bits per transform + { 13, 12, 17 }, // 144 bits per transform + { 13, 13, 16 }, // 144 bits per transform + { 13, 14, 15 }, // 144 bits per transform + { 13, 15, 14 }, // 144 bits per transform + { 13, 16, 13 }, // 144 bits per transform + { 13, 17, 12 }, // 144 bits per transform + { 14, 0, 18 }, // 144 bits per transform + { 14, 11, 17 }, // 144 bits per transform + { 14, 12, 16 }, // 144 bits per transform + { 14, 13, 15 }, // 144 bits per transform + { 14, 14, 14 }, // 144 bits per transform + { 14, 15, 13 }, // 144 bits per transform + { 14, 16, 12 }, // 144 bits per transform + { 14, 17, 11 }, // 144 bits per transform + { 14, 18, 0 }, // 144 bits per transform + { 15, 10, 17 }, // 144 bits per transform + { 15, 11, 16 }, // 144 bits per transform + { 15, 12, 15 }, // 144 bits per transform + { 15, 13, 14 }, // 144 bits per transform + { 15, 14, 13 }, // 144 bits per transform + { 15, 15, 12 }, // 144 bits per transform + { 15, 16, 11 }, // 144 bits per transform + { 15, 17, 10 }, // 144 bits per transform + { 16, 9, 17 }, // 144 bits per transform + { 16, 10, 16 }, // 144 bits per transform + { 16, 11, 15 }, // 144 bits per transform + { 16, 12, 14 }, // 144 bits per transform + { 16, 13, 13 }, // 144 bits per transform + { 16, 14, 12 }, // 144 bits per transform + { 16, 15, 11 }, // 144 bits per transform + { 16, 16, 10 }, // 144 bits per transform + { 16, 17, 9 }, // 144 bits per transform + { 17, 8, 17 }, // 144 bits per transform + { 17, 9, 16 }, // 144 bits per transform + { 17, 10, 15 }, // 144 bits per transform + { 17, 11, 14 }, // 144 bits per transform + { 17, 12, 13 }, // 144 bits per transform + { 17, 13, 12 }, // 144 bits per transform + { 17, 14, 11 }, // 144 bits per transform + { 17, 15, 10 }, // 144 bits per transform + { 17, 16, 9 }, // 144 bits per transform + { 17, 17, 8 }, // 144 bits per transform + { 18, 0, 14 }, // 144 bits per transform + { 18, 1, 11 }, // 144 bits per transform + { 18, 2, 10 }, // 144 bits per transform + { 18, 3, 9 }, // 144 bits per transform + { 18, 4, 8 }, // 144 bits per transform + { 18, 5, 7 }, // 144 bits per transform + { 18, 6, 6 }, // 144 bits per transform + { 18, 7, 5 }, // 144 bits per transform + { 18, 8, 4 }, // 144 bits per transform + { 18, 9, 3 }, // 144 bits per transform + { 18, 10, 2 }, // 144 bits per transform + { 18, 11, 1 }, // 144 bits per transform + { 18, 14, 0 }, // 144 bits per transform + { 0, 15, 18 }, // 147 bits per transform + { 0, 18, 15 }, // 147 bits per transform + { 1, 12, 18 }, // 147 bits per transform + { 1, 18, 12 }, // 147 bits per transform + { 2, 11, 18 }, // 147 bits per transform + { 2, 18, 11 }, // 147 bits per transform + { 3, 10, 18 }, // 147 bits per transform + { 3, 18, 10 }, // 147 bits per transform + { 4, 9, 18 }, // 147 bits per transform + { 4, 18, 9 }, // 147 bits per transform + { 5, 8, 18 }, // 147 bits per transform + { 5, 18, 8 }, // 147 bits per transform + { 6, 7, 18 }, // 147 bits per transform + { 6, 18, 7 }, // 147 bits per transform + { 7, 6, 18 }, // 147 bits per transform + { 7, 18, 6 }, // 147 bits per transform + { 8, 5, 18 }, // 147 bits per transform + { 8, 18, 5 }, // 147 bits per transform + { 9, 4, 18 }, // 147 bits per transform + { 9, 17, 17 }, // 147 bits per transform + { 9, 18, 4 }, // 147 bits per transform + { 10, 3, 18 }, // 147 bits per transform + { 10, 16, 17 }, // 147 bits per transform + { 10, 17, 16 }, // 147 bits per transform + { 10, 18, 3 }, // 147 bits per transform + { 11, 2, 18 }, // 147 bits per transform + { 11, 15, 17 }, // 147 bits per transform + { 11, 16, 16 }, // 147 bits per transform + { 11, 17, 15 }, // 147 bits per transform + { 11, 18, 2 }, // 147 bits per transform + { 12, 1, 18 }, // 147 bits per transform + { 12, 14, 17 }, // 147 bits per transform + { 12, 15, 16 }, // 147 bits per transform + { 12, 16, 15 }, // 147 bits per transform + { 12, 17, 14 }, // 147 bits per transform + { 12, 18, 1 }, // 147 bits per transform + { 13, 13, 17 }, // 147 bits per transform + { 13, 14, 16 }, // 147 bits per transform + { 13, 15, 15 }, // 147 bits per transform + { 13, 16, 14 }, // 147 bits per transform + { 13, 17, 13 }, // 147 bits per transform + { 14, 12, 17 }, // 147 bits per transform + { 14, 13, 16 }, // 147 bits per transform + { 14, 14, 15 }, // 147 bits per transform + { 14, 15, 14 }, // 147 bits per transform + { 14, 16, 13 }, // 147 bits per transform + { 14, 17, 12 }, // 147 bits per transform + { 15, 0, 18 }, // 147 bits per transform + { 15, 11, 17 }, // 147 bits per transform + { 15, 12, 16 }, // 147 bits per transform + { 15, 13, 15 }, // 147 bits per transform + { 15, 14, 14 }, // 147 bits per transform + { 15, 15, 13 }, // 147 bits per transform + { 15, 16, 12 }, // 147 bits per transform + { 15, 17, 11 }, // 147 bits per transform + { 15, 18, 0 }, // 147 bits per transform + { 16, 10, 17 }, // 147 bits per transform + { 16, 11, 16 }, // 147 bits per transform + { 16, 12, 15 }, // 147 bits per transform + { 16, 13, 14 }, // 147 bits per transform + { 16, 14, 13 }, // 147 bits per transform + { 16, 15, 12 }, // 147 bits per transform + { 16, 16, 11 }, // 147 bits per transform + { 16, 17, 10 }, // 147 bits per transform + { 17, 9, 17 }, // 147 bits per transform + { 17, 10, 16 }, // 147 bits per transform + { 17, 11, 15 }, // 147 bits per transform + { 17, 12, 14 }, // 147 bits per transform + { 17, 13, 13 }, // 147 bits per transform + { 17, 14, 12 }, // 147 bits per transform + { 17, 15, 11 }, // 147 bits per transform + { 17, 16, 10 }, // 147 bits per transform + { 17, 17, 9 }, // 147 bits per transform + { 18, 0, 15 }, // 147 bits per transform + { 18, 1, 12 }, // 147 bits per transform + { 18, 2, 11 }, // 147 bits per transform + { 18, 3, 10 }, // 147 bits per transform + { 18, 4, 9 }, // 147 bits per transform + { 18, 5, 8 }, // 147 bits per transform + { 18, 6, 7 }, // 147 bits per transform + { 18, 7, 6 }, // 147 bits per transform + { 18, 8, 5 }, // 147 bits per transform + { 18, 9, 4 }, // 147 bits per transform + { 18, 10, 3 }, // 147 bits per transform + { 18, 11, 2 }, // 147 bits per transform + { 18, 12, 1 }, // 147 bits per transform + { 18, 15, 0 }, // 147 bits per transform + { 0, 16, 18 }, // 150 bits per transform + { 0, 18, 16 }, // 150 bits per transform + { 1, 13, 18 }, // 150 bits per transform + { 1, 18, 13 }, // 150 bits per transform + { 2, 12, 18 }, // 150 bits per transform + { 2, 18, 12 }, // 150 bits per transform + { 3, 11, 18 }, // 150 bits per transform + { 3, 18, 11 }, // 150 bits per transform + { 4, 10, 18 }, // 150 bits per transform + { 4, 18, 10 }, // 150 bits per transform + { 5, 9, 18 }, // 150 bits per transform + { 5, 18, 9 }, // 150 bits per transform + { 6, 8, 18 }, // 150 bits per transform + { 6, 18, 8 }, // 150 bits per transform + { 7, 7, 18 }, // 150 bits per transform + { 7, 18, 7 }, // 150 bits per transform + { 8, 6, 18 }, // 150 bits per transform + { 8, 18, 6 }, // 150 bits per transform + { 9, 5, 18 }, // 150 bits per transform + { 9, 18, 5 }, // 150 bits per transform + { 10, 4, 18 }, // 150 bits per transform + { 10, 17, 17 }, // 150 bits per transform + { 10, 18, 4 }, // 150 bits per transform + { 11, 3, 18 }, // 150 bits per transform + { 11, 16, 17 }, // 150 bits per transform + { 11, 17, 16 }, // 150 bits per transform + { 11, 18, 3 }, // 150 bits per transform + { 12, 2, 18 }, // 150 bits per transform + { 12, 15, 17 }, // 150 bits per transform + { 12, 16, 16 }, // 150 bits per transform + { 12, 17, 15 }, // 150 bits per transform + { 12, 18, 2 }, // 150 bits per transform + { 13, 1, 18 }, // 150 bits per transform + { 13, 14, 17 }, // 150 bits per transform + { 13, 15, 16 }, // 150 bits per transform + { 13, 16, 15 }, // 150 bits per transform + { 13, 17, 14 }, // 150 bits per transform + { 13, 18, 1 }, // 150 bits per transform + { 14, 13, 17 }, // 150 bits per transform + { 14, 14, 16 }, // 150 bits per transform + { 14, 15, 15 }, // 150 bits per transform + { 14, 16, 14 }, // 150 bits per transform + { 14, 17, 13 }, // 150 bits per transform + { 15, 12, 17 }, // 150 bits per transform + { 15, 13, 16 }, // 150 bits per transform + { 15, 14, 15 }, // 150 bits per transform + { 15, 15, 14 }, // 150 bits per transform + { 15, 16, 13 }, // 150 bits per transform + { 15, 17, 12 }, // 150 bits per transform + { 16, 0, 18 }, // 150 bits per transform + { 16, 11, 17 }, // 150 bits per transform + { 16, 12, 16 }, // 150 bits per transform + { 16, 13, 15 }, // 150 bits per transform + { 16, 14, 14 }, // 150 bits per transform + { 16, 15, 13 }, // 150 bits per transform + { 16, 16, 12 }, // 150 bits per transform + { 16, 17, 11 }, // 150 bits per transform + { 16, 18, 0 }, // 150 bits per transform + { 17, 10, 17 }, // 150 bits per transform + { 17, 11, 16 }, // 150 bits per transform + { 17, 12, 15 }, // 150 bits per transform + { 17, 13, 14 }, // 150 bits per transform + { 17, 14, 13 }, // 150 bits per transform + { 17, 15, 12 }, // 150 bits per transform + { 17, 16, 11 }, // 150 bits per transform + { 17, 17, 10 }, // 150 bits per transform + { 18, 0, 16 }, // 150 bits per transform + { 18, 1, 13 }, // 150 bits per transform + { 18, 2, 12 }, // 150 bits per transform + { 18, 3, 11 }, // 150 bits per transform + { 18, 4, 10 }, // 150 bits per transform + { 18, 5, 9 }, // 150 bits per transform + { 18, 6, 8 }, // 150 bits per transform + { 18, 7, 7 }, // 150 bits per transform + { 18, 8, 6 }, // 150 bits per transform + { 18, 9, 5 }, // 150 bits per transform + { 18, 10, 4 }, // 150 bits per transform + { 18, 11, 3 }, // 150 bits per transform + { 18, 12, 2 }, // 150 bits per transform + { 18, 13, 1 }, // 150 bits per transform + { 18, 16, 0 }, // 150 bits per transform + { 0, 17, 18 }, // 153 bits per transform + { 0, 18, 17 }, // 153 bits per transform + { 1, 14, 18 }, // 153 bits per transform + { 1, 18, 14 }, // 153 bits per transform + { 2, 13, 18 }, // 153 bits per transform + { 2, 18, 13 }, // 153 bits per transform + { 3, 12, 18 }, // 153 bits per transform + { 3, 18, 12 }, // 153 bits per transform + { 4, 11, 18 }, // 153 bits per transform + { 4, 18, 11 }, // 153 bits per transform + { 5, 10, 18 }, // 153 bits per transform + { 5, 18, 10 }, // 153 bits per transform + { 6, 9, 18 }, // 153 bits per transform + { 6, 18, 9 }, // 153 bits per transform + { 7, 8, 18 }, // 153 bits per transform + { 7, 18, 8 }, // 153 bits per transform + { 8, 7, 18 }, // 153 bits per transform + { 8, 18, 7 }, // 153 bits per transform + { 9, 6, 18 }, // 153 bits per transform + { 9, 18, 6 }, // 153 bits per transform + { 10, 5, 18 }, // 153 bits per transform + { 10, 18, 5 }, // 153 bits per transform + { 11, 4, 18 }, // 153 bits per transform + { 11, 17, 17 }, // 153 bits per transform + { 11, 18, 4 }, // 153 bits per transform + { 12, 3, 18 }, // 153 bits per transform + { 12, 16, 17 }, // 153 bits per transform + { 12, 17, 16 }, // 153 bits per transform + { 12, 18, 3 }, // 153 bits per transform + { 13, 2, 18 }, // 153 bits per transform + { 13, 15, 17 }, // 153 bits per transform + { 13, 16, 16 }, // 153 bits per transform + { 13, 17, 15 }, // 153 bits per transform + { 13, 18, 2 }, // 153 bits per transform + { 14, 1, 18 }, // 153 bits per transform + { 14, 14, 17 }, // 153 bits per transform + { 14, 15, 16 }, // 153 bits per transform + { 14, 16, 15 }, // 153 bits per transform + { 14, 17, 14 }, // 153 bits per transform + { 14, 18, 1 }, // 153 bits per transform + { 15, 13, 17 }, // 153 bits per transform + { 15, 14, 16 }, // 153 bits per transform + { 15, 15, 15 }, // 153 bits per transform + { 15, 16, 14 }, // 153 bits per transform + { 15, 17, 13 }, // 153 bits per transform + { 16, 12, 17 }, // 153 bits per transform + { 16, 13, 16 }, // 153 bits per transform + { 16, 14, 15 }, // 153 bits per transform + { 16, 15, 14 }, // 153 bits per transform + { 16, 16, 13 }, // 153 bits per transform + { 16, 17, 12 }, // 153 bits per transform + { 17, 0, 18 }, // 153 bits per transform + { 17, 11, 17 }, // 153 bits per transform + { 17, 12, 16 }, // 153 bits per transform + { 17, 13, 15 }, // 153 bits per transform + { 17, 14, 14 }, // 153 bits per transform + { 17, 15, 13 }, // 153 bits per transform + { 17, 16, 12 }, // 153 bits per transform + { 17, 17, 11 }, // 153 bits per transform + { 17, 18, 0 }, // 153 bits per transform + { 18, 0, 17 }, // 153 bits per transform + { 18, 1, 14 }, // 153 bits per transform + { 18, 2, 13 }, // 153 bits per transform + { 18, 3, 12 }, // 153 bits per transform + { 18, 4, 11 }, // 153 bits per transform + { 18, 5, 10 }, // 153 bits per transform + { 18, 6, 9 }, // 153 bits per transform + { 18, 7, 8 }, // 153 bits per transform + { 18, 8, 7 }, // 153 bits per transform + { 18, 9, 6 }, // 153 bits per transform + { 18, 10, 5 }, // 153 bits per transform + { 18, 11, 4 }, // 153 bits per transform + { 18, 12, 3 }, // 153 bits per transform + { 18, 13, 2 }, // 153 bits per transform + { 18, 14, 1 }, // 153 bits per transform + { 18, 17, 0 }, // 153 bits per transform + { 1, 15, 18 }, // 156 bits per transform + { 1, 18, 15 }, // 156 bits per transform + { 2, 14, 18 }, // 156 bits per transform + { 2, 18, 14 }, // 156 bits per transform + { 3, 13, 18 }, // 156 bits per transform + { 3, 18, 13 }, // 156 bits per transform + { 4, 12, 18 }, // 156 bits per transform + { 4, 18, 12 }, // 156 bits per transform + { 5, 11, 18 }, // 156 bits per transform + { 5, 18, 11 }, // 156 bits per transform + { 6, 10, 18 }, // 156 bits per transform + { 6, 18, 10 }, // 156 bits per transform + { 7, 9, 18 }, // 156 bits per transform + { 7, 18, 9 }, // 156 bits per transform + { 8, 8, 18 }, // 156 bits per transform + { 8, 18, 8 }, // 156 bits per transform + { 9, 7, 18 }, // 156 bits per transform + { 9, 18, 7 }, // 156 bits per transform + { 10, 6, 18 }, // 156 bits per transform + { 10, 18, 6 }, // 156 bits per transform + { 11, 5, 18 }, // 156 bits per transform + { 11, 18, 5 }, // 156 bits per transform + { 12, 4, 18 }, // 156 bits per transform + { 12, 17, 17 }, // 156 bits per transform + { 12, 18, 4 }, // 156 bits per transform + { 13, 3, 18 }, // 156 bits per transform + { 13, 16, 17 }, // 156 bits per transform + { 13, 17, 16 }, // 156 bits per transform + { 13, 18, 3 }, // 156 bits per transform + { 14, 2, 18 }, // 156 bits per transform + { 14, 15, 17 }, // 156 bits per transform + { 14, 16, 16 }, // 156 bits per transform + { 14, 17, 15 }, // 156 bits per transform + { 14, 18, 2 }, // 156 bits per transform + { 15, 1, 18 }, // 156 bits per transform + { 15, 14, 17 }, // 156 bits per transform + { 15, 15, 16 }, // 156 bits per transform + { 15, 16, 15 }, // 156 bits per transform + { 15, 17, 14 }, // 156 bits per transform + { 15, 18, 1 }, // 156 bits per transform + { 16, 13, 17 }, // 156 bits per transform + { 16, 14, 16 }, // 156 bits per transform + { 16, 15, 15 }, // 156 bits per transform + { 16, 16, 14 }, // 156 bits per transform + { 16, 17, 13 }, // 156 bits per transform + { 17, 12, 17 }, // 156 bits per transform + { 17, 13, 16 }, // 156 bits per transform + { 17, 14, 15 }, // 156 bits per transform + { 17, 15, 14 }, // 156 bits per transform + { 17, 16, 13 }, // 156 bits per transform + { 17, 17, 12 }, // 156 bits per transform + { 18, 1, 15 }, // 156 bits per transform + { 18, 2, 14 }, // 156 bits per transform + { 18, 3, 13 }, // 156 bits per transform + { 18, 4, 12 }, // 156 bits per transform + { 18, 5, 11 }, // 156 bits per transform + { 18, 6, 10 }, // 156 bits per transform + { 18, 7, 9 }, // 156 bits per transform + { 18, 8, 8 }, // 156 bits per transform + { 18, 9, 7 }, // 156 bits per transform + { 18, 10, 6 }, // 156 bits per transform + { 18, 11, 5 }, // 156 bits per transform + { 18, 12, 4 }, // 156 bits per transform + { 18, 13, 3 }, // 156 bits per transform + { 18, 14, 2 }, // 156 bits per transform + { 18, 15, 1 }, // 156 bits per transform + { 1, 16, 18 }, // 159 bits per transform + { 1, 18, 16 }, // 159 bits per transform + { 2, 15, 18 }, // 159 bits per transform + { 2, 18, 15 }, // 159 bits per transform + { 3, 14, 18 }, // 159 bits per transform + { 3, 18, 14 }, // 159 bits per transform + { 4, 13, 18 }, // 159 bits per transform + { 4, 18, 13 }, // 159 bits per transform + { 5, 12, 18 }, // 159 bits per transform + { 5, 18, 12 }, // 159 bits per transform + { 6, 11, 18 }, // 159 bits per transform + { 6, 18, 11 }, // 159 bits per transform + { 7, 10, 18 }, // 159 bits per transform + { 7, 18, 10 }, // 159 bits per transform + { 8, 9, 18 }, // 159 bits per transform + { 8, 18, 9 }, // 159 bits per transform + { 9, 8, 18 }, // 159 bits per transform + { 9, 18, 8 }, // 159 bits per transform + { 10, 7, 18 }, // 159 bits per transform + { 10, 18, 7 }, // 159 bits per transform + { 11, 6, 18 }, // 159 bits per transform + { 11, 18, 6 }, // 159 bits per transform + { 12, 5, 18 }, // 159 bits per transform + { 12, 18, 5 }, // 159 bits per transform + { 13, 4, 18 }, // 159 bits per transform + { 13, 17, 17 }, // 159 bits per transform + { 13, 18, 4 }, // 159 bits per transform + { 14, 3, 18 }, // 159 bits per transform + { 14, 16, 17 }, // 159 bits per transform + { 14, 17, 16 }, // 159 bits per transform + { 14, 18, 3 }, // 159 bits per transform + { 15, 2, 18 }, // 159 bits per transform + { 15, 15, 17 }, // 159 bits per transform + { 15, 16, 16 }, // 159 bits per transform + { 15, 17, 15 }, // 159 bits per transform + { 15, 18, 2 }, // 159 bits per transform + { 16, 1, 18 }, // 159 bits per transform + { 16, 14, 17 }, // 159 bits per transform + { 16, 15, 16 }, // 159 bits per transform + { 16, 16, 15 }, // 159 bits per transform + { 16, 17, 14 }, // 159 bits per transform + { 16, 18, 1 }, // 159 bits per transform + { 17, 13, 17 }, // 159 bits per transform + { 17, 14, 16 }, // 159 bits per transform + { 17, 15, 15 }, // 159 bits per transform + { 17, 16, 14 }, // 159 bits per transform + { 17, 17, 13 }, // 159 bits per transform + { 18, 1, 16 }, // 159 bits per transform + { 18, 2, 15 }, // 159 bits per transform + { 18, 3, 14 }, // 159 bits per transform + { 18, 4, 13 }, // 159 bits per transform + { 18, 5, 12 }, // 159 bits per transform + { 18, 6, 11 }, // 159 bits per transform + { 18, 7, 10 }, // 159 bits per transform + { 18, 8, 9 }, // 159 bits per transform + { 18, 9, 8 }, // 159 bits per transform + { 18, 10, 7 }, // 159 bits per transform + { 18, 11, 6 }, // 159 bits per transform + { 18, 12, 5 }, // 159 bits per transform + { 18, 13, 4 }, // 159 bits per transform + { 18, 14, 3 }, // 159 bits per transform + { 18, 15, 2 }, // 159 bits per transform + { 18, 16, 1 }, // 159 bits per transform + { 1, 17, 18 }, // 162 bits per transform + { 1, 18, 17 }, // 162 bits per transform + { 2, 16, 18 }, // 162 bits per transform + { 2, 18, 16 }, // 162 bits per transform + { 3, 15, 18 }, // 162 bits per transform + { 3, 18, 15 }, // 162 bits per transform + { 4, 14, 18 }, // 162 bits per transform + { 4, 18, 14 }, // 162 bits per transform + { 5, 13, 18 }, // 162 bits per transform + { 5, 18, 13 }, // 162 bits per transform + { 6, 12, 18 }, // 162 bits per transform + { 6, 18, 12 }, // 162 bits per transform + { 7, 11, 18 }, // 162 bits per transform + { 7, 18, 11 }, // 162 bits per transform + { 8, 10, 18 }, // 162 bits per transform + { 8, 18, 10 }, // 162 bits per transform + { 9, 9, 18 }, // 162 bits per transform + { 9, 18, 9 }, // 162 bits per transform + { 10, 8, 18 }, // 162 bits per transform + { 10, 18, 8 }, // 162 bits per transform + { 11, 7, 18 }, // 162 bits per transform + { 11, 18, 7 }, // 162 bits per transform + { 12, 6, 18 }, // 162 bits per transform + { 12, 18, 6 }, // 162 bits per transform + { 13, 5, 18 }, // 162 bits per transform + { 13, 18, 5 }, // 162 bits per transform + { 14, 4, 18 }, // 162 bits per transform + { 14, 17, 17 }, // 162 bits per transform + { 14, 18, 4 }, // 162 bits per transform + { 15, 3, 18 }, // 162 bits per transform + { 15, 16, 17 }, // 162 bits per transform + { 15, 17, 16 }, // 162 bits per transform + { 15, 18, 3 }, // 162 bits per transform + { 16, 2, 18 }, // 162 bits per transform + { 16, 15, 17 }, // 162 bits per transform + { 16, 16, 16 }, // 162 bits per transform + { 16, 17, 15 }, // 162 bits per transform + { 16, 18, 2 }, // 162 bits per transform + { 17, 1, 18 }, // 162 bits per transform + { 17, 14, 17 }, // 162 bits per transform + { 17, 15, 16 }, // 162 bits per transform + { 17, 16, 15 }, // 162 bits per transform + { 17, 17, 14 }, // 162 bits per transform + { 17, 18, 1 }, // 162 bits per transform + { 18, 1, 17 }, // 162 bits per transform + { 18, 2, 16 }, // 162 bits per transform + { 18, 3, 15 }, // 162 bits per transform + { 18, 4, 14 }, // 162 bits per transform + { 18, 5, 13 }, // 162 bits per transform + { 18, 6, 12 }, // 162 bits per transform + { 18, 7, 11 }, // 162 bits per transform + { 18, 8, 10 }, // 162 bits per transform + { 18, 9, 9 }, // 162 bits per transform + { 18, 10, 8 }, // 162 bits per transform + { 18, 11, 7 }, // 162 bits per transform + { 18, 12, 6 }, // 162 bits per transform + { 18, 13, 5 }, // 162 bits per transform + { 18, 14, 4 }, // 162 bits per transform + { 18, 15, 3 }, // 162 bits per transform + { 18, 16, 2 }, // 162 bits per transform + { 18, 17, 1 }, // 162 bits per transform + { 2, 17, 18 }, // 165 bits per transform + { 2, 18, 17 }, // 165 bits per transform + { 3, 16, 18 }, // 165 bits per transform + { 3, 18, 16 }, // 165 bits per transform + { 4, 15, 18 }, // 165 bits per transform + { 4, 18, 15 }, // 165 bits per transform + { 5, 14, 18 }, // 165 bits per transform + { 5, 18, 14 }, // 165 bits per transform + { 6, 13, 18 }, // 165 bits per transform + { 6, 18, 13 }, // 165 bits per transform + { 7, 12, 18 }, // 165 bits per transform + { 7, 18, 12 }, // 165 bits per transform + { 8, 11, 18 }, // 165 bits per transform + { 8, 18, 11 }, // 165 bits per transform + { 9, 10, 18 }, // 165 bits per transform + { 9, 18, 10 }, // 165 bits per transform + { 10, 9, 18 }, // 165 bits per transform + { 10, 18, 9 }, // 165 bits per transform + { 11, 8, 18 }, // 165 bits per transform + { 11, 18, 8 }, // 165 bits per transform + { 12, 7, 18 }, // 165 bits per transform + { 12, 18, 7 }, // 165 bits per transform + { 13, 6, 18 }, // 165 bits per transform + { 13, 18, 6 }, // 165 bits per transform + { 14, 5, 18 }, // 165 bits per transform + { 14, 18, 5 }, // 165 bits per transform + { 15, 4, 18 }, // 165 bits per transform + { 15, 17, 17 }, // 165 bits per transform + { 15, 18, 4 }, // 165 bits per transform + { 16, 3, 18 }, // 165 bits per transform + { 16, 16, 17 }, // 165 bits per transform + { 16, 17, 16 }, // 165 bits per transform + { 16, 18, 3 }, // 165 bits per transform + { 17, 2, 18 }, // 165 bits per transform + { 17, 15, 17 }, // 165 bits per transform + { 17, 16, 16 }, // 165 bits per transform + { 17, 17, 15 }, // 165 bits per transform + { 17, 18, 2 }, // 165 bits per transform + { 18, 2, 17 }, // 165 bits per transform + { 18, 3, 16 }, // 165 bits per transform + { 18, 4, 15 }, // 165 bits per transform + { 18, 5, 14 }, // 165 bits per transform + { 18, 6, 13 }, // 165 bits per transform + { 18, 7, 12 }, // 165 bits per transform + { 18, 8, 11 }, // 165 bits per transform + { 18, 9, 10 }, // 165 bits per transform + { 18, 10, 9 }, // 165 bits per transform + { 18, 11, 8 }, // 165 bits per transform + { 18, 12, 7 }, // 165 bits per transform + { 18, 13, 6 }, // 165 bits per transform + { 18, 14, 5 }, // 165 bits per transform + { 18, 15, 4 }, // 165 bits per transform + { 18, 16, 3 }, // 165 bits per transform + { 18, 17, 2 }, // 165 bits per transform + { 3, 17, 18 }, // 168 bits per transform + { 3, 18, 17 }, // 168 bits per transform + { 4, 16, 18 }, // 168 bits per transform + { 4, 18, 16 }, // 168 bits per transform + { 5, 15, 18 }, // 168 bits per transform + { 5, 18, 15 }, // 168 bits per transform + { 6, 14, 18 }, // 168 bits per transform + { 6, 18, 14 }, // 168 bits per transform + { 7, 13, 18 }, // 168 bits per transform + { 7, 18, 13 }, // 168 bits per transform + { 8, 12, 18 }, // 168 bits per transform + { 8, 18, 12 }, // 168 bits per transform + { 9, 11, 18 }, // 168 bits per transform + { 9, 18, 11 }, // 168 bits per transform + { 10, 10, 18 }, // 168 bits per transform + { 10, 18, 10 }, // 168 bits per transform + { 11, 9, 18 }, // 168 bits per transform + { 11, 18, 9 }, // 168 bits per transform + { 12, 8, 18 }, // 168 bits per transform + { 12, 18, 8 }, // 168 bits per transform + { 13, 7, 18 }, // 168 bits per transform + { 13, 18, 7 }, // 168 bits per transform + { 14, 6, 18 }, // 168 bits per transform + { 14, 18, 6 }, // 168 bits per transform + { 15, 5, 18 }, // 168 bits per transform + { 15, 18, 5 }, // 168 bits per transform + { 16, 4, 18 }, // 168 bits per transform + { 16, 17, 17 }, // 168 bits per transform + { 16, 18, 4 }, // 168 bits per transform + { 17, 3, 18 }, // 168 bits per transform + { 17, 16, 17 }, // 168 bits per transform + { 17, 17, 16 }, // 168 bits per transform + { 17, 18, 3 }, // 168 bits per transform + { 18, 3, 17 }, // 168 bits per transform + { 18, 4, 16 }, // 168 bits per transform + { 18, 5, 15 }, // 168 bits per transform + { 18, 6, 14 }, // 168 bits per transform + { 18, 7, 13 }, // 168 bits per transform + { 18, 8, 12 }, // 168 bits per transform + { 18, 9, 11 }, // 168 bits per transform + { 18, 10, 10 }, // 168 bits per transform + { 18, 11, 9 }, // 168 bits per transform + { 18, 12, 8 }, // 168 bits per transform + { 18, 13, 7 }, // 168 bits per transform + { 18, 14, 6 }, // 168 bits per transform + { 18, 15, 5 }, // 168 bits per transform + { 18, 16, 4 }, // 168 bits per transform + { 18, 17, 3 }, // 168 bits per transform + { 4, 17, 18 }, // 171 bits per transform + { 4, 18, 17 }, // 171 bits per transform + { 5, 16, 18 }, // 171 bits per transform + { 5, 18, 16 }, // 171 bits per transform + { 6, 15, 18 }, // 171 bits per transform + { 6, 18, 15 }, // 171 bits per transform + { 7, 14, 18 }, // 171 bits per transform + { 7, 18, 14 }, // 171 bits per transform + { 8, 13, 18 }, // 171 bits per transform + { 8, 18, 13 }, // 171 bits per transform + { 9, 12, 18 }, // 171 bits per transform + { 9, 18, 12 }, // 171 bits per transform + { 10, 11, 18 }, // 171 bits per transform + { 10, 18, 11 }, // 171 bits per transform + { 11, 10, 18 }, // 171 bits per transform + { 11, 18, 10 }, // 171 bits per transform + { 12, 9, 18 }, // 171 bits per transform + { 12, 18, 9 }, // 171 bits per transform + { 13, 8, 18 }, // 171 bits per transform + { 13, 18, 8 }, // 171 bits per transform + { 14, 7, 18 }, // 171 bits per transform + { 14, 18, 7 }, // 171 bits per transform + { 15, 6, 18 }, // 171 bits per transform + { 15, 18, 6 }, // 171 bits per transform + { 16, 5, 18 }, // 171 bits per transform + { 16, 18, 5 }, // 171 bits per transform + { 17, 4, 18 }, // 171 bits per transform + { 17, 17, 17 }, // 171 bits per transform + { 17, 18, 4 }, // 171 bits per transform + { 18, 4, 17 }, // 171 bits per transform + { 18, 5, 16 }, // 171 bits per transform + { 18, 6, 15 }, // 171 bits per transform + { 18, 7, 14 }, // 171 bits per transform + { 18, 8, 13 }, // 171 bits per transform + { 18, 9, 12 }, // 171 bits per transform + { 18, 10, 11 }, // 171 bits per transform + { 18, 11, 10 }, // 171 bits per transform + { 18, 12, 9 }, // 171 bits per transform + { 18, 13, 8 }, // 171 bits per transform + { 18, 14, 7 }, // 171 bits per transform + { 18, 15, 6 }, // 171 bits per transform + { 18, 16, 5 }, // 171 bits per transform + { 18, 17, 4 }, // 171 bits per transform + { 5, 17, 18 }, // 174 bits per transform + { 5, 18, 17 }, // 174 bits per transform + { 6, 16, 18 }, // 174 bits per transform + { 6, 18, 16 }, // 174 bits per transform + { 7, 15, 18 }, // 174 bits per transform + { 7, 18, 15 }, // 174 bits per transform + { 8, 14, 18 }, // 174 bits per transform + { 8, 18, 14 }, // 174 bits per transform + { 9, 13, 18 }, // 174 bits per transform + { 9, 18, 13 }, // 174 bits per transform + { 10, 12, 18 }, // 174 bits per transform + { 10, 18, 12 }, // 174 bits per transform + { 11, 11, 18 }, // 174 bits per transform + { 11, 18, 11 }, // 174 bits per transform + { 12, 10, 18 }, // 174 bits per transform + { 12, 18, 10 }, // 174 bits per transform + { 13, 9, 18 }, // 174 bits per transform + { 13, 18, 9 }, // 174 bits per transform + { 14, 8, 18 }, // 174 bits per transform + { 14, 18, 8 }, // 174 bits per transform + { 15, 7, 18 }, // 174 bits per transform + { 15, 18, 7 }, // 174 bits per transform + { 16, 6, 18 }, // 174 bits per transform + { 16, 18, 6 }, // 174 bits per transform + { 17, 5, 18 }, // 174 bits per transform + { 17, 18, 5 }, // 174 bits per transform + { 18, 5, 17 }, // 174 bits per transform + { 18, 6, 16 }, // 174 bits per transform + { 18, 7, 15 }, // 174 bits per transform + { 18, 8, 14 }, // 174 bits per transform + { 18, 9, 13 }, // 174 bits per transform + { 18, 10, 12 }, // 174 bits per transform + { 18, 11, 11 }, // 174 bits per transform + { 18, 12, 10 }, // 174 bits per transform + { 18, 13, 9 }, // 174 bits per transform + { 18, 14, 8 }, // 174 bits per transform + { 18, 15, 7 }, // 174 bits per transform + { 18, 16, 6 }, // 174 bits per transform + { 18, 17, 5 }, // 174 bits per transform + { 6, 17, 18 }, // 177 bits per transform + { 6, 18, 17 }, // 177 bits per transform + { 7, 16, 18 }, // 177 bits per transform + { 7, 18, 16 }, // 177 bits per transform + { 8, 15, 18 }, // 177 bits per transform + { 8, 18, 15 }, // 177 bits per transform + { 9, 14, 18 }, // 177 bits per transform + { 9, 18, 14 }, // 177 bits per transform + { 10, 13, 18 }, // 177 bits per transform + { 10, 18, 13 }, // 177 bits per transform + { 11, 12, 18 }, // 177 bits per transform + { 11, 18, 12 }, // 177 bits per transform + { 12, 11, 18 }, // 177 bits per transform + { 12, 18, 11 }, // 177 bits per transform + { 13, 10, 18 }, // 177 bits per transform + { 13, 18, 10 }, // 177 bits per transform + { 14, 9, 18 }, // 177 bits per transform + { 14, 18, 9 }, // 177 bits per transform + { 15, 8, 18 }, // 177 bits per transform + { 15, 18, 8 }, // 177 bits per transform + { 16, 7, 18 }, // 177 bits per transform + { 16, 18, 7 }, // 177 bits per transform + { 17, 6, 18 }, // 177 bits per transform + { 17, 18, 6 }, // 177 bits per transform + { 18, 6, 17 }, // 177 bits per transform + { 18, 7, 16 }, // 177 bits per transform + { 18, 8, 15 }, // 177 bits per transform + { 18, 9, 14 }, // 177 bits per transform + { 18, 10, 13 }, // 177 bits per transform + { 18, 11, 12 }, // 177 bits per transform + { 18, 12, 11 }, // 177 bits per transform + { 18, 13, 10 }, // 177 bits per transform + { 18, 14, 9 }, // 177 bits per transform + { 18, 15, 8 }, // 177 bits per transform + { 18, 16, 7 }, // 177 bits per transform + { 18, 17, 6 }, // 177 bits per transform + { 7, 17, 18 }, // 180 bits per transform + { 7, 18, 17 }, // 180 bits per transform + { 8, 16, 18 }, // 180 bits per transform + { 8, 18, 16 }, // 180 bits per transform + { 9, 15, 18 }, // 180 bits per transform + { 9, 18, 15 }, // 180 bits per transform + { 10, 14, 18 }, // 180 bits per transform + { 10, 18, 14 }, // 180 bits per transform + { 11, 13, 18 }, // 180 bits per transform + { 11, 18, 13 }, // 180 bits per transform + { 12, 12, 18 }, // 180 bits per transform + { 12, 18, 12 }, // 180 bits per transform + { 13, 11, 18 }, // 180 bits per transform + { 13, 18, 11 }, // 180 bits per transform + { 14, 10, 18 }, // 180 bits per transform + { 14, 18, 10 }, // 180 bits per transform + { 15, 9, 18 }, // 180 bits per transform + { 15, 18, 9 }, // 180 bits per transform + { 16, 8, 18 }, // 180 bits per transform + { 16, 18, 8 }, // 180 bits per transform + { 17, 7, 18 }, // 180 bits per transform + { 17, 18, 7 }, // 180 bits per transform + { 18, 7, 17 }, // 180 bits per transform + { 18, 8, 16 }, // 180 bits per transform + { 18, 9, 15 }, // 180 bits per transform + { 18, 10, 14 }, // 180 bits per transform + { 18, 11, 13 }, // 180 bits per transform + { 18, 12, 12 }, // 180 bits per transform + { 18, 13, 11 }, // 180 bits per transform + { 18, 14, 10 }, // 180 bits per transform + { 18, 15, 9 }, // 180 bits per transform + { 18, 16, 8 }, // 180 bits per transform + { 18, 17, 7 }, // 180 bits per transform + { 8, 17, 18 }, // 183 bits per transform + { 8, 18, 17 }, // 183 bits per transform + { 9, 16, 18 }, // 183 bits per transform + { 9, 18, 16 }, // 183 bits per transform + { 10, 15, 18 }, // 183 bits per transform + { 10, 18, 15 }, // 183 bits per transform + { 11, 14, 18 }, // 183 bits per transform + { 11, 18, 14 }, // 183 bits per transform + { 12, 13, 18 }, // 183 bits per transform + { 12, 18, 13 }, // 183 bits per transform + { 13, 12, 18 }, // 183 bits per transform + { 13, 18, 12 }, // 183 bits per transform + { 14, 11, 18 }, // 183 bits per transform + { 14, 18, 11 }, // 183 bits per transform + { 15, 10, 18 }, // 183 bits per transform + { 15, 18, 10 }, // 183 bits per transform + { 16, 9, 18 }, // 183 bits per transform + { 16, 18, 9 }, // 183 bits per transform + { 17, 8, 18 }, // 183 bits per transform + { 17, 18, 8 }, // 183 bits per transform + { 18, 8, 17 }, // 183 bits per transform + { 18, 9, 16 }, // 183 bits per transform + { 18, 10, 15 }, // 183 bits per transform + { 18, 11, 14 }, // 183 bits per transform + { 18, 12, 13 }, // 183 bits per transform + { 18, 13, 12 }, // 183 bits per transform + { 18, 14, 11 }, // 183 bits per transform + { 18, 15, 10 }, // 183 bits per transform + { 18, 16, 9 }, // 183 bits per transform + { 18, 17, 8 }, // 183 bits per transform + { 9, 17, 18 }, // 186 bits per transform + { 9, 18, 17 }, // 186 bits per transform + { 10, 16, 18 }, // 186 bits per transform + { 10, 18, 16 }, // 186 bits per transform + { 11, 15, 18 }, // 186 bits per transform + { 11, 18, 15 }, // 186 bits per transform + { 12, 14, 18 }, // 186 bits per transform + { 12, 18, 14 }, // 186 bits per transform + { 13, 13, 18 }, // 186 bits per transform + { 13, 18, 13 }, // 186 bits per transform + { 14, 12, 18 }, // 186 bits per transform + { 14, 18, 12 }, // 186 bits per transform + { 15, 11, 18 }, // 186 bits per transform + { 15, 18, 11 }, // 186 bits per transform + { 16, 10, 18 }, // 186 bits per transform + { 16, 18, 10 }, // 186 bits per transform + { 17, 9, 18 }, // 186 bits per transform + { 17, 18, 9 }, // 186 bits per transform + { 18, 9, 17 }, // 186 bits per transform + { 18, 10, 16 }, // 186 bits per transform + { 18, 11, 15 }, // 186 bits per transform + { 18, 12, 14 }, // 186 bits per transform + { 18, 13, 13 }, // 186 bits per transform + { 18, 14, 12 }, // 186 bits per transform + { 18, 15, 11 }, // 186 bits per transform + { 18, 16, 10 }, // 186 bits per transform + { 18, 17, 9 }, // 186 bits per transform + { 10, 17, 18 }, // 189 bits per transform + { 10, 18, 17 }, // 189 bits per transform + { 11, 16, 18 }, // 189 bits per transform + { 11, 18, 16 }, // 189 bits per transform + { 12, 15, 18 }, // 189 bits per transform + { 12, 18, 15 }, // 189 bits per transform + { 13, 14, 18 }, // 189 bits per transform + { 13, 18, 14 }, // 189 bits per transform + { 14, 13, 18 }, // 189 bits per transform + { 14, 18, 13 }, // 189 bits per transform + { 15, 12, 18 }, // 189 bits per transform + { 15, 18, 12 }, // 189 bits per transform + { 16, 11, 18 }, // 189 bits per transform + { 16, 18, 11 }, // 189 bits per transform + { 17, 10, 18 }, // 189 bits per transform + { 17, 18, 10 }, // 189 bits per transform + { 18, 10, 17 }, // 189 bits per transform + { 18, 11, 16 }, // 189 bits per transform + { 18, 12, 15 }, // 189 bits per transform + { 18, 13, 14 }, // 189 bits per transform + { 18, 14, 13 }, // 189 bits per transform + { 18, 15, 12 }, // 189 bits per transform + { 18, 16, 11 }, // 189 bits per transform + { 18, 17, 10 }, // 189 bits per transform + { 0, 18, 18 }, // 192 bits per transform + { 11, 17, 18 }, // 192 bits per transform + { 11, 18, 17 }, // 192 bits per transform + { 12, 16, 18 }, // 192 bits per transform + { 12, 18, 16 }, // 192 bits per transform + { 13, 15, 18 }, // 192 bits per transform + { 13, 18, 15 }, // 192 bits per transform + { 14, 14, 18 }, // 192 bits per transform + { 14, 18, 14 }, // 192 bits per transform + { 15, 13, 18 }, // 192 bits per transform + { 15, 18, 13 }, // 192 bits per transform + { 16, 12, 18 }, // 192 bits per transform + { 16, 18, 12 }, // 192 bits per transform + { 17, 11, 18 }, // 192 bits per transform + { 17, 18, 11 }, // 192 bits per transform + { 18, 0, 18 }, // 192 bits per transform + { 18, 11, 17 }, // 192 bits per transform + { 18, 12, 16 }, // 192 bits per transform + { 18, 13, 15 }, // 192 bits per transform + { 18, 14, 14 }, // 192 bits per transform + { 18, 15, 13 }, // 192 bits per transform + { 18, 16, 12 }, // 192 bits per transform + { 18, 17, 11 }, // 192 bits per transform + { 18, 18, 0 }, // 192 bits per transform + { 12, 17, 18 }, // 195 bits per transform + { 12, 18, 17 }, // 195 bits per transform + { 13, 16, 18 }, // 195 bits per transform + { 13, 18, 16 }, // 195 bits per transform + { 14, 15, 18 }, // 195 bits per transform + { 14, 18, 15 }, // 195 bits per transform + { 15, 14, 18 }, // 195 bits per transform + { 15, 18, 14 }, // 195 bits per transform + { 16, 13, 18 }, // 195 bits per transform + { 16, 18, 13 }, // 195 bits per transform + { 17, 12, 18 }, // 195 bits per transform + { 17, 18, 12 }, // 195 bits per transform + { 18, 12, 17 }, // 195 bits per transform + { 18, 13, 16 }, // 195 bits per transform + { 18, 14, 15 }, // 195 bits per transform + { 18, 15, 14 }, // 195 bits per transform + { 18, 16, 13 }, // 195 bits per transform + { 18, 17, 12 }, // 195 bits per transform + { 13, 17, 18 }, // 198 bits per transform + { 13, 18, 17 }, // 198 bits per transform + { 14, 16, 18 }, // 198 bits per transform + { 14, 18, 16 }, // 198 bits per transform + { 15, 15, 18 }, // 198 bits per transform + { 15, 18, 15 }, // 198 bits per transform + { 16, 14, 18 }, // 198 bits per transform + { 16, 18, 14 }, // 198 bits per transform + { 17, 13, 18 }, // 198 bits per transform + { 17, 18, 13 }, // 198 bits per transform + { 18, 13, 17 }, // 198 bits per transform + { 18, 14, 16 }, // 198 bits per transform + { 18, 15, 15 }, // 198 bits per transform + { 18, 16, 14 }, // 198 bits per transform + { 18, 17, 13 }, // 198 bits per transform + { 1, 18, 18 }, // 201 bits per transform + { 14, 17, 18 }, // 201 bits per transform + { 14, 18, 17 }, // 201 bits per transform + { 15, 16, 18 }, // 201 bits per transform + { 15, 18, 16 }, // 201 bits per transform + { 16, 15, 18 }, // 201 bits per transform + { 16, 18, 15 }, // 201 bits per transform + { 17, 14, 18 }, // 201 bits per transform + { 17, 18, 14 }, // 201 bits per transform + { 18, 1, 18 }, // 201 bits per transform + { 18, 14, 17 }, // 201 bits per transform + { 18, 15, 16 }, // 201 bits per transform + { 18, 16, 15 }, // 201 bits per transform + { 18, 17, 14 }, // 201 bits per transform + { 18, 18, 1 }, // 201 bits per transform + { 2, 18, 18 }, // 204 bits per transform + { 15, 17, 18 }, // 204 bits per transform + { 15, 18, 17 }, // 204 bits per transform + { 16, 16, 18 }, // 204 bits per transform + { 16, 18, 16 }, // 204 bits per transform + { 17, 15, 18 }, // 204 bits per transform + { 17, 18, 15 }, // 204 bits per transform + { 18, 2, 18 }, // 204 bits per transform + { 18, 15, 17 }, // 204 bits per transform + { 18, 16, 16 }, // 204 bits per transform + { 18, 17, 15 }, // 204 bits per transform + { 18, 18, 2 }, // 204 bits per transform + { 3, 18, 18 }, // 207 bits per transform + { 16, 17, 18 }, // 207 bits per transform + { 16, 18, 17 }, // 207 bits per transform + { 17, 16, 18 }, // 207 bits per transform + { 17, 18, 16 }, // 207 bits per transform + { 18, 3, 18 }, // 207 bits per transform + { 18, 16, 17 }, // 207 bits per transform + { 18, 17, 16 }, // 207 bits per transform + { 18, 18, 3 }, // 207 bits per transform + { 4, 18, 18 }, // 210 bits per transform + { 17, 17, 18 }, // 210 bits per transform + { 17, 18, 17 }, // 210 bits per transform + { 18, 4, 18 }, // 210 bits per transform + { 18, 17, 17 }, // 210 bits per transform + { 18, 18, 4 }, // 210 bits per transform + { 5, 18, 18 }, // 213 bits per transform + { 18, 5, 18 }, // 213 bits per transform + { 18, 18, 5 }, // 213 bits per transform + { 6, 18, 18 }, // 216 bits per transform + { 18, 6, 18 }, // 216 bits per transform + { 18, 18, 6 }, // 216 bits per transform + { 7, 18, 18 }, // 219 bits per transform + { 18, 7, 18 }, // 219 bits per transform + { 18, 18, 7 }, // 219 bits per transform + { 8, 18, 18 }, // 222 bits per transform + { 18, 8, 18 }, // 222 bits per transform + { 18, 18, 8 }, // 222 bits per transform + { 9, 18, 18 }, // 225 bits per transform + { 18, 9, 18 }, // 225 bits per transform + { 18, 18, 9 }, // 225 bits per transform + { 10, 18, 18 }, // 228 bits per transform + { 18, 10, 18 }, // 228 bits per transform + { 18, 18, 10 }, // 228 bits per transform + { 11, 18, 18 }, // 231 bits per transform + { 18, 11, 18 }, // 231 bits per transform + { 18, 18, 11 }, // 231 bits per transform + { 12, 18, 18 }, // 234 bits per transform + { 18, 12, 18 }, // 234 bits per transform + { 18, 18, 12 }, // 234 bits per transform + { 13, 18, 18 }, // 237 bits per transform + { 18, 13, 18 }, // 237 bits per transform + { 18, 18, 13 }, // 237 bits per transform + { 14, 18, 18 }, // 240 bits per transform + { 18, 14, 18 }, // 240 bits per transform + { 18, 18, 14 }, // 240 bits per transform + { 15, 18, 18 }, // 243 bits per transform + { 18, 15, 18 }, // 243 bits per transform + { 18, 18, 15 }, // 243 bits per transform + { 16, 18, 18 }, // 246 bits per transform + { 18, 16, 18 }, // 246 bits per transform + { 18, 18, 16 }, // 246 bits per transform + { 17, 18, 18 }, // 249 bits per transform + { 18, 17, 18 }, // 249 bits per transform + { 18, 18, 17 }, // 249 bits per transform + { 18, 18, 18 }, // 288 bits per transform + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_compression_stats_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_compression_stats_impl.h new file mode 100644 index 00000000..fb18c5af --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_compression_stats_impl.h @@ -0,0 +1,75 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SJSON_CPP_WRITER) + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/scope_profiler.h" +#include "acl/compression/impl/track_list_context.h" +#include "acl/compression/output_stats.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline void write_compression_stats(const track_list_context& context, const compressed_tracks& tracks, const scope_profiler& compression_time, output_stats& stats) + { + ACL_ASSERT(stats.writer != nullptr, "Attempted to log stats without a writer"); + if (stats.writer == nullptr) + return; + + const uint32_t raw_size = context.reference_list->get_raw_size(); + const uint32_t compressed_size = tracks.get_size(); + const double compression_ratio = double(raw_size) / double(compressed_size); + + sjson::ObjectWriter& writer = *stats.writer; + writer["algorithm_name"] = get_algorithm_name(algorithm_type8::uniformly_sampled); + //writer["algorithm_uid"] = settings.get_hash(); + //writer["clip_name"] = clip.get_name().c_str(); + writer["raw_size"] = raw_size; + writer["compressed_size"] = compressed_size; + writer["compression_ratio"] = compression_ratio; + writer["compression_time"] = compression_time.get_elapsed_seconds(); + writer["duration"] = context.duration; + writer["num_samples"] = context.num_samples; + writer["num_tracks"] = context.num_tracks; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP + +#endif // #if defined(SJSON_CPP_WRITER) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_range_data.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_range_data.h new file mode 100644 index 00000000..c36d9331 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_range_data.h @@ -0,0 +1,347 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/enum_utils.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/core/range_reduction_types.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/math/quat_packing.h" +#include "acl/math/vector4_packing.h" +#include "acl/compression/impl/animated_track_utils.h" +#include "acl/compression/impl/clip_context.h" + +#include "acl/core/impl/compressed_headers.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t get_clip_range_data_size(const clip_context& clip, range_reduction_flags8 range_reduction, rotation_format8 rotation_format) + { + const uint32_t rotation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) ? get_range_reduction_rotation_size(rotation_format) : 0; + const uint32_t translation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) ? k_clip_range_reduction_vector3_range_size : 0; + const uint32_t scale_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) ? k_clip_range_reduction_vector3_range_size : 0; + uint32_t range_data_size = 0; + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + if (!bone_stream.is_rotation_constant) + range_data_size += rotation_size; + + if (!bone_stream.is_translation_constant) + range_data_size += translation_size; + + if (!bone_stream.is_scale_constant) + range_data_size += scale_size; + } + + return range_data_size; + } + + inline uint32_t write_clip_range_data(const clip_context& clip, range_reduction_flags8 range_reduction, uint8_t* range_data, uint32_t range_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + (void)range_reduction; + (void)range_data_size; + + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Groups are sorted per sub-track type. All rotation groups come first followed by translations then scales. + // The last group of each sub-track may or may not have padding. The last group might be less than 4 sub-tracks. + + const uint8_t* range_data_start = range_data; + const uint8_t* range_data_end = add_offset_to_ptr(range_data, range_data_size); + + const rotation_format8 rotation_format = segment.bone_streams[0].rotations.get_rotation_format(); // The same for every track + + // Each range entry is a min/extent at most sizeof(float4f) each, 32 bytes total max per sub-track, 4 sub-tracks per group + rtm::vector4f range_group_min[4]; + rtm::vector4f range_group_extent[4]; + + auto group_filter_action = [range_reduction](animation_track_type8 group_type, uint32_t bone_index) + { + (void)bone_index; + + if (group_type == animation_track_type8::rotation) + return are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations); + else if (group_type == animation_track_type8::translation) + return are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations); + else + return are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales); + }; + + auto group_entry_action = [&clip, &range_group_min, &range_group_extent](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index) + { + if (group_type == animation_track_type8::rotation) + { + const transform_range& bone_range = clip.ranges[bone_index]; + + const rtm::vector4f range_min = bone_range.rotation.get_min(); + const rtm::vector4f range_extent = bone_range.rotation.get_extent(); + + range_group_min[group_size] = range_min; + range_group_extent[group_size] = range_extent; + } + else if (group_type == animation_track_type8::translation) + { + const transform_range& bone_range = clip.ranges[bone_index]; + + const rtm::vector4f range_min = bone_range.translation.get_min(); + const rtm::vector4f range_extent = bone_range.translation.get_extent(); + + range_group_min[group_size] = range_min; + range_group_extent[group_size] = range_extent; + } + else + { + const transform_range& bone_range = clip.ranges[bone_index]; + + const rtm::vector4f range_min = bone_range.scale.get_min(); + const rtm::vector4f range_extent = bone_range.scale.get_extent(); + + range_group_min[group_size] = range_min; + range_group_extent[group_size] = range_extent; + } + }; + + auto group_flush_action = [rotation_format, &range_data, range_data_end, &range_group_min, &range_group_extent](animation_track_type8 group_type, uint32_t group_size) + { + if (group_type == animation_track_type8::rotation) + { + // 2x float3f or float4f, we swizzle into SOA form + RTM_MATRIXF_TRANSPOSE_4X4(range_group_min[0], range_group_min[1], range_group_min[2], range_group_min[3], + range_group_min[0], range_group_min[1], range_group_min[2], range_group_min[3]); + + RTM_MATRIXF_TRANSPOSE_4X4(range_group_extent[0], range_group_extent[1], range_group_extent[2], range_group_extent[3], + range_group_extent[0], range_group_extent[1], range_group_extent[2], range_group_extent[3]); + + if (rotation_format == rotation_format8::quatf_full) + { + // min + std::memcpy(range_data + group_size * sizeof(float) * 0, &range_group_min[0], group_size * sizeof(float)); // xxxx + std::memcpy(range_data + group_size * sizeof(float) * 1, &range_group_min[1], group_size * sizeof(float)); // yyyy + std::memcpy(range_data + group_size * sizeof(float) * 2, &range_group_min[2], group_size * sizeof(float)); // zzzz + std::memcpy(range_data + group_size * sizeof(float) * 3, &range_group_min[3], group_size * sizeof(float)); // wwww + + // extent + std::memcpy(range_data + group_size * sizeof(float) * 4, &range_group_extent[0], group_size * sizeof(float)); // xxxx + std::memcpy(range_data + group_size * sizeof(float) * 5, &range_group_extent[1], group_size * sizeof(float)); // yyyy + std::memcpy(range_data + group_size * sizeof(float) * 6, &range_group_extent[2], group_size * sizeof(float)); // zzzz + std::memcpy(range_data + group_size * sizeof(float) * 7, &range_group_extent[3], group_size * sizeof(float)); // wwww + + range_data += group_size * sizeof(float) * 8; + } + else + { + // min + std::memcpy(range_data + group_size * sizeof(float) * 0, &range_group_min[0], group_size * sizeof(float)); // xxxx + std::memcpy(range_data + group_size * sizeof(float) * 1, &range_group_min[1], group_size * sizeof(float)); // yyyy + std::memcpy(range_data + group_size * sizeof(float) * 2, &range_group_min[2], group_size * sizeof(float)); // zzzz + + // extent + std::memcpy(range_data + group_size * sizeof(float) * 3, &range_group_extent[0], group_size * sizeof(float)); // xxxx + std::memcpy(range_data + group_size * sizeof(float) * 4, &range_group_extent[1], group_size * sizeof(float)); // yyyy + std::memcpy(range_data + group_size * sizeof(float) * 5, &range_group_extent[2], group_size * sizeof(float)); // zzzz + + range_data += group_size * sizeof(float) * 6; + } + } + else + { + // 2x float3f + for (uint32_t group_index = 0; group_index < group_size; ++group_index) + { + std::memcpy(range_data, &range_group_min[group_index], sizeof(rtm::float3f)); + std::memcpy(range_data + sizeof(rtm::float3f), &range_group_extent[group_index], sizeof(rtm::float3f)); + range_data += sizeof(rtm::float3f) * 2; + } + } + + ACL_ASSERT(range_data <= range_data_end, "Invalid range data offset. Wrote too little data."); (void)range_data_end; + }; + + animated_group_writer(segment, output_bone_mapping, num_output_bones, group_filter_action, group_entry_action, group_flush_action); + + ACL_ASSERT(range_data == range_data_end, "Invalid range data offset. Wrote too little data."); + + return safe_static_cast(range_data - range_data_start); + } + + inline uint32_t write_segment_range_data(const segment_context& segment, range_reduction_flags8 range_reduction, uint8_t* range_data, uint32_t range_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + ACL_ASSERT(range_data != nullptr, "'range_data' cannot be null!"); + (void)range_reduction; + (void)range_data_size; + + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Groups are sorted per sub-track type. All rotation groups come first followed by translations then scales. + // The last group of each sub-track may or may not have padding. The last group might be less than 4 sub-tracks. + + // normalized value is between [0.0 .. 1.0] + // value = (normalized value * range extent) + range min + // normalized value = (value - range min) / range extent + + const uint8_t* range_data_start = range_data; + const uint8_t* range_data_end = add_offset_to_ptr(range_data, range_data_size); + + // For rotations contains: min.xxxx, min.yyyy, min.zzzz, extent.xxxx, extent.yyyy, extent.zzzz + // For trans/scale: min0.xyz, extent0.xyz, min1.xyz, extent1.xyz, min2.xyz, extent2.xyz, min3.xyz, extent3.xyz + // To keep decompression simpler, rotations are padded to 4 elements even if the last group is partial + alignas(16) uint8_t range_data_group[6 * 4] = { 0 }; + + auto group_filter_action = [range_reduction](animation_track_type8 group_type, uint32_t bone_index) + { + (void)bone_index; + + if (group_type == animation_track_type8::rotation) + return are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations); + else if (group_type == animation_track_type8::translation) + return are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations); + else + return are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales); + }; + + auto group_entry_action = [&segment, &range_data_group](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (group_type == animation_track_type8::rotation) + { + if (is_constant_bit_rate(bone_stream.rotations.get_bit_rate())) + { + const uint8_t* sample = bone_stream.rotations.get_raw_sample_ptr(0); + + // Swizzle into SOA form + range_data_group[group_size + 0] = sample[1]; // sample.x top 8 bits + range_data_group[group_size + 4] = sample[0]; // sample.x bottom 8 bits + range_data_group[group_size + 8] = sample[3]; // sample.y top 8 bits + range_data_group[group_size + 12] = sample[2]; // sample.y bottom 8 bits + range_data_group[group_size + 16] = sample[5]; // sample.z top 8 bits + range_data_group[group_size + 20] = sample[4]; // sample.z bottom 8 bits + } + else + { + const transform_range& bone_range = segment.ranges[bone_index]; + + const rtm::vector4f range_min = bone_range.rotation.get_min(); + const rtm::vector4f range_extent = bone_range.rotation.get_extent(); + + // Swizzle into SOA form + alignas(16) uint8_t range_min_buffer[16]; + alignas(16) uint8_t range_extent_buffer[16]; + pack_vector3_u24_unsafe(range_min, range_min_buffer); + pack_vector3_u24_unsafe(range_extent, range_extent_buffer); + + range_data_group[group_size + 0] = range_min_buffer[0]; + range_data_group[group_size + 4] = range_min_buffer[1]; + range_data_group[group_size + 8] = range_min_buffer[2]; + + range_data_group[group_size + 12] = range_extent_buffer[0]; + range_data_group[group_size + 16] = range_extent_buffer[1]; + range_data_group[group_size + 20] = range_extent_buffer[2]; + } + } + else if (group_type == animation_track_type8::translation) + { + if (is_constant_bit_rate(bone_stream.translations.get_bit_rate())) + { + const uint8_t* sample = bone_stream.translations.get_raw_sample_ptr(0); + uint8_t* sub_track_range_data = &range_data_group[group_size * 6]; + std::memcpy(sub_track_range_data, sample, 6); + } + else + { + const transform_range& bone_range = segment.ranges[bone_index]; + + const rtm::vector4f range_min = bone_range.translation.get_min(); + const rtm::vector4f range_extent = bone_range.translation.get_extent(); + + uint8_t* sub_track_range_data = &range_data_group[group_size * 6]; + pack_vector3_u24_unsafe(range_min, sub_track_range_data); + pack_vector3_u24_unsafe(range_extent, sub_track_range_data + 3); + } + } + else + { + if (is_constant_bit_rate(bone_stream.scales.get_bit_rate())) + { + const uint8_t* sample = bone_stream.scales.get_raw_sample_ptr(0); + uint8_t* sub_track_range_data = &range_data_group[group_size * 6]; + std::memcpy(sub_track_range_data, sample, 6); + } + else + { + const transform_range& bone_range = segment.ranges[bone_index]; + + const rtm::vector4f range_min = bone_range.scale.get_min(); + const rtm::vector4f range_extent = bone_range.scale.get_extent(); + + uint8_t* sub_track_range_data = &range_data_group[group_size * 6]; + pack_vector3_u24_unsafe(range_min, sub_track_range_data); + pack_vector3_u24_unsafe(range_extent, sub_track_range_data + 3); + } + } + }; + + auto group_flush_action = [&range_data, range_data_end, &range_data_group](animation_track_type8 group_type, uint32_t group_size) + { + const size_t copy_size = group_type == animation_track_type8::rotation ? 4 : group_size; + std::memcpy(range_data, &range_data_group[0], copy_size * 6); + range_data += copy_size * 6; + + // Zero out the temporary buffer for the final group to not contain partial garbage + std::memset(&range_data_group[0], 0, sizeof(range_data_group)); + + ACL_ASSERT(range_data <= range_data_end, "Invalid range data offset. Wrote too little data."); (void)range_data_end; + }; + + animated_group_writer(segment, output_bone_mapping, num_output_bones, group_filter_action, group_entry_action, group_flush_action); + + ACL_ASSERT(range_data == range_data_end, "Invalid range data offset. Wrote too little data."); + + return safe_static_cast(range_data - range_data_start); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_segment_data.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_segment_data.h new file mode 100644 index 00000000..02ec16ed --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_segment_data.h @@ -0,0 +1,146 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/bitset.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/impl/compressed_headers.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/impl/clip_context.h" +#include "acl/compression/impl/segment_context.h" +#include "acl/compression/impl/write_range_data.h" +#include "acl/compression/impl/write_stream_data.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t write_segment_start_indices(const clip_context& clip, uint32_t* segment_start_indices) + { + uint32_t size_written = 0; + + const uint32_t num_segments = clip.num_segments; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const segment_context& segment = clip.segments[segment_index]; + segment_start_indices[segment_index] = segment.clip_sample_offset; + size_written += sizeof(uint32_t); + } + + // Write our sentinel value + segment_start_indices[clip.num_segments] = 0xFFFFFFFFU; + size_written += sizeof(uint32_t); + + return size_written; + } + + inline uint32_t write_segment_headers(const clip_context& clip, const compression_settings& settings, segment_header* segment_headers, uint32_t segment_data_start_offset) + { + uint32_t size_written = 0; + + const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip, settings.rotation_format, settings.translation_format, settings.scale_format); + + uint32_t segment_data_offset = segment_data_start_offset; + for (uint32_t segment_index = 0; segment_index < clip.num_segments; ++segment_index) + { + const segment_context& segment = clip.segments[segment_index]; + segment_header& header = segment_headers[segment_index]; + + ACL_ASSERT(header.animated_pose_bit_size == 0, "Buffer overrun detected"); + + header.animated_pose_bit_size = segment.animated_pose_bit_size; + header.animated_rotation_bit_size = segment.animated_rotation_bit_size; + header.animated_translation_bit_size = segment.animated_translation_bit_size; + header.segment_data = segment_data_offset; + + segment_data_offset = align_to(segment_data_offset + format_per_track_data_size, 2); // Aligned to 2 bytes + segment_data_offset = align_to(segment_data_offset + segment.range_data_size, 4); // Aligned to 4 bytes + segment_data_offset = segment_data_offset + segment.animated_data_size; + size_written += sizeof(segment_header); + + ACL_ASSERT((segment_data_offset - (uint32_t)header.segment_data) == segment.segment_data_size, "Unexpected segment size"); + } + + return size_written; + } + + inline uint32_t write_segment_data(const clip_context& clip, const compression_settings& settings, range_reduction_flags8 range_reduction, segment_header* segment_headers, transform_tracks_header& header, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip, settings.rotation_format, settings.translation_format, settings.scale_format); + + uint32_t size_written = 0; + + const uint32_t num_segments = clip.num_segments; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const segment_context& segment = clip.segments[segment_index]; + segment_header& segment_header_ = segment_headers[segment_index]; + + uint8_t* format_per_track_data = nullptr; + uint8_t* range_data = nullptr; + uint8_t* animated_data = nullptr; + header.get_segment_data(segment_header_, format_per_track_data, range_data, animated_data); + + ACL_ASSERT(format_per_track_data[0] == 0, "Buffer overrun detected"); + ACL_ASSERT(range_data[0] == 0, "Buffer overrun detected"); + ACL_ASSERT(animated_data[0] == 0, "Buffer overrun detected"); + + if (format_per_track_data_size != 0) + { + const uint32_t size = write_format_per_track_data(segment, format_per_track_data, format_per_track_data_size, output_bone_mapping, num_output_bones); + ACL_ASSERT(size == format_per_track_data_size, "Unexpected format per track data size"); (void)size; + } + + if (segment.range_data_size != 0) + { + const uint32_t size = write_segment_range_data(segment, range_reduction, range_data, segment.range_data_size, output_bone_mapping, num_output_bones); + ACL_ASSERT(size == segment.range_data_size, "Unexpected range data size"); (void)size; + } + + if (segment.animated_data_size != 0) + { + const uint32_t size = write_animated_track_data(segment, animated_data, segment.animated_data_size, output_bone_mapping, num_output_bones); + ACL_ASSERT(size == segment.animated_data_size, "Unexpected animated data size"); (void)size; + } + + size_written += segment.segment_data_size; + } + + return size_written; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_stats.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_stats.h new file mode 100644 index 00000000..991f3379 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_stats.h @@ -0,0 +1,562 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SJSON_CPP_WRITER) + +#include "acl/version.h" +#include "acl/core/track_formats.h" +#include "acl/core/utils.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/transform_error_metrics.h" +#include "acl/compression/track_error.h" +#include "acl/compression/impl/clip_context.h" + +#include +#include +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline void write_summary_segment_stats(const segment_context& segment, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, sjson::ObjectWriter& writer) + { + writer["segment_index"] = segment.segment_index; + writer["num_samples"] = segment.num_samples; + + const uint32_t format_per_track_data_size = get_format_per_track_data_size(*segment.clip, rotation_format, translation_format, scale_format); + + uint32_t segment_size = 0; + segment_size += format_per_track_data_size; // Format per track data + segment_size = align_to(segment_size, 2); // Align range data + segment_size += segment.range_data_size; // Range data + segment_size = align_to(segment_size, 4); // Align animated data + segment_size += segment.animated_data_size; // Animated track data + + writer["segment_size"] = segment_size; + writer["animated_frame_size"] = double(segment.animated_data_size) / double(segment.num_samples); + } + + inline void write_detailed_segment_stats(const segment_context& segment, sjson::ObjectWriter& writer) + { + uint32_t bit_rate_counts[k_num_bit_rates] = { 0 }; + + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + const uint8_t rotation_bit_rate = bone_stream.rotations.get_bit_rate(); + if (rotation_bit_rate != k_invalid_bit_rate) + bit_rate_counts[rotation_bit_rate]++; + + const uint8_t translation_bit_rate = bone_stream.translations.get_bit_rate(); + if (translation_bit_rate != k_invalid_bit_rate) + bit_rate_counts[translation_bit_rate]++; + + const uint8_t scale_bit_rate = bone_stream.scales.get_bit_rate(); + if (scale_bit_rate != k_invalid_bit_rate) + bit_rate_counts[scale_bit_rate]++; + } + + writer["bit_rate_counts"] = [&](sjson::ArrayWriter& bitrate_writer) + { + for (uint32_t bit_rate = 0; bit_rate < k_num_bit_rates; ++bit_rate) + bitrate_writer.push(bit_rate_counts[bit_rate]); + }; + + // We assume that we always interpolate between 2 poses + const uint32_t animated_pose_byte_size = align_to(segment.animated_pose_bit_size * 2, 8) / 8; + constexpr uint32_t k_cache_line_byte_size = 64; + + const uint32_t num_segment_header_cache_lines = align_to(segment.total_header_size, k_cache_line_byte_size) / k_cache_line_byte_size; + const uint32_t num_animated_pose_cache_lines = align_to(animated_pose_byte_size, k_cache_line_byte_size) / k_cache_line_byte_size; + writer["decomp_touched_bytes"] = segment.clip->decomp_touched_bytes + segment.total_header_size + animated_pose_byte_size; + writer["decomp_touched_cache_lines"] = segment.clip->decomp_touched_cache_lines + num_segment_header_cache_lines + num_animated_pose_cache_lines; + } + + inline void write_exhaustive_segment_stats(iallocator& allocator, const segment_context& segment, const clip_context& raw_clip_context, const clip_context& additive_base_clip_context, const compression_settings& settings, const track_array_qvvf& track_list, sjson::ObjectWriter& writer) + { + const uint32_t num_bones = raw_clip_context.num_bones; + const bool has_scale = segment_context_has_scale(segment); + + ACL_ASSERT(!settings.error_metric->needs_conversion(has_scale), "Error metric conversion not supported"); + + const auto local_to_object_space_impl = std::mem_fn(has_scale ? &itransform_error_metric::local_to_object_space : &itransform_error_metric::local_to_object_space_no_scale); + const auto calculate_error_impl = std::mem_fn(has_scale ? &itransform_error_metric::calculate_error : &itransform_error_metric::calculate_error_no_scale); + const auto apply_additive_to_base_impl = std::mem_fn(has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale); + + rtm::qvvf* raw_local_pose = allocate_type_array(allocator, num_bones); + rtm::qvvf* base_local_pose = allocate_type_array(allocator, num_bones); + rtm::qvvf* lossy_local_pose = allocate_type_array(allocator, num_bones); + + rtm::qvvf* raw_object_pose = allocate_type_array(allocator, num_bones); + rtm::qvvf* lossy_object_pose = allocate_type_array(allocator, num_bones); + + uint32_t* parent_transform_indices = allocate_type_array(allocator, num_bones); + uint32_t* self_transform_indices = allocate_type_array(allocator, num_bones); + + for (uint32_t transform_index = 0; transform_index < num_bones; ++transform_index) + { + const track_qvvf& track = track_list[transform_index]; + const track_desc_transformf& desc = track.get_description(); + + parent_transform_indices[transform_index] = desc.parent_index; + self_transform_indices[transform_index] = transform_index; + } + + const float sample_rate = raw_clip_context.sample_rate; + const float ref_duration = calculate_duration(raw_clip_context.num_samples, sample_rate); + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_raw; + apply_additive_to_base_args_raw.dirty_transform_indices = self_transform_indices; + apply_additive_to_base_args_raw.num_dirty_transforms = num_bones; + apply_additive_to_base_args_raw.local_transforms = raw_local_pose; + apply_additive_to_base_args_raw.base_transforms = base_local_pose; + apply_additive_to_base_args_raw.num_transforms = num_bones; + + itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy = apply_additive_to_base_args_raw; + apply_additive_to_base_args_lossy.local_transforms = lossy_local_pose; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw; + local_to_object_space_args_raw.dirty_transform_indices = self_transform_indices; + local_to_object_space_args_raw.num_dirty_transforms = num_bones; + local_to_object_space_args_raw.parent_transform_indices = parent_transform_indices; + local_to_object_space_args_raw.local_transforms = raw_local_pose; + local_to_object_space_args_raw.num_transforms = num_bones; + + itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw; + local_to_object_space_args_lossy.local_transforms = lossy_local_pose; + + track_error worst_bone_error; + + writer["error_per_frame_and_bone"] = [&](sjson::ArrayWriter& frames_writer) + { + for (uint32_t sample_index = 0; sample_index < segment.num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(segment.clip_sample_offset + sample_index) / sample_rate, ref_duration); + + sample_streams(raw_clip_context.segments[0].bone_streams, num_bones, sample_time, raw_local_pose); + sample_streams(segment.bone_streams, num_bones, sample_time, lossy_local_pose); + + if (raw_clip_context.has_additive_base) + { + const float normalized_sample_time = additive_base_clip_context.num_samples > 1 ? (sample_time / ref_duration) : 0.0F; + const float additive_sample_time = additive_base_clip_context.num_samples > 1 ? (normalized_sample_time * additive_base_clip_context.duration) : 0.0F; + sample_streams(additive_base_clip_context.segments[0].bone_streams, num_bones, additive_sample_time, base_local_pose); + + apply_additive_to_base_impl(settings.error_metric, apply_additive_to_base_args_raw, raw_local_pose); + apply_additive_to_base_impl(settings.error_metric, apply_additive_to_base_args_lossy, lossy_local_pose); + } + + local_to_object_space_impl(settings.error_metric, local_to_object_space_args_raw, raw_object_pose); + local_to_object_space_impl(settings.error_metric, local_to_object_space_args_lossy, lossy_object_pose); + + frames_writer.push_newline(); + frames_writer.push([&](sjson::ArrayWriter& frame_writer) + { + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + const track_qvvf& track = track_list[bone_index]; + const track_desc_transformf& desc = track.get_description(); + + itransform_error_metric::calculate_error_args calculate_error_args; + calculate_error_args.transform0 = raw_object_pose + bone_index; + calculate_error_args.transform1 = lossy_object_pose + bone_index; + calculate_error_args.construct_sphere_shell(desc.shell_distance); + + const float error = rtm::scalar_cast(calculate_error_impl(settings.error_metric, calculate_error_args)); + + frame_writer.push(error); + + if (error > worst_bone_error.error) + { + worst_bone_error.error = error; + worst_bone_error.index = bone_index; + worst_bone_error.sample_time = sample_time; + } + } + }); + } + }; + + writer["max_error"] = worst_bone_error.error; + writer["worst_bone"] = worst_bone_error.index; + writer["worst_time"] = worst_bone_error.sample_time; + + deallocate_type_array(allocator, raw_local_pose, num_bones); + deallocate_type_array(allocator, base_local_pose, num_bones); + deallocate_type_array(allocator, lossy_local_pose, num_bones); + + deallocate_type_array(allocator, raw_object_pose, num_bones); + deallocate_type_array(allocator, lossy_object_pose, num_bones); + + deallocate_type_array(allocator, parent_transform_indices, num_bones); + deallocate_type_array(allocator, self_transform_indices, num_bones); + } + + inline uint32_t calculate_clip_metadata_common_size(const clip_context& clip, const compressed_tracks& compressed_clip) + { + const uint32_t segment_header_size = compressed_clip.has_database() ? sizeof(segment_tier0_header) : sizeof(segment_header); + + uint32_t result = 0; + + // Segment start indices and headers + result += clip.num_segments > 1 ? (uint32_t(sizeof(uint32_t)) * (clip.num_segments + 1)) : 0; + result += segment_header_size * clip.num_segments; + + // Default/constant track bit sets + const bitset_description bitset_desc = bitset_description::make_from_num_bits(compressed_clip.get_num_tracks()); + result += bitset_desc.get_num_bytes(); + result += bitset_desc.get_num_bytes(); + + return result; + } + + inline uint32_t calculate_segment_metadata_common_size(const clip_context& clip, const compression_settings& settings) + { + const bool is_rotation_variable = is_rotation_format_variable(settings.rotation_format); + const bool is_translation_variable = is_vector_format_variable(settings.translation_format); + const bool is_scale_variable = is_vector_format_variable(settings.scale_format); + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t result = 0; + + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + if (bone_stream.is_stripped_from_output()) + continue; + + // Format per track + if (!bone_stream.is_rotation_constant && is_rotation_variable) + result++; + + if (!bone_stream.is_translation_constant && is_translation_variable) + result++; + + if (!bone_stream.is_scale_constant && is_scale_variable) + result++; + } + + return result * clip.num_segments; + } + + inline uint32_t calculate_segment_metadata_rotation_size(const clip_context& clip, range_reduction_flags8 range_reduction) + { + if (clip.num_segments == 1) + return 0; + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t result = 0; + + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + if (bone_stream.is_stripped_from_output()) + continue; + + // Range data + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) && !bone_stream.is_rotation_constant) + { + const uint32_t num_components = bone_stream.rotations.get_rotation_format() == rotation_format8::quatf_full ? 8 : 6; + result += num_components * k_segment_range_reduction_num_bytes_per_component; + } + } + + return result * clip.num_segments; + } + + inline uint32_t calculate_segment_metadata_translation_size(const clip_context& clip, range_reduction_flags8 range_reduction) + { + if (clip.num_segments == 1) + return 0; + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t result = 0; + + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + if (bone_stream.is_stripped_from_output()) + continue; + + // Range data + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) && !bone_stream.is_translation_constant) + result += k_segment_range_reduction_num_bytes_per_component * 6; + } + + return result * clip.num_segments; + } + + inline uint32_t calculate_segment_metadata_scale_size(const clip_context& clip, range_reduction_flags8 range_reduction) + { + if (clip.num_segments == 1) + return 0; + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t result = 0; + + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + if (bone_stream.is_stripped_from_output()) + continue; + + // Range data + if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) && !bone_stream.is_scale_constant) + result += k_segment_range_reduction_num_bytes_per_component * 6; + } + + return result * clip.num_segments; + } + + inline uint32_t calculate_segment_animated_rotation_size(const clip_context& clip) + { + uint32_t result = 0; + + for (const segment_context& segment : clip.segment_iterator()) + result += align_to(segment.animated_rotation_bit_size * segment.num_samples, 8) / 8; // Convert bits to bytes; + + return result; + } + + inline uint32_t calculate_segment_animated_translation_size(const clip_context& clip) + { + uint32_t result = 0; + + for (const segment_context& segment : clip.segment_iterator()) + result += align_to(segment.animated_translation_bit_size * segment.num_samples, 8) / 8; // Convert bits to bytes; + + return result; + } + + inline uint32_t calculate_segment_animated_scale_size(const clip_context& clip) + { + uint32_t result = 0; + + for (const segment_context& segment : clip.segment_iterator()) + result += align_to(segment.animated_scale_bit_size * segment.num_samples, 8) / 8; // Convert bits to bytes; + + return result; + } + + inline uint32_t calculate_segment_animated_data_size(const clip_context& clip) + { + uint32_t result = 0; + + for (const segment_context& segment : clip.segment_iterator()) + result += segment.animated_data_size; + + return result; + } + + inline void write_stats(iallocator& allocator, const track_array_qvvf& track_list, const clip_context& clip, + const compressed_tracks& compressed_clip, const compression_settings& settings, const compression_segmenting_settings& segmenting_settings, + range_reduction_flags8 range_reduction, const clip_context& raw_clip, + const clip_context& additive_base_clip_context, const scope_profiler& compression_time, + output_stats& stats) + { + ACL_ASSERT(stats.writer != nullptr, "Attempted to log stats without a writer"); + if (stats.writer == nullptr) + return; + + const uint32_t raw_size = track_list.get_raw_size(); + const uint32_t compressed_size = compressed_clip.get_size(); + const double compression_ratio = double(raw_size) / double(compressed_size); + + sjson::ObjectWriter& writer = *stats.writer; + writer["algorithm_name"] = get_algorithm_name(algorithm_type8::uniformly_sampled); + writer["algorithm_uid"] = settings.get_hash(); + writer["clip_name"] = track_list.get_name().c_str(); + writer["raw_size"] = raw_size; + writer["compressed_size"] = compressed_size; + writer["compression_ratio"] = compression_ratio; + writer["compression_time"] = compression_time.get_elapsed_seconds(); + writer["duration"] = track_list.get_duration(); + writer["num_samples"] = track_list.get_num_samples_per_track(); + writer["num_bones"] = compressed_clip.get_num_tracks(); + writer["rotation_format"] = get_rotation_format_name(settings.rotation_format); + writer["translation_format"] = get_vector_format_name(settings.translation_format); + writer["scale_format"] = get_vector_format_name(settings.scale_format); + writer["has_scale"] = clip.has_scale; + writer["error_metric"] = settings.error_metric->get_name(); + + if (are_all_enum_flags_set(stats.logging, stat_logging::detailed) || are_all_enum_flags_set(stats.logging, stat_logging::exhaustive)) + { + uint32_t num_default_rotation_tracks = 0; + uint32_t num_default_translation_tracks = 0; + uint32_t num_default_scale_tracks = 0; + uint32_t num_constant_rotation_tracks = 0; + uint32_t num_constant_translation_tracks = 0; + uint32_t num_constant_scale_tracks = 0; + uint32_t num_animated_rotation_tracks = 0; + uint32_t num_animated_translation_tracks = 0; + uint32_t num_animated_scale_tracks = 0; + + for (const transform_streams& bone_stream : clip.segments[0].bone_iterator()) + { + if (bone_stream.is_stripped_from_output()) + continue; + + if (bone_stream.is_rotation_default) + num_default_rotation_tracks++; + else if (bone_stream.is_rotation_constant) + num_constant_rotation_tracks++; + else + num_animated_rotation_tracks++; + + if (bone_stream.is_translation_default) + num_default_translation_tracks++; + else if (bone_stream.is_translation_constant) + num_constant_translation_tracks++; + else + num_animated_translation_tracks++; + + if (bone_stream.is_scale_default) + num_default_scale_tracks++; + else if (bone_stream.is_scale_constant) + num_constant_scale_tracks++; + else + num_animated_scale_tracks++; + } + + const uint32_t num_default_tracks = num_default_rotation_tracks + num_default_translation_tracks + num_default_scale_tracks; + const uint32_t num_constant_tracks = num_constant_rotation_tracks + num_constant_translation_tracks + num_constant_scale_tracks; + const uint32_t num_animated_tracks = num_animated_rotation_tracks + num_animated_translation_tracks + num_animated_scale_tracks; + + writer["num_default_rotation_tracks"] = num_default_rotation_tracks; + writer["num_default_translation_tracks"] = num_default_translation_tracks; + writer["num_default_scale_tracks"] = num_default_scale_tracks; + + writer["num_constant_rotation_tracks"] = num_constant_rotation_tracks; + writer["num_constant_translation_tracks"] = num_constant_translation_tracks; + writer["num_constant_scale_tracks"] = num_constant_scale_tracks; + + writer["num_animated_rotation_tracks"] = num_animated_rotation_tracks; + writer["num_animated_translation_tracks"] = num_animated_translation_tracks; + writer["num_animated_scale_tracks"] = num_animated_scale_tracks; + + writer["num_default_tracks"] = num_default_tracks; + writer["num_constant_tracks"] = num_constant_tracks; + writer["num_animated_tracks"] = num_animated_tracks; + + const uint32_t clip_header_size = sizeof(raw_buffer_header) + sizeof(tracks_header) + sizeof(transform_tracks_header); + const uint32_t clip_metadata_common_size = calculate_clip_metadata_common_size(clip, compressed_clip); + const uint32_t clip_metadata_rotation_constant_size = get_packed_rotation_size(get_highest_variant_precision(get_rotation_variant(settings.rotation_format))) * num_constant_rotation_tracks; + const uint32_t clip_metadata_translation_constant_size = get_packed_vector_size(vector_format8::vector3f_full) * num_constant_translation_tracks; + const uint32_t clip_metadata_scale_constant_size = get_packed_vector_size(vector_format8::vector3f_full) * num_constant_scale_tracks; + writer["clip_header_size"] = clip_header_size; + writer["clip_metadata_common_size"] = clip_metadata_common_size; + writer["clip_metadata_rotation_constant_size"] = clip_metadata_rotation_constant_size; + writer["clip_metadata_translation_constant_size"] = clip_metadata_translation_constant_size; + writer["clip_metadata_scale_constant_size"] = clip_metadata_scale_constant_size; + + const uint32_t range_rotation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) ? get_range_reduction_rotation_size(settings.rotation_format) : 0; + const uint32_t range_translation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) ? k_clip_range_reduction_vector3_range_size : 0; + const uint32_t range_scale_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) ? k_clip_range_reduction_vector3_range_size : 0; + const uint32_t clip_metadata_rotation_animated_size = range_rotation_size * num_animated_rotation_tracks; + const uint32_t clip_metadata_translation_animated_size = range_translation_size * num_animated_translation_tracks; + const uint32_t clip_metadata_scale_animated_size = range_scale_size * num_animated_scale_tracks; + writer["clip_metadata_rotation_animated_size"] = clip_metadata_rotation_animated_size; + writer["clip_metadata_translation_animated_size"] = clip_metadata_translation_animated_size; + writer["clip_metadata_scale_animated_size"] = clip_metadata_scale_animated_size; + + const uint32_t segment_metadata_common_size = calculate_segment_metadata_common_size(clip, settings); + const uint32_t segment_metadata_rotation_size = calculate_segment_metadata_rotation_size(clip, range_reduction); + const uint32_t segment_metadata_translation_size = calculate_segment_metadata_translation_size(clip, range_reduction); + const uint32_t segment_metadata_scale_size = calculate_segment_metadata_scale_size(clip, range_reduction); + const uint32_t segment_animated_rotation_size = calculate_segment_animated_rotation_size(clip); + const uint32_t segment_animated_translation_size = calculate_segment_animated_translation_size(clip); + const uint32_t segment_animated_scale_size = calculate_segment_animated_scale_size(clip); + writer["segment_metadata_common_size"] = segment_metadata_common_size; + writer["segment_metadata_rotation_size"] = segment_metadata_rotation_size; + writer["segment_metadata_translation_size"] = segment_metadata_translation_size; + writer["segment_metadata_scale_size"] = segment_metadata_scale_size; + writer["segment_animated_rotation_size"] = segment_animated_rotation_size; + writer["segment_animated_translation_size"] = segment_animated_translation_size; + writer["segment_animated_scale_size"] = segment_animated_scale_size; + + uint32_t known_data_size = 0; + known_data_size += clip_header_size; + known_data_size += clip_metadata_common_size; + known_data_size += clip_metadata_rotation_constant_size; + known_data_size += clip_metadata_translation_constant_size; + known_data_size += clip_metadata_scale_constant_size; + known_data_size += clip_metadata_rotation_animated_size; + known_data_size += clip_metadata_translation_animated_size; + known_data_size += clip_metadata_scale_animated_size; + known_data_size += segment_metadata_common_size; + known_data_size += segment_metadata_rotation_size; + known_data_size += segment_metadata_translation_size; + known_data_size += segment_metadata_scale_size; + const uint32_t segment_animated_data_size = calculate_segment_animated_data_size(clip); + known_data_size += segment_animated_data_size; + + const int32_t unknown_overhead_size = compressed_size - known_data_size; + ACL_ASSERT(unknown_overhead_size >= 0, "Overhead size should be positive"); + writer["unknown_overhead_size"] = unknown_overhead_size; + } + + writer["segmenting"] = [&](sjson::ObjectWriter& segmenting_writer) + { + segmenting_writer["num_segments"] = clip.num_segments; + segmenting_writer["ideal_num_samples"] = segmenting_settings.ideal_num_samples; + segmenting_writer["max_num_samples"] = segmenting_settings.max_num_samples; + }; + + writer["segments"] = [&](sjson::ArrayWriter& segments_writer) + { + for (const segment_context& segment : clip.segment_iterator()) + { + segments_writer.push([&](sjson::ObjectWriter& segment_writer) + { + write_summary_segment_stats(segment, settings.rotation_format, settings.translation_format, settings.scale_format, segment_writer); + + if (are_all_enum_flags_set(stats.logging, stat_logging::detailed)) + write_detailed_segment_stats(segment, segment_writer); + + if (are_all_enum_flags_set(stats.logging, stat_logging::exhaustive)) + write_exhaustive_segment_stats(allocator, segment, raw_clip, additive_base_clip_context, settings, track_list, segment_writer); + }); + } + }; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP + +#endif // #if defined(SJSON_CPP_WRITER) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_stream_data.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_stream_data.h new file mode 100644 index 00000000..615369c6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_stream_data.h @@ -0,0 +1,512 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/error.h" +#include "acl/core/track_formats.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/compression/impl/animated_track_utils.h" +#include "acl/compression/impl/clip_context.h" + +#include "acl/core/impl/compressed_headers.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t get_constant_data_size(const clip_context& clip) + { + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t constant_data_size = 0; + + for (uint32_t bone_index = 0; bone_index < clip.num_bones; ++bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (bone_stream.output_index == k_invalid_track_index) + continue; // Stripped + + if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant) + constant_data_size += bone_stream.rotations.get_packed_sample_size(); + + if (!bone_stream.is_translation_default && bone_stream.is_translation_constant) + constant_data_size += bone_stream.translations.get_packed_sample_size(); + + if (clip.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant) + constant_data_size += bone_stream.scales.get_packed_sample_size(); + } + + return constant_data_size; + } + + inline void get_num_constant_samples(const clip_context& clip, uint32_t& out_num_constant_rotation_samples, uint32_t& out_num_constant_translation_samples, uint32_t& out_num_constant_scale_samples) + { + uint32_t num_constant_rotation_samples = 0; + uint32_t num_constant_translation_samples = 0; + uint32_t num_constant_scale_samples = 0; + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + for (uint32_t bone_index = 0; bone_index < clip.num_bones; ++bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (bone_stream.output_index == k_invalid_track_index) + continue; // Stripped + + if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant) + num_constant_rotation_samples++; + + if (!bone_stream.is_translation_default && bone_stream.is_translation_constant) + num_constant_translation_samples++; + + if (clip.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant) + num_constant_scale_samples++; + } + + out_num_constant_rotation_samples = num_constant_rotation_samples; + out_num_constant_translation_samples = num_constant_translation_samples; + out_num_constant_scale_samples = num_constant_scale_samples; + } + + inline void get_animated_variable_bit_rate_data_size(const track_stream& track_stream, uint32_t& out_num_animated_pose_bits) + { + const uint8_t bit_rate = track_stream.get_bit_rate(); + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3; // 3 components + out_num_animated_pose_bits += num_bits_at_bit_rate; + } + + inline void calculate_animated_data_size(const track_stream& track_stream, uint32_t& num_animated_pose_bits) + { + if (track_stream.is_bit_rate_variable()) + { + get_animated_variable_bit_rate_data_size(track_stream, num_animated_pose_bits); + } + else + { + const uint32_t sample_size = track_stream.get_packed_sample_size(); + num_animated_pose_bits += sample_size * 8; + } + } + + inline void calculate_animated_data_size(clip_context& clip, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + for (segment_context& segment : clip.segment_iterator()) + { + uint32_t num_animated_pose_rotation_data_bits = 0; + uint32_t num_animated_pose_translation_data_bits = 0; + uint32_t num_animated_pose_scale_data_bits = 0; + + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + if (!bone_stream.is_rotation_constant) + calculate_animated_data_size(bone_stream.rotations, num_animated_pose_rotation_data_bits); + + if (!bone_stream.is_translation_constant) + calculate_animated_data_size(bone_stream.translations, num_animated_pose_translation_data_bits); + + if (!bone_stream.is_scale_constant) + calculate_animated_data_size(bone_stream.scales, num_animated_pose_scale_data_bits); + } + + const uint32_t num_animated_pose_bits = num_animated_pose_rotation_data_bits + num_animated_pose_translation_data_bits + num_animated_pose_scale_data_bits; + const uint32_t num_animated_data_bits = num_animated_pose_bits * segment.num_samples; + + segment.animated_rotation_bit_size = num_animated_pose_rotation_data_bits; + segment.animated_translation_bit_size = num_animated_pose_translation_data_bits; + segment.animated_scale_bit_size = num_animated_pose_scale_data_bits; + segment.animated_data_size = align_to(num_animated_data_bits, 8) / 8; + segment.animated_pose_bit_size = num_animated_pose_bits; + } + } + + inline uint32_t get_format_per_track_data_size(const clip_context& clip, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, uint32_t* out_num_animated_variable_sub_tracks_padded = nullptr) + { + const bool is_rotation_variable = is_rotation_format_variable(rotation_format); + const bool is_translation_variable = is_vector_format_variable(translation_format); + const bool is_scale_variable = is_vector_format_variable(scale_format); + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t format_per_track_data_size = 0; + uint32_t num_animated_variable_rotations = 0; + + for (const transform_streams& bone_stream : segment.const_bone_iterator()) + { + if (bone_stream.is_stripped_from_output()) + continue; + + if (!bone_stream.is_rotation_constant && is_rotation_variable) + { + format_per_track_data_size++; + num_animated_variable_rotations++; + } + + if (!bone_stream.is_translation_constant && is_translation_variable) + format_per_track_data_size++; + + if (!bone_stream.is_scale_constant && is_scale_variable) + format_per_track_data_size++; + } + + // Rotations are padded for alignment + const uint32_t num_partial_rotations = num_animated_variable_rotations % 4; + if (num_partial_rotations != 0) + format_per_track_data_size += 4 - num_partial_rotations; + + if (out_num_animated_variable_sub_tracks_padded != nullptr) + *out_num_animated_variable_sub_tracks_padded = format_per_track_data_size; // 1 byte per sub-track + + return format_per_track_data_size; + } + + inline uint32_t write_constant_track_data(const clip_context& clip, rotation_format8 rotation_format, uint8_t* constant_data, uint32_t constant_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + ACL_ASSERT(constant_data != nullptr, "'constant_data' cannot be null!"); + (void)constant_data_size; + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + +#if defined(ACL_HAS_ASSERT_CHECKS) + const uint8_t* constant_data_end = add_offset_to_ptr(constant_data, constant_data_size); +#endif + + const uint8_t* constant_data_start = constant_data; + + // If our rotation format drops the W component, we swizzle the data to store XXXX, YYYY, ZZZZ + const bool swizzle_rotations = get_rotation_variant(rotation_format) == rotation_variant8::quat_drop_w; + float xxxx[4]; + float yyyy[4]; + float zzzz[4]; + uint32_t num_swizzle_written = 0; + + // Write rotations first + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant) + { + if (swizzle_rotations) + { + const rtm::vector4f rotation = bone_stream.rotations.get_raw_sample(0); + xxxx[num_swizzle_written] = rtm::vector_get_x(rotation); + yyyy[num_swizzle_written] = rtm::vector_get_y(rotation); + zzzz[num_swizzle_written] = rtm::vector_get_z(rotation); + num_swizzle_written++; + + if (num_swizzle_written >= 4) + { + std::memcpy(constant_data, &xxxx[0], sizeof(xxxx)); + constant_data += sizeof(xxxx); + std::memcpy(constant_data, &yyyy[0], sizeof(yyyy)); + constant_data += sizeof(yyyy); + std::memcpy(constant_data, &zzzz[0], sizeof(zzzz)); + constant_data += sizeof(zzzz); + num_swizzle_written = 0; + } + } + else + { + const uint8_t* rotation_ptr = bone_stream.rotations.get_raw_sample_ptr(0); + uint32_t sample_size = bone_stream.rotations.get_sample_size(); + std::memcpy(constant_data, rotation_ptr, sample_size); + constant_data += sample_size; + } + + ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data."); + } + } + + if (swizzle_rotations && num_swizzle_written != 0) + { + std::memcpy(constant_data, &xxxx[0], num_swizzle_written * sizeof(float)); + constant_data += num_swizzle_written * sizeof(float); + std::memcpy(constant_data, &yyyy[0], num_swizzle_written * sizeof(float)); + constant_data += num_swizzle_written * sizeof(float); + std::memcpy(constant_data, &zzzz[0], num_swizzle_written * sizeof(float)); + constant_data += num_swizzle_written * sizeof(float); + + ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data."); + } + + // Next, write translations + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + if (!bone_stream.is_translation_default && bone_stream.is_translation_constant) + { + const uint8_t* translation_ptr = bone_stream.translations.get_raw_sample_ptr(0); + uint32_t sample_size = bone_stream.translations.get_sample_size(); + std::memcpy(constant_data, translation_ptr, sample_size); + constant_data += sample_size; + + ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data."); + } + } + + // Finally, write scales + if (clip.has_scale) + { + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + if (!bone_stream.is_scale_default && bone_stream.is_scale_constant) + { + const uint8_t* scale_ptr = bone_stream.scales.get_raw_sample_ptr(0); + uint32_t sample_size = bone_stream.scales.get_sample_size(); + std::memcpy(constant_data, scale_ptr, sample_size); + constant_data += sample_size; + + ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data."); + } + } + } + + ACL_ASSERT(constant_data == constant_data_end, "Invalid constant data offset. Wrote too little data."); + return safe_static_cast(constant_data - constant_data_start); + } + + inline void write_animated_sample(const track_stream& track_stream, uint32_t sample_index, uint8_t* animated_track_data_begin, uint64_t& out_bit_offset) + { + const uint8_t* raw_sample_ptr = track_stream.get_raw_sample_ptr(sample_index); + + if (track_stream.is_bit_rate_variable()) + { + const uint8_t bit_rate = track_stream.get_bit_rate(); + const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3; // 3 components + + // Track is constant, our constant sample is stored in the range information + ACL_ASSERT(!is_constant_bit_rate(bit_rate), "Cannot write constant variable track data"); + + if (is_raw_bit_rate(bit_rate)) + { + const uint32_t* raw_sample_u32 = safe_ptr_cast(raw_sample_ptr); + + const uint32_t x = byte_swap(raw_sample_u32[0]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 0, &x, 0, 32); + const uint32_t y = byte_swap(raw_sample_u32[1]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 32, &y, 0, 32); + const uint32_t z = byte_swap(raw_sample_u32[2]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 64, &z, 0, 32); + } + else + { + const uint64_t raw_sample_u64 = *safe_ptr_cast(raw_sample_ptr); + memcpy_bits(animated_track_data_begin, out_bit_offset, &raw_sample_u64, 0, num_bits_at_bit_rate); + } + + out_bit_offset += num_bits_at_bit_rate; + } + else + { + const uint32_t* raw_sample_u32 = safe_ptr_cast(raw_sample_ptr); + + const uint32_t x = byte_swap(raw_sample_u32[0]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 0, &x, 0, 32); + const uint32_t y = byte_swap(raw_sample_u32[1]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 32, &y, 0, 32); + const uint32_t z = byte_swap(raw_sample_u32[2]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 64, &z, 0, 32); + + const uint32_t sample_size = track_stream.get_packed_sample_size(); + const bool has_w_component = sample_size == (sizeof(float) * 4); + if (has_w_component) + { + const uint32_t w = byte_swap(raw_sample_u32[3]); + memcpy_bits(animated_track_data_begin, out_bit_offset + 96, &w, 0, 32); + } + + out_bit_offset += has_w_component ? 128 : 96; + } + } + + inline uint32_t write_animated_track_data(const segment_context& segment, uint8_t* animated_track_data, uint32_t animated_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + ACL_ASSERT(animated_track_data != nullptr, "'animated_track_data' cannot be null!"); + (void)animated_data_size; + + uint8_t* animated_track_data_begin = animated_track_data; + + const uint8_t* animated_track_data_start = animated_track_data; + const uint8_t* animated_track_data_end = add_offset_to_ptr(animated_track_data, animated_data_size); + + uint64_t bit_offset = 0; + + // Data is sorted first by time, second by bone. + // This ensures that all bones are contiguous in memory when we sample a particular time. + + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Groups are sorted per sub-track type. All rotation groups come first followed by translations then scales. + // The last group of each sub-track may or may not have padding. The last group might be less than 4 sub-tracks. + + // For animated samples, when we have a constant bit rate (bit rate 0), we do not store samples + // and as such the group that contains that sub-track won't contain 4 samples. + // The largest sample is a full precision vector4f, we can contain at most 4 samples + alignas(16) uint8_t group_animated_track_data[sizeof(rtm::vector4f) * 4]; + uint64_t group_bit_offset = 0; + + auto group_filter_action = [](animation_track_type8 group_type, uint32_t bone_index) + { + (void)group_type; + (void)bone_index; + + // We want a group of every animated track + // If a track is variable with a constant bit rate (bit rate 0), the group will have fewer entries + return true; + }; + + auto group_flush_action = [animated_track_data_begin, &bit_offset, &group_animated_track_data, &group_bit_offset, &animated_track_data, animated_track_data_end](animation_track_type8 group_type, uint32_t group_size) + { + (void)group_type; + (void)group_size; + + memcpy_bits(animated_track_data_begin, bit_offset, &group_animated_track_data[0], 0, group_bit_offset); + + bit_offset += group_bit_offset; + group_bit_offset = 0; + + animated_track_data = animated_track_data_begin + (bit_offset / 8); + + ACL_ASSERT(animated_track_data <= animated_track_data_end, "Invalid animated track data offset. Wrote too much data."); (void)animated_track_data_end; + }; + + // TODO: Use a group writer context object to avoid alloc/free/work in loop for every sample when it doesn't change + for (uint32_t sample_index = 0; sample_index < segment.num_samples; ++sample_index) + { + auto group_entry_action = [&segment, sample_index, &group_animated_track_data, &group_bit_offset](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index) + { + (void)group_size; + + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (group_type == animation_track_type8::rotation) + { + if (!is_constant_bit_rate(bone_stream.rotations.get_bit_rate())) + write_animated_sample(bone_stream.rotations, sample_index, group_animated_track_data, group_bit_offset); + } + else if (group_type == animation_track_type8::translation) + { + if (!is_constant_bit_rate(bone_stream.translations.get_bit_rate())) + write_animated_sample(bone_stream.translations, sample_index, group_animated_track_data, group_bit_offset); + } + else + { + if (!is_constant_bit_rate(bone_stream.scales.get_bit_rate())) + write_animated_sample(bone_stream.scales, sample_index, group_animated_track_data, group_bit_offset); + } + }; + + animated_group_writer(segment, output_bone_mapping, num_output_bones, group_filter_action, group_entry_action, group_flush_action); + } + + if (bit_offset != 0) + animated_track_data = animated_track_data_begin + ((bit_offset + 7) / 8); + + ACL_ASSERT((bit_offset == 0 && segment.num_samples == 0) || ((bit_offset / segment.num_samples) == segment.animated_pose_bit_size), "Unexpected number of bits written"); + ACL_ASSERT(animated_track_data == animated_track_data_end, "Invalid animated track data offset. Wrote too little data."); + return safe_static_cast(animated_track_data - animated_track_data_start); + } + + inline uint32_t write_format_per_track_data(const segment_context& segment, uint8_t* format_per_track_data, uint32_t format_per_track_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + ACL_ASSERT(format_per_track_data != nullptr, "'format_per_track_data' cannot be null!"); + (void)format_per_track_data_size; + + const uint8_t* format_per_track_data_start = format_per_track_data; + const uint8_t* format_per_track_data_end = add_offset_to_ptr(format_per_track_data, format_per_track_data_size); + + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Groups are sorted per sub-track type. All rotation groups come first followed by translations then scales. + // The last group of each sub-track may or may not have padding. The last group might be less than 4 sub-tracks. + + // To keep decompression simpler, rotations are padded to 4 elements even if the last group is partial + uint8_t format_per_track_group[4]; + + auto group_filter_action = [&segment](animation_track_type8 group_type, uint32_t bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (group_type == animation_track_type8::rotation) + return bone_stream.rotations.is_bit_rate_variable(); + else if (group_type == animation_track_type8::translation) + return bone_stream.translations.is_bit_rate_variable(); + else + return bone_stream.scales.is_bit_rate_variable(); + }; + + auto group_entry_action = [&segment, &format_per_track_group](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index) + { + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + if (group_type == animation_track_type8::rotation) + format_per_track_group[group_size] = (uint8_t)get_num_bits_at_bit_rate(bone_stream.rotations.get_bit_rate()); + else if (group_type == animation_track_type8::translation) + format_per_track_group[group_size] = (uint8_t)get_num_bits_at_bit_rate(bone_stream.translations.get_bit_rate()); + else + format_per_track_group[group_size] = (uint8_t)get_num_bits_at_bit_rate(bone_stream.scales.get_bit_rate()); + }; + + auto group_flush_action = [&format_per_track_data, format_per_track_data_end, &format_per_track_group](animation_track_type8 group_type, uint32_t group_size) + { + const uint32_t copy_size = group_type == animation_track_type8::rotation ? 4 : group_size; + std::memcpy(format_per_track_data, &format_per_track_group[0], copy_size); + format_per_track_data += copy_size; + + // Zero out the temporary buffer for the final group to not contain partial garbage + std::memset(&format_per_track_group[0], 0, sizeof(format_per_track_group)); + + ACL_ASSERT(format_per_track_data <= format_per_track_data_end, "Invalid format per track data offset. Wrote too much data."); (void)format_per_track_data_end; + }; + + animated_group_writer(segment, output_bone_mapping, num_output_bones, group_filter_action, group_entry_action, group_flush_action); + + ACL_ASSERT(format_per_track_data == format_per_track_data_end, "Invalid format per track data offset. Wrote too little data."); + + return safe_static_cast(format_per_track_data - format_per_track_data_start); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_sub_track_types.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_sub_track_types.h new file mode 100644 index 00000000..5a6a2723 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_sub_track_types.h @@ -0,0 +1,169 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/impl/compressed_headers.h" +#include "acl/compression/impl/clip_context.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t write_packed_sub_track_types(const clip_context& clip, packed_sub_track_types* out_packed_types, const uint32_t* output_bone_mapping, uint32_t num_output_bones) + { + ACL_ASSERT(out_packed_types != nullptr, "'out_packed_types' cannot be null!"); + + // Only use the first segment, it contains the necessary information + const segment_context& segment = clip.segments[0]; + + uint32_t packed_entry_index = 0; + uint32_t packed_entry_size = 0; + uint32_t packed_entry = 0; + + // Write rotations + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + if (packed_entry_size == 16) + { + // We are starting a new entry, write our old one out + out_packed_types[packed_entry_index++] = packed_sub_track_types{ packed_entry }; + packed_entry_size = 0; + packed_entry = 0; + } + + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + uint32_t packed_type; + if (bone_stream.is_rotation_default) + packed_type = 0; // Default + else if (bone_stream.is_rotation_constant) + packed_type = 1; // Constant + else + packed_type = 2; // Animated + + const uint32_t packed_index = output_index % 16; + packed_entry |= packed_type << ((15 - packed_index) * 2); + packed_entry_size++; + } + + if (packed_entry_size != 0) + { + // We have a partial entry, write it out + out_packed_types[packed_entry_index++] = packed_sub_track_types{ packed_entry }; + packed_entry_size = 0; + packed_entry = 0; + } + + // Write translations + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + if (packed_entry_size == 16) + { + // We are starting a new entry, write our old one out + out_packed_types[packed_entry_index++] = packed_sub_track_types{ packed_entry }; + packed_entry_size = 0; + packed_entry = 0; + } + + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + uint32_t packed_type; + if (bone_stream.is_translation_default) + packed_type = 0; // Default + else if (bone_stream.is_translation_constant) + packed_type = 1; // Constant + else + packed_type = 2; // Animated + + const uint32_t packed_index = output_index % 16; + packed_entry |= packed_type << ((15 - packed_index) * 2); + packed_entry_size++; + } + + if (packed_entry_size != 0) + { + // We have a partial entry, write it out + out_packed_types[packed_entry_index++] = packed_sub_track_types{ packed_entry }; + packed_entry_size = 0; + packed_entry = 0; + } + + if (clip.has_scale) + { + // Write translations + for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index) + { + if (packed_entry_size == 16) + { + // We are starting a new entry, write our old one out + out_packed_types[packed_entry_index++] = packed_sub_track_types{ packed_entry }; + packed_entry_size = 0; + packed_entry = 0; + } + + const uint32_t bone_index = output_bone_mapping[output_index]; + const transform_streams& bone_stream = segment.bone_streams[bone_index]; + + uint32_t packed_type; + if (bone_stream.is_scale_default) + packed_type = 0; // Default + else if (bone_stream.is_scale_constant) + packed_type = 1; // Constant + else + packed_type = 2; // Animated + + const uint32_t packed_index = output_index % 16; + packed_entry |= packed_type << ((15 - packed_index) * 2); + packed_entry_size++; + } + + if (packed_entry_size != 0) + { + // We have a partial entry, write it out + out_packed_types[packed_entry_index++] = packed_sub_track_types{ packed_entry }; + packed_entry_size = 0; + packed_entry = 0; + } + } + + return packed_entry_index * sizeof(packed_sub_track_types); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_track_data_impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_track_data_impl.h new file mode 100644 index 00000000..59585ac9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_track_data_impl.h @@ -0,0 +1,194 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/impl/compressed_headers.h" +#include "acl/compression/impl/track_list_context.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t write_track_metadata(const track_list_context& context, track_metadata* per_track_metadata) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + uint8_t* output_buffer = reinterpret_cast(per_track_metadata); + const uint8_t* output_buffer_start = output_buffer; + + for (uint32_t output_index = 0; output_index < context.num_output_tracks; ++output_index) + { + const uint32_t track_index = context.track_output_indices[output_index]; + + if (per_track_metadata != nullptr) + per_track_metadata[output_index].bit_rate = context.is_constant(track_index) ? 0 : context.bit_rate_list[track_index].scalar.value; + + output_buffer += sizeof(track_metadata); + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_track_constant_values(const track_list_context& context, float* constant_values) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + uint8_t* output_buffer = reinterpret_cast(constant_values); + const uint8_t* output_buffer_start = output_buffer; + + for (uint32_t output_index = 0; output_index < context.num_output_tracks; ++output_index) + { + const uint32_t track_index = context.track_output_indices[output_index]; + + if (!context.is_constant(track_index)) + continue; + + const track& ref_track = (*context.reference_list)[track_index]; + const uint32_t element_size = ref_track.get_sample_size(); + + if (constant_values != nullptr) + std::memcpy(output_buffer, ref_track[0], element_size); + + output_buffer += element_size; + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_track_range_values(const track_list_context& context, float* range_values) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + uint8_t* output_buffer = reinterpret_cast(range_values); + const uint8_t* output_buffer_start = output_buffer; + + for (uint32_t output_index = 0; output_index < context.num_output_tracks; ++output_index) + { + const uint32_t track_index = context.track_output_indices[output_index]; + + if (context.is_constant(track_index)) + continue; + + const uint8_t bit_rate = context.bit_rate_list[track_index].scalar.value; + if (is_raw_bit_rate(bit_rate)) + continue; + + const track& ref_track = (*context.reference_list)[track_index]; + const track_range& range = context.range_list[track_index]; + + const uint32_t element_size = ref_track.get_sample_size(); + + if (range_values != nullptr) + { + // Only support scalarf for now + ACL_ASSERT(range.category == track_category8::scalarf, "Unsupported category"); + const rtm::vector4f range_min = range.range.scalarf.get_min(); + const rtm::vector4f range_extent = range.range.scalarf.get_extent(); + std::memcpy(output_buffer, &range_min, element_size); + std::memcpy(output_buffer + element_size, &range_extent, element_size); + } + + output_buffer += element_size; // Min + output_buffer += element_size; // Extent + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_track_animated_values(const track_list_context& context, uint8_t* animated_values) + { + ACL_ASSERT(context.is_valid(), "Invalid context"); + + uint8_t* output_buffer = animated_values; + uint64_t output_bit_offset = 0; + + const uint32_t num_components = get_track_num_sample_elements(context.reference_list->get_track_type()); + ACL_ASSERT(num_components <= 4, "Unexpected number of elements"); + + for (uint32_t sample_index = 0; sample_index < context.num_samples; ++sample_index) + { + for (uint32_t output_index = 0; output_index < context.num_output_tracks; ++output_index) + { + const uint32_t track_index = context.track_output_indices[output_index]; + + if (context.is_constant(track_index)) + continue; + + const track& ref_track = (*context.reference_list)[track_index]; + const track& mut_track = context.track_list[track_index]; + + // Only support scalarf for now + ACL_ASSERT(ref_track.get_category() == track_category8::scalarf, "Unsupported category"); + + const scalar_bit_rate bit_rate = context.bit_rate_list[track_index].scalar; + const uint64_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate.value); + + const track& src_track = is_raw_bit_rate(bit_rate.value) ? ref_track : mut_track; + + const uint32_t* sample_u32 = safe_ptr_cast(src_track[sample_index]); + const float* sample_f32 = safe_ptr_cast(src_track[sample_index]); + for (uint32_t component_index = 0; component_index < num_components; ++component_index) + { + if (animated_values != nullptr) + { + uint32_t value; + if (is_raw_bit_rate(bit_rate.value)) + value = byte_swap(sample_u32[component_index]); + else + { + // TODO: Hacked, our values are still as floats, cast to int, shift, and byte swap + // Ideally should be done in the cache/mutable track with SIMD + value = safe_static_cast(sample_f32[component_index]); + value = value << (32 - num_bits_per_component); + value = byte_swap(value); + } + + memcpy_bits(output_buffer, output_bit_offset, &value, 0, num_bits_per_component); + } + + output_bit_offset += num_bits_per_component; + } + } + } + + return safe_static_cast(output_bit_offset); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_track_metadata.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_track_metadata.h new file mode 100644 index 00000000..dd3f9c80 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/impl/write_track_metadata.h @@ -0,0 +1,223 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/memory_utils.h" +#include "acl/core/track_desc.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/track_array.h" +#include "acl/compression/impl/clip_context.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t write_track_list_name(const track_array& tracks, char* out_track_list_name) + { + ACL_ASSERT(out_track_list_name == nullptr || out_track_list_name[0] == 0, "Buffer overrun detected"); + + uint8_t* output_buffer = reinterpret_cast(out_track_list_name); + const uint8_t* output_buffer_start = output_buffer; + + const string& name = tracks.get_name(); + const uint32_t name_size = uint32_t(name.size() + 1); // Include null terminator too + + if (out_track_list_name != nullptr) + std::memcpy(out_track_list_name, name.c_str(), name_size); + + output_buffer += name_size; + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_track_names(const track_array& tracks, const uint32_t* track_output_indices, uint32_t num_output_tracks, uint32_t* out_track_names) + { + ACL_ASSERT(out_track_names == nullptr || out_track_names[0] == 0 || num_output_tracks == 0, "Buffer overrun detected"); + + uint8_t* output_buffer = reinterpret_cast(out_track_names); + const uint8_t* output_buffer_start = output_buffer; + + // Write offsets first + uint32_t* track_name_offsets = out_track_names; + uint32_t offset = sizeof(uint32_t) * num_output_tracks; + for (uint32_t output_index = 0; output_index < num_output_tracks; ++output_index) + { + const uint32_t track_index = track_output_indices[output_index]; + const string& name = tracks[track_index].get_name(); + const uint32_t name_size = uint32_t(name.size() + 1); // Include null terminator too + + if (out_track_names != nullptr) + *track_name_offsets = offset; + + track_name_offsets++; + output_buffer += sizeof(uint32_t); + offset += name_size; + } + + // Next write our track names + char* track_names = safe_ptr_cast(output_buffer); + for (uint32_t output_index = 0; output_index < num_output_tracks; ++output_index) + { + const uint32_t track_index = track_output_indices[output_index]; + const string& name = tracks[track_index].get_name(); + const uint32_t name_size = uint32_t(name.size() + 1); // Include null terminator too + + if (out_track_names != nullptr) + std::memcpy(track_names, name.c_str(), name_size); + + track_names += name_size; + output_buffer += name_size; + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_parent_track_indices(const track_array_qvvf& tracks, const uint32_t* track_output_indices, uint32_t num_output_tracks, uint32_t* out_parent_track_indices) + { + ACL_ASSERT(out_parent_track_indices == nullptr || out_parent_track_indices[0] == 0 || num_output_tracks == 0, "Buffer overrun detected"); + + auto find_output_index = [track_output_indices, num_output_tracks](uint32_t track_index) + { + if (track_index == k_invalid_track_index) + return k_invalid_track_index; + + for (uint32_t output_index = 0; output_index < num_output_tracks; ++output_index) + { + if (track_output_indices[output_index] == track_index) + return output_index; + } + + return k_invalid_track_index; + }; + + uint8_t* output_buffer = reinterpret_cast(out_parent_track_indices); + const uint8_t* output_buffer_start = output_buffer; + + for (uint32_t output_index = 0; output_index < num_output_tracks; ++output_index) + { + const uint32_t track_index = track_output_indices[output_index]; + const track_qvvf& track = tracks[track_index]; + const track_desc_transformf& desc = track.get_description(); + const uint32_t parent_track_index = desc.parent_index; + + const uint32_t parent_output_index = find_output_index(parent_track_index); + if (out_parent_track_indices != nullptr) + out_parent_track_indices[output_index] = parent_output_index; + + output_buffer += sizeof(uint32_t); + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_track_descriptions(const track_array& tracks, const uint32_t* track_output_indices, uint32_t num_output_tracks, uint8_t* out_track_descriptions) + { + ACL_ASSERT(out_track_descriptions == nullptr || out_track_descriptions[0] == 0 || num_output_tracks == 0, "Buffer overrun detected"); + + uint8_t* output_buffer = out_track_descriptions; + const uint8_t* output_buffer_start = output_buffer; + + const bool is_scalar = tracks.get_track_type() != track_type8::qvvf; + + for (uint32_t output_index = 0; output_index < num_output_tracks; ++output_index) + { + const uint32_t track_index = track_output_indices[output_index]; + + if (is_scalar) + { + const track_desc_scalarf& desc = tracks[track_index].get_description(); + + if (out_track_descriptions != nullptr) + { + // We don't write out the output index since the track has already been properly sorted or stripped + + float* data = reinterpret_cast(output_buffer); + data[0] = desc.precision; + } + + output_buffer += sizeof(float) * 1; + } + else + { + const track_desc_transformf& desc = tracks[track_index].get_description(); + + if (out_track_descriptions != nullptr) + { + // We don't write out the output index since the track has already been properly sorted or stripped + // We don't write out the parent index since it has already been included separately + + float* data = reinterpret_cast(output_buffer); + data[0] = desc.precision; + data[1] = desc.shell_distance; + data[2] = desc.constant_rotation_threshold_angle; + data[3] = desc.constant_translation_threshold; + data[4] = desc.constant_scale_threshold; + } + + output_buffer += sizeof(float) * 5; + } + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + + inline uint32_t write_contributing_error(const clip_context& clip, frame_contributing_error* out_contributing_error) + { + ACL_ASSERT(out_contributing_error == nullptr || clip.num_samples == 0 || out_contributing_error[0].index == 0, "Buffer overrun detected"); + + const uint8_t* output_buffer = reinterpret_cast(out_contributing_error); + const uint8_t* output_buffer_start = output_buffer; + frame_contributing_error* contributing_error = out_contributing_error; + + // Write the contributing error for each frame by iterating over our segments to retrieve it + // Values are thus sorted per segment + for (const segment_context& segment : clip.segment_iterator()) + { + for (uint32_t frame_index = 0; frame_index < segment.num_samples; ++frame_index) + { + if (out_contributing_error != nullptr) + *contributing_error = segment.contributing_error[frame_index]; + + contributing_error++; + output_buffer += sizeof(frame_contributing_error); + } + } + + return safe_static_cast(output_buffer - output_buffer_start); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/output_stats.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/output_stats.h new file mode 100644 index 00000000..fed31dc1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/output_stats.h @@ -0,0 +1,60 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/enum_utils.h" + +#if !defined(SJSON_CPP_WRITER) +namespace sjson { class ObjectWriter; } +#endif + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + enum class stat_logging + { + none = 0x0000, + summary = 0x0001, + detailed = 0x0002 | summary, + exhaustive = 0x0004 | detailed, + }; + + ACL_IMPL_ENUM_FLAGS_OPERATORS(stat_logging) + + struct output_stats + { + stat_logging logging = stat_logging::none; + sjson::ObjectWriter* writer = nullptr; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track.h new file mode 100644 index 00000000..8ccd120b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track.h @@ -0,0 +1,369 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/string.h" +#include "acl/core/track_desc.h" +#include "acl/core/track_traits.h" +#include "acl/core/track_types.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C4582: 'union': constructor is not implicitly called (/Wall) + // This is fine because a track is empty until it is constructed with a valid description. + // Afterwards, access is typesafe. + #pragma warning(disable : 4582) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // An untyped track of data. A track is a time series of values sampled + // uniformly over time at a specific sample rate. Tracks can either own + // their memory or reference an external buffer. + // For convenience, this type can be cast with the `track_cast(..)` family + // of functions. Each track type has the same size as every track description + // is contained within a union. + ////////////////////////////////////////////////////////////////////////// + class track + { + public: + ////////////////////////////////////////////////////////////////////////// + // Creates an empty, untyped track. + track() noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Move constructor for a track. + track(track&& other) noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Destroys the track. If it owns the memory referenced, it will be freed. + ~track(); + + ////////////////////////////////////////////////////////////////////////// + // Move assignment for a track. + track& operator=(track&& other) noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Returns a pointer to an untyped sample at the specified index. + void* operator[](uint32_t index); + + ////////////////////////////////////////////////////////////////////////// + // Returns a pointer to an untyped sample at the specified index. + const void* operator[](uint32_t index) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the track owns its memory, false otherwise. + bool is_owner() const { return m_allocator != nullptr; } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the track owns its memory, false otherwise. + bool is_ref() const { return m_allocator == nullptr; } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the track doesn't contain any data, false otherwise. + bool is_empty() const { return m_num_samples == 0; } + + ////////////////////////////////////////////////////////////////////////// + // Returns a pointer to the allocator instance or nullptr if there is none present. + iallocator* get_allocator() const { return m_allocator; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of samples contained within the track. + uint32_t get_num_samples() const { return m_num_samples; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the stride in bytes in between samples as laid out in memory. + // This is always sizeof(sample_type) unless the memory isn't owned internally. + uint32_t get_stride() const { return m_stride; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track type. + track_type8 get_type() const { return m_type; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track category. + track_category8 get_category() const { return m_category; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the size in bytes of each track sample. + uint32_t get_sample_size() const { return m_sample_size; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track sample rate. + // A track has its sampled uniformly distributed in time at a fixed rate (e.g. 30 samples per second). + float get_sample_rate() const { return m_sample_rate; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track name. + const string& get_name() const { return m_name; } + + ////////////////////////////////////////////////////////////////////////// + // Sets the track name. + void set_name(const string& name) { m_name = name.get_copy(); } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track output index. + // When compressing, it is often desirable to strip or re-order the tracks we output. + // This can be used to sort by LOD or to strip stale tracks. Tracks with an invalid + // track index are stripped in the output. + uint32_t get_output_index() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the track description. + template + desc_type& get_description(); + + ////////////////////////////////////////////////////////////////////////// + // Returns the track description. + template + const desc_type& get_description() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns a copy of the track where the memory will be owned by the copy. + track get_copy(iallocator& allocator) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns a reference to the track where the memory isn't owned. + track get_ref() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether a track is valid or not. + // A track is valid if: + // - It is empty + // - It has a positive and finite sample rate + // - A valid description + error_result is_valid() const; + + protected: + ////////////////////////////////////////////////////////////////////////// + // We prohibit copying, use get_copy() and get_ref() instead. + track(const track&) = delete; + track& operator=(const track&) = delete; + + ////////////////////////////////////////////////////////////////////////// + // Internal constructor. + // Creates an empty, untyped track. + track(track_type8 type, track_category8 category) noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Internal constructor. + track(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, track_type8 type, track_category8 category, uint8_t sample_size) noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Internal helper. + void get_copy_impl(iallocator& allocator, track& out_track) const; + + ////////////////////////////////////////////////////////////////////////// + // Internal helper. + void get_ref_impl(track& out_track) const; + + iallocator* m_allocator; // Optional allocator that owns the memory + uint8_t* m_data; // Pointer to the samples + + uint32_t m_num_samples; // The number of samples + uint32_t m_stride; // The stride in bytes in between samples as layed out in memory + size_t m_data_size; // The total size of the buffer used by the samples + + float m_sample_rate; // The track sample rate + + track_type8 m_type; // The track type + track_category8 m_category; // The track category + uint16_t m_sample_size; // The size in bytes of each sample + + ////////////////////////////////////////////////////////////////////////// + // A union of every track description. + // This ensures every track has the same size regardless of its type. + union track_desc_untyped + { + track_desc_scalarf scalar; + track_desc_transformf transform; + + track_desc_untyped() noexcept {}; + explicit track_desc_untyped(const track_desc_scalarf& desc) noexcept : scalar(desc) {} + explicit track_desc_untyped(const track_desc_transformf& desc) noexcept : transform(desc) {} + }; + + track_desc_untyped m_desc; // The track description + + string m_name; // An optional name + }; + + ////////////////////////////////////////////////////////////////////////// + // A typed track of data. See `track` for details. + ////////////////////////////////////////////////////////////////////////// + template + class track_typed final : public track + { + public: + ////////////////////////////////////////////////////////////////////////// + // The track type. + static constexpr track_type8 type = track_type_; + + ////////////////////////////////////////////////////////////////////////// + // The track category. + static constexpr track_category8 category = track_traits::category; + + ////////////////////////////////////////////////////////////////////////// + // The type of each sample in this track. + using sample_type = typename track_traits::sample_type; + + ////////////////////////////////////////////////////////////////////////// + // The type of the track description. + using desc_type = typename track_traits::desc_type; + + ////////////////////////////////////////////////////////////////////////// + // Constructs an empty typed track. + track_typed() noexcept : track(type, category) { static_assert(sizeof(track_typed) == sizeof(track), "You cannot add member variables to this class"); } + + ////////////////////////////////////////////////////////////////////////// + // Destroys the track and potentially frees any memory it might own. + ~track_typed() = default; + + ////////////////////////////////////////////////////////////////////////// + // Move assignment for a track. + track_typed(track_typed&& other) noexcept : track(static_cast(other)) {} + + ////////////////////////////////////////////////////////////////////////// + // Move assignment for a track. + track_typed& operator=(track_typed&& other) noexcept { return static_cast(track::operator=(static_cast(other))); } + + ////////////////////////////////////////////////////////////////////////// + // Returns the sample at the specified index. + // If this track does not own the memory, mutable references aren't allowed and an + // invalid reference will be returned, leading to a crash. + sample_type& operator[](uint32_t index); + + ////////////////////////////////////////////////////////////////////////// + // Returns the sample at the specified index. + const sample_type& operator[](uint32_t index) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the track description. + desc_type& get_description(); + + ////////////////////////////////////////////////////////////////////////// + // Returns the track description. + const desc_type& get_description() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the track type. + track_type8 get_type() const { return type; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track category. + track_category8 get_category() const { return category; } + + ////////////////////////////////////////////////////////////////////////// + // Returns a copy of the track where the memory will be owned by the copy. + track_typed get_copy(iallocator& allocator) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns a reference to the track where the memory isn't owned. + track_typed get_ref() const; + + ////////////////////////////////////////////////////////////////////////// + // Creates a track that copies the data and owns the memory. + static track_typed make_copy(const desc_type& desc, iallocator& allocator, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type)); + + ////////////////////////////////////////////////////////////////////////// + // Creates a track and preallocates but does not initialize the memory that it owns. + static track_typed make_reserve(const desc_type& desc, iallocator& allocator, uint32_t num_samples, float sample_rate); + + ////////////////////////////////////////////////////////////////////////// + // Creates a track and takes ownership of the already allocated memory. + static track_typed make_owner(const desc_type& desc, iallocator& allocator, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type)); + + ////////////////////////////////////////////////////////////////////////// + // Creates a track that just references the data without owning it. + static track_typed make_ref(const desc_type& desc, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type)); + + private: + ////////////////////////////////////////////////////////////////////////// + // We prohibit copying, use get_copy() and get_ref() instead. + track_typed(const track_typed&) = delete; + track_typed& operator=(const track_typed&) = delete; + + ////////////////////////////////////////////////////////////////////////// + // Internal constructor. + track_typed(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, const desc_type& desc) noexcept; + }; + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track into the desired track type while asserting for safety. + template + track_type& track_cast(track& track_); + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track into the desired track type while asserting for safety. + template + const track_type& track_cast(const track& track_); + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track into the desired track type. Returns nullptr if the types + // are not compatible or if the input is nullptr. + template + track_type* track_cast(track* track_); + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track into the desired track type. Returns nullptr if the types + // are not compatible or if the input is nullptr. + template + const track_type* track_cast(const track* track_); + + ////////////////////////////////////////////////////////////////////////// + // Create aliases for the various typed track types. + + using track_float1f = track_typed; + using track_float2f = track_typed; + using track_float3f = track_typed; + using track_float4f = track_typed; + using track_vector4f = track_typed; + using track_qvvf = track_typed; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/compression/impl/track.impl.h" + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track_array.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track_array.h new file mode 100644 index 00000000..6c0c4a6e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track_array.h @@ -0,0 +1,278 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/track_types.h" +#include "acl/core/track_writer.h" +#include "acl/core/utils.h" +#include "acl/compression/track.h" + +#include +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // An array of tracks. + // Although each track contained within is untyped, each track must have + // the same type. They must all have the same sample rate and the same + // number of samples. + ////////////////////////////////////////////////////////////////////////// + class track_array + { + public: + ////////////////////////////////////////////////////////////////////////// + // Constructs an empty track array. + track_array() noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Constructs an array with the specified number of tracks. + // Tracks will be empty and untyped by default. + track_array(iallocator& allocator, uint32_t num_tracks); + + ////////////////////////////////////////////////////////////////////////// + // Move constructor for a track array. + track_array(track_array&& other) noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Destroys a track array. + ~track_array(); + + ////////////////////////////////////////////////////////////////////////// + // Move assignment for a track array. + track_array& operator=(track_array&& other) noexcept; + + ////////////////////////////////////////////////////////////////////////// + // Returns a pointer to the allocator instance or nullptr if there is none present. + iallocator* get_allocator() const { return m_allocator; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of tracks contained in this array. + uint32_t get_num_tracks() const { return m_num_tracks; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of samples per track in this array. + uint32_t get_num_samples_per_track() const { return m_allocator != nullptr && m_num_tracks != 0 ? m_tracks->get_num_samples() : 0; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track type for tracks in this array. + track_type8 get_track_type() const { return m_allocator != nullptr && m_num_tracks != 0 ? m_tracks->get_type() : track_type8::float1f; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track category for tracks in this array. + track_category8 get_track_category() const { return m_allocator != nullptr && m_num_tracks != 0 ? m_tracks->get_category() : track_category8::scalarf; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the sample rate for tracks in this array. + float get_sample_rate() const { return m_allocator != nullptr && m_num_tracks != 0 ? m_tracks->get_sample_rate() : 0.0F; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the duration for tracks in this array. + float get_duration() const { return m_allocator != nullptr && m_num_tracks != 0 ? calculate_duration(m_tracks->get_num_samples(), m_tracks->get_sample_rate()) : 0.0F; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the finite duration for tracks in this array. + float get_finite_duration() const { return m_allocator != nullptr && m_num_tracks != 0 ? calculate_finite_duration(m_tracks->get_num_samples(), m_tracks->get_sample_rate()) : 0.0F; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track name. + const string& get_name() const { return m_name; } + + ////////////////////////////////////////////////////////////////////////// + // Sets the track name. + void set_name(const string& name) { m_name = name.get_copy(); } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track at the specified index. + track& operator[](uint32_t index); + + ////////////////////////////////////////////////////////////////////////// + // Returns the track at the specified index. + const track& operator[](uint32_t index) const; + + ////////////////////////////////////////////////////////////////////////// + // Iterator begin() and end() implementations. + track* begin() { return m_tracks; } + const track* begin() const { return m_tracks; } + track* end() { return m_tracks + m_num_tracks; } + const track* end() const { return m_tracks + m_num_tracks; } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the track array doesn't contain any data, false otherwise. + bool is_empty() const { return m_num_tracks == 0; } + + ////////////////////////////////////////////////////////////////////////// + // Returns whether a track array is valid or not. + // An array is valid if: + // - It is empty + // - All tracks have the same type + // - All tracks have the same number of samples + // - All tracks have the same sample rate + // - All tracks are valid + error_result is_valid() const; + + ////////////////////////////////////////////////////////////////////////// + // Sample all tracks within this array at the specified sample time and + // desired rounding policy. Track samples are written out using the `track_writer` provided. + template + void sample_tracks(float sample_time, sample_rounding_policy rounding_policy, track_writer_type& writer) const; + + ////////////////////////////////////////////////////////////////////////// + // Sample a single track within this array at the specified sample time and + // desired rounding policy. The track sample is written out using the `track_writer` provided. + template + void sample_track(uint32_t track_index, float sample_time, sample_rounding_policy rounding_policy, track_writer_type& writer) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the raw size for this track array. Note that this differs from the actual + // memory used by an instance of this class. It is meant for comparison against + // the compressed size. + uint32_t get_raw_size() const; + + protected: + ////////////////////////////////////////////////////////////////////////// + // We prohibit copying + track_array(const track_array&) = delete; + track_array& operator=(const track_array&) = delete; + + iallocator* m_allocator; // The allocator used to allocate our tracks + track* m_tracks; // The track list + uint32_t m_num_tracks; // The number of tracks + + string m_name; // An optional name + }; + + ////////////////////////////////////////////////////////////////////////// + // A typed track array. See `track_array` for details. + ////////////////////////////////////////////////////////////////////////// + template + class track_array_typed final : public track_array + { + public: + ////////////////////////////////////////////////////////////////////////// + // The track type. + static constexpr track_type8 type = track_type_; + + ////////////////////////////////////////////////////////////////////////// + // The track category. + static constexpr track_category8 category = track_traits::category; + + ////////////////////////////////////////////////////////////////////////// + // The track member type. + using track_member_type = track_typed; + + ////////////////////////////////////////////////////////////////////////// + // Constructs an empty track array. + track_array_typed() noexcept : track_array() { static_assert(sizeof(track_array_typed) == sizeof(track_array), "You cannot add member variables to this class"); } + + ////////////////////////////////////////////////////////////////////////// + // Constructs an array with the specified number of tracks. + // Tracks will be empty and untyped by default. + track_array_typed(iallocator& allocator, uint32_t num_tracks) : track_array(allocator, num_tracks) {} + + ////////////////////////////////////////////////////////////////////////// + // Move constructor for a track array. + track_array_typed(track_array_typed&& other) noexcept : track_array(static_cast(other)) {} + + ////////////////////////////////////////////////////////////////////////// + // Destroys a track array. + ~track_array_typed() = default; + + ////////////////////////////////////////////////////////////////////////// + // Move assignment for a track array. + track_array_typed& operator=(track_array_typed&& other) noexcept { return static_cast(track_array::operator=(static_cast(other))); } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track type for tracks in this array. + track_type8 get_track_type() const { return type; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track category for tracks in this array. + track_category8 get_track_category() const { return category; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track at the specified index. + track_member_type& operator[](uint32_t index); + + ////////////////////////////////////////////////////////////////////////// + // Returns the track at the specified index. + const track_member_type& operator[](uint32_t index) const; + + ////////////////////////////////////////////////////////////////////////// + // Iterator begin() and end() implementations. + track_member_type* begin() { return track_cast(m_tracks); } + const track_member_type* begin() const { return track_cast(m_tracks); } + track_member_type* end() { return track_cast(m_tracks) + m_num_tracks; } + const track_member_type* end() const { return track_cast(m_tracks) + m_num_tracks; } + }; + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track array into the desired track array type while asserting for safety. + template + track_array_type& track_array_cast(track_array& track_array_); + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track array into the desired track array type while asserting for safety. + template + const track_array_type& track_array_cast(const track_array& track_array_); + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track array into the desired track array type. Returns nullptr if the types + // are not compatible or if the input is nullptr. + template + track_array_type* track_array_cast(track_array* track_array_); + + ////////////////////////////////////////////////////////////////////////// + // Casts an untyped track array into the desired track array type. Returns nullptr if the types + // are not compatible or if the input is nullptr. + template + const track_array_type* track_array_cast(const track_array* track_array_); + + ////////////////////////////////////////////////////////////////////////// + // Create aliases for the various typed track array types. + + using track_array_float1f = track_array_typed; + using track_array_float2f = track_array_typed; + using track_array_float3f = track_array_typed; + using track_array_float4f = track_array_typed; + using track_array_vector4f = track_array_typed; + using track_array_qvvf = track_array_typed; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/compression/impl/track_array.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track_error.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track_error.h new file mode 100644 index 00000000..b329d4fd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/track_error.h @@ -0,0 +1,113 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error_result.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/compression/track_array.h" +#include "acl/compression/transform_error_metrics.h" +#include "acl/decompression/decompress.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // A struct that contains the track index that has the worst error, + // its error, and the sample time at which it happens. + ////////////////////////////////////////////////////////////////////////// + struct track_error + { + ////////////////////////////////////////////////////////////////////////// + // The track index with the worst error. + uint32_t index = k_invalid_track_index; + + ////////////////////////////////////////////////////////////////////////// + // The worst error for the track index. + float error = 0.0F; + + ////////////////////////////////////////////////////////////////////////// + // The sample time that has the worst error. + float sample_time = 0.0F; + }; + + ////////////////////////////////////////////////////////////////////////// + // Calculates the worst compression error between a raw track array and its + // compressed tracks. + // Supports scalar tracks only. + // + // Note: This function uses SFINAE to prevent it from matching when it shouldn't. + template = nullptr> + track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks, decompression_context_type& context); + + ////////////////////////////////////////////////////////////////////////// + // Calculates the worst compression error between a raw track array and its + // compressed tracks. + // Supports scalar and transform tracks. + // + // Note: This function uses SFINAE to prevent it from matching when it shouldn't. + template = nullptr> + track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks, decompression_context_type& context, const itransform_error_metric& error_metric); + + ////////////////////////////////////////////////////////////////////////// + // Calculates the worst compression error between a raw track array and its + // compressed tracks. + // Supports transform tracks with an additive base. + // + // Note: This function uses SFINAE to prevent it from matching when it shouldn't. + template = nullptr> + track_error calculate_compression_error(iallocator& allocator, const track_array_qvvf& raw_tracks, decompression_context_type& context, const itransform_error_metric& error_metric, const track_array_qvvf& additive_base_tracks); + + ////////////////////////////////////////////////////////////////////////// + // Calculates the worst compression error between two compressed tracks instances. + // Supports scalar tracks only. + // + // Note: This function uses SFINAE to prevent it from matching when it shouldn't. + template = nullptr, acl_impl::is_decompression_context = nullptr> + track_error calculate_compression_error(iallocator& allocator, decompression_context_type0& context0, decompression_context_type1& context1); + + ////////////////////////////////////////////////////////////////////////// + // Calculates the worst compression error between two raw track arrays. + // Supports scalar tracks only. + track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1); + + ////////////////////////////////////////////////////////////////////////// + // Calculates the worst compression error between two raw track arrays. + // Supports scalar and transform tracks. + track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1, const itransform_error_metric& error_metric); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/compression/impl/track_error.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/transform_error_metrics.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/transform_error_metrics.h new file mode 100644 index 00000000..24187da4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/transform_error_metrics.h @@ -0,0 +1,513 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/additive_utils.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/hash.h" + +#include +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Interface for all skeletal error metrics. + // An error metric is responsible for a few things: + // - converting from rtm::qvvf into whatever transform type the metric uses (optional) + // - applying local space transforms on top of base transforms (optional) + // - transforming local space transforms into object space + // - evaluating the error function + // + // Most functions require two implementations: with and without scale support. + // This is entirely for performance reasons as most clips do not have any scale. + ////////////////////////////////////////////////////////////////////////// + class itransform_error_metric + { + public: + virtual ~itransform_error_metric() = default; + + ////////////////////////////////////////////////////////////////////////// + // Returns the unique name of the error metric. + virtual const char* get_name() const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Returns a unique hash to represent the error metric. + virtual uint32_t get_hash() const { return hash32(get_name()); } + + ////////////////////////////////////////////////////////////////////////// + // Returns the transform size used by the error metric. + virtual size_t get_transform_size(bool has_scale) const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether or not the error metric uses a transform that isn't rtm::qvvf. + // If this is the case, we need to convert from rtm::qvvf into the transform type + // used by the error metric. + virtual bool needs_conversion(bool has_scale) const { (void)has_scale; return false; } + + ////////////////////////////////////////////////////////////////////////// + // Input arguments for the 'convert_transforms*' functions. + ////////////////////////////////////////////////////////////////////////// + struct convert_transforms_args + { + ////////////////////////////////////////////////////////////////////////// + // A list of transform indices that are dirty and need conversion. + const uint32_t* dirty_transform_indices; + + ////////////////////////////////////////////////////////////////////////// + // The number of dirty transforms that need conversion. + uint32_t num_dirty_transforms; + + ////////////////////////////////////////////////////////////////////////// + // The input transforms in rtm::qvvf format to be converted. + const rtm::qvvf* transforms; + + ////////////////////////////////////////////////////////////////////////// + // The number of transforms in the input and output buffers. + uint32_t num_transforms; + }; + + ////////////////////////////////////////////////////////////////////////// + // Converts from rtm::qvvf into the transform type used by the error metric. + // Called when 'needs_conversion' returns true. + virtual void convert_transforms(const convert_transforms_args& args, void* out_transforms) const + { + (void)args; + (void)out_transforms; + ACL_ASSERT(false, "Not implemented"); + } + + ////////////////////////////////////////////////////////////////////////// + // Converts from rtm::qvvf into the transform type used by the error metric. + // Called when 'needs_conversion' returns true. + virtual void convert_transforms_no_scale(const convert_transforms_args& args, void* out_transforms) const + { + (void)args; + (void)out_transforms; + ACL_ASSERT(false, "Not implemented"); + } + + ////////////////////////////////////////////////////////////////////////// + // Input arguments for the 'local_to_object_space*' functions. + ////////////////////////////////////////////////////////////////////////// + struct local_to_object_space_args + { + ////////////////////////////////////////////////////////////////////////// + // A list of transform indices that are dirty and need transformation. + const uint32_t* dirty_transform_indices; + + ////////////////////////////////////////////////////////////////////////// + // The number of dirty transforms that need transformation. + uint32_t num_dirty_transforms; + + ////////////////////////////////////////////////////////////////////////// + // A list of parent transform indices for every transform. + // An index of 0xFFFF represents a root transform with no parent. + const uint32_t* parent_transform_indices; + + ////////////////////////////////////////////////////////////////////////// + // The input transforms in the type expected by the error metric to be transformed. + const void* local_transforms; + + ////////////////////////////////////////////////////////////////////////// + // The number of transforms in the input and output buffers. + uint32_t num_transforms; + }; + + ////////////////////////////////////////////////////////////////////////// + // Takes local space transforms into object space. + virtual void local_to_object_space(const local_to_object_space_args& args, void* out_object_transforms) const + { + (void)args; + (void)out_object_transforms; + ACL_ASSERT(false, "Not implemented"); + } + + ////////////////////////////////////////////////////////////////////////// + // Takes local space transforms into object space. + virtual void local_to_object_space_no_scale(const local_to_object_space_args& args, void* out_object_transforms) const + { + (void)args; + (void)out_object_transforms; + ACL_ASSERT(false, "Not implemented"); + } + + ////////////////////////////////////////////////////////////////////////// + // Input arguments for the 'apply_additive_to_base*' functions. + ////////////////////////////////////////////////////////////////////////// + struct apply_additive_to_base_args + { + ////////////////////////////////////////////////////////////////////////// + // A list of transform indices that are dirty and need the base applied. + const uint32_t* dirty_transform_indices; + + ////////////////////////////////////////////////////////////////////////// + // The number of dirty transforms that need the base applied. + uint32_t num_dirty_transforms; + + ////////////////////////////////////////////////////////////////////////// + // The input local space transforms in the type expected by the error metric. + const void* local_transforms; + + ////////////////////////////////////////////////////////////////////////// + // The input base transforms in the type expected by the error metric. + const void* base_transforms; + + ////////////////////////////////////////////////////////////////////////// + // The number of transforms in the input and output buffers. + uint32_t num_transforms; + }; + + ////////////////////////////////////////////////////////////////////////// + // Applies local space transforms on top of base transforms. + // This is called when a clip has an additive base. + virtual void apply_additive_to_base(const apply_additive_to_base_args& args, void* out_transforms) const + { + (void)args; + (void)out_transforms; + ACL_ASSERT(false, "Not implemented"); + } + + ////////////////////////////////////////////////////////////////////////// + // Applies local space transforms on top of base transforms. + // This is called when a clip has an additive base. + virtual void apply_additive_to_base_no_scale(const apply_additive_to_base_args& args, void* out_transforms) const + { + (void)args; + (void)out_transforms; + ACL_ASSERT(false, "Not implemented"); + } + + ////////////////////////////////////////////////////////////////////////// + // Input arguments for the 'calculate_error*' functions. + ////////////////////////////////////////////////////////////////////////// + struct calculate_error_args + { + ////////////////////////////////////////////////////////////////////////// + // A point on our rigid shell along the X axis. + rtm::vector4f shell_point_x; + + ////////////////////////////////////////////////////////////////////////// + // A point on our rigid shell along the Y axis. + rtm::vector4f shell_point_y; + + ////////////////////////////////////////////////////////////////////////// + // A point on our rigid shell along the Z axis. + rtm::vector4f shell_point_z; + + ////////////////////////////////////////////////////////////////////////// + // The first transform used to measure the error. + // In the type expected by the error metric. + // Could be in local or object space (same space as lossy). + const void* transform0; + + ////////////////////////////////////////////////////////////////////////// + // The second transform used to measure the error. + // In the type expected by the error metric. + // Could be in local or object space (same space as raw). + const void* transform1; + + ////////////////////////////////////////////////////////////////////////// + // We measure the error on a rigid shell around each transform. + // This shell takes the form of a sphere at a certain distance. + // When no scale is present, measuring any two points is sufficient + // but when there is scale, measuring all three is necessary. + // See ./docs/error_metrics.md for details. + void construct_sphere_shell(float shell_distance) + { + shell_point_x = rtm::vector_set(shell_distance, 0.0F, 0.0F, 0.0F); + shell_point_y = rtm::vector_set(0.0F, shell_distance, 0.0F, 0.0F); + shell_point_z = rtm::vector_set(0.0F, 0.0F, shell_distance, 0.0F); + } + }; + + ////////////////////////////////////////////////////////////////////////// + // Measures the error between a raw and lossy transform. + virtual rtm::scalarf RTM_SIMD_CALL calculate_error(const calculate_error_args& args) const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Measures the error between a raw and lossy transform. + virtual rtm::scalarf RTM_SIMD_CALL calculate_error_no_scale(const calculate_error_args& args) const = 0; + }; + + ////////////////////////////////////////////////////////////////////////// + // Uses rtm::qvvf arithmetic for local and object space error. + // Note that this can cause inaccuracy when dealing with shear/skew. + ////////////////////////////////////////////////////////////////////////// + class qvvf_transform_error_metric : public itransform_error_metric + { + public: + virtual const char* get_name() const override { return "qvvf_transform_error_metric"; } + + virtual size_t get_transform_size(bool has_scale) const override { (void)has_scale; return sizeof(rtm::qvvf); } + virtual bool needs_conversion(bool has_scale) const override { (void)has_scale; return false; } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK void local_to_object_space(const local_to_object_space_args& args, void* out_object_transforms) const override + { + const uint32_t* dirty_transform_indices = args.dirty_transform_indices; + const uint32_t* parent_transform_indices = args.parent_transform_indices; + const rtm::qvvf* local_transforms_ = static_cast(args.local_transforms); + rtm::qvvf* out_object_transforms_ = static_cast(out_object_transforms); + + const uint32_t num_dirty_transforms = args.num_dirty_transforms; + for (uint32_t dirty_transform_index = 0; dirty_transform_index < num_dirty_transforms; ++dirty_transform_index) + { + const uint32_t transform_index = dirty_transform_indices[dirty_transform_index]; + const uint32_t parent_transform_index = parent_transform_indices[transform_index]; + + rtm::qvvf obj_transform; + if (parent_transform_index == k_invalid_track_index) + obj_transform = local_transforms_[transform_index]; // Just copy the root as-is, it has no parent and thus local and object space transforms are equal + else + obj_transform = rtm::qvv_normalize(rtm::qvv_mul(local_transforms_[transform_index], out_object_transforms_[parent_transform_index])); + + out_object_transforms_[transform_index] = obj_transform; + } + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK void local_to_object_space_no_scale(const local_to_object_space_args& args, void* out_object_transforms) const override + { + const uint32_t* dirty_transform_indices = args.dirty_transform_indices; + const uint32_t* parent_transform_indices = args.parent_transform_indices; + const rtm::qvvf* local_transforms_ = static_cast(args.local_transforms); + rtm::qvvf* out_object_transforms_ = static_cast(out_object_transforms); + + const uint32_t num_dirty_transforms = args.num_dirty_transforms; + for (uint32_t dirty_transform_index = 0; dirty_transform_index < num_dirty_transforms; ++dirty_transform_index) + { + const uint32_t transform_index = dirty_transform_indices[dirty_transform_index]; + const uint32_t parent_transform_index = parent_transform_indices[transform_index]; + + rtm::qvvf obj_transform; + if (parent_transform_index == k_invalid_track_index) + obj_transform = local_transforms_[transform_index]; // Just copy the root as-is, it has no parent and thus local and object space transforms are equal + else + obj_transform = rtm::qvv_normalize(rtm::qvv_mul_no_scale(local_transforms_[transform_index], out_object_transforms_[parent_transform_index])); + + out_object_transforms_[transform_index] = obj_transform; + } + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::scalarf RTM_SIMD_CALL calculate_error(const calculate_error_args& args) const override + { + const rtm::qvvf& raw_transform_ = *static_cast(args.transform0); + const rtm::qvvf& lossy_transform_ = *static_cast(args.transform1); + + // Note that because we have scale, we must measure all three axes + const rtm::vector4f vtx0 = args.shell_point_x; + const rtm::vector4f vtx1 = args.shell_point_y; + const rtm::vector4f vtx2 = args.shell_point_z; + + const rtm::vector4f raw_vtx0 = rtm::qvv_mul_point3(vtx0, raw_transform_); + const rtm::vector4f raw_vtx1 = rtm::qvv_mul_point3(vtx1, raw_transform_); + const rtm::vector4f raw_vtx2 = rtm::qvv_mul_point3(vtx2, raw_transform_); + + const rtm::vector4f lossy_vtx0 = rtm::qvv_mul_point3(vtx0, lossy_transform_); + const rtm::vector4f lossy_vtx1 = rtm::qvv_mul_point3(vtx1, lossy_transform_); + const rtm::vector4f lossy_vtx2 = rtm::qvv_mul_point3(vtx2, lossy_transform_); + + const rtm::scalarf vtx0_error = rtm::vector_distance3(raw_vtx0, lossy_vtx0); + const rtm::scalarf vtx1_error = rtm::vector_distance3(raw_vtx1, lossy_vtx1); + const rtm::scalarf vtx2_error = rtm::vector_distance3(raw_vtx2, lossy_vtx2); + + return rtm::scalar_max(rtm::scalar_max(vtx0_error, vtx1_error), vtx2_error); + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::scalarf RTM_SIMD_CALL calculate_error_no_scale(const calculate_error_args& args) const override + { + const rtm::qvvf& raw_transform_ = *static_cast(args.transform0); + const rtm::qvvf& lossy_transform_ = *static_cast(args.transform1); + + const rtm::vector4f vtx0 = args.shell_point_x; + const rtm::vector4f vtx1 = args.shell_point_y; + + const rtm::vector4f raw_vtx0 = rtm::qvv_mul_point3_no_scale(vtx0, raw_transform_); + const rtm::vector4f raw_vtx1 = rtm::qvv_mul_point3_no_scale(vtx1, raw_transform_); + + const rtm::vector4f lossy_vtx0 = rtm::qvv_mul_point3_no_scale(vtx0, lossy_transform_); + const rtm::vector4f lossy_vtx1 = rtm::qvv_mul_point3_no_scale(vtx1, lossy_transform_); + + const rtm::scalarf vtx0_error = rtm::vector_distance3(raw_vtx0, lossy_vtx0); + const rtm::scalarf vtx1_error = rtm::vector_distance3(raw_vtx1, lossy_vtx1); + + return rtm::scalar_max(vtx0_error, vtx1_error); + } + }; + + ////////////////////////////////////////////////////////////////////////// + // Uses a mix of rtm::qvvf and rtm::matrix3x4f arithmetic. + // The local space error is always calculated with rtm::qvvf arithmetic. + // The object space error is calculated with rtm::qvvf arithmetic if there is no scale + // and with rtm::matrix3x4f arithmetic if there is scale. + // Note that this can cause inaccuracy issues if there are very large or very small + // scale values. + ////////////////////////////////////////////////////////////////////////// + class qvvf_matrix3x4f_transform_error_metric : public qvvf_transform_error_metric + { + public: + virtual const char* get_name() const override { return "qvvf_matrix3x4f_transform_error_metric"; } + + virtual size_t get_transform_size(bool has_scale) const override { return has_scale ? sizeof(rtm::matrix3x4f) : sizeof(rtm::qvvf); } + virtual bool needs_conversion(bool has_scale) const override { return has_scale; } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK void convert_transforms(const convert_transforms_args& args, void* out_transforms) const override + { + const uint32_t* dirty_transform_indices = args.dirty_transform_indices; + const rtm::qvvf* transforms_ = args.transforms; + rtm::matrix3x4f* out_transforms_ = static_cast(out_transforms); + + const uint32_t num_dirty_transforms = args.num_dirty_transforms; + for (uint32_t dirty_transform_index = 0; dirty_transform_index < num_dirty_transforms; ++dirty_transform_index) + { + const uint32_t transform_index = dirty_transform_indices[dirty_transform_index]; + + const rtm::qvvf& transform_qvv = transforms_[transform_index]; + rtm::matrix3x4f transform_mtx = rtm::matrix_from_qvv(transform_qvv); + + out_transforms_[transform_index] = transform_mtx; + } + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK void local_to_object_space(const local_to_object_space_args& args, void* out_object_transforms) const override + { + const uint32_t* dirty_transform_indices = args.dirty_transform_indices; + const uint32_t* parent_transform_indices = args.parent_transform_indices; + const rtm::matrix3x4f* local_transforms_ = static_cast(args.local_transforms); + rtm::matrix3x4f* out_object_transforms_ = static_cast(out_object_transforms); + + const uint32_t num_dirty_transforms = args.num_dirty_transforms; + for (uint32_t dirty_transform_index = 0; dirty_transform_index < num_dirty_transforms; ++dirty_transform_index) + { + const uint32_t transform_index = dirty_transform_indices[dirty_transform_index]; + const uint32_t parent_transform_index = parent_transform_indices[transform_index]; + + rtm::matrix3x4f obj_transform; + if (parent_transform_index == k_invalid_track_index) + obj_transform = local_transforms_[transform_index]; // Just copy the root as-is, it has no parent and thus local and object space transforms are equal + else + obj_transform = rtm::matrix_mul(local_transforms_[transform_index], out_object_transforms_[parent_transform_index]); + + out_object_transforms_[transform_index] = obj_transform; + } + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::scalarf RTM_SIMD_CALL calculate_error(const calculate_error_args& args) const override + { + const rtm::matrix3x4f& raw_transform_ = *static_cast(args.transform0); + const rtm::matrix3x4f& lossy_transform_ = *static_cast(args.transform1); + + // Note that because we have scale, we must measure all three axes + const rtm::vector4f vtx0 = args.shell_point_x; + const rtm::vector4f vtx1 = args.shell_point_y; + const rtm::vector4f vtx2 = args.shell_point_z; + + const rtm::vector4f raw_vtx0 = rtm::matrix_mul_point3(vtx0, raw_transform_); + const rtm::vector4f raw_vtx1 = rtm::matrix_mul_point3(vtx1, raw_transform_); + const rtm::vector4f raw_vtx2 = rtm::matrix_mul_point3(vtx2, raw_transform_); + + const rtm::vector4f lossy_vtx0 = rtm::matrix_mul_point3(vtx0, lossy_transform_); + const rtm::vector4f lossy_vtx1 = rtm::matrix_mul_point3(vtx1, lossy_transform_); + const rtm::vector4f lossy_vtx2 = rtm::matrix_mul_point3(vtx2, lossy_transform_); + + const rtm::scalarf vtx0_error = rtm::vector_distance3(raw_vtx0, lossy_vtx0); + const rtm::scalarf vtx1_error = rtm::vector_distance3(raw_vtx1, lossy_vtx1); + const rtm::scalarf vtx2_error = rtm::vector_distance3(raw_vtx2, lossy_vtx2); + + return rtm::scalar_max(rtm::scalar_max(vtx0_error, vtx1_error), vtx2_error); + } + }; + + ////////////////////////////////////////////////////////////////////////// + // Uses rtm::qvvf arithmetic for local and object space error. + // This error metric should be used whenever a clip is additive or relative. + // Note that this can cause inaccuracy when dealing with shear/skew. + ////////////////////////////////////////////////////////////////////////// + template + class additive_qvvf_transform_error_metric : public qvvf_transform_error_metric + { + public: + virtual const char* get_name() const override + { + switch (additive_format) + { + default: + case additive_clip_format8::none: return "additive_qvvf_transform_error_metric"; + case additive_clip_format8::relative: return "additive_qvvf_transform_error_metric"; + case additive_clip_format8::additive0: return "additive_qvvf_transform_error_metric"; + case additive_clip_format8::additive1: return "additive_qvvf_transform_error_metric"; + } + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK void apply_additive_to_base(const apply_additive_to_base_args& args, void* out_transforms) const override + { + const uint32_t* dirty_transform_indices = args.dirty_transform_indices; + const rtm::qvvf* local_transforms_ = static_cast(args.local_transforms); + const rtm::qvvf* base_transforms_ = static_cast(args.base_transforms); + rtm::qvvf* out_transforms_ = static_cast(out_transforms); + + const uint32_t num_dirty_transforms = args.num_dirty_transforms; + for (uint32_t dirty_transform_index = 0; dirty_transform_index < num_dirty_transforms; ++dirty_transform_index) + { + const uint32_t transform_index = dirty_transform_indices[dirty_transform_index]; + + const rtm::qvvf& local_transform = local_transforms_[transform_index]; + const rtm::qvvf& base_transform = base_transforms_[transform_index]; + const rtm::qvvf transform = acl::apply_additive_to_base(additive_format, base_transform, local_transform); + + out_transforms_[transform_index] = transform; + } + } + + virtual RTM_DISABLE_SECURITY_COOKIE_CHECK void apply_additive_to_base_no_scale(const apply_additive_to_base_args& args, void* out_transforms) const override + { + const uint32_t* dirty_transform_indices = args.dirty_transform_indices; + const rtm::qvvf* local_transforms_ = static_cast(args.local_transforms); + const rtm::qvvf* base_transforms_ = static_cast(args.base_transforms); + rtm::qvvf* out_transforms_ = static_cast(out_transforms); + + const uint32_t num_dirty_transforms = args.num_dirty_transforms; + for (uint32_t dirty_transform_index = 0; dirty_transform_index < num_dirty_transforms; ++dirty_transform_index) + { + const uint32_t transform_index = dirty_transform_indices[dirty_transform_index]; + + const rtm::qvvf& local_transform = local_transforms_[transform_index]; + const rtm::qvvf& base_transform = base_transforms_[transform_index]; + const rtm::qvvf transform = acl::apply_additive_to_base_no_scale(additive_format, base_transform, local_transform); + + out_transforms_[transform_index] = transform; + } + } + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/transform_pose_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/transform_pose_utils.h new file mode 100644 index 00000000..642dea44 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/compression/transform_pose_utils.h @@ -0,0 +1,79 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + // Note: It is safe for both pose buffers to alias since the data if sorted parent first + inline void local_to_object_space(const uint32_t* parent_indices, const rtm::qvvf* local_pose, uint32_t num_transforms, rtm::qvvf* out_object_pose) + { + if (num_transforms == 0) + return; // Nothing to do + + out_object_pose[0] = local_pose[0]; + + for (uint32_t bone_index = 1; bone_index < num_transforms; ++bone_index) + { + const uint32_t parent_bone_index = parent_indices[bone_index]; + ACL_ASSERT(parent_bone_index < num_transforms, "Invalid parent bone index: %u >= %u", parent_bone_index, num_transforms); + + out_object_pose[bone_index] = rtm::qvv_normalize(rtm::qvv_mul(local_pose[bone_index], out_object_pose[parent_bone_index])); + } + } + + // Note: It is safe for both pose buffers to alias since the data if sorted parent first + inline void object_to_local_space(const uint32_t* parent_indices, const rtm::qvvf* object_pose, uint32_t num_transforms, rtm::qvvf* out_local_pose) + { + if (num_transforms == 0) + return; // Nothing to do + + out_local_pose[0] = object_pose[0]; + + for (uint32_t bone_index = 1; bone_index < num_transforms; ++bone_index) + { + const uint32_t parent_bone_index = parent_indices[bone_index]; + ACL_ASSERT(parent_bone_index < num_transforms, "Invalid parent bone index: %u >= %u", parent_bone_index, num_transforms); + + const rtm::qvvf inv_parent_transform = rtm::qvv_inverse(object_pose[parent_bone_index]); + out_local_pose[bone_index] = rtm::qvv_normalize(rtm::qvv_mul(inv_parent_transform, object_pose[bone_index])); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/additive_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/additive_utils.h new file mode 100644 index 00000000..72f04398 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/additive_utils.h @@ -0,0 +1,201 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Describes the format used by the additive clip. + enum class additive_clip_format8 : uint8_t + { + ////////////////////////////////////////////////////////////////////////// + // Clip is not additive + none = 0, + + ////////////////////////////////////////////////////////////////////////// + // Clip is in relative space, transform_mul or equivalent is used to combine them. + // transform = transform_mul(additive_transform, base_transform) + relative = 1, + + ////////////////////////////////////////////////////////////////////////// + // Clip is in additive space where scale is combined with: base_scale * additive_scale + // transform = transform_add0(additive_transform, base_transform) + additive0 = 2, + + ////////////////////////////////////////////////////////////////////////// + // Clip is in additive space where scale is combined with: base_scale * (1.0 + additive_scale) + // transform = transform_add1(additive_transform, base_transform) + additive1 = 3, + }; + + ////////////////////////////////////////////////////////////////////////// + + // TODO: constexpr + inline const char* get_additive_clip_format_name(additive_clip_format8 format) + { + switch (format) + { + case additive_clip_format8::none: return "none"; + case additive_clip_format8::relative: return "relative"; + case additive_clip_format8::additive0: return "additive0"; + case additive_clip_format8::additive1: return "additive1"; + default: return ""; + } + } + + inline bool get_additive_clip_format(const char* format, additive_clip_format8& out_format) + { + ACL_ASSERT(format != nullptr, "Format name cannot be null"); + if (format == nullptr) + return false; + + const char* none_format = "None"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* none_format_new = "none"; + if (std::strncmp(format, none_format, std::strlen(none_format)) == 0 + || std::strncmp(format, none_format_new, std::strlen(none_format_new)) == 0) + { + out_format = additive_clip_format8::none; + return true; + } + + const char* relative_format = "Relative"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* relative_format_new = "relative"; + if (std::strncmp(format, relative_format, std::strlen(relative_format)) == 0 + || std::strncmp(format, relative_format_new, std::strlen(relative_format_new)) == 0) + { + out_format = additive_clip_format8::relative; + return true; + } + + const char* additive0_format = "Additive0"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* additive0_format_new = "additive0"; + if (std::strncmp(format, additive0_format, std::strlen(additive0_format)) == 0 + || std::strncmp(format, additive0_format_new, std::strlen(additive0_format_new)) == 0) + { + out_format = additive_clip_format8::additive0; + return true; + } + + const char* additive1_format = "Additive1"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* additive1_format_new = "additive1"; + if (std::strncmp(format, additive1_format, std::strlen(additive1_format)) == 0 + || std::strncmp(format, additive1_format_new, std::strlen(additive1_format_new)) == 0) + { + out_format = additive_clip_format8::additive1; + return true; + } + + return false; + } + + inline rtm::vector4f RTM_SIMD_CALL get_default_scale(additive_clip_format8 additive_format) + { + return additive_format == additive_clip_format8::additive1 ? rtm::vector_zero() : rtm::vector_set(1.0F); + } + + inline rtm::qvvf RTM_SIMD_CALL transform_add0(rtm::qvvf_arg0 base, rtm::qvvf_arg1 additive) + { + const rtm::quatf rotation = rtm::quat_mul(additive.rotation, base.rotation); + const rtm::vector4f translation = rtm::vector_add(additive.translation, base.translation); + const rtm::vector4f scale = rtm::vector_mul(additive.scale, base.scale); + return rtm::qvv_set(rotation, translation, scale); + } + + inline rtm::qvvf RTM_SIMD_CALL transform_add1(rtm::qvvf_arg0 base, rtm::qvvf_arg1 additive) + { + const rtm::quatf rotation = rtm::quat_mul(additive.rotation, base.rotation); + const rtm::vector4f translation = rtm::vector_add(additive.translation, base.translation); + const rtm::vector4f scale = rtm::vector_mul(rtm::vector_add(rtm::vector_set(1.0F), additive.scale), base.scale); + return rtm::qvv_set(rotation, translation, scale); + } + + inline rtm::qvvf RTM_SIMD_CALL transform_add_no_scale(rtm::qvvf_arg0 base, rtm::qvvf_arg1 additive) + { + const rtm::quatf rotation = rtm::quat_mul(additive.rotation, base.rotation); + const rtm::vector4f translation = rtm::vector_add(additive.translation, base.translation); + return rtm::qvv_set(rotation, translation, rtm::vector_set(1.0F)); + } + + inline rtm::qvvf RTM_SIMD_CALL apply_additive_to_base(additive_clip_format8 additive_format, rtm::qvvf_arg0 base, rtm::qvvf_arg1 additive) + { + switch (additive_format) + { + default: + case additive_clip_format8::none: return additive; + case additive_clip_format8::relative: return rtm::qvv_mul(additive, base); + case additive_clip_format8::additive0: return transform_add0(base, additive); + case additive_clip_format8::additive1: return transform_add1(base, additive); + } + } + + inline rtm::qvvf RTM_SIMD_CALL apply_additive_to_base_no_scale(additive_clip_format8 additive_format, rtm::qvvf_arg0 base, rtm::qvvf_arg1 additive) + { + switch (additive_format) + { + default: + case additive_clip_format8::none: return additive; + case additive_clip_format8::relative: return rtm::qvv_mul_no_scale(additive, base); + case additive_clip_format8::additive0: return transform_add_no_scale(base, additive); + case additive_clip_format8::additive1: return transform_add_no_scale(base, additive); + } + } + + inline rtm::qvvf RTM_SIMD_CALL convert_to_relative(rtm::qvvf_arg0 base, rtm::qvvf_arg1 transform) + { + return rtm::qvv_mul(transform, rtm::qvv_inverse(base)); + } + + inline rtm::qvvf RTM_SIMD_CALL convert_to_additive0(rtm::qvvf_arg0 base, rtm::qvvf_arg1 transform) + { + const rtm::quatf rotation = rtm::quat_mul(transform.rotation, rtm::quat_conjugate(base.rotation)); + const rtm::vector4f translation = rtm::vector_sub(transform.translation, base.translation); + const rtm::vector4f scale = rtm::vector_div(transform.scale, base.scale); + return rtm::qvv_set(rotation, translation, scale); + } + + inline rtm::qvvf RTM_SIMD_CALL convert_to_additive1(rtm::qvvf_arg0 base, rtm::qvvf_arg1 transform) + { + const rtm::quatf rotation = rtm::quat_mul(transform.rotation, rtm::quat_conjugate(base.rotation)); + const rtm::vector4f translation = rtm::vector_sub(transform.translation, base.translation); + const rtm::vector4f scale = rtm::vector_sub(rtm::vector_mul(transform.scale, rtm::vector_reciprocal(base.scale)), rtm::vector_set(1.0F)); + return rtm::qvv_set(rotation, translation, scale); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/algorithm_types.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/algorithm_types.h new file mode 100644 index 00000000..4b8f257e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/algorithm_types.h @@ -0,0 +1,112 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // algorithm_type8 is an enum that represents every supported algorithm. + // + // BE CAREFUL WHEN CHANGING VALUES IN THIS ENUM + // The algorithm type is serialized in the compressed data, if you change a value + // the compressed clips will be invalid. If you do, bump the appropriate algorithm versions. + enum class algorithm_type8 : uint8_t + { + uniformly_sampled = 0, + //LinearKeyReduction = 1, + //SplineKeyReduction = 2, + }; + + ////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if the algorithm type is a valid value. Used to validate if + // memory has been corrupted. + // TODO: constexpr + inline bool is_valid_algorithm_type(algorithm_type8 type) + { + switch (type) + { + case algorithm_type8::uniformly_sampled: + //case algorithm_type8::LinearKeyReduction: + //case algorithm_type8::SplineKeyReduction: + return true; + default: + return false; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns a string of the algorithm name suitable for display. + // TODO: constexpr + inline const char* get_algorithm_name(algorithm_type8 type) + { + switch (type) + { + case algorithm_type8::uniformly_sampled: return "uniformly_sampled"; + //case algorithm_type8::LinearKeyReduction: return "LinearKeyReduction"; + //case algorithm_type8::SplineKeyReduction: return "SplineKeyReduction"; + default: return ""; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if the algorithm type was properly parsed from an input string. + // + // type: A string representing the algorithm name to parse. It must match the get_algorithm_name(..) output. + // out_type: On success, it will contain the the parsed algorithm type otherwise it is left untouched. + inline bool get_algorithm_type(const char* type, algorithm_type8& out_type) + { + ACL_ASSERT(type != nullptr, "Algorithm type name cannot be null"); + if (type == nullptr) + return false; + + const char* uniformly_sampled_name = "UniformlySampled"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* uniformly_sampled_name_new = "uniformly_sampled"; + if (std::strncmp(type, uniformly_sampled_name, std::strlen(uniformly_sampled_name)) == 0 + || std::strncmp(type, uniformly_sampled_name_new, std::strlen(uniformly_sampled_name_new)) == 0) + { + out_type = algorithm_type8::uniformly_sampled; + return true; + } + + return false; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/ansi_allocator.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/ansi_allocator.h new file mode 100644 index 00000000..002da545 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/ansi_allocator.h @@ -0,0 +1,201 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/error.h" + +#if defined(__APPLE__) || defined(__EMSCRIPTEN__) + #include // For posix_memalign +#elif defined(_WIN32) + #include +#endif + +#if defined(ACL_HAS_ASSERT_CHECKS) && !defined(ACL_NO_ALLOCATOR_TRACKING) && !defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + #define ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS +#endif + +// This is used for debugging memory leaks, double frees, etc. +// It should never be enabled in production! +//#define ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS + +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + #include +#endif + +#if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS) + #include +#endif + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // An ANSI allocator implementation. It uses the system malloc/free to manage + // memory as well as provides some debugging functionality to track memory leaks. + //////////////////////////////////////////////////////////////////////////////// + class ansi_allocator : public iallocator + { + public: + ansi_allocator() + : iallocator() +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + , m_allocation_count(0) +#endif +#if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS) + , m_debug_allocations() +#endif + {} + + virtual ~ansi_allocator() override + { +#if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS) + if (!m_debug_allocations.empty()) + { + for (const auto& pair : m_debug_allocations) + { + printf("Live allocation at the allocator destruction: 0x%p (%zu)\n", pair.second.ptr, pair.second.size); + } + } +#endif + +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + ACL_ASSERT(m_allocation_count == 0, "The number of allocations and deallocations does not match"); +#endif + } + + ansi_allocator(const ansi_allocator&) = delete; + ansi_allocator& operator=(const ansi_allocator&) = delete; + + virtual void* allocate(size_t size, size_t alignment = k_default_alignment) override + { + /* + * This is a common requirement for many of the aligned allocators, see + * http://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc?view=vs-2017 + */ + ACL_ASSERT(is_power_of_two(alignment), "The alignment must be power of two."); + /* + * Another common requirement is for size to be an integral multiple of alignment, + * i.e. aligned to alignment. As this interface here is supposed to help the user out, + * just silently align it for them, while keeping the tracked size intact. + */ + size_t aligned_size = align_to(size, alignment); +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + m_allocation_count.fetch_add(1, std::memory_order_relaxed); +#endif + + void* ptr; + +#if defined(_WIN32) + ptr = _aligned_malloc(aligned_size, alignment); +#elif defined(__APPLE__) || defined(__EMSCRIPTEN__) + ptr = nullptr; + posix_memalign(&ptr, std::max(alignment, sizeof(void*)), aligned_size); +#elif defined(__ANDROID__) + // Don't bother using aligned_size here, as we're doing custom alignment, just mark the var as unused. + (void)aligned_size; + alignment = std::max(std::max(alignment, sizeof(void*)), sizeof(size_t)); + const size_t padded_size = size + alignment + sizeof(size_t); + ptr = malloc(padded_size); + if (ptr != nullptr) + { + const void* allocated_ptr = ptr; + ptr = align_to(add_offset_to_ptr(ptr, sizeof(size_t)), alignment); + + const size_t padding_size = safe_static_cast(reinterpret_cast(ptr) - reinterpret_cast(allocated_ptr)); + size_t* padding_size_ptr = add_offset_to_ptr(ptr, -sizeof(size_t)); + *padding_size_ptr = padding_size; + } +#else + ptr = aligned_alloc(alignment, aligned_size); +#endif + +#if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS) + m_debug_allocations.insert({ {ptr, AllocationEntry{ptr, size}} }); +#endif + + return ptr; + } + + virtual void deallocate(void* ptr, size_t size) override + { + if (ptr == nullptr) + return; + + (void)size; + +#if defined(_WIN32) + _aligned_free(ptr); +#elif defined(__ANDROID__) + const size_t* padding_size_ptr = add_offset_to_ptr(ptr, -sizeof(size_t)); + void* allocated_ptr = add_offset_to_ptr(ptr, -*padding_size_ptr); + free(allocated_ptr); +#else + free(ptr); +#endif + +#if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS) + const auto it = m_debug_allocations.find(ptr); + ACL_ASSERT(it != m_debug_allocations.end(), "Attempting to deallocate a pointer that isn't allocated"); + ACL_ASSERT(it->second.size == size, "Allocation and deallocation size do not match"); + m_debug_allocations.erase(ptr); +#endif + +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + const int32_t old_value = m_allocation_count.fetch_sub(1, std::memory_order_relaxed); + ACL_ASSERT(old_value > 0, "The number of allocations and deallocations does not match"); +#endif + } + +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + int32_t get_allocation_count() const { return m_allocation_count.load(std::memory_order_relaxed); } +#endif + + private: +#if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS) + std::atomic m_allocation_count; +#endif + +#if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS) + struct AllocationEntry + { + void* ptr; + size_t size; + }; + + std::unordered_map m_debug_allocations; +#endif + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/bit_manip_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/bit_manip_utils.h new file mode 100644 index 00000000..f8349c5a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/bit_manip_utils.h @@ -0,0 +1,210 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" + +#include + +#include + +#if !defined(ACL_USE_POPCOUNT) && !defined(RTM_NO_INTRINSICS) + // TODO: Enable this for PlayStation 4 as well, what is the define and can we use it in public code? + #if defined(_DURANGO) || defined(_XBOX_ONE) + // Enable pop-count type instructions on Xbox One + #define ACL_USE_POPCOUNT + #endif +#endif + +#if defined(ACL_USE_POPCOUNT) + #include +#endif + +#if !defined(ACL_BMI_INTRINSICS) && !defined(RTM_NO_INTRINSICS) + // TODO: Enable this for PlayStation 4 as well, what is the define and can we use it in public code? + #if defined(_DURANGO) || defined(_XBOX_ONE) + // Enable BMI type instructions on Xbox One + #define ACL_BMI_INTRINSICS + #elif defined(RTM_AVX_INTRINSICS) && !(defined(_MSC_VER) && defined(__clang__)) + // Enable BMI when AVX is enabled except with clang under Windows + // Note: It seems that the Clang toolchain with MSVC enables BMI only with AVX2 unlike + // MSVC which enables it with AVX + #define ACL_BMI_INTRINSICS + #endif +#endif + +#if defined(ACL_BMI_INTRINSICS) + #include // MSVC uses this header for _andn_u32 BMI intrinsic + #include // Intel documentation says _andn_u32 and others are here +#endif + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C4146: unary minus operator applied to unsigned type, result still unsigned + // This is fine because we use bitwise arithmetic and rely on the fact that unsigned + // integers use twos complement. + #pragma warning(disable : 4146) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Counts the number of '1' bits (aka: pop-count) + inline uint8_t count_set_bits(uint8_t value) + { +#if defined(ACL_USE_POPCOUNT) + return (uint8_t)_mm_popcnt_u32(value); +#elif defined(RTM_NEON_INTRINSICS) + return (uint8_t)vget_lane_u64(vcnt_u8(vcreate_u8(value)), 0); +#else + value = value - ((value >> 1) & 0x55); + value = (value & 0x33) + ((value >> 2) & 0x33); + return ((value + (value >> 4)) & 0x0F); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Counts the number of '1' bits (aka: pop-count) + inline uint16_t count_set_bits(uint16_t value) + { +#if defined(ACL_USE_POPCOUNT) + return (uint16_t)_mm_popcnt_u32(value); +#elif defined(RTM_NEON_INTRINSICS) + return (uint16_t)vget_lane_u64(vpaddl_u8(vcnt_u8(vcreate_u8(value))), 0); +#else + value = value - ((value >> 1) & 0x5555); + value = (value & 0x3333) + ((value >> 2) & 0x3333); + return uint16_t(((value + (value >> 4)) & 0x0F0F) * 0x0101) >> 8; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Counts the number of '1' bits (aka: pop-count) + inline uint32_t count_set_bits(uint32_t value) + { +#if defined(ACL_USE_POPCOUNT) + return _mm_popcnt_u32(value); +#elif defined(RTM_NEON_INTRINSICS) + return (uint32_t)vget_lane_u64(vpaddl_u16(vpaddl_u8(vcnt_u8(vcreate_u8(value)))), 0); +#else + value = value - ((value >> 1) & 0x55555555); + value = (value & 0x33333333) + ((value >> 2) & 0x33333333); + return (((value + (value >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Counts the number of '1' bits (aka: pop-count) + inline uint64_t count_set_bits(uint64_t value) + { +#if defined(ACL_USE_POPCOUNT) + return _mm_popcnt_u64(value); +#elif defined(RTM_NEON_INTRINSICS) + return vget_lane_u64(vpaddl_u32(vpaddl_u16(vpaddl_u8(vcnt_u8(vcreate_u8(value))))), 0); +#else + value = value - ((value >> 1) & 0x5555555555555555ULL); + value = (value & 0x3333333333333333ULL) + ((value >> 2) & 0x3333333333333333ULL); + return (((value + (value >> 4)) & 0x0F0F0F0F0F0F0F0FULL) * 0x0101010101010101ULL) >> 56; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Starting at the MSB, counts the number of leading zeros + inline uint32_t count_leading_zeros(uint32_t value) + { +#if defined(ACL_USE_POPCOUNT) + return _lzcnt_u32(value); +#elif defined(RTM_COMPILER_MSVC) + unsigned long first_set_bit_index; + return _BitScanReverse(&first_set_bit_index, value) ? (31 - first_set_bit_index) : 32; +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + return value != 0 ? __builtin_clz(value) : 32; +#else + value = value | (value >> 1); + value = value | (value >> 2); + value = value | (value >> 4); + value = value | (value >> 8); + value = value | (value >> 16); + return count_set_bits(~value); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Starting at the LSB, counts the number of trailing zeros + inline uint32_t count_trailing_zeros(uint32_t value) + { +#if defined(ACL_BMI_INTRINSICS) + return _tzcnt_u32(value); +#elif defined(RTM_COMPILER_MSVC) + unsigned long first_set_bit_index; + return _BitScanForward(&first_set_bit_index, value) ? first_set_bit_index : 32; +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + return value != 0 ? __builtin_ctz(value) : 32; +#else + return value != 0 ? (31 - count_leading_zeros(value & -value)) : 32; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Rotate the bits left by some amount + inline uint32_t rotate_bits_left(uint32_t value, int32_t num_bits) + { + ACL_ASSERT(num_bits >= 0, "Attempting to rotate by negative bits"); + ACL_ASSERT(num_bits < 32, "Attempting to rotate by too many bits"); + const uint32_t mask = 32 - 1; + num_bits &= mask; + return (value << num_bits) | (value >> ((-num_bits) & mask)); + } + + ////////////////////////////////////////////////////////////////////////// + // Perform: ~not_value & and_value + inline uint32_t and_not(uint32_t not_value, uint32_t and_value) + { +#if defined(ACL_BMI_INTRINSICS) + // Use BMI +#if defined(RTM_COMPILER_GCC) && !defined(_andn_u32) + return __andn_u32(not_value, and_value); // GCC doesn't define the right intrinsic symbol +#else + return _andn_u32(not_value, and_value); +#endif +#else + return ~not_value & and_value; +#endif + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/bitset.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/bitset.h new file mode 100644 index 00000000..c7468c45 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/bitset.h @@ -0,0 +1,221 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/bit_manip_utils.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // A bit set description holds the required information to ensure type and memory safety + // with the various bit set functions. + //////////////////////////////////////////////////////////////////////////////// + class bitset_description + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Creates an invalid bit set description. + constexpr bitset_description() : m_size(0) {} + + //////////////////////////////////////////////////////////////////////////////// + // Creates a bit set description from a compile time known number of bits. + template + static constexpr bitset_description make_from_num_bits() + { + static_assert(num_bits <= std::numeric_limits::max() - 31, "Number of bits exceeds the maximum number allowed"); + return bitset_description((uint32_t(num_bits) + 32 - 1) / 32); + } + + //////////////////////////////////////////////////////////////////////////////// + // Creates a bit set description from a runtime known number of bits. + inline static bitset_description make_from_num_bits(uint32_t num_bits) + { + ACL_ASSERT(num_bits <= std::numeric_limits::max() - 31, "Number of bits exceeds the maximum number allowed"); + return bitset_description((num_bits + 32 - 1) / 32); + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the number of 32 bit words used to represent the bitset. + // 1 == 32 bits, 2 == 64 bits, etc. + constexpr uint32_t get_size() const { return m_size; } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the number of bits contained within the bit set. + constexpr uint32_t get_num_bits() const { return m_size * 32; } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the number of bytes used by the bit set. + constexpr uint32_t get_num_bytes() const { return m_size * sizeof(uint32_t); } + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if the index is valid within the bit set. + constexpr bool is_bit_index_valid(uint32_t index) const { return index < get_num_bits(); } + + private: + //////////////////////////////////////////////////////////////////////////////// + // Creates a bit set description from a specified size. + explicit constexpr bitset_description(uint32_t size) : m_size(size) {} + + // Number of words required to hold the bit set + // 1 == 32 bits, 2 == 64 bits, etc. + uint32_t m_size; + }; + + ////////////////////////////////////////////////////////////////////////// + // A bit set index reference is created from a bit set description and a bit index. + // It holds the bit set word offset as well as the bit mask required. + // This is useful if you sample multiple bit sets at the same index. + ////////////////////////////////////////////////////////////////////////// + struct bitset_index_ref + { + bitset_index_ref() + : desc() + , offset(0) + , mask(0) + {} + + bitset_index_ref(bitset_description desc_, uint32_t bit_index) + : desc(desc_) + , offset(bit_index / 32) + , mask(1 << (31 - (bit_index % 32))) + { + ACL_ASSERT(desc_.is_bit_index_valid(bit_index), "Invalid bit index: %d", bit_index); + } + + bitset_description desc; + uint32_t offset; + uint32_t mask; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Resets the entire bit set to the provided value. + inline void bitset_reset(uint32_t* bitset, bitset_description desc, bool value) + { + const uint32_t mask = value ? 0xFFFFFFFF : 0x00000000; + const uint32_t size = desc.get_size(); + + for (uint32_t offset = 0; offset < size; ++offset) + bitset[offset] = mask; + } + + //////////////////////////////////////////////////////////////////////////////// + // Sets a specific bit to its desired value. + inline void bitset_set(uint32_t* bitset, bitset_description desc, uint32_t bit_index, bool value) + { + ACL_ASSERT(desc.is_bit_index_valid(bit_index), "Invalid bit index: %d", bit_index); + (void)desc; + + const uint32_t offset = bit_index / 32; + const uint32_t mask = 1 << (31 - (bit_index % 32)); + + if (value) + bitset[offset] |= mask; + else + bitset[offset] &= ~mask; + } + + //////////////////////////////////////////////////////////////////////////////// + // Sets a specific bit to its desired value. + inline void bitset_set(uint32_t* bitset, const bitset_index_ref& ref, bool value) + { + if (value) + bitset[ref.offset] |= ref.mask; + else + bitset[ref.offset] &= ~ref.mask; + } + + //////////////////////////////////////////////////////////////////////////////// + // Sets a specified range of bits to a specified value. + inline void bitset_set_range(uint32_t* bitset, bitset_description desc, uint32_t start_bit_index, uint32_t num_bits, bool value) + { + ACL_ASSERT(desc.is_bit_index_valid(start_bit_index), "Invalid start bit index: %d", start_bit_index); + ACL_ASSERT(start_bit_index + num_bits <= desc.get_num_bits(), "Invalid num bits: %d > %d", start_bit_index + num_bits, desc.get_num_bits()); + + const uint32_t end_bit_offset = start_bit_index + num_bits; + for (uint32_t offset = start_bit_index; offset < end_bit_offset; ++offset) + bitset_set(bitset, desc, offset, value); + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the bit value as a specific index. + inline bool bitset_test(const uint32_t* bitset, bitset_description desc, uint32_t bit_index) + { + ACL_ASSERT(desc.is_bit_index_valid(bit_index), "Invalid bit index: %d", bit_index); + (void)desc; + + const uint32_t offset = bit_index / 32; + const uint32_t mask = 1 << (31 - (bit_index % 32)); + + return (bitset[offset] & mask) != 0; + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the bit value as a specific index. + inline bool bitset_test(const uint32_t* bitset, const bitset_index_ref& ref) + { + return (bitset[ref.offset] & ref.mask) != 0; + } + + //////////////////////////////////////////////////////////////////////////////// + // Counts the total number of set (true) bits within the bit set. + inline uint32_t bitset_count_set_bits(const uint32_t* bitset, bitset_description desc) + { + const uint32_t size = desc.get_size(); + + // TODO: Optimize for NEON by using the intrinsic directly and unrolling the loop to + // reduce the number of pairwise add instructions. + uint32_t num_set_bits = 0; + for (uint32_t offset = 0; offset < size; ++offset) + num_set_bits += count_set_bits(bitset[offset]); + + return num_set_bits; + } + + ////////////////////////////////////////////////////////////////////////// + // Performs the operation: result = ~not_value & and_value + // Bit sets must have the same description + // Bit sets can alias + inline void bitset_and_not(uint32_t* bitset_result, const uint32_t* bitset_not_value, const uint32_t* bitset_and_value, bitset_description desc) + { + const uint32_t size = desc.get_size(); + + for (uint32_t offset = 0; offset < size; ++offset) + bitset_result[offset] = and_not(bitset_not_value[offset], bitset_and_value[offset]); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/buffer_tag.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/buffer_tag.h new file mode 100644 index 00000000..ae472724 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/buffer_tag.h @@ -0,0 +1,59 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // This enum represents raw buffer identification tags + enum class buffer_tag32 : uint32_t + { + ////////////////////////////////////////////////////////////////////////// + // Identifies a 'CompressedClip' buffer. + // Deprecated, no longer used. Belonged to pre-2.0 file format. + compressed_clip = 0xac10ac10, + + ////////////////////////////////////////////////////////////////////////// + // Identifies a 'compressed_tracks' buffer. + compressed_tracks = 0xac11ac11, + + ////////////////////////////////////////////////////////////////////////// + // Identifies a 'compressed_database' buffer. + compressed_database = 0xac11db01, + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_database.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_database.h new file mode 100644 index 00000000..a623ee04 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_database.h @@ -0,0 +1,172 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/buffer_tag.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/error_result.h" +#include "acl/core/hash.h" +#include "acl/core/quality_tiers.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/impl/compressed_headers.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // An instance of a compressed database. + // The compressed data immediately follows this instance in memory. + // The total size of the buffer can be queried with `get_size()`. + // A compressed database can either contain all the data inline within its buffer + // in one blob or it can be split into smaller chunks that can be streamed in and out. + //////////////////////////////////////////////////////////////////////////////// + class alignas(16) compressed_database final + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Returns the size in bytes of the compressed database. + // Includes the 'compressed_database' instance size and the size of the inline bulk data (if present). + uint32_t get_size() const { return m_buffer_header.size; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the total size in bytes of the compressed database. + // Includes non-inline bulk data. + uint32_t get_total_size() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the size in bytes of the bulk data for the specified tier (medium or low). + uint32_t get_bulk_data_size(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether or not the specified quality tier contains bulk data. + bool has_bulk_data(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the hash for the compressed database. + // This is only used for sanity checking in case of memory corruption. + uint32_t get_hash() const { return m_buffer_header.hash; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the hash of the bulk data for the specified tier (medium or low). + // This is only used for sanity checking in case of memory corruption. + uint32_t get_bulk_data_hash(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the binary tag for the compressed database. + // This uniquely identifies the buffer as a proper 'compressed_database' object. + buffer_tag32 get_tag() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the binary format version. + compressed_tracks_version16 get_version() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of chunks contained in this database for the specified tier (medium or low). + uint32_t get_num_chunks(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the maximum size in bytes of each chunk. + uint32_t get_max_chunk_size() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of clips contained in this database. + uint32_t get_num_clips() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of segments contained in this database (for all clips). + uint32_t get_num_segments() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether or not the bulk data is stored inline in this compressed database. + bool is_bulk_data_inline() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns a pointer to the bulk data for the specified tier (medium or low) when it is inline, nullptr otherwise. + const uint8_t* get_bulk_data(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the compressed database contains the provided compressed tracks instance. + bool contains(const compressed_tracks& tracks) const; + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if the compressed database is valid and usable. + // This mainly validates some invariants as well as ensuring that the + // memory has not been corrupted. + // + // check_hash: If true, the compressed tracks hash will also be compared. + error_result is_valid(bool check_hash) const; + + private: + //////////////////////////////////////////////////////////////////////////////// + // Hide everything + compressed_database() = delete; + compressed_database(const compressed_database&) = delete; + compressed_database(compressed_database&&) = delete; + compressed_database* operator=(const compressed_database&) = delete; + compressed_database* operator=(compressed_database&&) = delete; + + //////////////////////////////////////////////////////////////////////////////// + // Raw buffer header that isn't included in the hash. + //////////////////////////////////////////////////////////////////////////////// + + acl_impl::raw_buffer_header m_buffer_header; + + //////////////////////////////////////////////////////////////////////////////// + // Everything starting here is included in the hash. + //////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // Compressed data follows here in memory. + ////////////////////////////////////////////////////////////////////////// + + // Here we define some unspecified padding but the 'database_header' starts here. + // This is done to ensure that this class is 16 byte aligned without requiring further padding + // if the 'database_header' ends up causing us to be unaligned. + uint32_t m_padding[2]; + }; + + ////////////////////////////////////////////////////////////////////////// + // Create a compressed_database instance in place from a raw memory buffer. + // If the buffer does not contain a valid compressed_database instance, nullptr is returned + // along with an optional error result. + ////////////////////////////////////////////////////////////////////////// + const compressed_database* make_compressed_database(const void* buffer, error_result* out_error_result = nullptr); + compressed_database* make_compressed_database(void* buffer, error_result* out_error_result = nullptr); + + constexpr uint32_t k_database_bulk_data_alignment = alignof(acl_impl::database_chunk_header); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/core/impl/compressed_database.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_tracks.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_tracks.h new file mode 100644 index 00000000..38c0891c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_tracks.h @@ -0,0 +1,180 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/algorithm_types.h" +#include "acl/core/buffer_tag.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/error_result.h" +#include "acl/core/hash.h" +#include "acl/core/track_desc.h" +#include "acl/core/track_types.h" +#include "acl/core/utils.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/impl/compressed_headers.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // An instance of a compressed tracks. + // The compressed data immediately follows this instance in memory. + // The total size of the buffer can be queried with `get_size()`. + //////////////////////////////////////////////////////////////////////////////// + class alignas(16) compressed_tracks final + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Returns the algorithm type used to compress the tracks. + algorithm_type8 get_algorithm_type() const; + + //////////////////////////////////////////////////////////////////////////////// + // Returns the size in bytes of the compressed tracks. + // Includes the 'compressed_tracks' instance size. + uint32_t get_size() const { return m_buffer_header.size; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the hash for the compressed tracks. + // This is only used for sanity checking in case of memory corruption. + uint32_t get_hash() const { return m_buffer_header.hash; } + + ////////////////////////////////////////////////////////////////////////// + // Returns the binary tag for the compressed tracks. + // This uniquely identifies the buffer as a proper 'compressed_tracks' object. + buffer_tag32 get_tag() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the binary format version. + compressed_tracks_version16 get_version() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of tracks contained. + uint32_t get_num_tracks() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the number of samples each track contains. + uint32_t get_num_samples_per_track() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the type of the compressed tracks. + track_type8 get_track_type() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the duration of each track. + float get_duration() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the finite duration of each track. + float get_finite_duration() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the sample rate used by each track. + float get_sample_rate() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether or not this clip is split into a compressed database instance. + bool has_database() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the track list name if metadata is present, nullptr otherwise. + const char* get_name() const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the track name for the specified track index if metadata is present, k_invalid_track_index otherwise. + const char* get_track_name(uint32_t track_index) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the parent track index for the specified track index if metadata is present, nullptr otherwise. + uint32_t get_parent_track_index(uint32_t track_index) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns the track description for the specified track index if metadata is present. + // Returns true on success, false otherwise. + bool get_track_description(uint32_t track_index, track_desc_scalarf& out_description) const; + bool get_track_description(uint32_t track_index, track_desc_transformf& out_description) const; + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if the compressed tracks are valid and usable. + // This mainly validates some invariants as well as ensuring that the + // memory has not been corrupted. + // + // check_hash: If true, the compressed tracks hash will also be compared. + error_result is_valid(bool check_hash) const; + + private: + //////////////////////////////////////////////////////////////////////////////// + // Hide everything + compressed_tracks() = delete; + compressed_tracks(const compressed_tracks&) = delete; + compressed_tracks(compressed_tracks&&) = delete; + compressed_tracks* operator=(const compressed_tracks&) = delete; + compressed_tracks* operator=(compressed_tracks&&) = delete; + + //////////////////////////////////////////////////////////////////////////////// + // Raw buffer header that isn't included in the hash. + //////////////////////////////////////////////////////////////////////////////// + + acl_impl::raw_buffer_header m_buffer_header; + + //////////////////////////////////////////////////////////////////////////////// + // Everything starting here is included in the hash. + //////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // Compressed data follows here in memory. + ////////////////////////////////////////////////////////////////////////// + + // Here we define some unspecified padding but the 'tracks_header' starts here. + // This is done to ensure that this class is 16 byte aligned without requiring further padding + // if the 'tracks_header' ends up causing us to be unaligned. + uint32_t m_padding[2]; + }; + + ////////////////////////////////////////////////////////////////////////// + // Create a compressed_tracks instance in place from a raw memory buffer. + // If the buffer does not contain a valid compressed_tracks instance, nullptr is returned + // along with an optional error result. + ////////////////////////////////////////////////////////////////////////// + const compressed_tracks* make_compressed_tracks(const void* buffer, error_result* out_error_result = nullptr); + + ////////////////////////////////////////////////////////////////////////// + // Create a compressed_tracks instance in place from a raw memory buffer. + // If the buffer does not contain a valid compressed_tracks instance, nullptr is returned + // along with an optional error result. + ////////////////////////////////////////////////////////////////////////// + compressed_tracks* make_compressed_tracks(void* buffer, error_result* out_error_result = nullptr); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/core/impl/compressed_tracks.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_tracks_version.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_tracks_version.h new file mode 100644 index 00000000..4ee05a54 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/compressed_tracks_version.h @@ -0,0 +1,87 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Enum for versions used by 'compressed_tracks'. + // These values are used by serialization, do not change them once assigned. + // To add a new version, create a new entry and follow the naming and numbering scheme + // described below. + enum class compressed_tracks_version16 : uint16_t + { + ////////////////////////////////////////////////////////////////////////// + // Special version identifier used when decompressing. + // This indicates that any version is supported by decompression and + // the code isn't optimized for any one version in particular. + // It is not a valid value for compressed tracks. + any = 0, + + ////////////////////////////////////////////////////////////////////////// + // Special version identifier used when decompressing. + // This indicates that no version is supported by decompression. + // It is not a valid value for compressed tracks. + none = 0xFFFF, + + ////////////////////////////////////////////////////////////////////////// + // Actual versions in sequential order. + // Keep the enum values sequential. + // Enum value name should be of the form: major, minor, patch version. + // Two digits are reserved for safety and future proofing. + + //v00_01_00 = 0, // ACL v0.1.0 + //v00_06_00 = 1, // ACL v0.6.0 + //v00_08_00 = 2, // ACL v0.8.0 + //v01_01_00 = 3, // ACL v1.1.0 + //v01_02_00 = 4, // ACL v1.2.0 + //v01_03_00 = 5, // ACL v1.3.0 + //v01_99_99 = 6, // ACL v2.0.0-wip + v02_00_00 = 7, // ACL v2.0.0 + + ////////////////////////////////////////////////////////////////////////// + // First version marker, this is equal to the first version supported: ACL 2.0.0 + // Versions prior to ACL 2.0 are not backwards compatible. + // It is used for range checks. + first = v02_00_00, + + ////////////////////////////////////////////////////////////////////////// + // Always assigned to the latest version supported. + latest = v02_00_00, + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/enum_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/enum_utils.h new file mode 100644 index 00000000..cd656688 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/enum_utils.h @@ -0,0 +1,105 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +// This macro defines common operators for manipulating bit flags + +#define ACL_IMPL_ENUM_FLAGS_OPERATORS(enum_type) \ + constexpr enum_type operator|(enum_type lhs, enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + return static_cast(static_cast(lhs) | static_cast(rhs)); \ + } \ + inline void operator|=(enum_type& lhs, enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + lhs = static_cast(static_cast(lhs) | static_cast(rhs)); \ + } \ + constexpr enum_type operator&(enum_type lhs, enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + return static_cast(static_cast(lhs) & static_cast(rhs)); \ + } \ + inline void operator&=(enum_type& lhs, enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + lhs = static_cast(static_cast(lhs) & static_cast(rhs)); \ + } \ + constexpr enum_type operator^(enum_type lhs, enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + return static_cast(static_cast(lhs) ^ static_cast(rhs)); \ + } \ + inline void operator^=(enum_type& lhs, enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + lhs = static_cast(static_cast(lhs) ^ static_cast(rhs)); \ + } \ + constexpr enum_type operator~(enum_type rhs) \ + { \ + using integral_type = typename std::underlying_type::type; \ + using raw_type = typename std::make_unsigned::type; \ + return static_cast(~static_cast(rhs)); \ + } + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if any of the requested flags are set. + template + constexpr bool are_any_enum_flags_set(enum_type flags, enum_type flags_to_test) + { + using IntegralType = typename std::underlying_type::type; + return static_cast(flags & flags_to_test) != 0; + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if all of the requested flags are set. + template + constexpr bool are_all_enum_flags_set(enum_type flags, enum_type flags_to_test) + { + using IntegralType = typename std::underlying_type::type; + return static_cast(flags & flags_to_test) == static_cast(flags_to_test); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/error.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/error.h new file mode 100644 index 00000000..a0f86c31 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/error.h @@ -0,0 +1,191 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +ACL_IMPL_FILE_PRAGMA_PUSH + +////////////////////////////////////////////////////////////////////////// +// This library uses a simple system to handle asserts. Asserts are fatal and must terminate +// otherwise the behavior is undefined if execution continues. +// +// A total of 4 behaviors are supported: +// - We can print to stderr and abort +// - We can throw and exception +// - We can call a custom function +// - Do nothing and strip the check at compile time (default behavior) +// +// Aborting: +// In order to enable the aborting behavior, simply define the macro ACL_ON_ASSERT_ABORT: +// #define ACL_ON_ASSERT_ABORT +// +// Throwing: +// In order to enable the throwing behavior, simply define the macro ACL_ON_ASSERT_THROW: +// #define ACL_ON_ASSERT_THROW +// Note that the type of the exception thrown is acl::runtime_assert. +// +// Custom function: +// In order to enable the custom function calling behavior, define the macro ACL_ON_ASSERT_CUSTOM +// with the name of the function to call: +// #define ACL_ON_ASSERT_CUSTOM on_custom_assert_impl +// Note that the function signature is as follow: +// void on_custom_assert_impl(const char* expression, int line, const char* file, const char* format, ...) {} +// +// You can also define your own assert implementation by defining the ACL_ASSERT macro as well: +// #define ACL_ON_ASSERT_CUSTOM +// #define ACL_ASSERT(expression, format, ...) checkf(expression, ANSI_TO_TCHAR(format), #__VA_ARGS__) +// +// No checks: +// By default if no macro mentioned above is defined, all asserts will be stripped +// at compile time. +////////////////////////////////////////////////////////////////////////// + +#if defined(ACL_ON_ASSERT_ABORT) + + #include "acl/version.h" + + #include + #include + #include + + namespace acl + { + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace error_impl + { + inline void on_assert_abort(const char* expression, int line, const char* file, const char* format, ...) + { + (void)expression; + (void)line; + (void)file; + + va_list args; + va_start(args, format); + + std::vfprintf(stderr, format, args); + std::fprintf(stderr, "\n"); + + va_end(args); + + std::abort(); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END + } + + #define ACL_ASSERT(expression, format, ...) do { if (!(expression)) ACL_IMPL_NAMESPACE::error_impl::on_assert_abort(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(0) + #define ACL_HAS_ASSERT_CHECKS + #define ACL_NO_EXCEPT noexcept + +#elif defined(ACL_ON_ASSERT_THROW) + + #include "acl/version.h" + + #include + #include + #include + #include + + namespace acl + { + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + class runtime_assert final : public std::runtime_error + { + using std::runtime_error::runtime_error; // Inherit constructors + }; + + namespace error_impl + { + inline void on_assert_throw(const char* expression, int line, const char* file, const char* format, ...) + { + (void)expression; + (void)line; + (void)file; + + constexpr int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + va_list args; + va_start(args, format); + + const int count = vsnprintf(buffer, buffer_size, format, args); + + va_end(args); + + if (count >= 0 && count < buffer_size) + throw runtime_assert(std::string(&buffer[0], count)); + else + throw runtime_assert("Failed to format assert message!\n"); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END + } + + #define ACL_ASSERT(expression, format, ...) do { if (!(expression)) ACL_IMPL_NAMESPACE::error_impl::on_assert_throw(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(0) + #define ACL_HAS_ASSERT_CHECKS + #define ACL_NO_EXCEPT + +#elif defined(ACL_ON_ASSERT_CUSTOM) + + #if !defined(ACL_ASSERT) + #define ACL_ASSERT(expression, format, ...) do { if (!(expression)) ACL_ON_ASSERT_CUSTOM(#expression, __LINE__, __FILE__, (format), ## __VA_ARGS__); } while(0) + #endif + + #define ACL_HAS_ASSERT_CHECKS + #define ACL_NO_EXCEPT + +#else + + #define ACL_ASSERT(expression, format, ...) ((void)0) + #define ACL_NO_EXCEPT noexcept + +#endif + +////////////////////////////////////////////////////////////////////////// + +// Allow deprecation support +#if defined(__has_cpp_attribute) && __cplusplus >= 201402L + #if __has_cpp_attribute(deprecated) + #define ACL_DEPRECATED(msg) [[deprecated(msg)]] + #endif +#endif + +#if !defined(ACL_DEPRECATED) + #if defined(__GNUC__) || defined(__clang__) + #define ACL_DEPRECATED(msg) __attribute__((deprecated)) + #elif defined(_MSC_VER) + #define ACL_DEPRECATED(msg) __declspec(deprecated) + #else + #define ACL_DEPRECATED(msg) + #endif +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/error_result.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/error_result.h new file mode 100644 index 00000000..87ad6eec --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/error_result.h @@ -0,0 +1,61 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // This class is meant to encapsulate a simple error message and easily + // coerce with 'bool' in order to simplify and clean up error handling. + // In an ideal world, I would love for a C++11 equivalent of std::result in Rust. + ////////////////////////////////////////////////////////////////////////// + class error_result + { + public: + error_result() : m_error(nullptr) {} + explicit error_result(const char* error) : m_error(error) {} + + bool empty() const { return m_error == nullptr; } + bool any() const { return m_error != nullptr; } + + const char* c_str() const { return m_error != nullptr ? m_error : ""; } + + void reset() { m_error = nullptr; } + + private: + const char* m_error; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/floating_point_exceptions.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/floating_point_exceptions.h new file mode 100644 index 00000000..e4f7b37e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/floating_point_exceptions.h @@ -0,0 +1,157 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Stores the floating point environment information. + ////////////////////////////////////////////////////////////////////////// + struct fp_environment + { +#if defined(RTM_SSE2_INTRINSICS) + unsigned int exception_mask; +#elif defined(RTM_NEON_INTRINSICS) + // TODO: Implement on ARM. API to do this is not consistent across Android, Windows ARM, and iOS + // and on top of it, most ARM CPUs out there do not raise the SIGFPE trap so they are silent +#endif + }; + + ////////////////////////////////////////////////////////////////////////// + // Enables floating point exceptions for invalid operations, division by zero, and overflow. + ////////////////////////////////////////////////////////////////////////// + inline void enable_fp_exceptions(fp_environment& out_old_env) + { +#if defined(RTM_SSE2_INTRINSICS) + // We only care about SSE and not x87 + // Clear any exceptions that might have been raised already + _MM_SET_EXCEPTION_STATE(0); + // Cache the exception mask we had so we can restore it later + out_old_env.exception_mask = _MM_GET_EXCEPTION_MASK(); + // Enable our exceptions + const unsigned int exception_flags = _MM_MASK_INVALID | _MM_MASK_DIV_ZERO | _MM_MASK_OVERFLOW; + _MM_SET_EXCEPTION_MASK(~exception_flags & _MM_MASK_MASK); +#else + (void)out_old_env; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Disables all floating point exceptions. + ////////////////////////////////////////////////////////////////////////// + inline void disable_fp_exceptions(fp_environment& out_old_env) + { +#if defined(RTM_SSE2_INTRINSICS) + // We only care about SSE and not x87 + // Cache the exception mask we had so we can restore it later + out_old_env.exception_mask = _MM_GET_EXCEPTION_MASK(); + // Disable all exceptions + _MM_SET_EXCEPTION_MASK(_MM_MASK_MASK); +#else + (void)out_old_env; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Restores a previously set floating point environment. + ////////////////////////////////////////////////////////////////////////// + inline void restore_fp_exceptions(const fp_environment& env) + { +#if defined(RTM_SSE2_INTRINSICS) + // We only care about SSE and not x87 + // Clear any exceptions that might have been raised already + _MM_SET_EXCEPTION_STATE(0); + // Restore our old mask value + _MM_SET_EXCEPTION_MASK(env.exception_mask); +#else + (void)env; +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // Enables floating point exceptions in the parent scope for invalid operations, division by zero, and overflow. + ////////////////////////////////////////////////////////////////////////// + class scope_enable_fp_exceptions + { + public: + scope_enable_fp_exceptions() + { + enable_fp_exceptions(env); + } + + ~scope_enable_fp_exceptions() + { + restore_fp_exceptions(env); + } + + private: + // Prevent copy or move + scope_enable_fp_exceptions(const scope_enable_fp_exceptions&) = delete; + scope_enable_fp_exceptions(scope_enable_fp_exceptions&&) = delete; + scope_enable_fp_exceptions& operator=(const scope_enable_fp_exceptions&) = delete; + scope_enable_fp_exceptions& operator=(scope_enable_fp_exceptions&&) = delete; + + fp_environment env; + }; + + ////////////////////////////////////////////////////////////////////////// + // Disables all floating point exceptions in the parent scope. + ////////////////////////////////////////////////////////////////////////// + class scope_disable_fp_exceptions + { + public: + scope_disable_fp_exceptions() + { + disable_fp_exceptions(env); + } + + ~scope_disable_fp_exceptions() + { + restore_fp_exceptions(env); + } + + private: + // Prevent copy or move + scope_disable_fp_exceptions(const scope_disable_fp_exceptions&) = delete; + scope_disable_fp_exceptions(scope_disable_fp_exceptions&&) = delete; + scope_disable_fp_exceptions& operator=(const scope_disable_fp_exceptions&) = delete; + scope_disable_fp_exceptions& operator=(scope_disable_fp_exceptions&&) = delete; + + fp_environment env; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/fwd.h new file mode 100644 index 00000000..fdb70192 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/fwd.h @@ -0,0 +1,111 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public ACL types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + enum class additive_clip_format8 : uint8_t; + enum class algorithm_type8 : uint8_t; + enum class buffer_tag32 : uint32_t; + enum class compressed_tracks_version16 : uint16_t; + enum class range_reduction_flags8 : uint8_t; + enum class rotation_format8 : uint8_t; + enum class vector_format8 : uint8_t; + union track_format8; + enum class animation_track_type8 : uint8_t; + enum class rotation_variant8 : uint8_t; + + enum class quality_tier; + enum class sample_rounding_policy; + + class iallocator; + class ansi_allocator; + + class bitset_description; + struct bitset_index_ref; + + class compressed_database; + class compressed_tracks; + + class error_result; + class runtime_assert; + + struct fp_environment; + class scope_enable_fp_exceptions; + class scope_disable_fp_exceptions; + + namespace hash_impl + { + template + class fnv1a_impl; + } + + using fnv1a_32 = hash_impl::fnv1a_impl; + using fnv1a_64 = hash_impl::fnv1a_impl; + + namespace acl_impl + { + template + class iterator_impl; + } + + template + using iterator = acl_impl::iterator_impl; + + template + using const_iterator = acl_impl::iterator_impl; + + struct invalid_ptr_offset; + template class ptr_offset; + template using ptr_offset16 = ptr_offset; + template using ptr_offset32 = ptr_offset; + + class scope_profiler; + + class string; + + struct track_writer; + struct track_desc_scalarf; + struct track_desc_transformf; + + template class deleter; + + struct BoneBitRate; // TODO: Should be in impl namespace in ACL 3.0 + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/hash.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/hash.h new file mode 100644 index 00000000..e450ff33 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/hash.h @@ -0,0 +1,146 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace hash_impl + { + //////////////////////////////////////////////////////////////////////////////// + // FNV 1a hash implementation for 32/64 bit hashes. + //////////////////////////////////////////////////////////////////////////////// + template + class fnv1a_impl final + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Constructs a hash structures and initializes it with the offset basis. + constexpr fnv1a_impl() + : m_state(OffsetBasis) + {} + + //////////////////////////////////////////////////////////////////////////////// + // Updates the current hash with the new provided data. + // + // data: A pointer to the memory buffer to hash. + // size: The memory buffer size in bytes to hash. + void update(const void* data, size_t size) + { + const uint8_t* cdata = static_cast(data); + ResultType acc = m_state; + for (size_t i = 0; i < size; ++i) + { + const ResultType next = cdata[i]; + acc = (acc ^ next) * Prime; + } + m_state = acc; + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the current hash digest value. + constexpr ResultType digest() const { return m_state; } + + private: + static_assert(std::is_unsigned::value, "need unsigned integer"); + + // The running hash digest value. + ResultType m_state; + }; + } + + //////////////////////////////////////////////////////////////////////////////// + // A 32 bit hash instance type + using fnv1a_32 = hash_impl::fnv1a_impl; + + //////////////////////////////////////////////////////////////////////////////// + // A 64 bit hash instance type + using fnv1a_64 = hash_impl::fnv1a_impl; + + //////////////////////////////////////////////////////////////////////////////// + // Returns the 32 bit hash of the provided buffer and size in bytes. + inline uint32_t hash32(const void* buffer, size_t buffer_size) + { + fnv1a_32 hashfn = fnv1a_32(); + hashfn.update(buffer, buffer_size); + return hashfn.digest(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the 32 bit hash of the provided element. + template + inline uint32_t hash32(const ElementType& element) { return hash32(&element, sizeof(ElementType)); } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the 32 bit hash of the provided string. + // The null terminator not included in the hash. + inline uint32_t hash32(const char* str) + { + const size_t buffer_size = str != nullptr ? std::strlen(str) : 0; + return hash32(str, buffer_size); + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the 64 bit hash of the provided buffer and size in bytes. + inline uint64_t hash64(const void* buffer, size_t buffer_size) + { + fnv1a_64 hashfn = fnv1a_64(); + hashfn.update(buffer, buffer_size); + return hashfn.digest(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the 64 bit hash of the provided element. + template + inline uint64_t hash64(const ElementType& element) { return hash64(&element, sizeof(ElementType)); } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the 64 bit hash of the provided string. + // The null terminator not included in the hash. + inline uint64_t hash64(const char* str) + { + const size_t buffer_size = str != nullptr ? std::strlen(str) : 0; + return hash64(str, buffer_size); + } + + //////////////////////////////////////////////////////////////////////////////// + // Combines two hashes into a new one. + inline uint32_t hash_combine(uint32_t hash_a, uint32_t hash_b) { return (hash_a ^ hash_b) * 16777619U; } + inline uint64_t hash_combine(uint64_t hash_a, uint64_t hash_b) { return (hash_a ^ hash_b) * 1099511628211ULL; } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/iallocator.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/iallocator.h new file mode 100644 index 00000000..a920c214 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/iallocator.h @@ -0,0 +1,148 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/memory_utils.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // A simple memory allocator interface. + // + // In order to integrate this library into your own code base, you will + // need to provide some functions with an allocator instance that derives + // from this interface. + // + // See ansi_allocator.h for an implementation that uses the system malloc/free. + //////////////////////////////////////////////////////////////////////////////// + class iallocator + { + public: + static constexpr size_t k_default_alignment = 16; + + iallocator() noexcept = default; + virtual ~iallocator() = default; + + //////////////////////////////////////////////////////////////////////////////// + // Allocates memory with the specified size and alignment. + // + // size: Size in bytes to allocate. + // alignment: Alignment to allocate the memory with. + virtual void* allocate(size_t size, size_t alignment = k_default_alignment) = 0; + + //////////////////////////////////////////////////////////////////////////////// + // Deallocates previously allocated memory and releases it. + // + // ptr: A pointer to memory previously allocated or nullptr. + // size: Size in bytes of the allocation. This will match the original size requested through 'allocate'. + virtual void deallocate(void* ptr, size_t size) = 0; + }; + + ////////////////////////////////////////////////////////////////////////// + + template + allocated_type* allocate_type(iallocator& allocator, args&&... arguments) + { + allocated_type* ptr = reinterpret_cast(allocator.allocate(sizeof(allocated_type), alignof(allocated_type))); + if (acl_impl::is_trivially_default_constructible::value) + return ptr; + return new(ptr) allocated_type(std::forward(arguments)...); + } + + template + allocated_type* allocate_type_aligned(iallocator& allocator, size_t alignment, args&&... arguments) + { + ACL_ASSERT(is_alignment_valid(alignment), "Invalid alignment: %u. Expected a power of two at least equal to %u", alignment, alignof(allocated_type)); + allocated_type* ptr = reinterpret_cast(allocator.allocate(sizeof(allocated_type), alignment)); + if (acl_impl::is_trivially_default_constructible::value) + return ptr; + return new(ptr) allocated_type(std::forward(arguments)...); + } + + template + void deallocate_type(iallocator& allocator, allocated_type* ptr) + { + if (ptr == nullptr) + return; + + if (!std::is_trivially_destructible::value) + ptr->~allocated_type(); + + allocator.deallocate(ptr, sizeof(allocated_type)); + } + + template + allocated_type* allocate_type_array(iallocator& allocator, size_t num_elements, args&&... arguments) + { + allocated_type* ptr = reinterpret_cast(allocator.allocate(sizeof(allocated_type) * num_elements, alignof(allocated_type))); + if (acl_impl::is_trivially_default_constructible::value) + return ptr; + for (size_t element_index = 0; element_index < num_elements; ++element_index) + new(&ptr[element_index]) allocated_type(std::forward(arguments)...); + return ptr; + } + + template + allocated_type* allocate_type_array_aligned(iallocator& allocator, size_t num_elements, size_t alignment, args&&... arguments) + { + ACL_ASSERT(is_alignment_valid(alignment), "Invalid alignment: %zu. Expected a power of two at least equal to %zu", alignment, alignof(allocated_type)); + allocated_type* ptr = reinterpret_cast(allocator.allocate(sizeof(allocated_type) * num_elements, alignment)); + if (acl_impl::is_trivially_default_constructible::value) + return ptr; + for (size_t element_index = 0; element_index < num_elements; ++element_index) + new(&ptr[element_index]) allocated_type(std::forward(arguments)...); + return ptr; + } + + template + void deallocate_type_array(iallocator& allocator, allocated_type* elements, size_t num_elements) + { + if (elements == nullptr) + return; + + if (!std::is_trivially_destructible::value) + { + for (size_t element_index = 0; element_index < num_elements; ++element_index) + elements[element_index].~allocated_type(); + } + + allocator.deallocate(elements, sizeof(allocated_type) * num_elements); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compiler_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compiler_utils.h new file mode 100644 index 00000000..d84bc9f5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compiler_utils.h @@ -0,0 +1,100 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" + +#include + +#include + +#if !defined(__GNUG__) || defined(_LIBCPP_VERSION) || defined(_GLIBCXX_USE_CXX11_ABI) + #include +#else + #include +#endif + +////////////////////////////////////////////////////////////////////////// +// Because this library is made entirely of headers, we have no control over the +// compilation flags used. However, in some cases, certain options must be forced. +// To do this, every header is wrapped in two macros to push and pop the necessary +// pragmas. +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_COMPILER_MSVC) + #define ACL_IMPL_FILE_PRAGMA_PUSH \ + /* Disable fast math, it can hurt precision for little to no performance gain due to the high level of hand tuned optimizations. */ \ + __pragma(float_control(precise, on, push)) + + #define ACL_IMPL_FILE_PRAGMA_POP \ + __pragma(float_control(pop)) +#else + #define ACL_IMPL_FILE_PRAGMA_PUSH + #define ACL_IMPL_FILE_PRAGMA_POP +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // The version of the STL shipped with versions of GCC older than 5.1 are missing a number of type traits and functions, + // such as std::is_trivially_default_constructible. + // In this case, we polyfill the proper standard names using the deprecated std::has_trivial_default_constructor. + // This must also be done when the compiler is clang when it makes use of the GCC implementation of the STL, + // which is the default behavior on linux. Properly detecting the version of the GCC STL used by clang cannot + // be done with the __GNUC__ macro, which are overridden by clang. Instead, we check for the definition + // of the macro ``_GLIBCXX_USE_CXX11_ABI`` which is only defined with GCC versions greater than 5. + ////////////////////////////////////////////////////////////////////////// + namespace acl_impl + { +#if !defined(__GNUG__) || defined(_LIBCPP_VERSION) || defined(_GLIBCXX_USE_CXX11_ABI) + using std::strtoull; + + template + using is_trivially_default_constructible = std::is_trivially_default_constructible; +#else + using ::strtoull; + + template + using is_trivially_default_constructible = std::has_trivial_default_constructor; +#endif + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +////////////////////////////////////////////////////////////////////////// +// Silence compiler warnings within switch cases that fall through +// Note: C++17 has [[fallthrough]]; +////////////////////////////////////////////////////////////////////////// +#if defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + #if defined(__has_attribute) && __has_attribute(fallthrough) + #define ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL __attribute__ ((fallthrough)) + #else + #define ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL (void)0 + #endif +#else + #define ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL (void)0 +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_database.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_database.impl.h new file mode 100644 index 00000000..1bf0691d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_database.impl.h @@ -0,0 +1,198 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compressed_database.h + +#include "acl/version.h" + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + // Hide these implementations, they shouldn't be needed in user-space + inline const database_header& get_database_header(const compressed_database& db) + { + return *reinterpret_cast(reinterpret_cast(&db) + sizeof(raw_buffer_header)); + } + } + + inline uint32_t compressed_database::get_total_size() const + { + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + if (header.get_is_bulk_data_inline()) + return m_buffer_header.size; + else + return m_buffer_header.size + header.bulk_data_size[0] + header.bulk_data_size[1]; + } + + inline uint32_t compressed_database::get_bulk_data_size(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return 0; + + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + const uint32_t tier_index = uint32_t(tier) - 1; + return header.bulk_data_size[tier_index]; + } + + inline bool compressed_database::has_bulk_data(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return false; + + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + const uint32_t tier_index = uint32_t(tier) - 1; + return header.bulk_data_size[tier_index] != 0; + } + + inline uint32_t compressed_database::get_bulk_data_hash(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return 0; + + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + const uint32_t tier_index = uint32_t(tier) - 1; + return header.bulk_data_hash[tier_index]; + } + + inline buffer_tag32 compressed_database::get_tag() const { return static_cast(acl_impl::get_database_header(*this).tag); } + + inline compressed_tracks_version16 compressed_database::get_version() const { return acl_impl::get_database_header(*this).version; } + + inline uint32_t compressed_database::get_num_chunks(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return 0; + + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + const uint32_t tier_index = uint32_t(tier) - 1; + return header.num_chunks[tier_index]; + } + + inline uint32_t compressed_database::get_max_chunk_size() const { return acl_impl::get_database_header(*this).max_chunk_size; } + + inline uint32_t compressed_database::get_num_clips() const { return acl_impl::get_database_header(*this).num_clips; } + + inline uint32_t compressed_database::get_num_segments() const { return acl_impl::get_database_header(*this).num_segments; } + + inline bool compressed_database::is_bulk_data_inline() const { return acl_impl::get_database_header(*this).get_is_bulk_data_inline(); } + + inline const uint8_t* compressed_database::get_bulk_data(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return nullptr; + + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + const uint32_t tier_index = uint32_t(tier) - 1; + return header.bulk_data_offset[tier_index].safe_add_to(&header); + } + + inline bool compressed_database::contains(const compressed_tracks& tracks) const + { + if (!tracks.has_database()) + return false; // Clip not bound to anything + + const acl_impl::database_header& db_header = acl_impl::get_database_header(*this); + const acl_impl::database_clip_metadata* clips = db_header.get_clip_metadatas(); + const uint32_t clip_hash = tracks.get_hash(); + const uint32_t num_clips = db_header.num_clips; + for (uint32_t clip_index = 0; clip_index < num_clips; ++clip_index) + { + if (clips[clip_index].clip_hash == clip_hash) + return true; // Contained! + } + + // We didn't find our clip + return false; + } + + inline error_result compressed_database::is_valid(bool check_hash) const + { + if (!is_aligned_to(this, alignof(compressed_database))) + return error_result("Invalid alignment"); + + const acl_impl::database_header& header = acl_impl::get_database_header(*this); + if (header.tag != static_cast(buffer_tag32::compressed_database)) + return error_result("Invalid tag"); + + if (header.version < compressed_tracks_version16::first || header.version > compressed_tracks_version16::latest) + return error_result("Invalid database version"); + + if (check_hash) + { + const uint32_t hash = hash32(safe_ptr_cast(&m_padding[0]), m_buffer_header.size - sizeof(acl_impl::raw_buffer_header)); + if (hash != m_buffer_header.hash) + return error_result("Invalid hash"); + } + + return error_result(); + } + + namespace acl_impl + { + inline const compressed_database* make_compressed_database_impl(const void* buffer, error_result* out_error_result) + { + if (buffer == nullptr) + { + if (out_error_result != nullptr) + *out_error_result = error_result("Buffer is not a valid pointer"); + + return nullptr; + } + + const compressed_database* db = static_cast(buffer); + if (out_error_result != nullptr) + { + const error_result result = db->is_valid(false); + *out_error_result = result; + + if (result.any()) + return nullptr; + } + + return db; + } + } + + inline const compressed_database* make_compressed_database(const void* buffer, error_result* out_error_result) + { + return acl_impl::make_compressed_database_impl(buffer, out_error_result); + } + + inline compressed_database* make_compressed_database(void* buffer, error_result* out_error_result) + { + return const_cast(acl_impl::make_compressed_database_impl(buffer, out_error_result)); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_headers.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_headers.h new file mode 100644 index 00000000..a4e354cc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_headers.h @@ -0,0 +1,592 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/algorithm_types.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/ptr_offset.h" +#include "acl/core/quality_tiers.h" +#include "acl/core/range_reduction_types.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + class compressed_tracks; + + namespace acl_impl + { + // Common header to all binary formats + struct raw_buffer_header + { + // Total size in bytes of the raw buffer. + uint32_t size; + + // Hash of the raw buffer. + uint32_t hash; + }; + + // Header for 'compressed_tracks' + struct tracks_header + { + // Serialization tag used to distinguish raw buffer types. + uint32_t tag; + + // Serialization version used to compress the tracks. + compressed_tracks_version16 version; + + // Algorithm type used to compress the tracks. + algorithm_type8 algorithm_type; + + // Type of the tracks contained in this compressed stream. + track_type8 track_type; + + // The total number of tracks. + uint32_t num_tracks; + + // The total number of samples per track. + uint32_t num_samples; + + // The sample rate our tracks use. + float sample_rate; // TODO: Store duration as float instead? + + // Miscellaneous packed values + uint32_t misc_packed; + + ////////////////////////////////////////////////////////////////////////// + // Accessors for 'misc_packed' + + // Scalar tracks use it like this (listed from LSB): + // Bits [0, 31): unused (31 bits) + // Bit [31, 32): has metadata? + + // Transform tracks use it like this (listed from LSB): + // Bit 0: has scale? + // Bit 1: default scale: 0,0,0 or 1,1,1 (bool/bit) + // Bit 2: scale format + // Bit 3: translation format + // Bits [4, 8): rotation format (4 bits) + // Bit 8: has database? + // Bits [9, 31): unused (22 bits) + // Bit 31: has metadata? + + bool get_has_scale() const { return (misc_packed & 1) != 0; } + void set_has_scale(bool has_scale) { misc_packed = (misc_packed & ~1) | static_cast(has_scale); } + int32_t get_default_scale() const { return (misc_packed >> 1) & 1; } + void set_default_scale(uint32_t scale) { ACL_ASSERT(scale == 0 || scale == 1, "Invalid default scale"); misc_packed = (misc_packed & ~(1 << 1)) | (scale << 1); } + vector_format8 get_scale_format() const { return static_cast((misc_packed >> 2) & 1); } + void set_scale_format(vector_format8 format) { misc_packed = (misc_packed & ~(1 << 2)) | (static_cast(format) << 2); } + vector_format8 get_translation_format() const { return static_cast((misc_packed >> 3) & 1); } + void set_translation_format(vector_format8 format) { misc_packed = (misc_packed & ~(1 << 3)) | (static_cast(format) << 3); } + rotation_format8 get_rotation_format() const { return static_cast((misc_packed >> 4) & 15); } + void set_rotation_format(rotation_format8 format) { misc_packed = (misc_packed & ~(15 << 4)) | (static_cast(format) << 4); } + bool get_has_database() const { return (misc_packed & (1 << 8)) != 0; } + void set_has_database(bool has_database) { misc_packed = (misc_packed & ~(1 << 8)) | (static_cast(has_database) << 8); } + bool get_has_metadata() const { return (misc_packed >> 31) != 0; } + void set_has_metadata(bool has_metadata) { misc_packed = (misc_packed & ~(1 << 31)) | (static_cast(has_metadata) << 31); } + }; + + // Scalar track metadata + struct track_metadata + { + uint8_t bit_rate; + }; + + // Header for scalar 'compressed_tracks' + struct scalar_tracks_header + { + // The number of bits used for a whole frame of data. + // The sum of one sample per track with all bit rates taken into account. + uint32_t num_bits_per_frame; + + // Various data offsets relative to the start of this header. + ptr_offset32 metadata_per_track; + ptr_offset32 track_constant_values; + ptr_offset32 track_range_values; + ptr_offset32 track_animated_values; + + ////////////////////////////////////////////////////////////////////////// + + track_metadata* get_track_metadata() { return metadata_per_track.add_to(this); } + const track_metadata* get_track_metadata() const { return metadata_per_track.add_to(this); } + + float* get_track_constant_values() { return track_constant_values.add_to(this); } + const float* get_track_constant_values() const { return track_constant_values.add_to(this); } + + float* get_track_range_values() { return track_range_values.add_to(this); } + const float* get_track_range_values() const { return track_range_values.add_to(this); } + + uint8_t* get_track_animated_values() { return track_animated_values.add_to(this); } + const uint8_t* get_track_animated_values() const { return track_animated_values.add_to(this); } + }; + + //////////////////////////////////////////////////////////////////////////////// + // A compressed clip segment header. Each segment is built from a uniform number + // of samples per track. A clip is split into one or more segments. + //////////////////////////////////////////////////////////////////////////////// + struct segment_header + { + // Number of bits used by a fully animated pose (excludes default/constant tracks). + uint32_t animated_pose_bit_size; + + // Number of bits used by a fully animated pose per sub-track type (excludes default/constant tracks). + uint32_t animated_rotation_bit_size; + uint32_t animated_translation_bit_size; + + // Offset to the animated segment data, relative to the start of the transform_tracks_header + // Segment data is partitioned as follows: + // - format per variable track (no alignment) + // - range data per variable track (only when more than one segment) (2 byte alignment) + // - track data sorted per sample then per track (4 byte alignment) + ptr_offset32 segment_data; + }; + + //////////////////////////////////////////////////////////////////////////////// + // A compressed clip segment header. Each segment is built from a uniform number + // of samples per track. A clip is split into one or more segments. + // Only valid when a clip is split into a database. + //////////////////////////////////////////////////////////////////////////////// + struct segment_tier0_header + { + // Same layout as segment_header with new data at the end to allow safe usage under the segment_header type + + // Number of bits used by a fully animated pose (excludes default/constant tracks). + uint32_t animated_pose_bit_size; + + // Number of bits used by a fully animated pose per sub-track type (excludes default/constant tracks). + uint32_t animated_rotation_bit_size; + uint32_t animated_translation_bit_size; + + // Offset to the animated segment data, relative to the start of the transform_tracks_header + // Segment data is partitioned as follows: + // - format per variable track (no alignment) + // - range data per variable track (only when more than one segment) (2 byte alignment) + // - track data sorted per sample then per track (4 byte alignment) + ptr_offset32 segment_data; + + // Bit set of which sample indices are stored in this clip (tier 0). + uint32_t sample_indices; + }; + + ////////////////////////////////////////////////////////////////////////// + // A packed structure with metadata for animated groups. + ////////////////////////////////////////////////////////////////////////// + struct animated_group_metadata + { + // Bits [0, 14): the group size + // Bits [14, 16): the group type + uint16_t metadata; + + bool is_valid() const { return metadata != 0xFFFF; } + + animation_track_type8 get_type() const { return static_cast(metadata >> 6); } + void set_type(animation_track_type8 type) { metadata = (metadata & ~(3 << 6)) | static_cast(type) << 6; } + + uint32_t get_size() const { return static_cast(metadata) & ((1 << 14) - 1); } + void set_size(uint32_t size) { ACL_ASSERT(size < (1 << 14), "Group size too large"); metadata = (metadata & ~((1 << 14) - 1)) | static_cast(size); } + }; + + struct database_runtime_clip_header; // Forward declare + + // Header for database related metadata in 'compressed_tracks' + struct tracks_database_header + { + // Offset of the runtime clip header to update when we stream in/out. + ptr_offset32 clip_header_offset; + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + database_runtime_clip_header* get_clip_header(void* base) { return clip_header_offset.add_to(base); } + const database_runtime_clip_header* get_clip_header(const void* base) const { return clip_header_offset.add_to(base); } + }; + + ////////////////////////////////////////////////////////////////////////// + // A 32 bit integer that contains packed sub-track types. + // Each sub-track type is packed on 2 bits starting with the MSB. + // 0 = default/padding, 1 = constant, 2 = animated + ////////////////////////////////////////////////////////////////////////// + struct packed_sub_track_types + { + uint32_t types; + }; + + const uint32_t k_num_sub_tracks_per_packed_entry = 16; // 2 bits each within a 32 bit entry + + // Header for transform 'compressed_tracks' + struct transform_tracks_header + { + // The number of segments contained. + uint32_t num_segments; + + // The number of animated rot/trans/scale tracks. + uint32_t num_animated_variable_sub_tracks; // Might be padded with dummy tracks for alignment + uint32_t num_animated_rotation_sub_tracks; + uint32_t num_animated_translation_sub_tracks; + uint32_t num_animated_scale_sub_tracks; + + // The number of constant sub-track samples stored, does not include default samples + uint32_t num_constant_rotation_samples; + uint32_t num_constant_translation_samples; + uint32_t num_constant_scale_samples; + + // Offset to the database metadata header. + ptr_offset32 database_header_offset; + + // Offset to the segment headers data (tier 0 if split into database). + union + { + ptr_offset32 segment_headers_offset; + ptr_offset32 segment_tier0_headers_offset; + }; + + // Offset to the packed sub-track types. + ptr_offset32 sub_track_types_offset; + + // Offset to the constant tracks data. + ptr_offset32 constant_track_data_offset; + + // Offset to the clip range data. + ptr_offset32 clip_range_data_offset; // TODO: Make this offset optional? Only present if using variable bit rate + + ////////////////////////////////////////////////////////////////////////// + + bool has_multiple_segments() const { return num_segments > 1; } + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + uint32_t* get_segment_start_indices() { ACL_ASSERT(has_multiple_segments(), "Must have multiple segments to contain segment indices"); return add_offset_to_ptr(this, align_to(sizeof(transform_tracks_header), 4)); } + const uint32_t* get_segment_start_indices() const { ACL_ASSERT(has_multiple_segments(), "Must have multiple segments to contain segment indices"); return add_offset_to_ptr(this, align_to(sizeof(transform_tracks_header), 4)); } + + tracks_database_header* get_database_header() { return database_header_offset.safe_add_to(this); } + const tracks_database_header* get_database_header() const { return database_header_offset.safe_add_to(this); } + + segment_header* get_segment_headers() { return segment_headers_offset.add_to(this); } + const segment_header* get_segment_headers() const { return segment_headers_offset.add_to(this); } + + segment_tier0_header* get_segment_tier0_headers() { return segment_tier0_headers_offset.add_to(this); } + const segment_tier0_header* get_segment_tier0_headers() const { return segment_tier0_headers_offset.add_to(this); } + + packed_sub_track_types* get_sub_track_types() { return sub_track_types_offset.add_to(this); } + const packed_sub_track_types* get_sub_track_types() const { return sub_track_types_offset.add_to(this); } + + uint8_t* get_constant_track_data() { return constant_track_data_offset.add_to(this); } + const uint8_t* get_constant_track_data() const { return constant_track_data_offset.add_to(this); } + + uint8_t* get_clip_range_data() { return clip_range_data_offset.add_to(this); } + const uint8_t* get_clip_range_data() const { return clip_range_data_offset.add_to(this); } + + template + void get_segment_data(const segment_header_type& header, uint8_t*& out_format_per_track_data, uint8_t*& out_range_data, uint8_t*& out_animated_data) + { + uint8_t* segment_data = header.segment_data.add_to(this); + + uint8_t* format_per_track_data = segment_data; + + uint8_t* range_data = align_to(format_per_track_data + num_animated_variable_sub_tracks, 2); + const uint32_t range_data_size = has_multiple_segments() ? (k_segment_range_reduction_num_bytes_per_component * 6 * num_animated_variable_sub_tracks) : 0; + + uint8_t* animated_data = align_to(range_data + range_data_size, 4); + + out_format_per_track_data = format_per_track_data; + out_range_data = range_data; + out_animated_data = animated_data; + } + + template + void get_segment_data(const segment_header_type& header, const uint8_t*& out_format_per_track_data, const uint8_t*& out_range_data, const uint8_t*& out_animated_data) const + { + const uint8_t* segment_data = header.segment_data.add_to(this); + + const uint8_t* format_per_track_data = segment_data; + + const uint8_t* range_data = align_to(format_per_track_data + num_animated_variable_sub_tracks, 2); + const uint32_t range_data_size = has_multiple_segments() ? (k_segment_range_reduction_num_bytes_per_component * 6 * num_animated_variable_sub_tracks) : 0; + + const uint8_t* animated_data = align_to(range_data + range_data_size, 4); + + out_format_per_track_data = format_per_track_data; + out_range_data = range_data; + out_animated_data = animated_data; + } + }; + + ////////////////////////////////////////////////////////////////////////// + // How much error each frame contributes to at most + // Frames that cannot be removed have infinite error (e.g. first and last frame of the clip) + struct frame_contributing_error + { + uint32_t index; // Segment relative frame index + float error; // Contributing error + }; + + // Header for optional track metadata, must be at least 15 bytes + struct optional_metadata_header + { + ptr_offset32 track_list_name; + ptr_offset32 track_name_offsets; + ptr_offset32 parent_track_indices; + ptr_offset32 track_descriptions; + ptr_offset32 contributing_error; + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + char* get_track_list_name(compressed_tracks& tracks) { return track_list_name.safe_add_to(&tracks); } + const char* get_track_list_name(const compressed_tracks& tracks) const { return track_list_name.safe_add_to(&tracks); } + uint32_t* get_track_name_offsets(compressed_tracks& tracks) { return track_name_offsets.safe_add_to(&tracks); } + const uint32_t* get_track_name_offsets(const compressed_tracks& tracks) const { return track_name_offsets.safe_add_to(&tracks); } + uint32_t* get_parent_track_indices(compressed_tracks& tracks) { return parent_track_indices.safe_add_to(&tracks); } + const uint32_t* get_parent_track_indices(const compressed_tracks& tracks) const { return parent_track_indices.safe_add_to(&tracks); } + uint8_t* get_track_descriptions(compressed_tracks& tracks) { return track_descriptions.safe_add_to(&tracks); } + const uint8_t* get_track_descriptions(const compressed_tracks& tracks) const { return track_descriptions.safe_add_to(&tracks); } + frame_contributing_error* get_contributing_error(compressed_tracks& tracks) { return contributing_error.safe_add_to(&tracks); } + const frame_contributing_error* get_contributing_error(const compressed_tracks& tracks) const { return contributing_error.safe_add_to(&tracks); } + }; + + static_assert(sizeof(optional_metadata_header) >= 15, "Optional metadata must be at least 15 bytes"); + + ////////////////////////////////////////////////////////////////////////// + // Runtime metadata has the following layout: + // [ database_runtime_clip_header, database_runtime_segment_header+, database_runtime_clip_header, database_runtime_segment_header+, ... ] + // Each clip header is followed by a list of segment headers, one for each segment contained in the clip. + // The pattern repeats for every clip contained in the database. + + // Header for runtime database segments + struct database_runtime_segment_header + { + // Each segment can be split into at most 3 tiers with tier 0 being in the compressed clip itself. + // As such, each segment can be split into at most 2 tiers within the database, each with it's own + // chunk. Each segment contains at most 32 samples. Tiers are sorted in order from most important + // to least important and as such should stream in that order. + + // For thread safety reasons when streaming in asynchronously, we use a 64 bit atomic value per tier. + // This ensures that when we read and write to it, both the offset and the indices are updated in lock + // step. This avoids the need for fences on 64 bit systems. + // Each tier value contains: (sample offset << 32) | sample indices + // Sample indices is a bit set of which sample indices are stored in our chunk + // Sample offset to the data. Zero if the data isn't used or streamed in. Relative to start of bulk data. + std::atomic tier_metadata[2]; + }; + + // Header for runtime database clips, 8 byte alignment to match database_runtime_segment_header + struct alignas(8) database_runtime_clip_header + { + // Hash of the compressed clip stored in this entry + uint32_t clip_hash; + + uint32_t padding; + + // Segment headers follow in memory + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + database_runtime_segment_header* get_segment_headers() { return add_offset_to_ptr(this, sizeof(database_runtime_clip_header)); } + const database_runtime_segment_header* get_segment_headers() const { return add_offset_to_ptr(this, sizeof(database_runtime_clip_header)); } + }; + + ////////////////////////////////////////////////////////////////////////// + // Chunk data has the following layout: + // [ database_chunk_header, database_chunk_segment_header+, sample data ... ] + // Each chunk starts with a header for itself and one header per segment it contains. + // Segments might belong to different clips within the same chunk. + // The actual sample data follows afterwards. + + // Header for compressed database chunk segment header + struct database_chunk_segment_header + { + // Hash of the compressed clip stored in this segment + uint32_t clip_hash; + + // Bit set of which sample indices are stored in this chunk. + uint32_t sample_indices; + + // Offset in the chunk where the segment sample data begins. Relative to start of bulk data. + ptr_offset32 samples_offset; + + // Offset of the runtime clip header to update when we stream in/out + ptr_offset32 clip_header_offset; + + // Offset of the runtime segment header to update when we stream in/out + ptr_offset32 segment_header_offset; + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + database_runtime_clip_header* get_clip_header(void* base) const { return clip_header_offset.add_to(base); } + const database_runtime_clip_header* get_clip_header(const void* base) const { return clip_header_offset.add_to(base); } + + database_runtime_segment_header* get_segment_header(void* base) const { return segment_header_offset.add_to(base); } + const database_runtime_segment_header* get_segment_header(const void* base) const { return segment_header_offset.add_to(base); } + }; + + static_assert(alignof(database_chunk_segment_header), "Alignment must be 4 bytes"); + + // Header for compressed database bulk data chunks + struct database_chunk_header + { + // Each chunk contains multiple segments and is at most 1 MB in size. + + // Chunk index. + uint32_t index; + + // Chunk size in bytes + uint32_t size; + + // Number of segments stored in this chunk. + uint32_t num_segments; + + // Chunk segment headers and sample data follows in memory + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + database_chunk_segment_header* get_segment_headers() { return add_offset_to_ptr(this, sizeof(database_chunk_header)); } + const database_chunk_segment_header* get_segment_headers() const { return add_offset_to_ptr(this, sizeof(database_chunk_header)); } + }; + + static_assert((sizeof(database_chunk_header) % 4) == 0, "Header size must be a multiple of 4 bytes"); + + ////////////////////////////////////////////////////////////////////////// + // Standalone database metadata headers + + // Header for compressed database chunk descriptions + struct database_chunk_description + { + // Size in bytes of this chunk. + uint32_t size; + + // Offset in bulk data for this chunk, relative to the start of the bulk data. + ptr_offset32 offset; + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + database_chunk_header* get_chunk_header(void* base) const { return offset.add_to(base); } + const database_chunk_header* get_chunk_header(const void* base) const { return offset.add_to(base); } + }; + + struct database_clip_metadata + { + // Hash of the compressed clip stored in this entry + uint32_t clip_hash; + + // Offset of the runtime clip header to update when we stream in/out + ptr_offset32 clip_header_offset; + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + database_runtime_clip_header* get_clip_header(void* base) const { return clip_header_offset.add_to(base); } + const database_runtime_clip_header* get_clip_header(const void* base) const { return clip_header_offset.add_to(base); } + }; + + // Header for 'compressed_database' + // We use arrays so we can index with (tier - 1) as our index + // Index 0 = medium importance tier, index 1 = low importance + struct database_header + { + // Serialization tag used to distinguish raw buffer types. + uint32_t tag; + + // Serialization version used to compress the database. + compressed_tracks_version16 version; + + // Misc packed data. + uint16_t misc_packed; + + // Number of chunks stored in the bulk data for each tier. + uint32_t num_chunks[k_num_database_tiers]; + + // Max chunk size contained within + uint32_t max_chunk_size; + + // Number of clips stored in the database. + uint32_t num_clips; + + // Number of segments stored in the database. + uint32_t num_segments; + + // Offset to a list of clip metadata contained in this database. + ptr_offset32 clip_metadata_offset; + + // Size in bytes of the bulk data for each tier. + uint32_t bulk_data_size[k_num_database_tiers]; + + // Offset to the bulk data for each tier (optional, omitted if not inline). + ptr_offset32 bulk_data_offset[k_num_database_tiers]; + + // Hash of the bulk data for each tier. + uint32_t bulk_data_hash[k_num_database_tiers]; + + // Chunk descriptions follow in memory + + ////////////////////////////////////////////////////////////////////////// + // Accessors for 'misc_packed' + + // Listed from LSB: + // Bit 0: is bulk data inline? + // Bits [1, 16): unused (15 bits) + + bool get_is_bulk_data_inline() const { return (misc_packed & (1 << 0)) != 0; } + void set_is_bulk_data_inline(bool is_inline) { misc_packed = (misc_packed & ~(1 << 0)) | (static_cast(is_inline) << 0); } + + ////////////////////////////////////////////////////////////////////////// + // Utility functions that return pointers from their respective offsets. + + // Follows the header + database_chunk_description* get_chunk_descriptions_medium() { return add_offset_to_ptr(this, align_to(sizeof(database_header), 4)); } + const database_chunk_description* get_chunk_descriptions_medium() const { return add_offset_to_ptr(this, align_to(sizeof(database_header), 4)); } + + // Follows the medium descriptions + database_chunk_description* get_chunk_descriptions_low() { return add_offset_to_ptr(this, align_to(align_to(sizeof(database_header), 4) + num_chunks[0] * sizeof(database_chunk_description), 4)); } + const database_chunk_description* get_chunk_descriptions_low() const { return add_offset_to_ptr(this, align_to(align_to(sizeof(database_header), 4) + num_chunks[0] * sizeof(database_chunk_description), 4)); } + + database_clip_metadata* get_clip_metadatas() { return clip_metadata_offset.add_to(this); } + const database_clip_metadata* get_clip_metadatas() const { return clip_metadata_offset.add_to(this); } + + uint8_t* get_bulk_data_medium() { return bulk_data_offset[0].safe_add_to(this); } + const uint8_t* get_bulk_data_medium() const { return bulk_data_offset[0].safe_add_to(this); } + + uint8_t* get_bulk_data_low() { return bulk_data_offset[1].safe_add_to(this); } + const uint8_t* get_bulk_data_low() const { return bulk_data_offset[1].safe_add_to(this); } + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_tracks.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_tracks.impl.h new file mode 100644 index 00000000..c9148eca --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/compressed_tracks.impl.h @@ -0,0 +1,260 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from compressed_tracks.h + +#include "acl/version.h" +#include "acl/core/track_desc.h" + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + // Hide these implementations, they shouldn't be needed in user-space + inline const tracks_header& get_tracks_header(const compressed_tracks& tracks) + { + return *reinterpret_cast(reinterpret_cast(&tracks) + sizeof(raw_buffer_header)); + } + + inline const scalar_tracks_header& get_scalar_tracks_header(const compressed_tracks& tracks) + { + return *reinterpret_cast(reinterpret_cast(&tracks) + sizeof(raw_buffer_header) + sizeof(tracks_header)); + } + + inline transform_tracks_header& get_transform_tracks_header(compressed_tracks& tracks) + { + return *reinterpret_cast(reinterpret_cast(&tracks) + sizeof(raw_buffer_header) + sizeof(tracks_header)); + } + + inline const transform_tracks_header& get_transform_tracks_header(const compressed_tracks& tracks) + { + return *reinterpret_cast(reinterpret_cast(&tracks) + sizeof(raw_buffer_header) + sizeof(tracks_header)); + } + + inline const optional_metadata_header& get_optional_metadata_header(const compressed_tracks& tracks) + { + return *reinterpret_cast(reinterpret_cast(&tracks) + tracks.get_size() - sizeof(optional_metadata_header)); + } + } + + inline algorithm_type8 compressed_tracks::get_algorithm_type() const { return acl_impl::get_tracks_header(*this).algorithm_type; } + + inline buffer_tag32 compressed_tracks::get_tag() const { return static_cast(acl_impl::get_tracks_header(*this).tag); } + + inline compressed_tracks_version16 compressed_tracks::get_version() const { return acl_impl::get_tracks_header(*this).version; } + + inline uint32_t compressed_tracks::get_num_tracks() const { return acl_impl::get_tracks_header(*this).num_tracks; } + + inline uint32_t compressed_tracks::get_num_samples_per_track() const { return acl_impl::get_tracks_header(*this).num_samples; } + + inline track_type8 compressed_tracks::get_track_type() const { return acl_impl::get_tracks_header(*this).track_type; } + + inline float compressed_tracks::get_duration() const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + return calculate_duration(header.num_samples, header.sample_rate); + } + + inline float compressed_tracks::get_finite_duration() const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + return calculate_finite_duration(header.num_samples, header.sample_rate); + } + + inline float compressed_tracks::get_sample_rate() const { return acl_impl::get_tracks_header(*this).sample_rate; } + + inline bool compressed_tracks::has_database() const { return acl_impl::get_tracks_header(*this).get_has_database(); } + + inline const char* compressed_tracks::get_name() const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + if (!header.get_has_metadata()) + return nullptr; // No metadata is stored + + const acl_impl::optional_metadata_header& metadata_header = acl_impl::get_optional_metadata_header(*this); + if (!metadata_header.track_name_offsets.is_valid()) + return nullptr; // Metadata isn't stored + + return metadata_header.get_track_list_name(*this); + } + + inline const char* compressed_tracks::get_track_name(uint32_t track_index) const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + if (!header.get_has_metadata()) + return nullptr; // No metadata is stored + + ACL_ASSERT(track_index < header.num_tracks, "Invalid track index"); + if (track_index >= header.num_tracks) + return nullptr; // Invalid track index + + const acl_impl::optional_metadata_header& metadata_header = acl_impl::get_optional_metadata_header(*this); + if (!metadata_header.track_name_offsets.is_valid()) + return nullptr; // Metadata isn't stored + + const uint32_t* track_names_offsets = metadata_header.get_track_name_offsets(*this); + const ptr_offset32 offset = track_names_offsets[track_index]; + return offset.add_to(track_names_offsets); + } + + inline uint32_t compressed_tracks::get_parent_track_index(uint32_t track_index) const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + if (!header.get_has_metadata()) + return k_invalid_track_index; // No metadata is stored + + ACL_ASSERT(track_index < header.num_tracks, "Invalid track index"); + if (track_index >= header.num_tracks) + return k_invalid_track_index; // Invalid track index + + const acl_impl::optional_metadata_header& metadata_header = acl_impl::get_optional_metadata_header(*this); + if (!metadata_header.parent_track_indices.is_valid()) + return k_invalid_track_index; // Metadata isn't stored + + const uint32_t* parent_track_indices = metadata_header.get_parent_track_indices(*this); + return parent_track_indices[track_index]; + } + + inline bool compressed_tracks::get_track_description(uint32_t track_index, track_desc_scalarf& out_description) const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + if (!header.get_has_metadata()) + return false; // No metadata is stored + + ACL_ASSERT(track_index < header.num_tracks, "Invalid track index"); + if (track_index >= header.num_tracks) + return false; // Invalid track index + + const acl_impl::optional_metadata_header& metadata_header = acl_impl::get_optional_metadata_header(*this); + if (!metadata_header.track_descriptions.is_valid()) + return false; // Metadata isn't stored + + const uint8_t* descriptions = metadata_header.get_track_descriptions(*this); + const float* description_data = reinterpret_cast(descriptions + (track_index * sizeof(float) * 1)); + + out_description.output_index = track_index; + out_description.precision = description_data[0]; + + return true; + } + + inline bool compressed_tracks::get_track_description(uint32_t track_index, track_desc_transformf& out_description) const + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + if (!header.get_has_metadata()) + return false; // No metadata is stored + + ACL_ASSERT(track_index < header.num_tracks, "Invalid track index"); + if (track_index >= header.num_tracks) + return false; // Invalid track index + + const acl_impl::optional_metadata_header& metadata_header = acl_impl::get_optional_metadata_header(*this); + if (!metadata_header.track_descriptions.is_valid()) + return false; // Metadata isn't stored + + if (!metadata_header.parent_track_indices.is_valid()) + return false; // Metadata isn't stored + + const uint32_t* parent_track_indices = metadata_header.get_parent_track_indices(*this); + const uint8_t* descriptions = metadata_header.get_track_descriptions(*this); + const float* description_data = reinterpret_cast(descriptions + (track_index * sizeof(float) * 5)); + + out_description.output_index = track_index; + out_description.parent_index = parent_track_indices[track_index]; + out_description.precision = description_data[0]; + out_description.shell_distance = description_data[1]; + out_description.constant_rotation_threshold_angle = description_data[2]; + out_description.constant_translation_threshold = description_data[3]; + out_description.constant_scale_threshold = description_data[4]; + + return true; + } + + inline error_result compressed_tracks::is_valid(bool check_hash) const + { + if (!is_aligned_to(this, alignof(compressed_tracks))) + return error_result("Invalid alignment"); + + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*this); + if (header.tag != static_cast(buffer_tag32::compressed_tracks)) + return error_result("Invalid tag"); + + if (!is_valid_algorithm_type(header.algorithm_type)) + return error_result("Invalid algorithm type"); + + if (header.version < compressed_tracks_version16::first || header.version > compressed_tracks_version16::latest) + return error_result("Invalid algorithm version"); + + if (check_hash) + { + const uint32_t hash = hash32(safe_ptr_cast(&m_padding[0]), m_buffer_header.size - sizeof(acl_impl::raw_buffer_header)); + if (hash != m_buffer_header.hash) + return error_result("Invalid hash"); + } + + return error_result(); + } + + namespace acl_impl + { + inline const compressed_tracks* make_compressed_tracks_impl(const void* buffer, error_result* out_error_result) + { + if (buffer == nullptr) + { + if (out_error_result != nullptr) + *out_error_result = error_result("Buffer is not a valid pointer"); + + return nullptr; + } + + const compressed_tracks* clip = static_cast(buffer); + if (out_error_result != nullptr) + { + const error_result result = clip->is_valid(false); + *out_error_result = result; + + if (result.any()) + return nullptr; + } + + return clip; + } + } + + inline const compressed_tracks* make_compressed_tracks(const void* buffer, error_result* out_error_result) + { + return acl_impl::make_compressed_tracks_impl(buffer, out_error_result); + } + + inline compressed_tracks* make_compressed_tracks(void* buffer, error_result* out_error_result) + { + return const_cast(acl_impl::make_compressed_tracks_impl(buffer, out_error_result)); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/debug_track_writer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/debug_track_writer.h new file mode 100644 index 00000000..e46f1bde --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/debug_track_writer.h @@ -0,0 +1,189 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/track_types.h" +#include "acl/core/track_writer.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct debug_track_writer final : public track_writer + { + debug_track_writer(iallocator& allocator_, track_type8 type_, uint32_t num_tracks_) + : allocator(allocator_) + , tracks_typed{ nullptr } + , buffer_size(0) + , num_tracks(num_tracks_) + , type(type_) + { + // Large enough to accommodate the largest type + buffer_size = sizeof(rtm::qvvf) * num_tracks_; + tracks_typed.any = allocator_.allocate(buffer_size, alignof(rtm::qvvf)); + } + + ~debug_track_writer() + { + allocator.deallocate(tracks_typed.any, buffer_size); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float1(uint32_t track_index, rtm::scalarf_arg0 value) + { + ACL_ASSERT(type == track_type8::float1f, "Unexpected track type access"); + rtm::scalar_store(value, &tracks_typed.float1f[track_index]); + } + + float RTM_SIMD_CALL read_float1(uint32_t track_index) const + { + ACL_ASSERT(type == track_type8::float1f, "Unexpected track type access"); + return tracks_typed.float1f[track_index]; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float2(uint32_t track_index, rtm::vector4f_arg0 value) + { + ACL_ASSERT(type == track_type8::float2f, "Unexpected track type access"); + rtm::vector_store2(value, &tracks_typed.float2f[track_index]); + } + + rtm::vector4f RTM_SIMD_CALL read_float2(uint32_t track_index) const + { + ACL_ASSERT(type == track_type8::float2f, "Unexpected track type access"); + return rtm::vector_load2(&tracks_typed.float2f[track_index]); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float3(uint32_t track_index, rtm::vector4f_arg0 value) + { + ACL_ASSERT(type == track_type8::float3f, "Unexpected track type access"); + rtm::vector_store3(value, &tracks_typed.float3f[track_index]); + } + + rtm::vector4f RTM_SIMD_CALL read_float3(uint32_t track_index) const + { + ACL_ASSERT(type == track_type8::float3f, "Unexpected track type access"); + return rtm::vector_load3(&tracks_typed.float3f[track_index]); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float4(uint32_t track_index, rtm::vector4f_arg0 value) + { + ACL_ASSERT(type == track_type8::float4f, "Unexpected track type access"); + rtm::vector_store(value, &tracks_typed.float4f[track_index]); + } + + rtm::vector4f RTM_SIMD_CALL read_float4(uint32_t track_index) const + { + ACL_ASSERT(type == track_type8::float4f, "Unexpected track type access"); + return rtm::vector_load(&tracks_typed.float4f[track_index]); + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_vector4(uint32_t track_index, rtm::vector4f_arg0 value) + { + ACL_ASSERT(type == track_type8::vector4f, "Unexpected track type access"); + tracks_typed.vector4f[track_index] = value; + } + + rtm::vector4f RTM_SIMD_CALL read_vector4(uint32_t track_index) const + { + ACL_ASSERT(type == track_type8::vector4f, "Unexpected track type access"); + return tracks_typed.vector4f[track_index]; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a quaternion rotation value for a specified bone index. + void RTM_SIMD_CALL write_rotation(uint32_t track_index, rtm::quatf_arg0 rotation) + { + ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access"); + tracks_typed.qvvf[track_index].rotation = rotation; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a translation value for a specified bone index. + void RTM_SIMD_CALL write_translation(uint32_t track_index, rtm::vector4f_arg0 translation) + { + ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access"); + tracks_typed.qvvf[track_index].translation = translation; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a scale value for a specified bone index. + void RTM_SIMD_CALL write_scale(uint32_t track_index, rtm::vector4f_arg0 scale) + { + ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access"); + tracks_typed.qvvf[track_index].scale = scale; + } + + const rtm::qvvf& RTM_SIMD_CALL read_qvv(uint32_t track_index) const + { + ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access"); + return tracks_typed.qvvf[track_index]; + } + + union ptr_union + { + void* any; + float* float1f; + rtm::float2f* float2f; + rtm::float3f* float3f; + rtm::float4f* float4f; + rtm::vector4f* vector4f; + rtm::qvvf* qvvf; + }; + + iallocator& allocator; + + ptr_union tracks_typed; + size_t buffer_size; + uint32_t num_tracks; + + track_type8 type; + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/track_desc.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/track_desc.impl.h new file mode 100644 index 00000000..74759550 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/track_desc.impl.h @@ -0,0 +1,69 @@ +#pragma once +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from track_desc.h + +#include "acl/version.h" +#include "acl/core/error_result.h" +#include "acl/core/track_types.h" + +#include + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline error_result track_desc_scalarf::is_valid() const + { + if (precision < 0.0F || !rtm::scalar_is_finite(precision)) + return error_result("Invalid precision"); + + return error_result(); + } + + inline error_result track_desc_transformf::is_valid() const + { + if (precision < 0.0F || !rtm::scalar_is_finite(precision)) + return error_result("Invalid precision"); + + if (shell_distance < 0.0F || !rtm::scalar_is_finite(shell_distance)) + return error_result("Invalid shell_distance"); + + if (constant_rotation_threshold_angle < 0.0F || !rtm::scalar_is_finite(constant_rotation_threshold_angle)) + return error_result("Invalid constant_rotation_threshold_angle"); + + if (constant_translation_threshold < 0.0F || !rtm::scalar_is_finite(constant_translation_threshold)) + return error_result("Invalid constant_translation_threshold"); + + if (constant_scale_threshold < 0.0F || !rtm::scalar_is_finite(constant_scale_threshold)) + return error_result("Invalid constant_scale_threshold"); + + return error_result(); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/track_formats.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/track_formats.impl.h new file mode 100644 index 00000000..646109ef --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/impl/track_formats.impl.h @@ -0,0 +1,148 @@ +#pragma once +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from track_formats.h + +#include "acl/version.h" +#include "acl/core/error.h" + +#include +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline const char* get_rotation_format_name(rotation_format8 format) + { + switch (format) + { + case rotation_format8::quatf_full: return "quatf_full"; + case rotation_format8::quatf_drop_w_full: return "quatf_drop_w_full"; + case rotation_format8::quatf_drop_w_variable: return "quatf_drop_w_variable"; + default: return ""; + } + } + + inline bool get_rotation_format(const char* format, rotation_format8& out_format) + { + ACL_ASSERT(format != nullptr, "Rotation format name cannot be null"); + if (format == nullptr) + return false; + + const char* quat_128_format = "Quat_128"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* quatf_full_format = "quatf_full"; + if (std::strncmp(format, quat_128_format, std::strlen(quat_128_format)) == 0 + || std::strncmp(format, quatf_full_format, std::strlen(quatf_full_format)) == 0) + { + out_format = rotation_format8::quatf_full; + return true; + } + + const char* quatdropw_96_format = "QuatDropW_96"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* quatf_drop_w_full_format = "quatf_drop_w_full"; + if (std::strncmp(format, quatdropw_96_format, std::strlen(quatdropw_96_format)) == 0 + || std::strncmp(format, quatf_drop_w_full_format, std::strlen(quatf_drop_w_full_format)) == 0) + { + out_format = rotation_format8::quatf_drop_w_full; + return true; + } + + const char* quatdropw_variable_format = "QuatDropW_Variable"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* quatf_drop_w_variable_format = "quatf_drop_w_variable"; + if (std::strncmp(format, quatdropw_variable_format, std::strlen(quatdropw_variable_format)) == 0 + || std::strncmp(format, quatf_drop_w_variable_format, std::strlen(quatf_drop_w_variable_format)) == 0) + { + out_format = rotation_format8::quatf_drop_w_variable; + return true; + } + + return false; + } + + constexpr const char* get_vector_format_name(vector_format8 format) + { + return format == vector_format8::vector3f_full ? "vector3f_full" : "vector3f_variable"; + } + + inline bool get_vector_format(const char* format, vector_format8& out_format) + { + ACL_ASSERT(format != nullptr, "Vector format name cannot be null"); + if (format == nullptr) + return false; + + const char* vector3_96_format = "Vector3_96"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* vector3f_full_format = "vector3f_full"; + if (std::strncmp(format, vector3_96_format, std::strlen(vector3_96_format)) == 0 + || std::strncmp(format, vector3f_full_format, std::strlen(vector3f_full_format)) == 0) + { + out_format = vector_format8::vector3f_full; + return true; + } + + const char* vector3_variable_format = "Vector3_Variable"; // ACL_DEPRECATED Legacy name, keep for backwards compatibility, remove in 3.0 + const char* vector3f_variable_format = "vector3f_variable"; + if (std::strncmp(format, vector3_variable_format, std::strlen(vector3_variable_format)) == 0 + || std::strncmp(format, vector3f_variable_format, std::strlen(vector3f_variable_format)) == 0) + { + out_format = vector_format8::vector3f_variable; + return true; + } + + return false; + } + + constexpr rotation_variant8 get_rotation_variant(rotation_format8 rotation_format) + { + return rotation_format == rotation_format8::quatf_full ? rotation_variant8::quat : rotation_variant8::quat_drop_w; + } + + constexpr rotation_format8 get_highest_variant_precision(rotation_variant8 variant) + { + return variant == rotation_variant8::quat ? rotation_format8::quatf_full : rotation_format8::quatf_drop_w_full; + } + + constexpr bool is_rotation_format_variable(rotation_format8 format) + { + return format == rotation_format8::quatf_drop_w_variable; + } + + constexpr bool is_rotation_format_full_precision(rotation_format8 format) + { + return format == rotation_format8::quatf_full || format == rotation_format8::quatf_drop_w_full; + } + + constexpr bool is_vector_format_variable(vector_format8 format) + { + return format == vector_format8::vector3f_variable; + } + + constexpr bool is_vector_format_full_precision(vector_format8 format) + { + return format == vector_format8::vector3f_full; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/interpolation_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/interpolation_utils.h new file mode 100644 index 00000000..fa8c370c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/interpolation_utils.h @@ -0,0 +1,189 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // This enum dictates how interpolation samples are calculated based on the sample time. + enum class sample_rounding_policy + { + ////////////////////////////////////////////////////////////////////////// + // If the sample time lies between two samples, both sample indices + // are returned and the interpolation alpha lies in between. + none, + + ////////////////////////////////////////////////////////////////////////// + // If the sample time lies between two samples, both sample indices + // are returned and the interpolation will be 0.0. + floor, + + ////////////////////////////////////////////////////////////////////////// + // If the sample time lies between two samples, both sample indices + // are returned and the interpolation will be 1.0. + ceil, + + ////////////////////////////////////////////////////////////////////////// + // If the sample time lies between two samples, both sample indices + // are returned and the interpolation will be 0.0 or 1.0 depending + // on which sample is nearest. + nearest, + }; + + ////////////////////////////////////////////////////////////////////////// + // Calculates the sample indices and the interpolation required to linearly + // interpolate when the samples are uniform. + // The returned sample indices are clamped and do not loop. + // If the sample rate is available, prefer using find_linear_interpolation_samples_with_sample_rate + // instead. It is faster and more accurate. + inline void find_linear_interpolation_samples_with_duration(uint32_t num_samples, float duration, float sample_time, sample_rounding_policy rounding_policy, + uint32_t& out_sample_index0, uint32_t& out_sample_index1, float& out_interpolation_alpha) + { + // Samples are evenly spaced, trivially calculate the indices that we need + ACL_ASSERT(duration >= 0.0F, "Invalid duration: %f", duration); + ACL_ASSERT(sample_time >= 0.0F && sample_time <= duration, "Invalid sample time: 0.0 <= %f <= %f", sample_time, duration); + ACL_ASSERT(num_samples > 0, "Invalid num_samples: %u", num_samples); + + const float sample_rate = duration == 0.0F ? 0.0F : (float(num_samples - 1) / duration); + ACL_ASSERT(sample_rate >= 0.0F && rtm::scalar_is_finite(sample_rate), "Invalid sample_rate: %f", sample_rate); + + const float sample_index = sample_time * sample_rate; + const uint32_t sample_index0 = static_cast(sample_index); + const uint32_t sample_index1 = std::min(sample_index0 + 1, num_samples - 1); + ACL_ASSERT(sample_index0 <= sample_index1 && sample_index1 < num_samples, "Invalid sample indices: 0 <= %u <= %u < %u", sample_index0, sample_index1, num_samples); + + const float interpolation_alpha = sample_index - float(sample_index0); + ACL_ASSERT(interpolation_alpha >= 0.0F && interpolation_alpha <= 1.0F, "Invalid interpolation alpha: 0.0 <= %f <= 1.0", interpolation_alpha); + + out_sample_index0 = sample_index0; + out_sample_index1 = sample_index1; + + switch (rounding_policy) + { + default: + case sample_rounding_policy::none: + out_interpolation_alpha = interpolation_alpha; + break; + case sample_rounding_policy::floor: + out_interpolation_alpha = 0.0F; + break; + case sample_rounding_policy::ceil: + out_interpolation_alpha = 1.0F; + break; + case sample_rounding_policy::nearest: + out_interpolation_alpha = rtm::scalar_floor(interpolation_alpha + 0.5F); + break; + } + } + + ////////////////////////////////////////////////////////////////////////// + // Calculates the sample indices and the interpolation required to linearly + // interpolate when the samples are uniform. + // The returned sample indices are clamped and do not loop. + inline void find_linear_interpolation_samples_with_sample_rate(uint32_t num_samples, float sample_rate, float sample_time, sample_rounding_policy rounding_policy, + uint32_t& out_sample_index0, uint32_t& out_sample_index1, float& out_interpolation_alpha) + { + // Samples are evenly spaced, trivially calculate the indices that we need + ACL_ASSERT(sample_rate >= 0.0F, "Invalid sample rate: %f", sample_rate); + ACL_ASSERT(num_samples > 0, "Invalid num_samples: %u", num_samples); + + // TODO: Would it be faster to do the index calculation entirely with floating point? + // SSE4 can floor with a single instruction. + // We don't need the index1, there are no dependencies there, we can still convert the float index0 into an integer, do the min. + // This would break the dependency chains, right now the index0 depends on sample_index and interpolation_alpha depends on index0. + // Generating index0 is slow, and converting it back to float is slow. + // If we keep index0 as a float and floor it as a float, we can calculate index1 at the same time as the interpolation alpha. + const float sample_index = sample_time * sample_rate; + const uint32_t sample_index0 = static_cast(sample_index); + const uint32_t sample_index1 = std::min(sample_index0 + 1, num_samples - 1); + ACL_ASSERT(sample_index0 <= sample_index1 && sample_index1 < num_samples, "Invalid sample indices: 0 <= %u <= %u < %u", sample_index0, sample_index1, num_samples); + + const float interpolation_alpha = sample_index - float(sample_index0); + ACL_ASSERT(interpolation_alpha >= 0.0F && interpolation_alpha <= 1.0F, "Invalid interpolation alpha: 0.0 <= %f <= 1.0", interpolation_alpha); + + out_sample_index0 = sample_index0; + out_sample_index1 = sample_index1; + + switch (rounding_policy) + { + default: + case sample_rounding_policy::none: + out_interpolation_alpha = interpolation_alpha; + break; + case sample_rounding_policy::floor: + out_interpolation_alpha = 0.0F; + break; + case sample_rounding_policy::ceil: + out_interpolation_alpha = 1.0F; + break; + case sample_rounding_policy::nearest: + out_interpolation_alpha = rtm::scalar_floor(interpolation_alpha + 0.5F); + break; + } + } + + ////////////////////////////////////////////////////////////////////////// + // Calculates the sample indices and the interpolation required to linearly + // interpolate when the samples are uniform. + // This function does not support looping. + inline float find_linear_interpolation_alpha(float sample_index, uint32_t sample_index0, uint32_t sample_index1, sample_rounding_policy rounding_policy) + { + ACL_ASSERT(sample_index >= 0.0F, "Invalid sample rate: %f", sample_index); + + if (rounding_policy == sample_rounding_policy::floor) + return 0.0F; + else if (rounding_policy == sample_rounding_policy::ceil) + return 1.0F; + else if (sample_index0 == sample_index1) + return 0.0F; + + ACL_ASSERT(sample_index0 < sample_index1, "Invalid sample indices: %u >= %u", sample_index0, sample_index1); + + const float interpolation_alpha = (sample_index - float(sample_index0)) / float(sample_index1 - sample_index0); + ACL_ASSERT(interpolation_alpha >= 0.0F && interpolation_alpha <= 1.0F, "Invalid interpolation alpha: 0.0 <= %f <= 1.0", interpolation_alpha); + + if (rounding_policy == sample_rounding_policy::none) + return interpolation_alpha; + else // sample_rounding_policy::nearest + return rtm::scalar_floor(interpolation_alpha + 0.5F); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/iterator.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/iterator.h new file mode 100644 index 00000000..4a7eec81 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/iterator.h @@ -0,0 +1,67 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + template + class iterator_impl + { + public: + using item_ptr_type = typename std::conditional::type; + + constexpr iterator_impl(item_ptr_type items, size_t num_items) : m_items(items), m_num_items(num_items) {} + + constexpr item_ptr_type begin() const { return m_items; } + constexpr item_ptr_type end() const { return m_items + m_num_items; } + + private: + item_ptr_type m_items; + size_t m_num_items; + }; + } + + template + using iterator = acl_impl::iterator_impl; + + template + using const_iterator = acl_impl::iterator_impl; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/memory_utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/memory_utils.h new file mode 100644 index 00000000..c47231a2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/memory_utils.h @@ -0,0 +1,371 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" + +#include + +#include +#include +#include +#include +#include +#include + +// For byte swapping intrinsics +#if defined(RTM_COMPILER_MSVC) + #include +#elif defined(__APPLE__) + #include +#endif + +// For __prefetch +#if defined(RTM_NEON64_INTRINSICS) && defined(RTM_COMPILER_MSVC) + #include +#endif + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Allows static branching without any warnings + + template + struct static_condition { static constexpr bool test() { return true; } }; + + template<> + struct static_condition { static constexpr bool test() { return false; } }; + + ////////////////////////////////////////////////////////////////////////// + // Various miscellaneous utilities related to alignment + + RTM_FORCE_INLINE constexpr bool is_power_of_two(size_t input) + { + return input != 0 && (input & (input - 1)) == 0; + } + + template + RTM_FORCE_INLINE constexpr bool is_alignment_valid(size_t alignment) + { + return is_power_of_two(alignment) && alignment >= alignof(Type); + } + + template + RTM_FORCE_INLINE bool is_aligned_to(PtrType* value, size_t alignment) + { + ACL_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return (reinterpret_cast(value) & (alignment - 1)) == 0; + } + + template + RTM_FORCE_INLINE bool is_aligned_to(IntegralType value, size_t alignment) + { + ACL_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return (static_cast(value) & (alignment - 1)) == 0; + } + + template + RTM_FORCE_INLINE constexpr bool is_aligned(PtrType* value) + { + return is_aligned_to(value, alignof(PtrType)); + } + + template + RTM_FORCE_INLINE PtrType* align_to(PtrType* value, size_t alignment) + { + ACL_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return reinterpret_cast((reinterpret_cast(value) + (alignment - 1)) & ~(alignment - 1)); + } + + template + RTM_FORCE_INLINE IntegralType align_to(IntegralType value, size_t alignment) + { + ACL_ASSERT(is_power_of_two(alignment), "Alignment value must be a power of two"); + return static_cast((static_cast(value) + (alignment - 1)) & ~(alignment - 1)); + } + + template + RTM_FORCE_INLINE constexpr size_t get_required_padding() + { + // align_to(sizeof(PreviousMemberType), alignof(NextMemberType)) - sizeof(PreviousMemberType) + return ((sizeof(PreviousMemberType) + (alignof(NextMemberType) - 1)) & ~(alignof(NextMemberType)- 1)) - sizeof(PreviousMemberType); + } + + template + RTM_FORCE_INLINE constexpr size_t get_array_size(ElementType const (&)[num_elements]) { return num_elements; } + + ////////////////////////////////////////////////////////////////////////// + // Type safe casting + + namespace memory_impl + { + template + struct safe_ptr_to_ptr_cast_impl + { + RTM_FORCE_INLINE static DestPtrType* cast(SrcType* input) + { + ACL_ASSERT(is_aligned_to(input, alignof(DestPtrType)), "reinterpret_cast would result in an unaligned pointer"); + return reinterpret_cast(input); + } + }; + + template + struct safe_ptr_to_ptr_cast_impl + { + RTM_FORCE_INLINE static constexpr void* cast(SrcType* input) { return input; } + }; + + template + struct safe_int_to_ptr_cast_impl + { + RTM_FORCE_INLINE static DestPtrType* cast(SrcType input) + { + ACL_ASSERT(is_aligned_to(input, alignof(DestPtrType)), "reinterpret_cast would result in an unaligned pointer"); + return reinterpret_cast(input); + } + }; + + template + struct safe_int_to_ptr_cast_impl + { + RTM_FORCE_INLINE static constexpr void* cast(SrcType input) { return reinterpret_cast(input); } + }; + } + + template + RTM_FORCE_INLINE DestPtrType* safe_ptr_cast(SrcType* input) + { + return memory_impl::safe_ptr_to_ptr_cast_impl::cast(input); + } + + template + RTM_FORCE_INLINE DestPtrType* safe_ptr_cast(SrcType input) + { + return memory_impl::safe_int_to_ptr_cast_impl::cast(input); + } + +#if defined(RTM_COMPILER_GCC) + // GCC sometimes complains about comparisons being always true due to partial template + // evaluation. Disable that warning since we know it is safe. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wtype-limits" +#endif + + namespace memory_impl + { + template + struct safe_underlying_type { using type = typename std::underlying_type::type; }; + + template + struct safe_underlying_type { using type = Type; }; + + template + struct is_static_cast_safe_s + { + RTM_FORCE_INLINE static bool test(SrcType input) + { + using SrcRealType = typename safe_underlying_type::value>::type; + + if (static_condition<(std::is_signed::value == std::is_signed::value)>::test()) + return SrcType(DstType(input)) == input; + else if (static_condition<(std::is_signed::value)>::test()) + return int64_t(input) >= 0 && SrcType(DstType(input)) == input; + else + return uint64_t(input) <= uint64_t(std::numeric_limits::max()); + }; + }; + + template + struct is_static_cast_safe_s + { + RTM_FORCE_INLINE static bool test(SrcType input) + { + return SrcType(DstType(input)) == input; + } + }; + + template + RTM_FORCE_INLINE bool is_static_cast_safe(SrcType input) + { + // TODO: In C++17 this should be folded to constexpr if + return is_static_cast_safe_s::value || std::is_floating_point::value)>::test()>::test(input); + } + } + + template + RTM_FORCE_INLINE DstType safe_static_cast(SrcType input) + { +#if defined(ACL_HAS_ASSERT_CHECKS) + const bool is_safe = memory_impl::is_static_cast_safe(input); + ACL_ASSERT(is_safe, "Unsafe static cast resulted in data loss"); +#endif + + return static_cast(input); + } + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif + + ////////////////////////////////////////////////////////////////////////// + // Endian and raw memory support + + template + RTM_FORCE_INLINE OutputPtrType* add_offset_to_ptr(InputPtrType* ptr, offset_type offset) + { + return safe_ptr_cast(reinterpret_cast(ptr) + offset); + } + + RTM_FORCE_INLINE uint16_t byte_swap(uint16_t value) + { +#if defined(RTM_COMPILER_MSVC) + return _byteswap_ushort(value); +#elif defined(__APPLE__) + return OSSwapInt16(value); +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + return __builtin_bswap16(value); +#else + return (value & 0x00FF) << 8 | (value & 0xFF00) >> 8; +#endif + } + + RTM_FORCE_INLINE uint32_t byte_swap(uint32_t value) + { +#if defined(RTM_COMPILER_MSVC) + return _byteswap_ulong(value); +#elif defined(__APPLE__) + return OSSwapInt32(value); +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + return __builtin_bswap32(value); +#else + value = (value & 0x0000FFFF) << 16 | (value & 0xFFFF0000) >> 16; + value = (value & 0x00FF00FF) << 8 | (value & 0xFF00FF00) >> 8; + return value; +#endif + } + + RTM_FORCE_INLINE uint64_t byte_swap(uint64_t value) + { +#if defined(RTM_COMPILER_MSVC) + return _byteswap_uint64(value); +#elif defined(__APPLE__) + return OSSwapInt64(value); +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + return __builtin_bswap64(value); +#else + value = (value & 0x00000000FFFFFFFF) << 32 | (value & 0xFFFFFFFF00000000) >> 32; + value = (value & 0x0000FFFF0000FFFF) << 16 | (value & 0xFFFF0000FFFF0000) >> 16; + value = (value & 0x00FF00FF00FF00FF) << 8 | (value & 0xFF00FF00FF00FF00) >> 8; + return value; +#endif + } + + // We copy bits assuming big-endian ordering for 'dest' and 'src' + inline void memcpy_bits(void* dest, uint64_t dest_bit_offset, const void* src, uint64_t src_bit_offset, uint64_t num_bits_to_copy) + { + if (num_bits_to_copy == 0) + return; // Nothing to copy + + while (true) + { + uint64_t src_byte_offset = src_bit_offset / 8; + uint8_t src_byte_bit_offset = safe_static_cast(src_bit_offset % 8); + uint64_t dest_byte_offset = dest_bit_offset / 8; + uint8_t dest_byte_bit_offset = safe_static_cast(dest_bit_offset % 8); + + const uint8_t* src_bytes = add_offset_to_ptr(src, src_byte_offset); + uint8_t* dest_byte = add_offset_to_ptr(dest, dest_byte_offset); + + // We'll copy only as many bits as there fits within 'dest' or as there are left + uint8_t num_bits_dest_remain_in_byte = 8 - dest_byte_bit_offset; + uint8_t num_bits_src_remain_in_byte = 8 - src_byte_bit_offset; + uint64_t num_bits_copied = std::min(std::min(num_bits_dest_remain_in_byte, num_bits_src_remain_in_byte), num_bits_to_copy); + uint8_t num_bits_copied_u8 = safe_static_cast(num_bits_copied); + + // We'll shift and mask to retain the 'dest' bits prior to our offset and whatever remains after the copy + uint8_t dest_shift_offset = dest_byte_bit_offset; + uint8_t dest_byte_mask = ~(0xFF >> dest_shift_offset) | ~(0xFF << (8 - num_bits_copied_u8 - dest_byte_bit_offset)); + + uint8_t src_shift_offset = 8 - src_byte_bit_offset - num_bits_copied_u8; + uint8_t src_byte_mask = 0xFF >> (8 - num_bits_copied_u8); + uint8_t src_insert_shift_offset = 8 - num_bits_copied_u8 - dest_byte_bit_offset; + + uint8_t partial_dest_value = *dest_byte & dest_byte_mask; + uint8_t partial_src_value = (*src_bytes >> src_shift_offset) & src_byte_mask; + *dest_byte = partial_dest_value | (partial_src_value << src_insert_shift_offset); + + if (num_bits_to_copy <= num_bits_copied) + break; // Done + + num_bits_to_copy -= num_bits_copied; + dest_bit_offset += num_bits_copied; + src_bit_offset += num_bits_copied; + } + } + + template + RTM_FORCE_INLINE data_type unaligned_load(const void* input) + { + data_type result; + std::memcpy(&result, input, sizeof(data_type)); + return result; + } + + template + RTM_FORCE_INLINE data_type aligned_load(const void* input) + { + return *safe_ptr_cast(input); + } + + template + RTM_FORCE_INLINE void unaligned_write(data_type input, void* output) + { + std::memcpy(output, &input, sizeof(data_type)); + } + + // TODO: Add support for streaming prefetch (ptr, 0, 0) for arm + RTM_FORCE_INLINE void memory_prefetch(const void* ptr) + { +#if defined(RTM_SSE2_INTRINSICS) + _mm_prefetch(reinterpret_cast(ptr), _MM_HINT_T0); +#elif defined(RTM_COMPILER_GCC) || defined(RTM_COMPILER_CLANG) + __builtin_prefetch(ptr, 0, 3); +#elif defined(RTM_NEON64_INTRINSICS) && defined(RTM_COMPILER_MSVC) + __prefetch(ptr); +#else + (void)ptr; +#endif + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/ptr_offset.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/ptr_offset.h new file mode 100644 index 00000000..55ac02b6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/ptr_offset.h @@ -0,0 +1,141 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/memory_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // Represents an invalid pointer offset, used by 'ptr_offset'. + //////////////////////////////////////////////////////////////////////////////// + struct invalid_ptr_offset final {}; + + //////////////////////////////////////////////////////////////////////////////// + // A type safe pointer offset. + // + // This class only wraps an integer of the 'offset_type' type and adds type safety + // by only casting to 'data_type'. + //////////////////////////////////////////////////////////////////////////////// + template + class ptr_offset + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Constructs a valid but empty offset. + constexpr ptr_offset() : m_value(0) {} + + //////////////////////////////////////////////////////////////////////////////// + // Constructs a valid offset with the specified value. + // Expanded to avoid overflow warnings during assignment. + constexpr ptr_offset(uint64_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(int64_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(uint32_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(int32_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(uint16_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(int16_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(uint8_t value) : m_value(safe_static_cast(value)) {} + constexpr ptr_offset(int8_t value) : m_value(safe_static_cast(value)) {} + + ////////////////////////////////////////////////////////////////////////// + // Constructs a valid offset from a base pointer and another pointer part of the same aggregate. + constexpr ptr_offset(const void* base, const void* ptr) : m_value(safe_static_cast(reinterpret_cast(ptr) - reinterpret_cast(base))) {} + + //////////////////////////////////////////////////////////////////////////////// + // Constructs an invalid offset. + constexpr ptr_offset(invalid_ptr_offset) : m_value(k_invalid_value) {} + + //////////////////////////////////////////////////////////////////////////////// + // Adds this offset to the provided pointer. + template + inline data_type* add_to(BaseType* ptr) const + { + ACL_ASSERT(is_valid(), "Invalid ptr_offset!"); + return add_offset_to_ptr(ptr, m_value); + } + + //////////////////////////////////////////////////////////////////////////////// + // Adds this offset to the provided pointer. + template + inline const data_type* add_to(const BaseType* ptr) const + { + ACL_ASSERT(is_valid(), "Invalid ptr_offset!"); + return add_offset_to_ptr(ptr, m_value); + } + + //////////////////////////////////////////////////////////////////////////////// + // Adds this offset to the provided pointer or returns nullptr if the offset is invalid. + template + inline data_type* safe_add_to(BaseType* ptr) const + { + return is_valid() ? add_offset_to_ptr(ptr, m_value) : nullptr; + } + + //////////////////////////////////////////////////////////////////////////////// + // Adds this offset to the provided pointer or returns nullptr if the offset is invalid. + template + inline const data_type* safe_add_to(const BaseType* ptr) const + { + return is_valid() ? add_offset_to_ptr(ptr, m_value) : nullptr; + } + + //////////////////////////////////////////////////////////////////////////////// + // Coercion operator to the underlying 'offset_type'. + constexpr operator offset_type() const { return m_value; } + + //////////////////////////////////////////////////////////////////////////////// + // Returns true if the offset is valid. + constexpr bool is_valid() const { return m_value != k_invalid_value; } + + private: + // Value representing an invalid offset + static constexpr offset_type k_invalid_value = std::numeric_limits::max(); + + // Actual offset value. + offset_type m_value; + }; + + //////////////////////////////////////////////////////////////////////////////// + // A 16 bit offset. + template + using ptr_offset16 = ptr_offset; + + //////////////////////////////////////////////////////////////////////////////// + // A 32 bit offset. + template + using ptr_offset32 = ptr_offset; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/quality_tiers.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/quality_tiers.h new file mode 100644 index 00000000..d09574f1 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/quality_tiers.h @@ -0,0 +1,65 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // What quality tier a key frame/sample belongs to + enum class quality_tier + { + // Highest importance frames remain in the compressed clip and can be used to interpolate even without the database present + highest_importance = 0, + + //high_importance, // RESERVED + + // Medium importance frames live in the compressed database and contribute more the quality than those of the lower importance tiers + medium_importance = 1, + + //low_importance, // RESERVED + + // Lowest importance frames live in the compressed database and contribute the least to the quality + lowest_importance = 2, + }; + + // We only support 3 tiers + constexpr uint32_t k_num_quality_tiers = 3; + + // Database contains 2 tiers + constexpr uint32_t k_num_database_tiers = 2; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/range_reduction_types.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/range_reduction_types.h new file mode 100644 index 00000000..4956c405 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/range_reduction_types.h @@ -0,0 +1,97 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/enum_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // Various constants for range reduction. + constexpr uint8_t k_segment_range_reduction_num_bits_per_component = 8; + constexpr uint8_t k_segment_range_reduction_num_bytes_per_component = 1; + constexpr uint32_t k_clip_range_reduction_vector3_range_size = sizeof(float) * 6; + + //////////////////////////////////////////////////////////////////////////////// + // range_reduction_flags8 represents the types of range reduction we support as a bit field. + // + // BE CAREFUL WHEN CHANGING VALUES IN THIS ENUM + // The range reduction strategy is serialized in the compressed data, if you change a value + // the compressed clips will be invalid. If you do, bump the appropriate algorithm versions. + enum class range_reduction_flags8 : uint8_t + { + none = 0x00, + + // Flags to determine which tracks have range reduction applied + rotations = 0x01, + translations = 0x02, + scales = 0x04, + //Properties = 0x08, // TODO: Implement this + + all_tracks = 0x07, // rotations | translations | scales + }; + + ACL_IMPL_ENUM_FLAGS_OPERATORS(range_reduction_flags8) + + ////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // Returns a string of the algorithm name suitable for display. + // TODO: constexpr + inline const char* get_range_reduction_name(range_reduction_flags8 flags) + { + // Some compilers have trouble with constexpr operator| with enums in a case switch statement + if (flags == range_reduction_flags8::none) + return "range_reduction::none"; + else if (flags == range_reduction_flags8::rotations) + return "range_reduction::rotations"; + else if (flags == range_reduction_flags8::translations) + return "range_reduction::translations"; + else if (flags == range_reduction_flags8::scales) + return "range_reduction::scales"; + else if (flags == (range_reduction_flags8::rotations | range_reduction_flags8::translations)) + return "range_reduction::rotations | range_reduction::translations"; + else if (flags == (range_reduction_flags8::rotations | range_reduction_flags8::scales)) + return "range_reduction::rotations | range_reduction::scales"; + else if (flags == (range_reduction_flags8::translations | range_reduction_flags8::scales)) + return "range_reduction::translations | range_reduction::scales"; + else if (flags == (range_reduction_flags8::rotations | range_reduction_flags8::translations | range_reduction_flags8::scales)) + return "range_reduction::rotations | range_reduction::translations | range_reduction::scales"; + else + return ""; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/scope_profiler.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/scope_profiler.h new file mode 100644 index 00000000..0386db98 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/scope_profiler.h @@ -0,0 +1,100 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // A scope activated profiler. + //////////////////////////////////////////////////////////////////////////////// + class scope_profiler + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Creates and starts a scope profiler and automatically starts it. + scope_profiler(); + + //////////////////////////////////////////////////////////////////////////////// + // Destroys a scope profiler and automatically stops it. + ~scope_profiler() = default; + + //////////////////////////////////////////////////////////////////////////////// + // Manually stops the profiler. + void stop(); + + //////////////////////////////////////////////////////////////////////////////// + // Returns the elapsed time in nanoseconds since the profiler was started. + std::chrono::nanoseconds get_elapsed_time() const { return std::chrono::duration_cast(m_end_time - m_start_time); } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the elapsed time in microseconds since the profiler was started. + double get_elapsed_microseconds() const { return std::chrono::duration(get_elapsed_time()).count(); } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the elapsed time in milliseconds since the profiler was started. + double get_elapsed_milliseconds() const { return std::chrono::duration(get_elapsed_time()).count(); } + + //////////////////////////////////////////////////////////////////////////////// + // Returns the elapsed time in seconds since the profiler was started. + double get_elapsed_seconds() const { return std::chrono::duration(get_elapsed_time()).count(); } + + private: + scope_profiler(const scope_profiler&) = delete; + scope_profiler& operator=(const scope_profiler&) = delete; + + // The time at which the profiler started. + std::chrono::time_point m_start_time; + + // The time at which the profiler stopped. + std::chrono::time_point m_end_time; + }; + + ////////////////////////////////////////////////////////////////////////// + + inline scope_profiler::scope_profiler() + { + m_start_time = m_end_time = std::chrono::high_resolution_clock::now(); + } + + inline void scope_profiler::stop() + { + if (m_start_time == m_end_time) + m_end_time = std::chrono::high_resolution_clock::now(); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/string.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/string.h new file mode 100644 index 00000000..5cb629d5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/string.h @@ -0,0 +1,155 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/error.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // A basic string class that uses our custom allocator. + // It is used exclusively for debug purposes. + // + // Strings are immutable. + ////////////////////////////////////////////////////////////////////////// + class string + { + public: + string() noexcept : m_allocator(nullptr), m_c_str(nullptr) {} + + string(iallocator& allocator, const char* c_str, size_t length) + : m_allocator(&allocator) + { + if (length > 0) + { +#if defined(ACL_HAS_ASSERT_CHECKS) && !defined(NDEBUG) + for (size_t i = 0; i < length; ++i) + ACL_ASSERT(c_str[i] != '\0', "String cannot contain NULL terminators"); +#endif + + m_c_str = allocate_type_array(allocator, length + 1); + std::memcpy(m_c_str, c_str, length); + m_c_str[length] = '\0'; + } + else + { + m_c_str = nullptr; + } + } + + string(iallocator& allocator, const char* c_str) + : string(allocator, c_str, c_str != nullptr ? std::strlen(c_str) : 0) + {} + + string(iallocator& allocator, const string& str) + : string(allocator, str.c_str(), str.size()) + {} + + ~string() + { + if (m_c_str != nullptr) + deallocate_type_array(*m_allocator, m_c_str, std::strlen(m_c_str) + 1); + } + + string(string&& other) noexcept + : m_allocator(other.m_allocator) + , m_c_str(other.m_c_str) + { + new(&other) string(); + } + + string& operator=(string&& other) noexcept + { + std::swap(m_allocator, other.m_allocator); + std::swap(m_c_str, other.m_c_str); + return *this; + } + + bool operator==(const char* c_str) const noexcept + { + const size_t this_length = m_c_str == nullptr ? 0 : std::strlen(m_c_str); + const size_t other_length = c_str == nullptr ? 0 : std::strlen(c_str); + if (this_length != other_length) + return false; + + if (this_length == 0) + return true; + + return std::memcmp(m_c_str, c_str, other_length) == 0; + } + + bool operator!=(const char* c_str) const noexcept { return !(*this == c_str); } + + bool operator==(const string& other) const noexcept { return (*this == other.c_str()); } + bool operator!=(const string& other) const noexcept { return !(*this == other.c_str()); } + + ////////////////////////////////////////////////////////////////////////// + // Returns a pointer to the allocator instance or nullptr if there is none present. + iallocator* get_allocator() const noexcept { return m_allocator; } + + ////////////////////////////////////////////////////////////////////////// + // Returns a copy of the current string. + // Explicit instead of using the assignment operator or copy constructor + string get_copy() const + { + if (m_c_str == nullptr) + return string(); + + return string(*m_allocator, m_c_str, std::strlen(m_c_str)); + } + + ////////////////////////////////////////////////////////////////////////// + // Returns a copy of the current string. + // Explicit instead of using the assignment operator or copy constructor + string get_copy(iallocator& allocator) const + { + if (m_c_str == nullptr) + return string(); + + return string(allocator, m_c_str, std::strlen(m_c_str)); + } + + const char* c_str() const noexcept { return m_c_str != nullptr ? m_c_str : ""; } + size_t size() const noexcept { return m_c_str != nullptr ? std::strlen(m_c_str) : 0; } + bool empty() const noexcept { return m_c_str != nullptr ? (std::strlen(m_c_str) == 0) : true; } + + private: + iallocator* m_allocator; + char* m_c_str; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_desc.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_desc.h new file mode 100644 index 00000000..41692c58 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_desc.h @@ -0,0 +1,145 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error_result.h" +#include "acl/core/track_types.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // This structure describes the various settings for floating point scalar tracks. + // Used by: float1f, float2f, float3f, float4f, vector4f + struct track_desc_scalarf + { + ////////////////////////////////////////////////////////////////////////// + // The track category for this description. + static constexpr track_category8 category = track_category8::scalarf; + + ////////////////////////////////////////////////////////////////////////// + // The track output index. When writing out the compressed data stream, this index + // will be used instead of the track index. This allows custom reordering for things + // like LOD sorting or skeleton remapping. A value of 'k_invalid_track_index' will strip the track + // from the compressed data stream. Output indices must be unique and contiguous. + uint32_t output_index = k_invalid_track_index; + + ////////////////////////////////////////////////////////////////////////// + // The per component precision threshold to try and attain when optimizing the bit rate. + // If the error is below the precision threshold, we will remove bits until we reach it without + // exceeding it. If the error is above the precision threshold, we will add more bits until + // we lower it underneath. + // Defaults to '0.00001' + float precision = 0.00001F; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether a scalar track description is valid or not. + // It is valid if: + // - The precision is positive or zero and finite + error_result is_valid() const; + }; + + ////////////////////////////////////////////////////////////////////////// + // This structure describes the various settings for transform tracks. + // Used by: quatf, qvvf + struct track_desc_transformf + { + ////////////////////////////////////////////////////////////////////////// + // The track category for this description. + static constexpr track_category8 category = track_category8::transformf; + + ////////////////////////////////////////////////////////////////////////// + // The track output index. When writing out the compressed data stream, this index + // will be used instead of the track index. This allows custom reordering for things + // like LOD sorting or skeleton remapping. A value of 'k_invalid_track_index' will strip the track + // from the compressed data stream. Output indices must be unique and contiguous. + uint32_t output_index = k_invalid_track_index; + + ////////////////////////////////////////////////////////////////////////// + // The index of the parent transform track or `k_invalid_track_index` if it has no parent. + uint32_t parent_index = k_invalid_track_index; + + ////////////////////////////////////////////////////////////////////////// + // The shell precision threshold to try and attain when optimizing the bit rate. + // If the error is below the precision threshold, we will remove bits until we reach it without + // exceeding it. If the error is above the precision threshold, we will add more bits until + // we lower it underneath. + // Note that you will need to change this value if your units are not in centimeters. + // Defaults to '0.01' centimeters + float precision = 0.01F; + + ////////////////////////////////////////////////////////////////////////// + // The error is measured on a rigidly deformed shell around every transform at the specified distance. + // Defaults to '3.0' centimeters + float shell_distance = 3.0F; + + ////////////////////////////////////////////////////////////////////////// + // TODO: Use the precision and shell distance? + + ////////////////////////////////////////////////////////////////////////// + // Threshold angle when detecting if rotation tracks are constant or default. + // See the rtm::quatf quat_near_identity for details about how the default threshold + // was chosen. You will typically NEVER need to change this, the value has been + // selected to be as safe as possible and is independent of game engine units. + // Defaults to '0.00284714461' radians + float constant_rotation_threshold_angle = 0.00284714461F; + + ////////////////////////////////////////////////////////////////////////// + // Threshold value to use when detecting if translation tracks are constant or default. + // Note that you will need to change this value if your units are not in centimeters. + // Defaults to '0.001' centimeters. + float constant_translation_threshold = 0.001F; + + ////////////////////////////////////////////////////////////////////////// + // Threshold value to use when detecting if scale tracks are constant or default. + // There are no units for scale as such a value that was deemed safe was selected + // as a default. + // Defaults to '0.00001' + float constant_scale_threshold = 0.00001F; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether a transform track description is valid or not. + // It is valid if: + // - The precision is positive or zero and finite + // - The shell distance is positive or zero and finite + // - The constant rotation threshold angle is positive or zero and finite + // - The constant translation threshold is positive or zero and finite + // - The constant scale threshold is positive or zero and finite + error_result is_valid() const; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/core/impl/track_desc.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_formats.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_formats.h new file mode 100644 index 00000000..1d934b42 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_formats.h @@ -0,0 +1,129 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + // BE CAREFUL WHEN CHANGING VALUES IN THIS ENUM + // The rotation format is serialized in the compressed data, if you change a value + // the compressed clips will be invalid. If you do, bump the appropriate algorithm versions. + enum class rotation_format8 : uint8_t + { + quatf_full = 0, // Full precision quaternion, [x,y,z,w] stored with float32 + //quatf_variable = 1, // TODO Quantized quaternion, [x,y,z,w] stored with [N,N,N,N] bits (same number of bits per component) + quatf_drop_w_full = 2, // Full precision quaternion, [x,y,z] stored with float32 (w is dropped) + quatf_drop_w_variable = 3, // Quantized quaternion, [x,y,z] stored with [N,N,N] bits (w is dropped, same number of bits per component) + + //quatf_optimal = 15, // Mix of quatf_variable and quatf_drop_w_variable + + // TODO: Implement these? + //quatf_drop_largest_full // Full precision quaternion, [a,b,c] stored with float32 (largest component dropped) + //quatf_drop_largest_variable // Quantized quaternion, [a,b,c] stored with [N,N,N] bits (largest component dropped, same number of bits per component) + //quatf_log_full, // Full precision quaternion logarithm, [x,y,z] stored with float32 + //quatf_log_variable, // Quantized quaternion logarithm, [x,y,z] stored with [N,N,N] bits (same number of bits per component) + }; + + // BE CAREFUL WHEN CHANGING VALUES IN THIS ENUM + // The vector format is serialized in the compressed data, if you change a value + // the compressed clips will be invalid. If you do, bump the appropriate algorithm versions. + enum class vector_format8 : uint8_t + { + vector3f_full = 0, // Full precision vector3f, [x,y,z] stored with float32 + vector3f_variable = 1, // Quantized vector3f, [x,y,z] stored with [N,N,N] bits (same number of bits per component) + }; + + union track_format8 + { + rotation_format8 rotation; + vector_format8 vector; + + track_format8() noexcept {} + explicit track_format8(rotation_format8 format) noexcept : rotation(format) {} + explicit track_format8(vector_format8 format) noexcept : vector(format) {} + }; + + enum class animation_track_type8 : uint8_t + { + rotation, + translation, + scale, + }; + + enum class rotation_variant8 : uint8_t + { + quat, + quat_drop_w, + //quat_drop_largest, + //quat_log, + }; + + ////////////////////////////////////////////////////////////////////////// + + const char* get_rotation_format_name(rotation_format8 format); + + bool get_rotation_format(const char* format, rotation_format8& out_format); + + constexpr const char* get_vector_format_name(vector_format8 format); + + bool get_vector_format(const char* format, vector_format8& out_format); + + constexpr rotation_variant8 get_rotation_variant(rotation_format8 rotation_format); + + constexpr rotation_format8 get_highest_variant_precision(rotation_variant8 variant); + + constexpr bool is_rotation_format_variable(rotation_format8 format); + + constexpr bool is_rotation_format_full_precision(rotation_format8 format); + + constexpr bool is_vector_format_variable(vector_format8 format); + + constexpr bool is_vector_format_full_precision(vector_format8 format); + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/core/impl/track_formats.impl.h" + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_traits.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_traits.h new file mode 100644 index 00000000..0dff4b3f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_traits.h @@ -0,0 +1,112 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/track_desc.h" +#include "acl/core/track_types.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Type tracks for tracks. + // Each trait contains: + // - The category of the track + // - The type of each sample in the track + // - The type of the track description + ////////////////////////////////////////////////////////////////////////// + template + struct track_traits {}; + + ////////////////////////////////////////////////////////////////////////// + // Specializations for each track type. + + template<> + struct track_traits + { + static constexpr track_category8 category = track_category8::scalarf; + + using sample_type = float; + using desc_type = track_desc_scalarf; + }; + + template<> + struct track_traits + { + static constexpr track_category8 category = track_category8::scalarf; + + using sample_type = rtm::float2f; + using desc_type = track_desc_scalarf; + }; + + template<> + struct track_traits + { + static constexpr track_category8 category = track_category8::scalarf; + + using sample_type = rtm::float3f; + using desc_type = track_desc_scalarf; + }; + + template<> + struct track_traits + { + static constexpr track_category8 category = track_category8::scalarf; + + using sample_type = rtm::float4f; + using desc_type = track_desc_scalarf; + }; + + template<> + struct track_traits + { + static constexpr track_category8 category = track_category8::scalarf; + + using sample_type = rtm::vector4f; + using desc_type = track_desc_scalarf; + }; + + template<> + struct track_traits + { + static constexpr track_category8 category = track_category8::transformf; + + using sample_type = rtm::qvvf; + using desc_type = track_desc_transformf; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_types.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_types.h new file mode 100644 index 00000000..b440d461 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_types.h @@ -0,0 +1,225 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/error_result.h" +#include "acl/core/memory_utils.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // We only support up to 4294967295 tracks. We reserve 4294967295 for the invalid index + constexpr uint32_t k_invalid_track_index = 0xFFFFFFFFU; + + ////////////////////////////////////////////////////////////////////////// + // The various supported track types. + // Note: be careful when changing values here as they might be serialized. + enum class track_type8 : uint8_t + { + float1f = 0, + float2f = 1, + float3f = 2, + float4f = 3, + vector4f = 4, + + //float1d = 5, + //float2d = 6, + //float3d = 7, + //float4d = 8, + //vector4d = 9, + + //quatf = 10, + //quatd = 11, + + qvvf = 12, + //qvvd = 13, + + //int1i = 14, + //int2i = 15, + //int3i = 16, + //int4i = 17, + //vector4i = 18, + + //int1q = 19, + //int2q = 20, + //int3q = 21, + //int4q = 22, + //vector4q = 23, + }; + + ////////////////////////////////////////////////////////////////////////// + // The categories of track types. + enum class track_category8 : uint8_t + { + scalarf = 0, + scalard = 1, + //scalari = 2, + //scalarq = 3, + transformf = 4, + transformd = 5, + }; + + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // Returns the string representation for the provided track type. + // TODO: constexpr + inline const char* get_track_type_name(track_type8 type) + { + switch (type) + { + case track_type8::float1f: return "float1f"; + case track_type8::float2f: return "float2f"; + case track_type8::float3f: return "float3f"; + case track_type8::float4f: return "float4f"; + case track_type8::vector4f: return "vector4f"; + case track_type8::qvvf: return "qvvf"; + default: return ""; + } + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track type from its string representation. + // Returns true on success, false otherwise. + inline bool get_track_type(const char* type, track_type8& out_type) + { + // Entries in the same order as the enum integral value + static const char* k_track_type_names[] = + { + "float1f", + "float2f", + "float3f", + "float4f", + "vector4f", + + "float1d", + "float2d", + "float3d", + "float4d", + "vector4d", + + "quatf", + "quatd", + + "qvvf", + }; + + static_assert(get_array_size(k_track_type_names) == (size_t)track_type8::qvvf + 1, "Unexpected array size"); + + ACL_ASSERT(type != nullptr, "Track type name cannot be null"); + if (type == nullptr) + return false; + + for (size_t type_index = 0; type_index < get_array_size(k_track_type_names); ++type_index) + { + const char* type_name = k_track_type_names[type_index]; + if (std::strncmp(type, type_name, std::strlen(type_name)) == 0) + { + out_type = safe_static_cast(type_index); + return true; + } + } + + return false; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the track category for the provided track type. + inline track_category8 get_track_category(track_type8 type) + { + // Entries in the same order as the enum integral value + static constexpr track_category8 k_track_type_to_category[] + { + track_category8::scalarf, // float1f + track_category8::scalarf, // float2f + track_category8::scalarf, // float3f + track_category8::scalarf, // float4f + track_category8::scalarf, // vector4f + + track_category8::scalard, // float1d + track_category8::scalard, // float2d + track_category8::scalard, // float3d + track_category8::scalard, // float4d + track_category8::scalard, // vector4d + + track_category8::transformf, // quatf + track_category8::transformd, // quatd + + track_category8::transformf, // qvvf + }; + + static_assert(get_array_size(k_track_type_to_category) == (size_t)track_type8::qvvf + 1, "Unexpected array size"); + + ACL_ASSERT(type <= track_type8::qvvf, "Unexpected track type"); + return type <= track_type8::qvvf ? k_track_type_to_category[static_cast(type)] : track_category8::scalarf; + } + + ////////////////////////////////////////////////////////////////////////// + // Returns the num of elements within a sample for the provided track type. + inline uint32_t get_track_num_sample_elements(track_type8 type) + { + // Entries in the same order as the enum integral value + static constexpr uint32_t k_track_type_to_num_elements[] + { + 1, // float1f + 2, // float2f + 3, // float3f + 4, // float4f + 4, // vector4f + + 1, // float1d + 2, // float2d + 3, // float3d + 4, // float4d + 4, // vector4d + + 4, // quatf + 4, // quatd + + 12, // qvvf + }; + + static_assert(get_array_size(k_track_type_to_num_elements) == (size_t)track_type8::qvvf + 1, "Unexpected array size"); + + ACL_ASSERT(type <= track_type8::qvvf, "Unexpected track type"); + return type <= track_type8::qvvf ? k_track_type_to_num_elements[static_cast(type)] : 0; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_writer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_writer.h new file mode 100644 index 00000000..a0b64a3e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/track_writer.h @@ -0,0 +1,138 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // We use a struct like this to allow an arbitrary format on the end user side. + // Since our decode function is templated on this type implemented by the user, + // the callbacks can trivially be inlined and customized. + // Only called functions need to be overridden and implemented. + ////////////////////////////////////////////////////////////////////////// + struct track_writer + { + ////////////////////////////////////////////////////////////////////////// + // Scalar track writing + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float1(uint32_t track_index, rtm::scalarf_arg0 value) + { + (void)track_index; + (void)value; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float2(uint32_t track_index, rtm::vector4f_arg0 value) + { + (void)track_index; + (void)value; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float3(uint32_t track_index, rtm::vector4f_arg0 value) + { + (void)track_index; + (void)value; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_float4(uint32_t track_index, rtm::vector4f_arg0 value) + { + (void)track_index; + (void)value; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a value for a specified track index. + void RTM_SIMD_CALL write_vector4(uint32_t track_index, rtm::vector4f_arg0 value) + { + (void)track_index; + (void)value; + } + + ////////////////////////////////////////////////////////////////////////// + // Transform track writing + + ////////////////////////////////////////////////////////////////////////// + // These allow the caller of decompress_pose to control which track types they are interested in. + // This information allows the codecs to avoid unpacking values that are not needed. + // Must be static constexpr! + static constexpr bool skip_all_rotations() { return false; } + static constexpr bool skip_all_translations() { return false; } + static constexpr bool skip_all_scales() { return false; } + + ////////////////////////////////////////////////////////////////////////// + // These allow the caller of decompress_pose to control which tracks they are interested in. + // This information allows the codecs to avoid unpacking values that are not needed. + // Must be non-static member functions! + constexpr bool skip_track_rotation(uint32_t /*track_index*/) const { return false; } + constexpr bool skip_track_translation(uint32_t /*track_index*/) const { return false; } + constexpr bool skip_track_scale(uint32_t /*track_index*/) const { return false; } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a quaternion rotation value for a specified bone index. + void RTM_SIMD_CALL write_rotation(uint32_t track_index, rtm::quatf_arg0 rotation) + { + (void)track_index; + (void)rotation; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a translation value for a specified bone index. + void RTM_SIMD_CALL write_translation(uint32_t track_index, rtm::vector4f_arg0 translation) + { + (void)track_index; + (void)translation; + } + + ////////////////////////////////////////////////////////////////////////// + // Called by the decoder to write out a scale value for a specified bone index. + void RTM_SIMD_CALL write_scale(uint32_t track_index, rtm::vector4f_arg0 scale) + { + (void)track_index; + (void)scale; + } + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/unique_ptr.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/unique_ptr.h new file mode 100644 index 00000000..56a7d1e4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/unique_ptr.h @@ -0,0 +1,83 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + template + class deleter + { + public: + deleter() noexcept : m_allocator(nullptr) {} + explicit deleter(iallocator& allocator) noexcept : m_allocator(&allocator) {} + deleter(const deleter&) noexcept = default; + deleter(deleter&&) noexcept = default; + ~deleter() noexcept = default; + deleter& operator=(const deleter&) noexcept = default; + deleter& operator=(deleter&&) noexcept = default; + + void operator()(allocated_type* ptr) + { + if (ptr == nullptr) + return; + + if (!std::is_trivially_destructible::value) + ptr->~allocated_type(); + + m_allocator->deallocate(ptr, sizeof(allocated_type)); + } + + private: + iallocator* m_allocator; + }; + + template + std::unique_ptr> make_unique(iallocator& allocator, args&&... arguments) + { + return std::unique_ptr>( + allocate_type(allocator, std::forward(arguments)...), + deleter(allocator)); + } + + template + std::unique_ptr> make_unique_aligned(iallocator& allocator, size_t alignment, args&&... arguments) + { + return std::unique_ptr>( + allocate_type_aligned(allocator, alignment, std::forward(arguments)...), + deleter(allocator)); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/utils.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/utils.h new file mode 100644 index 00000000..610ed865 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/utils.h @@ -0,0 +1,106 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/memory_utils.h" + +#include + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Calculate the number of samples present from a duration and sample rate. + // Conceptually, a clip with 1 sample at any sample rate has a single static + // pose and as such no definite duration. A clip with 2 samples at 30 FPS + // has a sample at time 0.0 and another at time 1/30s for a duration of 1/30s. + // We consider a 0.0 duration as having no samples, an infinite duration as + // having 1 sample, and otherwise having at least 1 sample. + ////////////////////////////////////////////////////////////////////////// + inline uint32_t calculate_num_samples(float duration, float sample_rate) + { + ACL_ASSERT(duration >= 0.0F, "Invalid duration: %f", duration); + if (duration == 0.0F) + return 0; // No duration whatsoever, we have no samples + + if (duration == std::numeric_limits::infinity()) + return 1; // An infinite duration, we have a single sample (static pose) + + // Otherwise we have at least 1 sample + ACL_ASSERT(sample_rate > 0.0F, "Invalid sample rate: %f", sample_rate); + + return safe_static_cast(rtm::scalar_floor((duration * sample_rate) + 0.5F)) + 1; + } + + ////////////////////////////////////////////////////////////////////////// + // Calculate a clip duration from its number of samples and sample rate. + // Conceptually, a clip with 1 sample at any sample rate has a single static + // pose and as such no definite duration. A clip with 2 samples at 30 FPS + // has a sample at time 0.0 and another at time 1/30s for a duration of 1/30s. + // We consider a 0.0 duration as having no samples, an infinite duration as + // having 1 sample, and otherwise having at least 1 sample. + ////////////////////////////////////////////////////////////////////////// + inline float calculate_duration(uint32_t num_samples, float sample_rate) + { + if (num_samples == 0) + return 0.0F; // No samples means we have no duration + + if (num_samples == 1) + return std::numeric_limits::infinity(); // A single sample means we have an indefinite duration (static pose) + + // Otherwise we have some duration + ACL_ASSERT(sample_rate > 0.0F, "Invalid sample rate: %f", sample_rate); + return float(num_samples - 1) / sample_rate; + } + + ////////////////////////////////////////////////////////////////////////// + // Calculate a clip duration from its number of samples and sample rate + // and returns a finite duration suitable for clamping. + ////////////////////////////////////////////////////////////////////////// + inline float calculate_finite_duration(uint32_t num_samples, float sample_rate) + { + // No samples means we have no duration and a single sample means we have an indefinite duration (static pose) + // Either way, return a duration of 0.0 + if (num_samples <= 1) + return 0.0F; + + // Otherwise we have some duration + ACL_ASSERT(sample_rate > 0.0F, "Invalid sample rate: %f", sample_rate); + return float(num_samples - 1) / sample_rate; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/variable_bit_rates.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/variable_bit_rates.h new file mode 100644 index 00000000..4aea3e26 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/core/variable_bit_rates.h @@ -0,0 +1,69 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + // Bit rate 0 is reserved for tracks that are constant in a segment + constexpr uint8_t k_bit_rate_num_bits[] = { 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 32 }; + + constexpr uint8_t k_invalid_bit_rate = 0xFF; + constexpr uint8_t k_lowest_bit_rate = 1; + constexpr uint8_t k_highest_bit_rate = sizeof(k_bit_rate_num_bits) - 1; + constexpr uint32_t k_num_bit_rates = sizeof(k_bit_rate_num_bits); + + static_assert(k_num_bit_rates == 19, "Expecting 19 bit rates"); + + inline uint32_t get_num_bits_at_bit_rate(uint32_t bit_rate) + { + ACL_ASSERT(bit_rate <= k_highest_bit_rate, "Invalid bit rate: %u", bit_rate); + return k_bit_rate_num_bits[bit_rate]; + } + + // Track is constant, our constant sample is stored in the range information + constexpr bool is_constant_bit_rate(uint32_t bit_rate) { return bit_rate == 0; } + constexpr bool is_raw_bit_rate(uint32_t bit_rate) { return bit_rate == k_highest_bit_rate; } + + struct BoneBitRate + { + uint8_t rotation; + uint8_t translation; + uint8_t scale; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database.h new file mode 100644 index 00000000..07dba259 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database.h @@ -0,0 +1,169 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_database.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/error.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database_settings.h" +#include "acl/decompression/database/database_streamer.h" +#include "acl/decompression/database/impl/database_context.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Encapsulates the possible streaming request results. + ////////////////////////////////////////////////////////////////////////// + enum class database_stream_request_result + { + ////////////////////////////////////////////////////////////////////////// + // Streaming is done for the requested tier + done, + + ////////////////////////////////////////////////////////////////////////// + // The streaming request has been dispatched + dispatched, + + ////////////////////////////////////////////////////////////////////////// + // The streaming request has been ignored because streaming is already in progress + streaming_in_progress, + + ////////////////////////////////////////////////////////////////////////// + // The database context isn't initialized + context_not_initialized, + + ////////////////////////////////////////////////////////////////////////// + // Invalid database tier specified + invalid_database_tier, + + ////////////////////////////////////////////////////////////////////////// + // Ran out of streaming requests + no_free_streaming_requests, + }; + + ////////////////////////////////////////////////////////////////////////// + // Database decompression context for the uniformly sampled algorithm. The context + // allows various streaming actions to be performed on the database. + // + // Both the constructor and destructor are public because it is safe to place + // instances of this context on the stack or as member variables. + // + // Note that the context only manages the streaming bookkeeping, the actual IO is + // handled by the database_streamer implementation provided. + ////////////////////////////////////////////////////////////////////////// + template + class database_context + { + public: + ////////////////////////////////////////////////////////////////////////// + // An alias to the database settings type. + using settings_type = database_settings_type; + + ////////////////////////////////////////////////////////////////////////// + // Constructs a context instance. + database_context(); + + ////////////////////////////////////////////////////////////////////////// + // Destructs a context instance. + ~database_context(); + + ////////////////////////////////////////////////////////////////////////// + // Returns the compressed database bound to this context instance. + const compressed_database* get_compressed_database() const; + + ////////////////////////////////////////////////////////////////////////// + // Initializes the context instance to a particular compressed database instance. + // No streaming can be performed and it is assumed that all the data is present in memory. + // Returns whether initialization was successful or not. + bool initialize(iallocator& allocator, const compressed_database& database); + + ////////////////////////////////////////////////////////////////////////// + // Initializes the context instance to a particular compressed database instance. + // The streamer instances will be used to issue IO stream in/out requests. + // If a tier is stripped, the null_database_streamer can be used. + // Returns whether initialization was successful or not. + bool initialize(iallocator& allocator, const compressed_database& database, database_streamer& medium_tier_streamer, database_streamer& low_tier_streamer); + + ////////////////////////////////////////////////////////////////////////// + // Returns true if this context instance is bound to a compressed database instance, false otherwise. + bool is_initialized() const; + + ////////////////////////////////////////////////////////////////////////// + // Resets the context instance to its default constructed state. If the context was + // currently bound to a database instance, it will no longer be bound to it. + void reset(); + + ////////////////////////////////////////////////////////////////////////// + // Returns whether the database bound contains the provided compressed tracks instance data. + bool contains(const compressed_tracks& tracks) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether or not we have streamed in any of the database bulk data for the specified tier (medium or low). + bool is_streamed_in(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Returns whether or not a streaming request is in flight for the specified tier (medium or low). + bool is_streaming(quality_tier tier) const; + + ////////////////////////////////////////////////////////////////////////// + // Issues a stream in request and returns the current status for the specified tier (medium or low). + // By default, every chunk will be streamed in but they can be streamed progressively + // by providing a number of chunks. + database_stream_request_result stream_in(quality_tier tier, uint32_t num_chunks_to_stream = ~0U); + + ////////////////////////////////////////////////////////////////////////// + // Issues a stream out request and returns the current status for the specified tier (medium or low). + // By default, every chunk will be streamed out but they can be streamed progressively + // by providing a number of chunks. + database_stream_request_result stream_out(quality_tier tier, uint32_t num_chunks_to_stream = ~0U); + + private: + database_context(const database_context& other) = delete; + database_context& operator=(const database_context& other) = delete; + + // Internal context data + acl_impl::database_context_v0 m_context; + + static_assert(std::is_base_of::value, "database_settings_type must derive from database_settings!"); + + // TODO: I'd like to assert here but we use a dummy pointer to init the decompression context which triggers this + //static_assert(settings_type::version_supported() != compressed_tracks_version16::none, "database_settings_type must support at least one version"); + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/decompression/database/impl/database.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database_settings.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database_settings.h new file mode 100644 index 00000000..b4b2b2cb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database_settings.h @@ -0,0 +1,86 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Deriving from this struct and overriding these constexpr functions + // allow you to control which code is stripped for maximum performance. + // + // By default, everything is supported. + ////////////////////////////////////////////////////////////////////////// + struct database_settings + { + ////////////////////////////////////////////////////////////////////////// + // Which version we should optimize for. + // If 'any' is specified, the database context will support every single version + // with full backwards compatibility. + // Using a specific version allows the compiler to statically strip code for all other + // versions. This allows the creation of context objects specialized for specific + // versions which yields optimal performance. + // Must be static constexpr! + static constexpr compressed_tracks_version16 version_supported() { return compressed_tracks_version16::any; } + }; + + ////////////////////////////////////////////////////////////////////////// + // These settings disable database usage. + ////////////////////////////////////////////////////////////////////////// + struct null_database_settings final : public database_settings + { + static constexpr compressed_tracks_version16 version_supported() { return compressed_tracks_version16::none; } + }; + + ////////////////////////////////////////////////////////////////////////// + // These are debug settings, everything is enabled and nothing is stripped. + // It will have the worst performance but allows every feature. + ////////////////////////////////////////////////////////////////////////// + struct debug_database_settings : public database_settings + { + }; + + ////////////////////////////////////////////////////////////////////////// + // These are the default settings. Only the generally optimal settings + // are enabled and will offer the overall best performance. + // Supports every version. + ////////////////////////////////////////////////////////////////////////// + struct default_database_settings : public database_settings + { + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database_streamer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database_streamer.h new file mode 100644 index 00000000..e732780a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/database_streamer.h @@ -0,0 +1,182 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/quality_tiers.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct database_context_v0; + } + + ////////////////////////////////////////////////////////////////////////// + // Enum representing a streaming action + ////////////////////////////////////////////////////////////////////////// + enum class streaming_action + { + stream_in, + stream_out, + }; + + ////////////////////////////////////////////////////////////////////////// + // A streaming request + ////////////////////////////////////////////////////////////////////////// + struct streaming_request + { + streaming_action action = streaming_action::stream_in; + quality_tier tier = quality_tier::highest_importance; // Marks the request as invalid since we never stream the highest tier + uint32_t first_chunk_index = 0; + uint32_t num_streaming_chunks = 0; + uint32_t generation_id = 0; + + bool is_valid() const { return tier != quality_tier::highest_importance; } + void reset() { tier = quality_tier::highest_importance; } + }; + + ////////////////////////////////////////////////////////////////////////// + // A streaming request ID + // + // The internal value is an implementation detail. + ////////////////////////////////////////////////////////////////////////// + struct streaming_request_id + { + // Opaque value + uint64_t value; + + bool is_valid() const { return value != ~0ULL; } + }; + + // An invalid streaming request ID + constexpr streaming_request_id k_invalid_streamer_request_id = { ~0ULL }; + + //////////////////////////////////////////////////////////////////////////////// + // The base class for database streamers. + // + // Streamer implementations are responsible for allocating/freeing the bulk data as well as + // streaming the data in/out. Streaming in is safe from any thread but streaming out + // cannot happen while decompression is in progress otherwise the behavior is undefined. + // A single streamer instance can be shared between all quality tiers of a database. + // Streamers cannot be shared by multiple databases. + // A streamer implementation must also provide a list of streaming requests to use and + // recycle. A proper implementation should consider how many requests it needs. + //////////////////////////////////////////////////////////////////////////////// + class database_streamer + { + public: + ////////////////////////////////////////////////////////////////////////// + // Streamer destructor + virtual ~database_streamer() = default; + + ////////////////////////////////////////////////////////////////////////// + // Returns true if the streamer is initialized. + virtual bool is_initialized() const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Returns a valid pointer to the bulk data used to decompress from for the specified quality tier. + // Note that the pointer will not be used until after the first successful stream in + // request is completed. As such, it is safe to allocate the bulk data when the first + // stream in request happens. + virtual const uint8_t* get_bulk_data(quality_tier tier) const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Called when we request some data to be streamed in. + // Only one stream in/out request can be in flight at a time per quality tier. + // Streaming in animation data can be done while animations are decompressing (async). + // + // The offset into the bulk data and the size in bytes to stream in are provided as arguments. + // On the first stream in request, the bulk data can be allocated but its pointer cannot change with subsequent + // stream in requests until everything has been streamed out. + // Once the streaming request has been fulfilled (sync or async), call complete(..) or cancel(..) with the provided + // request ID. + virtual void stream_in(uint32_t offset, uint32_t size, bool can_allocate_bulk_data, quality_tier tier, streaming_request_id request_id) = 0; + + ////////////////////////////////////////////////////////////////////////// + // Called when we request some data to be streamed out. + // Only one stream in/out request can be in flight at a time per quality tier. + // Streaming out animation data cannot be done while animations are decompressing. + // Doing so will result in undefined behavior as the data could be in use while we stream it out. + // + // The offset into the bulk data and the size in bytes to stream out are provided as arguments. + // On the last stream out request, the bulk data can be deallocated. It will be allocated again + // if the data streams back in. + // Once the streaming request has been fulfilled (sync or async), call complete(..) or cancel(..) with the provided + // request ID. + // + // A stream out request CANNOT be canceled! + virtual void stream_out(uint32_t offset, uint32_t size, bool can_deallocate_bulk_data, quality_tier tier, streaming_request_id request_id) = 0; + + ////////////////////////////////////////////////////////////////////////// + // Signifies that the streamer has completed this streaming request successfully. + // The bulk data must be allocated and ready to use. + void complete(streaming_request_id request_id); + + ////////////////////////////////////////////////////////////////////////// + // Signifies that the streamer will not complete this streaming request. + void cancel(streaming_request_id request_id); + + protected: + ////////////////////////////////////////////////////////////////////////// + // Constructs the database streamer. + // The provided requests will be used and recycled internally when we stream in/out. + database_streamer(streaming_request* requests, uint32_t num_requests); + + private: + ////////////////////////////////////////////////////////////////////////// + // Binds this streamer instance to our database context. + // The same streamer can be bound multiple times to the same database context. + void bind(acl_impl::database_context_v0& context); + + ////////////////////////////////////////////////////////////////////////// + // Finds and builds a new streaming request if we have one to spare, otherwise + // the request ID is invalid. + streaming_request_id build_request(streaming_action action, quality_tier tier, uint32_t first_chunk_index, uint32_t num_streaming_chunks); + + acl_impl::database_context_v0* m_context; + streaming_request* m_requests; + + uint32_t m_generation_id; + uint32_t m_next_request_index; + uint32_t m_num_requests; + + template + friend class database_context; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/decompression/database/impl/database_streamer.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database.impl.h new file mode 100644 index 00000000..5678f34e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database.impl.h @@ -0,0 +1,583 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from database.h + +#include "acl/version.h" +#include "acl/core/bitset.h" +#include "acl/core/compressed_database.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/decompression/database/impl/database_context.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline uint32_t calculate_runtime_data_size(const compressed_database& database) + { + const database_header& header = get_database_header(database); + + const uint32_t num_medium_chunks = header.num_chunks[0]; + const uint32_t num_low_chunks = header.num_chunks[1]; + const uint32_t num_clips = header.num_clips; + const uint32_t num_segments = header.num_segments; + + const bitset_description medium_desc = bitset_description::make_from_num_bits(num_medium_chunks); + const bitset_description low_desc = bitset_description::make_from_num_bits(num_low_chunks); + + const uint32_t medium_bitset_size = medium_desc.get_num_bytes(); + const uint32_t low_bitset_size = low_desc.get_num_bytes(); + + uint32_t runtime_data_size = 0; + runtime_data_size += medium_bitset_size; // Loaded chunks + runtime_data_size += medium_bitset_size; // Streaming chunks + runtime_data_size += low_bitset_size; // Loaded chunks + runtime_data_size += low_bitset_size; // Streaming chunks + runtime_data_size = align_to(runtime_data_size, 8); // Align runtime headers + runtime_data_size += num_clips * sizeof(database_runtime_clip_header); + runtime_data_size += num_segments * sizeof(database_runtime_segment_header); + + return runtime_data_size; + } + } + + template + inline database_context::database_context() + : m_context() + { + m_context.reset(); + + static_assert(offsetof(database_context, m_context) == 0, "m_context must be the first data member!"); + } + + template + inline database_context::~database_context() + { + reset(); + } + + template + inline const compressed_database* database_context::get_compressed_database() const { return m_context.get_compressed_database(); } + + template + inline bool database_context::initialize(iallocator& allocator, const compressed_database& database) + { + const bool is_valid = database.is_valid(false).empty(); + ACL_ASSERT(is_valid, "Invalid compressed database instance"); + if (!is_valid) + return false; + + ACL_ASSERT(database.get_version() == compressed_tracks_version16::v02_00_00, "Unsupported version"); + if (database.get_version() != compressed_tracks_version16::v02_00_00) + return false; + + ACL_ASSERT(database.is_bulk_data_inline(), "Bulk data must be inline when initializing without a streamer"); + if (!database.is_bulk_data_inline()) + return false; + + ACL_ASSERT(!is_initialized(), "Cannot initialize database twice"); + if (is_initialized()) + return false; + + m_context.db = &database; + m_context.allocator = &allocator; + m_context.bulk_data[0] = database.get_bulk_data(quality_tier::medium_importance); + m_context.bulk_data[1] = database.get_bulk_data(quality_tier::lowest_importance); + m_context.streamers[0] = m_context.streamers[1] = nullptr; + + const acl_impl::database_header& header = acl_impl::get_database_header(database); + + const uint32_t num_medium_chunks = header.num_chunks[0]; + const uint32_t num_low_chunks = header.num_chunks[1]; + + const bitset_description medium_desc = bitset_description::make_from_num_bits(num_medium_chunks); + const bitset_description low_desc = bitset_description::make_from_num_bits(num_low_chunks); + + const uint32_t medium_bitset_size = medium_desc.get_num_bytes(); + const uint32_t low_bitset_size = low_desc.get_num_bytes(); + + // Allocate a single buffer for everything we need. This is faster to allocate and it ensures better virtual + // memory locality which should help reduce the cost of TLB misses. + const uint32_t runtime_data_size = acl_impl::calculate_runtime_data_size(database); + uint8_t* runtime_data_buffer = allocate_type_array_aligned(allocator, runtime_data_size, 16); + + // Initialize everything to 0 + std::memset(runtime_data_buffer, 0, runtime_data_size); + + m_context.loaded_chunks[0] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += medium_bitset_size; + + m_context.streaming_chunks[0] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += medium_bitset_size; + + m_context.loaded_chunks[1] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += low_bitset_size; + + m_context.streaming_chunks[1] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += low_bitset_size; + + m_context.clip_segment_headers = align_to(runtime_data_buffer, 8); // Align runtime headers + + // Copy our clip hashes to setup our headers + const uint32_t num_clips = header.num_clips; + const acl_impl::database_clip_metadata* clip_metadatas = header.get_clip_metadatas(); + for (uint32_t clip_index = 0; clip_index < num_clips; ++clip_index) + { + const acl_impl::database_clip_metadata& clip_metadata = clip_metadatas[clip_index]; + acl_impl::database_runtime_clip_header* clip_header = clip_metadata.get_clip_header(m_context.clip_segment_headers); + clip_header->clip_hash = clip_metadata.clip_hash; + } + + // Bulk data is inline so stream everything in right away + const acl_impl::database_chunk_description* chunk_descriptions = header.get_chunk_descriptions_medium(); + for (uint32_t chunk_index = 0; chunk_index < num_medium_chunks; ++chunk_index) + { + const acl_impl::database_chunk_description& chunk_description = chunk_descriptions[chunk_index]; + const acl_impl::database_chunk_header* chunk_header = chunk_description.get_chunk_header(m_context.bulk_data[0]); + ACL_ASSERT(chunk_header->index == chunk_index, "Unexpected chunk index"); + + const acl_impl::database_chunk_segment_header* chunk_segment_headers = chunk_header->get_segment_headers(); + const uint32_t num_segments = chunk_header->num_segments; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const acl_impl::database_chunk_segment_header& chunk_segment_header = chunk_segment_headers[segment_index]; + +#if defined(ACL_HAS_ASSERT_CHECKS) + const acl_impl::database_runtime_clip_header* clip_header = chunk_segment_header.get_clip_header(m_context.clip_segment_headers); + ACL_ASSERT(clip_header->clip_hash == chunk_segment_header.clip_hash, "Unexpected clip hash"); +#endif + + acl_impl::database_runtime_segment_header* segment_header = chunk_segment_header.get_segment_header(m_context.clip_segment_headers); + ACL_ASSERT(segment_header->tier_metadata[0].load(std::memory_order::memory_order_relaxed) == 0, "Tier metadata should not be initialized"); + segment_header->tier_metadata[0].store((uint64_t(chunk_segment_header.samples_offset) << 32) | chunk_segment_header.sample_indices, std::memory_order::memory_order_relaxed); + } + + bitset_set(m_context.loaded_chunks[0], medium_desc, chunk_index, true); + } + + chunk_descriptions = header.get_chunk_descriptions_low(); + for (uint32_t chunk_index = 0; chunk_index < num_low_chunks; ++chunk_index) + { + const acl_impl::database_chunk_description& chunk_description = chunk_descriptions[chunk_index]; + const acl_impl::database_chunk_header* chunk_header = chunk_description.get_chunk_header(m_context.bulk_data[1]); + ACL_ASSERT(chunk_header->index == chunk_index, "Unexpected chunk index"); + + const acl_impl::database_chunk_segment_header* chunk_segment_headers = chunk_header->get_segment_headers(); + const uint32_t num_segments = chunk_header->num_segments; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const acl_impl::database_chunk_segment_header& chunk_segment_header = chunk_segment_headers[segment_index]; + +#if defined(ACL_HAS_ASSERT_CHECKS) + const acl_impl::database_runtime_clip_header* clip_header = chunk_segment_header.get_clip_header(m_context.clip_segment_headers); + ACL_ASSERT(clip_header->clip_hash == chunk_segment_header.clip_hash, "Unexpected clip hash"); +#endif + + acl_impl::database_runtime_segment_header* segment_header = chunk_segment_header.get_segment_header(m_context.clip_segment_headers); + ACL_ASSERT(segment_header->tier_metadata[1].load(std::memory_order::memory_order_relaxed) == 0, "Tier metadata should not be initialized"); + segment_header->tier_metadata[1].store((uint64_t(chunk_segment_header.samples_offset) << 32) | chunk_segment_header.sample_indices, std::memory_order::memory_order_relaxed); + } + + bitset_set(m_context.loaded_chunks[1], low_desc, chunk_index, true); + } + + return true; + } + + template + inline bool database_context::initialize(iallocator& allocator, const compressed_database& database, database_streamer& medium_tier_streamer, database_streamer& low_tier_streamer) + { + const bool is_valid = database.is_valid(false).empty(); + ACL_ASSERT(is_valid, "Invalid compressed database instance"); + if (!is_valid) + return false; + + ACL_ASSERT(database.get_version() == compressed_tracks_version16::v02_00_00, "Unsupported version"); + if (database.get_version() != compressed_tracks_version16::v02_00_00) + return false; + + ACL_ASSERT(medium_tier_streamer.is_initialized(), "Medium importance tier streamer must be initialized"); + if (!medium_tier_streamer.is_initialized()) + return false; + + ACL_ASSERT(low_tier_streamer.is_initialized(), "Low importance tier streamer must be initialized"); + if (!low_tier_streamer.is_initialized()) + return false; + + ACL_ASSERT(!is_initialized(), "Cannot initialize database twice"); + if (is_initialized()) + return false; + + m_context.db = &database; + m_context.allocator = &allocator; + m_context.bulk_data[0] = m_context.bulk_data[1] = nullptr; // Will be set during the first stream in request + m_context.streamers[0] = &medium_tier_streamer; + m_context.streamers[1] = &low_tier_streamer; + + medium_tier_streamer.bind(m_context); + low_tier_streamer.bind(m_context); + + const acl_impl::database_header& header = acl_impl::get_database_header(database); + + const uint32_t num_medium_chunks = header.num_chunks[0]; + const uint32_t num_low_chunks = header.num_chunks[1]; + + const bitset_description medium_desc = bitset_description::make_from_num_bits(num_medium_chunks); + const bitset_description low_desc = bitset_description::make_from_num_bits(num_low_chunks); + + const uint32_t medium_bitset_size = medium_desc.get_num_bytes(); + const uint32_t low_bitset_size = low_desc.get_num_bytes(); + + // Allocate a single buffer for everything we need. This is faster to allocate and it ensures better virtual + // memory locality which should help reduce the cost of TLB misses. + const uint32_t runtime_data_size = acl_impl::calculate_runtime_data_size(database); + uint8_t* runtime_data_buffer = allocate_type_array_aligned(allocator, runtime_data_size, 16); + + // Initialize everything to 0 + std::memset(runtime_data_buffer, 0, runtime_data_size); + + m_context.loaded_chunks[0] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += medium_bitset_size; + + m_context.streaming_chunks[0] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += medium_bitset_size; + + m_context.loaded_chunks[1] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += low_bitset_size; + + m_context.streaming_chunks[1] = reinterpret_cast(runtime_data_buffer); + runtime_data_buffer += low_bitset_size; + + m_context.clip_segment_headers = align_to(runtime_data_buffer, 8); // Align runtime headers + + // Copy our clip hashes to setup our headers + const uint32_t num_clips = header.num_clips; + const acl_impl::database_clip_metadata* clip_metadatas = header.get_clip_metadatas(); + for (uint32_t clip_index = 0; clip_index < num_clips; ++clip_index) + { + const acl_impl::database_clip_metadata& clip_metadata = clip_metadatas[clip_index]; + acl_impl::database_runtime_clip_header* clip_header = clip_metadata.get_clip_header(m_context.clip_segment_headers); + clip_header->clip_hash = clip_metadata.clip_hash; + } + + return true; + } + + template + inline bool database_context::is_initialized() const { return m_context.is_initialized(); } + + template + inline void database_context::reset() + { + if (!is_initialized()) + return; // Nothing to do + + ACL_ASSERT(!is_streaming(quality_tier::medium_importance), "Behavior is undefined if context is reset while streaming is in progress"); + ACL_ASSERT(!is_streaming(quality_tier::lowest_importance), "Behavior is undefined if context is reset while streaming is in progress"); + + const uint32_t runtime_data_size = acl_impl::calculate_runtime_data_size(*m_context.db); + deallocate_type_array(*m_context.allocator, m_context.loaded_chunks[0], runtime_data_size); + + // Just reset the DB pointer, this will mark us as no longer initialized indicating everything is stale + m_context.db = nullptr; + } + + template + inline bool database_context::contains(const compressed_tracks& tracks) const + { + ACL_ASSERT(is_initialized(), "Database isn't initialized"); + if (!is_initialized()) + return false; + + if (!tracks.has_database()) + return false; // Clip not bound to anything + + const acl_impl::transform_tracks_header& transform_header = acl_impl::get_transform_tracks_header(tracks); + const acl_impl::tracks_database_header* tracks_db_header = transform_header.get_database_header(); + ACL_ASSERT(tracks_db_header != nullptr, "Expected a 'tracks_database_header'"); + + if (!tracks_db_header->clip_header_offset.is_valid()) + return false; // Invalid clip header offset + + const uint32_t num_clips = m_context.db->get_num_clips(); + const uint32_t num_segments = m_context.db->get_num_segments(); + + uint32_t largest_offset = 0; + largest_offset += num_clips * sizeof(acl_impl::database_runtime_clip_header); + largest_offset += num_segments * sizeof(acl_impl::database_runtime_segment_header); + + // We can't read past the end of the last entry + largest_offset -= sizeof(acl_impl::database_runtime_segment_header); + + if (tracks_db_header->clip_header_offset > largest_offset) + return false; // Invalid clip header offset + + const acl_impl::database_runtime_clip_header* db_clip_header = tracks_db_header->get_clip_header(m_context.clip_segment_headers); + if (db_clip_header->clip_hash != tracks.get_hash()) + return false; // Clip not bound to this database instance + + // All good + return true; + } + + template + inline bool database_context::is_streamed_in(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + ACL_ASSERT(is_initialized(), "Database isn't initialized"); + if (!is_initialized() || tier == quality_tier::highest_importance) + return false; + + const uint32_t num_chunks = m_context.db->get_num_chunks(tier); + const bitset_description desc = bitset_description::make_from_num_bits(num_chunks); + const uint32_t tier_index = uint32_t(tier) - 1; + + const uint32_t* loaded_chunks = m_context.loaded_chunks[tier_index]; + const uint32_t num_loaded_chunks = bitset_count_set_bits(loaded_chunks, desc); + + return num_loaded_chunks == num_chunks; + } + + template + inline bool database_context::is_streaming(quality_tier tier) const + { + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + ACL_ASSERT(is_initialized(), "Database isn't initialized"); + if (!is_initialized() || tier == quality_tier::highest_importance) + return false; + + const uint32_t num_chunks = m_context.db->get_num_chunks(tier); + const bitset_description desc = bitset_description::make_from_num_bits(num_chunks); + const uint32_t tier_index = uint32_t(tier) - 1; + + const uint32_t* streaming_chunks = m_context.streaming_chunks[tier_index]; + const uint32_t num_streaming_chunks = bitset_count_set_bits(streaming_chunks, desc); + + return num_streaming_chunks != 0; + } + + template + inline database_stream_request_result database_context::stream_in(quality_tier tier, uint32_t num_chunks_to_stream) + { + ACL_ASSERT(is_initialized(), "Database isn't initialized"); + if (!is_initialized()) + return database_stream_request_result::context_not_initialized; + + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return database_stream_request_result::invalid_database_tier; + + if (is_streaming(tier)) + return database_stream_request_result::streaming_in_progress; // Can't stream while we are streaming + + const acl_impl::database_header& header = acl_impl::get_database_header(*m_context.db); + const acl_impl::database_chunk_description* chunk_descriptions = tier == quality_tier::medium_importance ? header.get_chunk_descriptions_medium() : header.get_chunk_descriptions_low(); + + const uint32_t tier_index = uint32_t(tier) - 1; + + const uint32_t num_chunks = header.num_chunks[tier_index]; + const bitset_description desc = bitset_description::make_from_num_bits(num_chunks); + const uint32_t max_chunk_size = header.max_chunk_size; + + uint32_t first_chunk_index = ~0U; + + // Clamp the total number of chunks we can stream + num_chunks_to_stream = std::min(num_chunks_to_stream, num_chunks); + + // Look for chunks that aren't loaded yet and aren't streaming yet + const uint32_t num_entries = desc.get_size(); + const uint32_t* loaded_chunks = m_context.loaded_chunks[tier_index]; + for (uint32_t entry_index = 0; entry_index < num_entries; ++entry_index) + { + const uint32_t maybe_loaded = loaded_chunks[entry_index]; + const uint32_t num_pending_chunks = count_trailing_zeros(maybe_loaded); + if (num_pending_chunks != 0) + { + first_chunk_index = (entry_index * 32) + (32 - num_pending_chunks); + break; + } + } + + if (first_chunk_index == ~0U) + return database_stream_request_result::done; // Everything is streamed in or streaming, nothing to do + + // Calculate and clamp our last chunk index (and handle wrapping for safety) + const uint64_t last_chunk_index64 = uint64_t(first_chunk_index) + uint64_t(num_chunks_to_stream) - 1; + const uint32_t last_chunk_index = last_chunk_index64 >= uint64_t(num_chunks) ? (num_chunks - 1) : uint32_t(last_chunk_index64); + + // Calculate our stream size and account for the fact that the last chunk doesn't have the same size + const uint32_t num_streaming_chunks = last_chunk_index - first_chunk_index + 1; + + if (num_streaming_chunks == 0) + return database_stream_request_result::done; // Nothing more to stream + + database_streamer* streamer = m_context.streamers[tier_index]; + + const streaming_request_id request_id = streamer->build_request(streaming_action::stream_in, tier, first_chunk_index, num_streaming_chunks); + if (!request_id.is_valid()) + return database_stream_request_result::no_free_streaming_requests; + + // Find the stream start offset from our first chunk's offset + const acl_impl::database_chunk_description& first_chunk_description = chunk_descriptions[first_chunk_index]; + const uint32_t stream_start_offset = first_chunk_description.offset; + + const acl_impl::database_chunk_description& last_chunk_description = chunk_descriptions[last_chunk_index]; + const uint32_t stream_size = ((num_streaming_chunks - 1) * max_chunk_size) + last_chunk_description.size; + + // We can allocate our bulk data if we haven't already + const uint8_t* bulk_data = m_context.bulk_data[tier_index]; + const bool can_allocate_bulk_data = bulk_data == nullptr; + + // Mark chunks as in-streaming + uint32_t* streaming_chunks = m_context.streaming_chunks[tier_index]; + bitset_set_range(streaming_chunks, desc, first_chunk_index, num_streaming_chunks, true); + + // Fire the stream in request and let the streamer handle it (sync/async) + streamer->stream_in(stream_start_offset, stream_size, can_allocate_bulk_data, tier, request_id); + + return database_stream_request_result::dispatched; + } + + template + inline database_stream_request_result database_context::stream_out(quality_tier tier, uint32_t num_chunks_to_stream) + { + ACL_ASSERT(is_initialized(), "Database isn't initialized"); + if (!is_initialized()) + return database_stream_request_result::context_not_initialized; + + ACL_ASSERT(tier != quality_tier::highest_importance, "The database does not contain data for the high importance tier, it lives inside compressed_tracks"); + if (tier == quality_tier::highest_importance) + return database_stream_request_result::invalid_database_tier; + + if (is_streaming(tier)) + return database_stream_request_result::streaming_in_progress; // Can't stream while we are streaming + + const acl_impl::database_header& header = acl_impl::get_database_header(*m_context.db); + const acl_impl::database_chunk_description* chunk_descriptions = tier == quality_tier::medium_importance ? header.get_chunk_descriptions_medium() : header.get_chunk_descriptions_low(); + + const uint32_t tier_index = uint32_t(tier) - 1; + + const uint32_t num_chunks = header.num_chunks[tier_index]; + const bitset_description desc = bitset_description::make_from_num_bits(num_chunks); + const uint32_t max_chunk_size = header.max_chunk_size; + + // Clamp the total number of chunks we can stream + num_chunks_to_stream = std::min(num_chunks_to_stream, num_chunks); + + uint32_t first_chunk_index = ~0U; + + // Look for chunks that aren't unloaded yet and aren't streaming yet + const uint32_t num_entries = desc.get_size(); + const uint32_t* loaded_chunks = m_context.loaded_chunks[tier_index]; + for (uint32_t entry_index = 0; entry_index < num_entries; ++entry_index) + { + const uint32_t maybe_loaded = loaded_chunks[entry_index]; + const uint32_t num_pending_chunks = count_leading_zeros(maybe_loaded); + if (num_pending_chunks != 32) + { + first_chunk_index = (entry_index * 32) + num_pending_chunks; + break; + } + } + + if (first_chunk_index == ~0U) + return database_stream_request_result::done; // Everything is streamed out or streaming, nothing to do + + // Calculate and clamp our last chunk index (and handle wrapping for safety) + const uint64_t last_chunk_index64 = uint64_t(first_chunk_index) + uint64_t(num_chunks_to_stream) - 1; + const uint32_t last_chunk_index = last_chunk_index64 >= uint64_t(num_chunks) ? (num_chunks - 1) : uint32_t(last_chunk_index64); + + // Calculate our stream size and account for the fact that the last chunk doesn't have the same size + const uint32_t num_streaming_chunks = last_chunk_index - first_chunk_index + 1; + + if (num_streaming_chunks == 0) + return database_stream_request_result::done; // Nothing more to stream + + database_streamer* streamer = m_context.streamers[tier_index]; + + const streaming_request_id request_id = streamer->build_request(streaming_action::stream_out, tier, first_chunk_index, num_streaming_chunks); + if (!request_id.is_valid()) + return database_stream_request_result::no_free_streaming_requests; + + // Find the stream start offset from our first chunk's offset + const acl_impl::database_chunk_description& first_chunk_description = chunk_descriptions[first_chunk_index]; + const uint32_t stream_start_offset = first_chunk_description.offset; + + const acl_impl::database_chunk_description& last_chunk_description = chunk_descriptions[last_chunk_index]; + const uint32_t stream_size = ((num_streaming_chunks - 1) * max_chunk_size) + last_chunk_description.size; + + // Mark chunks as in-streaming + uint32_t* streaming_chunks = m_context.streaming_chunks[tier_index]; + bitset_set_range(streaming_chunks, desc, first_chunk_index, num_streaming_chunks, true); + + const uint8_t*& bulk_data_ref = m_context.bulk_data[tier_index]; + const uint8_t* bulk_data = bulk_data_ref; + ACL_ASSERT(bulk_data != nullptr, "Bulk data should be allocated when we stream out"); + + // We can deallocate our bulk data if we are streaming out the last chunks + const uint32_t num_loaded_chunks = bitset_count_set_bits(loaded_chunks, desc); + const bool can_deallocate_bulk_data = num_streaming_chunks == num_loaded_chunks; + if (can_deallocate_bulk_data) + bulk_data_ref = nullptr; + + // Unregister our chunks + const uint32_t end_chunk_index = first_chunk_index + num_streaming_chunks; + for (uint32_t chunk_index = first_chunk_index; chunk_index < end_chunk_index; ++chunk_index) + { + const acl_impl::database_chunk_description& chunk_description = chunk_descriptions[chunk_index]; + const acl_impl::database_chunk_header* chunk_header = chunk_description.get_chunk_header(bulk_data); + ACL_ASSERT(chunk_header->index == chunk_index, "Unexpected chunk index"); + + const acl_impl::database_chunk_segment_header* chunk_segment_headers = chunk_header->get_segment_headers(); + const uint32_t num_segments = chunk_header->num_segments; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const acl_impl::database_chunk_segment_header& chunk_segment_header = chunk_segment_headers[segment_index]; + +#if defined(ACL_HAS_ASSERT_CHECKS) + const acl_impl::database_runtime_clip_header* clip_header = chunk_segment_header.get_clip_header(m_context.clip_segment_headers); + ACL_ASSERT(clip_header->clip_hash == chunk_segment_header.clip_hash, "Unexpected clip hash"); +#endif + + acl_impl::database_runtime_segment_header* segment_header = chunk_segment_header.get_segment_header(m_context.clip_segment_headers); + const uint64_t tier_metadata = (uint64_t(chunk_segment_header.samples_offset) << 32) | chunk_segment_header.sample_indices; + ACL_ASSERT(segment_header->tier_metadata[tier_index].load(std::memory_order::memory_order_relaxed) == tier_metadata, "Database tier metadata should have been initialized"); (void)tier_metadata; + segment_header->tier_metadata[tier_index].store(0, std::memory_order::memory_order_relaxed); + } + } + + // Fire the stream out request and let the streamer handle it (sync/async) + streamer->stream_out(stream_start_offset, stream_size, can_deallocate_bulk_data, tier, request_id); + + return database_stream_request_result::dispatched; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database_context.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database_context.h new file mode 100644 index 00000000..4daa2178 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database_context.h @@ -0,0 +1,85 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + class database_streamer; + + namespace acl_impl + { + // TODO: If we need to make the context smaller, we can use offsets for the bitsets instead of pointers + // from the clip_segment_headers base pointer. The bitsets also follow linearly in memory, we could store only + // one offset for the base, and index with the tier * desc.size + struct alignas(64) database_context_v0 + { + // // offsets + // Only member used to detect if we are initialized, must be first + const compressed_database* db; // 0 | 0 + + // We use arrays so we can index with (tier - 1) as our index + // Index 0 = medium importance tier, index 1 = low importance + + // Runtime related data, commonly accessed + uint8_t* clip_segment_headers; // 4 | 8 + + const uint8_t* bulk_data[k_num_database_tiers]; // 8 | 16 + + // Streaming related data not commonly accessed + database_streamer* streamers[k_num_database_tiers]; // 16 | 32 + + uint32_t* loaded_chunks[k_num_database_tiers]; // 24 | 48 + uint32_t* streaming_chunks[k_num_database_tiers]; // 32 | 64 + + iallocator* allocator; // 40 | 72 + + uint8_t padding1[sizeof(void*) == 4 ? 84 : 40]; // 44 | 88 + + // Total size: 128 | 128 + + ////////////////////////////////////////////////////////////////////////// + + const compressed_database* get_compressed_database() const { return db; } + compressed_tracks_version16 get_version() const { return db->get_version(); } + bool is_initialized() const { return db != nullptr; } + void reset() { db = nullptr; } + }; + + static_assert(sizeof(database_context_v0) == 128, "Unexpected size"); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database_streamer.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database_streamer.impl.h new file mode 100644 index 00000000..6c9e93f3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/database_streamer.impl.h @@ -0,0 +1,225 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from database_streamer.h + +#include "acl/version.h" +#include "acl/core/bitset.h" +#include "acl/core/impl/compressed_headers.h" +#include "acl/decompression/database/impl/database_context.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline streaming_request_id make_request_id(uint32_t request_index, uint32_t generation_id) + { + return streaming_request_id{ uint64_t(generation_id) << 32 | request_index }; + } + + inline uint32_t get_request_index(streaming_request_id request_id) + { + return uint32_t(request_id.value); + } + + inline uint32_t get_generation_id(streaming_request_id request_id) + { + return uint32_t(request_id.value >> 32); + } + + inline void execute_request(bool success, database_context_v0& context, const streaming_request& request) + { + const quality_tier tier = request.tier; + const uint32_t first_chunk_index = request.first_chunk_index; + const uint32_t num_streaming_chunks = request.num_streaming_chunks; + + const uint32_t num_chunks_ = context.db->get_num_chunks(tier); + const bitset_description desc_ = bitset_description::make_from_num_bits(num_chunks_); + const uint32_t tier_index_ = uint32_t(tier) - 1; + + if (request.action == streaming_action::stream_in) + { + // Streaming in + if (success) + { + const uint8_t*& bulk_data_ref = context.bulk_data[tier_index_]; + const uint8_t* bulk_data_ = bulk_data_ref; + if (bulk_data_ == nullptr) + { + // This is the first stream in request, our bulk data should be allocated now, query and cache it + const database_streamer* streamer_ = context.streamers[tier_index_]; + bulk_data_ = streamer_->get_bulk_data(tier); + ACL_ASSERT(bulk_data_ != nullptr, "Bulk data should be allocated when we stream in"); + + bulk_data_ref = bulk_data_; + } + + // Register our new chunks + const database_header& header_ = get_database_header(*context.db); + const database_chunk_description* chunk_descriptions_ = tier == quality_tier::medium_importance ? header_.get_chunk_descriptions_medium() : header_.get_chunk_descriptions_low(); + const uint32_t end_chunk_index = first_chunk_index + num_streaming_chunks; + for (uint32_t chunk_index = first_chunk_index; chunk_index < end_chunk_index; ++chunk_index) + { + const database_chunk_description& chunk_description = chunk_descriptions_[chunk_index]; + const database_chunk_header* chunk_header = chunk_description.get_chunk_header(bulk_data_); + ACL_ASSERT(chunk_header->index == chunk_index, "Unexpected chunk index"); + + const database_chunk_segment_header* chunk_segment_headers = chunk_header->get_segment_headers(); + const uint32_t num_segments = chunk_header->num_segments; + for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index) + { + const database_chunk_segment_header& chunk_segment_header = chunk_segment_headers[segment_index]; + +#if defined(ACL_HAS_ASSERT_CHECKS) + const database_runtime_clip_header* clip_header = chunk_segment_header.get_clip_header(context.clip_segment_headers); + ACL_ASSERT(clip_header->clip_hash == chunk_segment_header.clip_hash, "Unexpected clip hash"); +#endif + + database_runtime_segment_header* segment_header = chunk_segment_header.get_segment_header(context.clip_segment_headers); + ACL_ASSERT(segment_header->tier_metadata[tier_index_].load(std::memory_order::memory_order_relaxed) == 0, "Tier metadata should not be initialized"); + segment_header->tier_metadata[tier_index_].store((uint64_t(chunk_segment_header.samples_offset) << 32) | chunk_segment_header.sample_indices, std::memory_order::memory_order_relaxed); + } + } + + // Mark chunks as done streaming + uint32_t* loaded_chunks_ = context.loaded_chunks[tier_index_]; + bitset_set_range(loaded_chunks_, desc_, first_chunk_index, num_streaming_chunks, true); + } + + // Mark chunks as no longer streaming + uint32_t* streaming_chunks_ = context.streaming_chunks[tier_index_]; + bitset_set_range(streaming_chunks_, desc_, first_chunk_index, num_streaming_chunks, false); + } + else + { + // Streaming out + ACL_ASSERT(success, "Cannot cancel/abort stream out request"); + + // Mark chunks as done streaming out + uint32_t* loaded_chunks_ = context.loaded_chunks[tier_index_]; + bitset_set_range(loaded_chunks_, desc_, first_chunk_index, num_streaming_chunks, false); + + // Mark chunks as no longer streaming + uint32_t* streaming_chunks_ = context.streaming_chunks[tier_index_]; + bitset_set_range(streaming_chunks_, desc_, first_chunk_index, num_streaming_chunks, false); + } + } + } + + inline database_streamer::database_streamer(streaming_request* requests, uint32_t num_requests) + : m_context(nullptr) + , m_requests(requests) + , m_generation_id(0) + , m_next_request_index(0) + , m_num_requests(num_requests) + { + ACL_ASSERT(requests != nullptr, "Requests must be valid"); + ACL_ASSERT(num_requests != 0, "Must have at least one request"); + } + + inline void database_streamer::complete(streaming_request_id request_id) + { + const uint32_t request_index = acl_impl::get_request_index(request_id); + ACL_ASSERT(request_index < m_num_requests, "Invalid request index"); + if (request_index >= m_num_requests) + return; + + const uint32_t generation_id = acl_impl::get_generation_id(request_id); + + streaming_request& request = m_requests[request_index]; + ACL_ASSERT(request.is_valid(), "Request is invalid"); + if (!request.is_valid()) + return; + + ACL_ASSERT(request.generation_id == generation_id, "Unexpected request generation id"); + if (request.generation_id != generation_id) + return; + + acl_impl::execute_request(true, *m_context, request); + + // Mark our request as invalid + request.reset(); + } + + inline void database_streamer::cancel(streaming_request_id request_id) + { + const uint32_t request_index = acl_impl::get_request_index(request_id); + ACL_ASSERT(request_index < m_num_requests, "Invalid request index"); + if (request_index >= m_num_requests) + return; + + const uint32_t generation_id = acl_impl::get_generation_id(request_id); + + streaming_request& request = m_requests[request_index]; + ACL_ASSERT(request.is_valid(), "Request is invalid"); + if (!request.is_valid()) + return; + + ACL_ASSERT(request.generation_id == generation_id, "Unexpected request generation id"); + if (request.generation_id != generation_id) + return; + + acl_impl::execute_request(false, *m_context, request); + + // Mark our request as invalid + request.reset(); + } + + inline void database_streamer::bind(acl_impl::database_context_v0& context) + { + ACL_ASSERT(m_context == nullptr || m_context == &context, "Streamer cannot be bound to two different database contexts"); + m_context = &context; + } + + inline streaming_request_id database_streamer::build_request(streaming_action action, quality_tier tier, uint32_t first_chunk_index, uint32_t num_streaming_chunks) + { + const uint32_t request_index = m_next_request_index; + streaming_request& request = m_requests[request_index]; + ACL_ASSERT(request.tier == quality_tier::highest_importance, "Ran out of free requests"); + if (request.tier != quality_tier::highest_importance) + return k_invalid_streamer_request_id; + + // This request entry is valid, we'll use it + m_next_request_index = (m_next_request_index + 1) % m_num_requests; + + // Get our generation id + const uint32_t generation_id = m_generation_id++; + + request.action = action; + request.tier = tier; + request.first_chunk_index = first_chunk_index; + request.num_streaming_chunks = num_streaming_chunks; + request.generation_id = generation_id; + + return acl_impl::make_request_id(request_index, generation_id); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/debug_database_streamer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/debug_database_streamer.h new file mode 100644 index 00000000..4d6d1f8b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/impl/debug_database_streamer.h @@ -0,0 +1,129 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/iallocator.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database_streamer.h" + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // Implements a debug streamer where we duplicate the bulk data in memory and use + // memcpy to stream in the data. Streamed out data is explicitly set to 0xCD with memset. + // It cannot be shared between tiers. + //////////////////////////////////////////////////////////////////////////////// + class debug_database_streamer final : public database_streamer + { + public: + debug_database_streamer(iallocator& allocator, const uint8_t* bulk_data, uint32_t bulk_data_size) + : database_streamer(m_requests, k_max_num_requests) + , m_allocator(allocator) + , m_src_bulk_data(bulk_data) + , m_streamed_bulk_data(nullptr) + , m_bulk_data_size(bulk_data_size) + { + } + + virtual ~debug_database_streamer() override + { + deallocate_type_array(m_allocator, m_streamed_bulk_data, m_bulk_data_size); + } + + virtual bool is_initialized() const override { return m_bulk_data_size == 0 || m_src_bulk_data != nullptr; } + + virtual const uint8_t* get_bulk_data(quality_tier tier) const override + { + ACL_ASSERT(tier != quality_tier::highest_importance, "Cannot stream the highest importance tier"); + (void)tier; + return m_streamed_bulk_data; + } + + virtual void stream_in(uint32_t offset, uint32_t size, bool can_allocate_bulk_data, quality_tier tier, streaming_request_id request_id) override + { + ACL_ASSERT(offset < m_bulk_data_size, "Steam offset is outside of the bulk data range"); + ACL_ASSERT(size <= m_bulk_data_size, "Stream size is larger than the bulk data size"); + ACL_ASSERT(uint64_t(offset) + uint64_t(size) <= uint64_t(m_bulk_data_size), "Streaming request is outside of the bulk data range"); + (void)tier; + + if (can_allocate_bulk_data) + { + ACL_ASSERT(m_streamed_bulk_data == nullptr, "Bulk data already allocated"); + + m_streamed_bulk_data = allocate_type_array(m_allocator, m_bulk_data_size); + + std::memset(m_streamed_bulk_data, 0xCD, m_bulk_data_size); + } + + std::memcpy(m_streamed_bulk_data + offset, m_src_bulk_data + offset, size); + complete(request_id); + } + + virtual void stream_out(uint32_t offset, uint32_t size, bool can_deallocate_bulk_data, quality_tier tier, streaming_request_id request_id) override + { + ACL_ASSERT(offset < m_bulk_data_size, "Steam offset is outside of the bulk data range"); + ACL_ASSERT(size <= m_bulk_data_size, "Stream size is larger than the bulk data size"); + ACL_ASSERT(uint64_t(offset) + uint64_t(size) <= uint64_t(m_bulk_data_size), "Streaming request is outside of the bulk data range"); + (void)tier; + + std::memset(m_streamed_bulk_data + offset, 0xCD, size); + + if (can_deallocate_bulk_data) + { + ACL_ASSERT(m_streamed_bulk_data != nullptr, "Bulk data already deallocated"); + + deallocate_type_array(m_allocator, m_streamed_bulk_data, m_bulk_data_size); + m_streamed_bulk_data = nullptr; + } + + complete(request_id); + } + + private: + debug_database_streamer(const debug_database_streamer&) = delete; + debug_database_streamer& operator=(const debug_database_streamer&) = delete; + + iallocator& m_allocator; + const uint8_t* m_src_bulk_data; + uint8_t* m_streamed_bulk_data; + uint32_t m_bulk_data_size; + + static constexpr uint32_t k_max_num_requests = k_num_database_tiers; // One per database tier + streaming_request m_requests[k_max_num_requests]; + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/null_database_streamer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/null_database_streamer.h new file mode 100644 index 00000000..2fcc7c07 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/database/null_database_streamer.h @@ -0,0 +1,105 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/error.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database_streamer.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + //////////////////////////////////////////////////////////////////////////////// + // Implements a null streamer where we simply use the provided bulk data buffer and + // perform no operations on it as everything is already streamed in. + // This streamer is the default in-memory streaming implementation. + // It cannot be shared between multiple tiers. + //////////////////////////////////////////////////////////////////////////////// + class null_database_streamer final : public database_streamer + { + public: + null_database_streamer(const uint8_t* bulk_data, uint32_t bulk_data_size) + : database_streamer(&m_request, 1) + , m_bulk_data(bulk_data) + , m_bulk_data_size(bulk_data_size) + { + ACL_ASSERT(bulk_data != nullptr, "Bulk data buffer cannot be null"); + } + + virtual bool is_initialized() const override { return m_bulk_data_size == 0 || m_bulk_data != nullptr; } + + virtual const uint8_t* get_bulk_data(quality_tier tier) const override + { + ACL_ASSERT(tier != quality_tier::highest_importance, "Cannot stream the highest importance tier"); + (void)tier; + return m_bulk_data; + } + + virtual void stream_in(uint32_t offset, uint32_t size, bool can_allocate_bulk_data, quality_tier tier, streaming_request_id request_id) override + { + ACL_ASSERT(offset < m_bulk_data_size, "Steam offset is outside of the bulk data range"); + ACL_ASSERT(size <= m_bulk_data_size, "Stream size is larger than the bulk data size"); + ACL_ASSERT(uint64_t(offset) + uint64_t(size) <= uint64_t(m_bulk_data_size), "Streaming request is outside of the bulk data range"); + (void)offset; + (void)size; + (void)can_allocate_bulk_data; + (void)tier; + + complete(request_id); + } + + virtual void stream_out(uint32_t offset, uint32_t size, bool can_deallocate_bulk_data, quality_tier tier, streaming_request_id request_id) override + { + ACL_ASSERT(offset < m_bulk_data_size, "Steam offset is outside of the bulk data range"); + ACL_ASSERT(size <= m_bulk_data_size, "Stream size is larger than the bulk data size"); + ACL_ASSERT(uint64_t(offset) + uint64_t(size) <= uint64_t(m_bulk_data_size), "Streaming request is outside of the bulk data range"); + (void)offset; + (void)size; + (void)can_deallocate_bulk_data; + (void)tier; + + complete(request_id); + } + + private: + null_database_streamer(const null_database_streamer&) = delete; + null_database_streamer& operator=(const null_database_streamer&) = delete; + + const uint8_t* m_bulk_data; + uint32_t m_bulk_data_size; + + streaming_request m_request; // Everything is synchronous, we only need one request + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/decompress.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/decompress.h new file mode 100644 index 00000000..c9785ac6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/decompress.h @@ -0,0 +1,172 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/error.h" +#include "acl/core/floating_point_exceptions.h" +#include "acl/core/iallocator.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_traits.h" +#include "acl/core/track_types.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/decompression_settings.h" +#include "acl/decompression/database/database.h" +#include "acl/decompression/impl/decompression_context_selector.h" +#include "acl/decompression/impl/decompression_version_selector.h" +#include "acl/decompression/impl/scalar_track_decompression.h" +#include "acl/decompression/impl/transform_track_decompression.h" +#include "acl/decompression/impl/universal_track_decompression.h" +#include "acl/math/vector4_packing.h" + +#include +#include +#include + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Decompression context for the uniformly sampled algorithm. The context + // allows various decompression actions to be performed on a compressed track list. + // + // Both the constructor and destructor are public because it is safe to place + // instances of this context on the stack or as member variables. + // + // This compression algorithm is the simplest by far and as such it offers + // the fastest compression and decompression. Every sample is retained and + // every track has the same number of samples playing back at the same + // sample rate. This means that when we sample at a particular time within + // the track list, we can trivially calculate the offsets required to read the + // desired data. All the data is sorted in order to ensure all reads are + // as contiguous as possible for optimal cache locality during decompression. + ////////////////////////////////////////////////////////////////////////// + template + class decompression_context + { + public: + ////////////////////////////////////////////////////////////////////////// + // An alias to the decompression settings type. + using settings_type = decompression_settings_type; + + ////////////////////////////////////////////////////////////////////////// + // An alias to the database settings type. + using db_settings_type = typename decompression_settings_type::database_settings_type; + + ////////////////////////////////////////////////////////////////////////// + // Constructs a context instance. + decompression_context(); + + ////////////////////////////////////////////////////////////////////////// + // Returns the compressed tracks bound to this context instance. + const compressed_tracks* get_compressed_tracks() const { return m_context.get_compressed_tracks(); } + + ////////////////////////////////////////////////////////////////////////// + // Initializes the context instance to a particular compressed tracks instance. + // Returns whether initialization was successful or not. + bool initialize(const compressed_tracks& tracks); + + ////////////////////////////////////////////////////////////////////////// + // Initializes the context instance to a particular compressed tracks instance and its database instance. + // Returns whether initialization was successful or not. + bool initialize(const compressed_tracks& tracks, const database_context& database); + + ////////////////////////////////////////////////////////////////////////// + // Returns true if this context instance is bound to a compressed tracks instance, false otherwise. + bool is_initialized() const { return m_context.is_initialized(); } + + ////////////////////////////////////////////////////////////////////////// + // Returns true if this context instance is bound to the specified compressed tracks instance, false otherwise. + bool is_dirty(const compressed_tracks& tracks) const; + + ////////////////////////////////////////////////////////////////////////// + // Seeks within the compressed tracks to a particular point in time with the + // desired rounding policy. + void seek(float sample_time, sample_rounding_policy rounding_policy); + + ////////////////////////////////////////////////////////////////////////// + // Decompress every track at the current sample time. + // The track_writer_type allows complete control over how the tracks are written out. + template + void decompress_tracks(track_writer_type& writer); + + ////////////////////////////////////////////////////////////////////////// + // Decompress a single track at the current sample time. + // The track_writer_type allows complete control over how the track is written out. + template + void decompress_track(uint32_t track_index, track_writer_type& writer); + + private: + decompression_context(const decompression_context& other) = delete; + decompression_context& operator=(const decompression_context& other) = delete; + + // Whether the decompression context should support scalar tracks + static constexpr bool k_supports_scalar_tracks = settings_type::is_track_type_supported(track_type8::float1f) + || settings_type::is_track_type_supported(track_type8::float2f) + || settings_type::is_track_type_supported(track_type8::float3f) + || settings_type::is_track_type_supported(track_type8::float4f) + || settings_type::is_track_type_supported(track_type8::vector4f); + + // Whether the decompression context should support transform tracks + static constexpr bool k_supports_transform_tracks = settings_type::is_track_type_supported(track_type8::qvvf); + + // The type of our persistent context based on what track types we support + using context_type = typename acl_impl::persistent_decompression_context_selector::type; + + // The type of our algorithm implementation based on the supported version + using version_impl_type = acl_impl::decompression_version_selector; + + // Internal context data + context_type m_context; + + static_assert(std::is_base_of::value, "decompression_settings_type must derive from decompression_settings!"); + static_assert(std::is_base_of::value, "database_settings_type must derive from database_settings!"); + static_assert(settings_type::version_supported() != compressed_tracks_version16::none, "decompression_settings_type must support at least one version"); + static_assert(db_settings_type::version_supported() == compressed_tracks_version16::none || db_settings_type::version_supported() == settings_type::version_supported(), "database_settings_type's supported version must be none or match the supported version from decompression_settings_type"); + }; + + ////////////////////////////////////////////////////////////////////////// + // Allocates and constructs an instance of the decompression context + template + inline decompression_context* make_decompression_context(iallocator& allocator) + { + return allocate_type>(allocator); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#include "acl/decompression/impl/decompress.impl.h" + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/decompression_settings.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/decompression_settings.h new file mode 100644 index 00000000..a3431abd --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/decompression_settings.h @@ -0,0 +1,176 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database_settings.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Deriving from this struct and overriding these constexpr functions + // allow you to control which code is stripped for maximum performance. + // With these, you can: + // - Support only a subset of the formats and statically strip the rest + // - Force a single format and statically strip the rest + // - Etc. + // + // By default, all formats are supported. + ////////////////////////////////////////////////////////////////////////// + struct decompression_settings + { + ////////////////////////////////////////////////////////////////////////// + // Common decompression settings + + ////////////////////////////////////////////////////////////////////////// + // Whether or not to clamp the sample time when `seek(..)` is called. Defaults to true. + // Must be static constexpr! + static constexpr bool clamp_sample_time() { return true; } + + ////////////////////////////////////////////////////////////////////////// + // Whether or not the specified track type is supported. Defaults to true. + // If a track type is statically known not to be supported, the compiler can strip + // the associated code. + // Must be static constexpr! + static constexpr bool is_track_type_supported(track_type8 /*type*/) { return true; } + + ////////////////////////////////////////////////////////////////////////// + // Whether to explicitly disable floating point exceptions during decompression. + // This has a cost, exceptions are usually disabled globally and do not need to be + // explicitly disabled during decompression. + // We assume that floating point exceptions are already disabled by the caller. + // Must be static constexpr! + static constexpr bool disable_fp_exeptions() { return false; } + + ////////////////////////////////////////////////////////////////////////// + // Which version we should optimize for. + // If 'any' is specified, the decompression context will support every single version + // with full backwards compatibility. + // Using a specific version allows the compiler to statically strip code for all other + // versions. This allows the creation of context objects specialized for specific + // versions which yields optimal performance. + // Must be static constexpr! + static constexpr compressed_tracks_version16 version_supported() { return compressed_tracks_version16::any; } + + ////////////////////////////////////////////////////////////////////////// + // Transform decompression settings + + ////////////////////////////////////////////////////////////////////////// + // Whether the specified rotation/translation/scale format are supported or not. + // Use this to strip code related to formats you do not need. + // Must be static constexpr! + static constexpr bool is_rotation_format_supported(rotation_format8 /*format*/) { return true; } + static constexpr bool is_translation_format_supported(vector_format8 /*format*/) { return true; } + static constexpr bool is_scale_format_supported(vector_format8 /*format*/) { return true; } + + ////////////////////////////////////////////////////////////////////////// + // Whether rotations should be normalized before being output or not. Some animation + // runtimes will normalize in a separate step and do not need the explicit normalization. + // Enabled by default for safety. + // Must be static constexpr! + static constexpr bool normalize_rotations() { return true; } + + ////////////////////////////////////////////////////////////////////////// + // Whether safety checks are performed when we initialize our context. + // When safety checks are disabled, initialization never fails even if it is invalid. + // This is meant as a performance optimization for shipping executables with all + // unnecessary checks removed. + // ENABLE AT YOUR OWN RISK! + // Disabled by default for safety. + // Must be static constexpr! + static constexpr bool skip_initialize_safety_checks() { return false; } + + ////////////////////////////////////////////////////////////////////////// + // The database settings to use when decompressing. + // By default, the database isn't supported. + using database_settings_type = null_database_settings; + }; + + ////////////////////////////////////////////////////////////////////////// + // These are debug settings, everything is enabled and nothing is stripped. + // It will have the worst performance but allows every feature. + ////////////////////////////////////////////////////////////////////////// + struct debug_scalar_decompression_settings : public decompression_settings + { + ////////////////////////////////////////////////////////////////////////// + // Only support scalar tracks + static constexpr bool is_track_type_supported(track_type8 type) { return type != track_type8::qvvf; } + }; + + ////////////////////////////////////////////////////////////////////////// + // These are debug settings, everything is enabled and nothing is stripped. + // It will have the worst performance but allows every feature. + ////////////////////////////////////////////////////////////////////////// + struct debug_transform_decompression_settings : public decompression_settings + { + ////////////////////////////////////////////////////////////////////////// + // Only support transform tracks + static constexpr bool is_track_type_supported(track_type8 type) { return type == track_type8::qvvf; } + }; + + ////////////////////////////////////////////////////////////////////////// + // These are the default settings. Only the generally optimal settings + // are enabled and will offer the overall best performance. + // Supports every version. + ////////////////////////////////////////////////////////////////////////// + struct default_scalar_decompression_settings : public decompression_settings + { + ////////////////////////////////////////////////////////////////////////// + // Only support scalar tracks + static constexpr bool is_track_type_supported(track_type8 type) { return type != track_type8::qvvf; } + }; + + ////////////////////////////////////////////////////////////////////////// + // These are the default settings. Only the generally optimal settings + // are enabled and will offer the overall best performance. + // Supports every version. + ////////////////////////////////////////////////////////////////////////// + struct default_transform_decompression_settings : public decompression_settings + { + ////////////////////////////////////////////////////////////////////////// + // Only support transform tracks + static constexpr bool is_track_type_supported(track_type8 type) { return type == track_type8::qvvf; } + + ////////////////////////////////////////////////////////////////////////// + // By default, we only support the variable bit rates as they are generally optimal + static constexpr bool is_rotation_format_supported(rotation_format8 format) { return format == rotation_format8::quatf_drop_w_variable; } + static constexpr bool is_translation_format_supported(vector_format8 format) { return format == vector_format8::vector3f_variable; } + static constexpr bool is_scale_format_supported(vector_format8 format) { return format == vector_format8::vector3f_variable; } + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/fwd.h new file mode 100644 index 00000000..73b42473 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/fwd.h @@ -0,0 +1,63 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public ACL types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + struct database_settings; + struct null_database_settings; + struct debug_database_settings; + struct default_database_settings; + + struct decompression_settings; + struct debug_scalar_decompression_settings; + struct default_scalar_decompression_settings; + struct debug_transform_decompression_settings; + struct default_transform_decompression_settings; + + enum class streaming_action; + struct streaming_request; + struct streaming_request_id; + class database_streamer; + class null_database_streamer; + + enum class database_stream_request_result; + template class database_context; + + template class decompression_context; + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompress.impl.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompress.impl.h new file mode 100644 index 00000000..a877e873 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompress.impl.h @@ -0,0 +1,147 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +// Included only once from decompress.h + +#include "acl/version.h" + +#include + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + ////////////////////////////////////////////////////////////////////////// + // SFINAE boilerplate to detect if a template argument derives from acl::decompression_context. + ////////////////////////////////////////////////////////////////////////// + template + using is_decompression_context = typename std::enable_if, T>::value, std::nullptr_t>::type; + } + + ////////////////////////////////////////////////////////////////////////// + // decompression_context implementation + + template + inline decompression_context::decompression_context() + : m_context() + { + m_context.reset(); + } + + template + inline bool decompression_context::initialize(const compressed_tracks& tracks) + { + constexpr bool skip_safety_checks = decompression_settings_type::skip_initialize_safety_checks(); + + const bool is_valid = skip_safety_checks || tracks.is_valid(false).empty(); + ACL_ASSERT(is_valid, "Invalid compressed tracks instance"); + if (!is_valid) + return false; // Invalid compressed tracks instance + + const bool is_version_supported = skip_safety_checks || version_impl_type::is_version_supported(tracks.get_version()); + ACL_ASSERT(is_version_supported, "Unsupported version"); + if (!is_version_supported) + return false; + + const database_context* database = nullptr; + return version_impl_type::template initialize(m_context, tracks, database); + } + + template + inline bool decompression_context::initialize(const compressed_tracks& tracks, const database_context& database) + { + constexpr bool skip_safety_checks = decompression_settings_type::skip_initialize_safety_checks(); + + bool is_valid = skip_safety_checks || tracks.is_valid(false).empty(); + ACL_ASSERT(is_valid, "Invalid compressed tracks instance"); + if (!is_valid) + return false; // Invalid compressed tracks instance + + is_valid = skip_safety_checks || database.is_initialized(); + ACL_ASSERT(is_valid, "Invalid compressed database instance"); + if (!is_valid) + return false; // Invalid compressed database instance + + const bool is_version_supported = skip_safety_checks || version_impl_type::is_version_supported(tracks.get_version()); + ACL_ASSERT(is_version_supported, "Unsupported version"); + if (!is_version_supported) + return false; + + const bool is_contained_in_db = skip_safety_checks || database.contains(tracks); + if (!is_contained_in_db) + return false; + + return version_impl_type::template initialize(m_context, tracks, &database); + } + + template + inline bool decompression_context::is_dirty(const compressed_tracks& tracks) const + { + return version_impl_type::template is_dirty(m_context, tracks); + } + + template + inline void decompression_context::seek(float sample_time, sample_rounding_policy rounding_policy) + { + ACL_ASSERT(m_context.is_initialized(), "Context is not initialized"); + ACL_ASSERT(rtm::scalar_is_finite(sample_time), "Invalid sample time"); + + if (!m_context.is_initialized()) + return; // Context is not initialized + + version_impl_type::template seek(m_context, sample_time, rounding_policy); + } + + template + template + inline void decompression_context::decompress_tracks(track_writer_type& writer) + { + static_assert(std::is_base_of::value, "track_writer_type must derive from track_writer"); + ACL_ASSERT(m_context.is_initialized(), "Context is not initialized"); + + if (!m_context.is_initialized()) + return; // Context is not initialized + + version_impl_type::template decompress_tracks(m_context, writer); + } + + template + template + inline void decompression_context::decompress_track(uint32_t track_index, track_writer_type& writer) + { + static_assert(std::is_base_of::value, "track_writer_type must derive from track_writer"); + ACL_ASSERT(m_context.is_initialized(), "Context is not initialized"); + + if (!m_context.is_initialized()) + return; // Context is not initialized + + version_impl_type::template decompress_track(m_context, track_index, writer); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompression_context_selector.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompression_context_selector.h new file mode 100644 index 00000000..30095e2f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompression_context_selector.h @@ -0,0 +1,68 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/impl/scalar_track_decompression.h" +#include "acl/decompression/impl/transform_track_decompression.h" +#include "acl/decompression/impl/universal_track_decompression.h" + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + ////////////////////////////////////////////////////////////////////////// + // Helper struct to choose the decompression context type based on what tracks we support + template + struct persistent_decompression_context_selector {}; + + template<> + struct persistent_decompression_context_selector + { + using type = persistent_scalar_decompression_context_v0; + }; + + template<> + struct persistent_decompression_context_selector + { + using type = persistent_transform_decompression_context_v0; + }; + + template<> + struct persistent_decompression_context_selector + { + using type = persistent_universal_decompression_context; + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompression_version_selector.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompression_version_selector.h new file mode 100644 index 00000000..0f9a84ec --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/decompression_version_selector.h @@ -0,0 +1,169 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/error.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database.h" +#include "acl/decompression/impl/scalar_track_decompression.h" +#include "acl/decompression/impl/transform_track_decompression.h" +#include "acl/decompression/impl/universal_track_decompression.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + ////////////////////////////////////////////////////////////////////////// + // Selector template for decompression implementation details. + // Specialized for each algorithm version. + ////////////////////////////////////////////////////////////////////////// + template + struct decompression_version_selector {}; + + ////////////////////////////////////////////////////////////////////////// + // Optimized for ACL 2.0.0 + ////////////////////////////////////////////////////////////////////////// + template<> + struct decompression_version_selector + { + static constexpr bool is_version_supported(compressed_tracks_version16 version) { return version == compressed_tracks_version16::v02_00_00; } + + template + RTM_FORCE_INLINE static bool initialize(context_type& context, const compressed_tracks& tracks, const database_context* database) { return acl_impl::initialize_v0(context, tracks, database); } + + template + RTM_FORCE_INLINE static bool is_dirty(const context_type& context, const compressed_tracks& tracks) { return acl_impl::is_dirty_v0(context, tracks); } + + template + RTM_FORCE_INLINE static void seek(context_type& context, float sample_time, sample_rounding_policy rounding_policy) { acl_impl::seek_v0(context, sample_time, rounding_policy); } + + template + RTM_FORCE_INLINE static void decompress_tracks(context_type& context, track_writer_type& writer) { acl_impl::decompress_tracks_v0(context, writer); } + + template + RTM_FORCE_INLINE static void decompress_track(context_type& context, uint32_t track_index, track_writer_type& writer) { acl_impl::decompress_track_v0(context, track_index, writer); } + }; + + ////////////////////////////////////////////////////////////////////////// + // Not optimized for any particular version. + ////////////////////////////////////////////////////////////////////////// + template<> + struct decompression_version_selector + { + static constexpr bool is_version_supported(compressed_tracks_version16 version) + { + return version >= compressed_tracks_version16::first && version <= compressed_tracks_version16::latest; + } + + template + static bool initialize(context_type& context, const compressed_tracks& tracks, const database_context* database) + { + // TODO: Use an array of lambdas and use the version to lookup? This could be a simple indirect function call. + const compressed_tracks_version16 version = tracks.get_version(); + switch (version) + { + case compressed_tracks_version16::v02_00_00: + return decompression_version_selector::initialize(context, tracks, database); + default: + ACL_ASSERT(false, "Unsupported version"); + return false; + } + } + + template + static bool is_dirty(const context_type& context, const compressed_tracks& tracks) + { + const compressed_tracks_version16 version = tracks.get_version(); + switch (version) + { + case compressed_tracks_version16::v02_00_00: + return decompression_version_selector::is_dirty(context, tracks); + default: + ACL_ASSERT(false, "Unsupported version"); + return false; + } + } + + template + static void seek(context_type& context, float sample_time, sample_rounding_policy rounding_policy) + { + const compressed_tracks_version16 version = context.get_version(); + switch (version) + { + case compressed_tracks_version16::v02_00_00: + decompression_version_selector::seek(context, sample_time, rounding_policy); + break; + default: + ACL_ASSERT(false, "Unsupported version"); + break; + } + } + + template + static void decompress_tracks(context_type& context, track_writer_type& writer) + { + const compressed_tracks_version16 version = context.get_version(); + switch (version) + { + case compressed_tracks_version16::v02_00_00: + decompression_version_selector::decompress_tracks(context, writer); + break; + default: + ACL_ASSERT(false, "Unsupported version"); + break; + } + } + + template + static void decompress_track(context_type& context, uint32_t track_index, track_writer_type& writer) + { + const compressed_tracks_version16 version = context.get_version(); + switch (version) + { + case compressed_tracks_version16::v02_00_00: + decompression_version_selector::decompress_track(context, track_index, writer); + break; + default: + ACL_ASSERT(false, "Unsupported version"); + break; + } + } + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/scalar_track_decompression.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/scalar_track_decompression.h new file mode 100644 index 00000000..a9578c32 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/scalar_track_decompression.h @@ -0,0 +1,588 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/track_writer.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database.h" +#include "acl/math/scalar_packing.h" +#include "acl/math/vector4_packing.h" + +#include +#include + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26451: Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow (io.2). + // We can't overflow because compressed clips cannot contain more than 4 GB worth of data + #pragma warning(disable : 26451) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct alignas(64) persistent_scalar_decompression_context_v0 + { + // Clip related data // offsets + // Only member used to detect if we are initialized, must be first + const compressed_tracks* tracks; // 0 | 0 + + uint32_t tracks_hash; // 4 | 8 + + float duration; // 8 | 12 + + // Seeking related data + float interpolation_alpha; // 12 | 16 + float sample_time; // 16 | 20 + + uint32_t key_frame_bit_offsets[2]; // 20 | 24 // Variable quantization + + uint8_t padding_tail[sizeof(void*) == 4 ? 36 : 32]; + + ////////////////////////////////////////////////////////////////////////// + + const compressed_tracks* get_compressed_tracks() const { return tracks; } + compressed_tracks_version16 get_version() const { return tracks->get_version(); } + bool is_initialized() const { return tracks != nullptr; } + void reset() { tracks = nullptr; } + }; + + static_assert(sizeof(persistent_scalar_decompression_context_v0) == 64, "Unexpected size"); + + template + inline bool initialize_v0(persistent_scalar_decompression_context_v0& context, const compressed_tracks& tracks, const database_context* database) + { + ACL_ASSERT(tracks.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(tracks.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled)); + + if (database != nullptr) + return false; // Database decompression is not supported for scalar tracks + + context.tracks = &tracks; + context.tracks_hash = tracks.get_hash(); + context.duration = tracks.get_finite_duration(); + context.sample_time = -1.0F; + context.interpolation_alpha = 0.0; + + return true; + } + + inline bool is_dirty_v0(const persistent_scalar_decompression_context_v0& context, const compressed_tracks& tracks) + { + if (context.tracks != &tracks) + return true; + + if (context.tracks_hash != tracks.get_hash()) + return true; + + return false; + } + + template + inline void seek_v0(persistent_scalar_decompression_context_v0& context, float sample_time, sample_rounding_policy rounding_policy) + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*context.tracks); + if (header.num_samples == 0) + return; // Empty track list + + // Clamp for safety, the caller should normally handle this but in practice, it often isn't the case + if (decompression_settings_type::clamp_sample_time()) + sample_time = rtm::scalar_clamp(sample_time, 0.0F, context.duration); + + if (context.sample_time == sample_time) + return; + + context.sample_time = sample_time; + + uint32_t key_frame0; + uint32_t key_frame1; + find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, context.interpolation_alpha); + + const acl_impl::scalar_tracks_header& scalars_header = acl_impl::get_scalar_tracks_header(*context.tracks); + + context.key_frame_bit_offsets[0] = key_frame0 * scalars_header.num_bits_per_frame; + context.key_frame_bit_offsets[1] = key_frame1 * scalars_header.num_bits_per_frame; + } + + template + inline void decompress_tracks_v0(const persistent_scalar_decompression_context_v0& context, track_writer_type& writer) + { + const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*context.tracks); + const uint32_t num_tracks = header.num_tracks; + if (num_tracks == 0) + return; // Empty track list + + ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time"); + if (context.sample_time < 0.0F) + return; // Invalid sample time, we didn't seek yet + + // Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used. + // Disable floating point exceptions to avoid issues. + fp_environment fp_env; + if (decompression_settings_type::disable_fp_exeptions()) + disable_fp_exceptions(fp_env); + + const acl_impl::scalar_tracks_header& scalars_header = acl_impl::get_scalar_tracks_header(*context.tracks); + const rtm::scalarf interpolation_alpha = rtm::scalar_set(context.interpolation_alpha); + + const acl_impl::track_metadata* per_track_metadata = scalars_header.get_track_metadata(); + const float* constant_values = scalars_header.get_track_constant_values(); + const float* range_values = scalars_header.get_track_range_values(); + const uint8_t* animated_values = scalars_header.get_track_animated_values(); + + uint32_t track_bit_offset0 = context.key_frame_bit_offsets[0]; + uint32_t track_bit_offset1 = context.key_frame_bit_offsets[1]; + + const track_type8 track_type = header.track_type; + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const acl_impl::track_metadata& metadata = per_track_metadata[track_index]; + const uint8_t bit_rate = metadata.bit_rate; + const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate); + + if (track_type == track_type8::float1f && decompression_settings_type::is_track_type_supported(track_type8::float1f)) + { + rtm::scalarf value; + if (is_constant_bit_rate(bit_rate)) + { + value = rtm::scalar_load(constant_values); + constant_values += 1; + } + else + { + rtm::scalarf value0; + rtm::scalarf value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_scalarf_32_unsafe(animated_values, track_bit_offset0); + value1 = unpack_scalarf_32_unsafe(animated_values, track_bit_offset1); + } + else + { + value0 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0); + value1 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1); + + const rtm::scalarf range_min = rtm::scalar_load(range_values); + const rtm::scalarf range_extent = rtm::scalar_load(range_values + 1); + value0 = rtm::scalar_mul_add(value0, range_extent, range_min); + value1 = rtm::scalar_mul_add(value1, range_extent, range_min); + range_values += 2; + } + + value = rtm::scalar_lerp(value0, value1, interpolation_alpha); + + const uint32_t num_sample_bits = num_bits_per_component; + track_bit_offset0 += num_sample_bits; + track_bit_offset1 += num_sample_bits; + } + + writer.write_float1(track_index, value); + } + else if (track_type == track_type8::float2f && decompression_settings_type::is_track_type_supported(track_type8::float2f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + { + value = rtm::vector_load(constant_values); + constant_values += 2; + } + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector2_64_unsafe(animated_values, track_bit_offset0); + value1 = unpack_vector2_64_unsafe(animated_values, track_bit_offset1); + } + else + { + value0 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0); + value1 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + 2); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + range_values += 4; + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + + const uint32_t num_sample_bits = num_bits_per_component * 2; + track_bit_offset0 += num_sample_bits; + track_bit_offset1 += num_sample_bits; + } + + writer.write_float2(track_index, value); + } + else if (track_type == track_type8::float3f && decompression_settings_type::is_track_type_supported(track_type8::float3f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + { + value = rtm::vector_load(constant_values); + constant_values += 3; + } + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector3_96_unsafe(animated_values, track_bit_offset0); + value1 = unpack_vector3_96_unsafe(animated_values, track_bit_offset1); + } + else + { + value0 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0); + value1 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + 3); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + range_values += 6; + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + + const uint32_t num_sample_bits = num_bits_per_component * 3; + track_bit_offset0 += num_sample_bits; + track_bit_offset1 += num_sample_bits; + } + + writer.write_float3(track_index, value); + } + else if (track_type == track_type8::float4f && decompression_settings_type::is_track_type_supported(track_type8::float4f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + { + value = rtm::vector_load(constant_values); + constant_values += 4; + } + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector4_128_unsafe(animated_values, track_bit_offset0); + value1 = unpack_vector4_128_unsafe(animated_values, track_bit_offset1); + } + else + { + value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0); + value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + 4); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + range_values += 8; + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + + const uint32_t num_sample_bits = num_bits_per_component * 4; + track_bit_offset0 += num_sample_bits; + track_bit_offset1 += num_sample_bits; + } + + writer.write_float4(track_index, value); + } + else if (track_type == track_type8::vector4f && decompression_settings_type::is_track_type_supported(track_type8::vector4f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + { + value = rtm::vector_load(constant_values); + constant_values += 4; + } + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector4_128_unsafe(animated_values, track_bit_offset0); + value1 = unpack_vector4_128_unsafe(animated_values, track_bit_offset1); + } + else + { + value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0); + value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + 4); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + range_values += 8; + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + + const uint32_t num_sample_bits = num_bits_per_component * 4; + track_bit_offset0 += num_sample_bits; + track_bit_offset1 += num_sample_bits; + } + + writer.write_vector4(track_index, value); + } + } + + if (decompression_settings_type::disable_fp_exeptions()) + restore_fp_exceptions(fp_env); + } + + template + inline void decompress_track_v0(const persistent_scalar_decompression_context_v0& context, uint32_t track_index, track_writer_type& writer) + { + const tracks_header& header = get_tracks_header(*context.tracks); + if (header.num_tracks == 0) + return; // Empty track list + + ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time"); + if (context.sample_time < 0.0F) + return; // Invalid sample time, we didn't seek yet + + ACL_ASSERT(track_index < header.num_tracks, "Invalid track index"); + if (track_index >= header.num_tracks) + return; // Invalid track index + + // Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used. + // Disable floating point exceptions to avoid issues. + fp_environment fp_env; + if (decompression_settings_type::disable_fp_exeptions()) + disable_fp_exceptions(fp_env); + + const scalar_tracks_header& scalars_header = get_scalar_tracks_header(*context.tracks); + const rtm::scalarf interpolation_alpha = rtm::scalar_set(context.interpolation_alpha); + + const float* constant_values = scalars_header.get_track_constant_values(); + const float* range_values = scalars_header.get_track_range_values(); + + const track_type8 track_type = header.track_type; + const uint32_t num_element_components = get_track_num_sample_elements(track_type); + uint32_t track_bit_offset = 0; + + const acl_impl::track_metadata* per_track_metadata = scalars_header.get_track_metadata(); + for (uint32_t scan_track_index = 0; scan_track_index < track_index; ++scan_track_index) + { + const acl_impl::track_metadata& metadata = per_track_metadata[scan_track_index]; + const uint8_t bit_rate = metadata.bit_rate; + const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate); + track_bit_offset += num_bits_per_component * num_element_components; + + if (is_constant_bit_rate(bit_rate)) + constant_values += num_element_components; + else if (!is_raw_bit_rate(bit_rate)) + range_values += num_element_components * 2; + } + + const acl_impl::track_metadata& metadata = per_track_metadata[track_index]; + const uint8_t bit_rate = metadata.bit_rate; + const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate); + + const uint8_t* animated_values = scalars_header.get_track_animated_values(); + + if (track_type == track_type8::float1f && decompression_settings_type::is_track_type_supported(track_type8::float1f)) + { + rtm::scalarf value; + if (is_constant_bit_rate(bit_rate)) + value = rtm::scalar_load(constant_values); + else + { + rtm::scalarf value0; + rtm::scalarf value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_scalarf_32_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_scalarf_32_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + } + else + { + value0 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + + const rtm::scalarf range_min = rtm::scalar_load(range_values); + const rtm::scalarf range_extent = rtm::scalar_load(range_values + num_element_components); + value0 = rtm::scalar_mul_add(value0, range_extent, range_min); + value1 = rtm::scalar_mul_add(value1, range_extent, range_min); + } + + value = rtm::scalar_lerp(value0, value1, interpolation_alpha); + } + + writer.write_float1(track_index, value); + } + else if (track_type == track_type8::float2f && decompression_settings_type::is_track_type_supported(track_type8::float2f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + value = rtm::vector_load(constant_values); + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector2_64_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector2_64_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + } + else + { + value0 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + } + + writer.write_float2(track_index, value); + } + else if (track_type == track_type8::float3f && decompression_settings_type::is_track_type_supported(track_type8::float3f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + value = rtm::vector_load(constant_values); + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector3_96_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector3_96_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + } + else + { + value0 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + } + + writer.write_float3(track_index, value); + } + else if (track_type == track_type8::float4f && decompression_settings_type::is_track_type_supported(track_type8::float4f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + value = rtm::vector_load(constant_values); + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + } + else + { + value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + } + + writer.write_float4(track_index, value); + } + else if (track_type == track_type8::vector4f && decompression_settings_type::is_track_type_supported(track_type8::vector4f)) + { + rtm::vector4f value; + if (is_constant_bit_rate(bit_rate)) + value = rtm::vector_load(constant_values); + else + { + rtm::vector4f value0; + rtm::vector4f value1; + if (is_raw_bit_rate(bit_rate)) + { + value0 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + } + else + { + value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset); + value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset); + + const rtm::vector4f range_min = rtm::vector_load(range_values); + const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components); + value0 = rtm::vector_mul_add(value0, range_extent, range_min); + value1 = rtm::vector_mul_add(value1, range_extent, range_min); + } + + value = rtm::vector_lerp(value0, value1, interpolation_alpha); + } + + writer.write_vector4(track_index, value); + } + + if (decompression_settings_type::disable_fp_exeptions()) + restore_fp_exceptions(fp_env); + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/track_cache.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/track_cache.h new file mode 100644 index 00000000..f5bbf722 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/track_cache.h @@ -0,0 +1,104 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + template + struct track_cache_v0 + { + // Our cached type + using type = typename cached_type::type; + + // Our cache size + static constexpr uint32_t k_cache_size = cache_size; + + // Our cached values + type cached_samples[cache_size]; + + // The index to write the next cache entry when we unpack + // Effective index value is modulo k_cache_size what is stored here, guaranteed to never wrap + uint32_t cache_write_index = 0; + + // The index to read the next cache entry when we consume + // Effective index value is modulo k_cache_size what is stored here, guaranteed to never wrap + uint32_t cache_read_index = 0; + + // How many we have left to unpack in total + uint32_t num_left_to_unpack; + + uint32_t padding; + + // Returns the number of cached entries + uint32_t get_num_cached() const { return cache_write_index - cache_read_index; } + }; + + // Type traits for the track_cache to avoid GCC warning about template argument attributes + struct track_cache_quatf_trait + { + using type = rtm::quatf; + }; + + struct track_cache_vector4f_trait + { + using type = rtm::vector4f; + }; + + template + using track_cache_quatf_v0 = track_cache_v0; + + template + using track_cache_vector4f_v0 = track_cache_v0; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_animated_track_cache.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_animated_track_cache.h new file mode 100644 index 00000000..c01aa0e2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_animated_track_cache.h @@ -0,0 +1,1863 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/impl/track_cache.h" +#include "acl/decompression/impl/transform_decompression_context.h" +#include "acl/math/quatf.h" +#include "acl/math/vector4f.h" + +#include +#include +#include +#include + +#include + +#define ACL_IMPL_USE_ANIMATED_PREFETCH + +// On x86/x64 platforms the prefetching instruction can have a long latency and it requires +// a few other registers to compute the address which is problematic when registers are scarce. +// As such, we attempt to hide the prefetching behind longer latency instructions like square-roots +// and divisions. +// On other platforms (e.g. ARM), the instruction is cheaper and we have more registers which gives +// the compiler more freedom to hide the address calculation cost between other instructions. +// Because the CPU is generally slower as well, we want to prefetch as soon as possible without +// waiting for the next expensive instruction. +// If your target CPU has a high clock rate, you might benefit from disabling early prefetching +#if !defined(ACL_NO_EARLY_PREFETCHING) && !defined(ACL_IMPL_PREFETCH_EARLY) + #if !defined(RTM_SSE2_INTRINSICS) + #define ACL_IMPL_PREFETCH_EARLY + #endif +#endif + +// This defined enables the SIMD 8 wide AVX decompression code path +// Note that currently, it is often slower than the regular SIMD 4 wide AVX code path +// On Intel Haswell and AMD Zen2 CPUs, the 8 wide code is measurably slower +// Perhaps it is faster on newer Intel CPUs but I don't have one to test with +// Enable at your own risk +//#define ACL_IMPL_USE_AVX_8_WIDE_DECOMP + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + #if !defined(RTM_AVX_INTRINSICS) + // AVX isn't enabled, disable the 8 wide code path + #undef ACL_IMPL_USE_AVX_8_WIDE_DECOMP + #endif +#endif + +ACL_IMPL_FILE_PRAGMA_PUSH + +// We only initialize some variables when we need them which prompts the compiler to complain +// The usage is perfectly safe and because this code is VERY hot and needs to be as fast as possible, +// we disable the warning to avoid zeroing out things we don't need +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) + // warning C26451: Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow (io.2). + // We can't overflow because compressed clips cannot contain more than 4 GB worth of data + #pragma warning(disable : 26451) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { +#if defined(ACL_IMPL_USE_ANIMATED_PREFETCH) +#define ACL_IMPL_ANIMATED_PREFETCH(ptr) memory_prefetch(ptr) +#else +#define ACL_IMPL_ANIMATED_PREFETCH(ptr) (void)(ptr) +#endif + + struct clip_animated_sampling_context_v0 + { + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Order depends on animated track order. If we have 6 animated rotation tracks before the first animated + // translation track, we'll have 8 animated rotation sub-tracks followed by 4 animated translation sub-tracks. + // Once we reach the end, there is no extra padding. The last group might be less than 4 sub-tracks. + // This is because we always process 4 animated sub-tracks at a time and cache the results. + + const uint8_t* clip_range_data; // Range information of the current sub-track in the clip + }; + + struct segment_animated_sampling_context_v0 + { + // Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3) + // Order depends on animated track order. If we have 6 animated rotation tracks before the first animated + // translation track, we'll have 8 animated rotation sub-tracks followed by 4 animated translation sub-tracks. + // Once we reach the end, there is no extra padding. The last group might be less than 4 sub-tracks. + // This is because we always process 4 animated sub-tracks at a time and cache the results. + + const uint8_t* format_per_track_data; // Metadata of the current sub-track + const uint8_t* segment_range_data; // Range information (or constant sample if bit rate is 0) of the current sub-track in this segment + + // For the animated samples, constant bit rate sub-tracks (with a bit rate of 0) do not contain samples. + // As such, their group will not contain 4 sub-tracks. + + const uint8_t* animated_track_data; // Base of animated sample data, constant and doesn't change after init + uint32_t animated_track_data_bit_offset; // Bit offset of the current animated sub-track + }; + + struct alignas(32) segment_animated_scratch_v0 + { + // We store out potential range data in SOA form and we have no W, just XYZ + // To facilitate AVX and wider SIMD usage, we store our data interleaved in a single contiguous array + // Segment 0 has a base offset of 0 bytes and afterwards every write has a 32 byte offset + // Segment 1 has a base offset of 16 bytes and afterwards every write has a 32 byte offset + + // segment_range_min_xxxx0, segment_range_min_xxxx1, segment_range_min_yyyy0, segment_range_min_yyyy1, segment_range_min_zzzz0, segment_range_min_zzzz1 + rtm::vector4f segment_range_min[6]; + + // segment_range_extent_xxxx0, segment_range_extent_xxxx1, segment_range_extent_yyyy0, segment_range_extent_yyyy1, segment_range_extent_zzzz0, segment_range_extent_zzzz1 + rtm::vector4f segment_range_extent[6]; + }; + +#if defined(RTM_SSE2_INTRINSICS) + using range_reduction_masks_t = __m128i; +#elif defined(RTM_NEON_INTRINSICS) + using range_reduction_masks_t = int16x8_t; +#else + using range_reduction_masks_t = uint64_t; +#endif + + // About 9 cycles with AVX on Skylake + inline RTM_DISABLE_SECURITY_COOKIE_CHECK void unpack_segment_range_data(const uint8_t* segment_range_data, uint32_t scratch_offset, segment_animated_scratch_v0& output_scratch) + { + // Segment range is packed: min.xxxx, min.yyyy, min.zzzz, extent.xxxx, extent.yyyy, extent.zzzz + +#if defined(RTM_SSE2_INTRINSICS) + const __m128i zero = _mm_setzero_si128(); + + const __m128i segment_range_min_xxxx_yyyy_zzzz_extent_xxxx_u8 = _mm_loadu_si128((const __m128i*)segment_range_data); + const __m128i segment_range_extent_yyyy_zzzz_u8 = _mm_loadu_si128((const __m128i*)(segment_range_data + 16)); + + // Convert from u8 to u32 + const __m128i segment_range_min_xxxx_yyyy_u16 = _mm_unpacklo_epi8(segment_range_min_xxxx_yyyy_zzzz_extent_xxxx_u8, zero); + const __m128i segment_range_min_zzzz_extent_xxxx_u16 = _mm_unpackhi_epi8(segment_range_min_xxxx_yyyy_zzzz_extent_xxxx_u8, zero); + const __m128i segment_range_extent_yyyy_zzzz_u16 = _mm_unpacklo_epi8(segment_range_extent_yyyy_zzzz_u8, zero); + + __m128i segment_range_min_xxxx_u32 = _mm_unpacklo_epi16(segment_range_min_xxxx_yyyy_u16, zero); + __m128i segment_range_min_yyyy_u32 = _mm_unpackhi_epi16(segment_range_min_xxxx_yyyy_u16, zero); + __m128i segment_range_min_zzzz_u32 = _mm_unpacklo_epi16(segment_range_min_zzzz_extent_xxxx_u16, zero); + + const __m128i segment_range_extent_xxxx_u32 = _mm_unpackhi_epi16(segment_range_min_zzzz_extent_xxxx_u16, zero); + const __m128i segment_range_extent_yyyy_u32 = _mm_unpacklo_epi16(segment_range_extent_yyyy_zzzz_u16, zero); + const __m128i segment_range_extent_zzzz_u32 = _mm_unpackhi_epi16(segment_range_extent_yyyy_zzzz_u16, zero); + + __m128 segment_range_min_xxxx = _mm_cvtepi32_ps(segment_range_min_xxxx_u32); + __m128 segment_range_min_yyyy = _mm_cvtepi32_ps(segment_range_min_yyyy_u32); + __m128 segment_range_min_zzzz = _mm_cvtepi32_ps(segment_range_min_zzzz_u32); + + __m128 segment_range_extent_xxxx = _mm_cvtepi32_ps(segment_range_extent_xxxx_u32); + __m128 segment_range_extent_yyyy = _mm_cvtepi32_ps(segment_range_extent_yyyy_u32); + __m128 segment_range_extent_zzzz = _mm_cvtepi32_ps(segment_range_extent_zzzz_u32); + + const __m128 normalization_value = _mm_set_ps1(1.0F / 255.0F); + + segment_range_min_xxxx = _mm_mul_ps(segment_range_min_xxxx, normalization_value); + segment_range_min_yyyy = _mm_mul_ps(segment_range_min_yyyy, normalization_value); + segment_range_min_zzzz = _mm_mul_ps(segment_range_min_zzzz, normalization_value); + + segment_range_extent_xxxx = _mm_mul_ps(segment_range_extent_xxxx, normalization_value); + segment_range_extent_yyyy = _mm_mul_ps(segment_range_extent_yyyy, normalization_value); + segment_range_extent_zzzz = _mm_mul_ps(segment_range_extent_zzzz, normalization_value); +#elif defined(RTM_NEON_INTRINSICS) + const uint8x16_t segment_range_min_xxxx_yyyy_zzzz_extent_xxxx_u8 = vld1q_u8(segment_range_data); + const uint8x8_t segment_range_extent_yyyy_zzzz_u8 = vld1_u8(segment_range_data + 16); + + // Convert from u8 to u32 + const uint16x8_t segment_range_min_xxxx_yyyy_u16 = vmovl_u8(vget_low_u8(segment_range_min_xxxx_yyyy_zzzz_extent_xxxx_u8)); + const uint16x8_t segment_range_min_zzzz_extent_xxxx_u16 = vmovl_u8(vget_high_u8(segment_range_min_xxxx_yyyy_zzzz_extent_xxxx_u8)); + const uint16x8_t segment_range_extent_yyyy_zzzz_u16 = vmovl_u8(segment_range_extent_yyyy_zzzz_u8); + + uint32x4_t segment_range_min_xxxx_u32 = vmovl_u16(vget_low_u16(segment_range_min_xxxx_yyyy_u16)); + uint32x4_t segment_range_min_yyyy_u32 = vmovl_u16(vget_high_u16(segment_range_min_xxxx_yyyy_u16)); + uint32x4_t segment_range_min_zzzz_u32 = vmovl_u16(vget_low_u16(segment_range_min_zzzz_extent_xxxx_u16)); + + const uint32x4_t segment_range_extent_xxxx_u32 = vmovl_u16(vget_high_u16(segment_range_min_zzzz_extent_xxxx_u16)); + const uint32x4_t segment_range_extent_yyyy_u32 = vmovl_u16(vget_low_u16(segment_range_extent_yyyy_zzzz_u16)); + const uint32x4_t segment_range_extent_zzzz_u32 = vmovl_u16(vget_high_u16(segment_range_extent_yyyy_zzzz_u16)); + + float32x4_t segment_range_min_xxxx = vcvtq_f32_u32(segment_range_min_xxxx_u32); + float32x4_t segment_range_min_yyyy = vcvtq_f32_u32(segment_range_min_yyyy_u32); + float32x4_t segment_range_min_zzzz = vcvtq_f32_u32(segment_range_min_zzzz_u32); + + float32x4_t segment_range_extent_xxxx = vcvtq_f32_u32(segment_range_extent_xxxx_u32); + float32x4_t segment_range_extent_yyyy = vcvtq_f32_u32(segment_range_extent_yyyy_u32); + float32x4_t segment_range_extent_zzzz = vcvtq_f32_u32(segment_range_extent_zzzz_u32); + + const float normalization_value = 1.0F / 255.0F; + + segment_range_min_xxxx = vmulq_n_f32(segment_range_min_xxxx, normalization_value); + segment_range_min_yyyy = vmulq_n_f32(segment_range_min_yyyy, normalization_value); + segment_range_min_zzzz = vmulq_n_f32(segment_range_min_zzzz, normalization_value); + + segment_range_extent_xxxx = vmulq_n_f32(segment_range_extent_xxxx, normalization_value); + segment_range_extent_yyyy = vmulq_n_f32(segment_range_extent_yyyy, normalization_value); + segment_range_extent_zzzz = vmulq_n_f32(segment_range_extent_zzzz, normalization_value); +#else + rtm::vector4f segment_range_min_xxxx = rtm::vector_set(float(segment_range_data[0]), float(segment_range_data[1]), float(segment_range_data[2]), float(segment_range_data[3])); + rtm::vector4f segment_range_min_yyyy = rtm::vector_set(float(segment_range_data[4]), float(segment_range_data[5]), float(segment_range_data[6]), float(segment_range_data[7])); + rtm::vector4f segment_range_min_zzzz = rtm::vector_set(float(segment_range_data[8]), float(segment_range_data[9]), float(segment_range_data[10]), float(segment_range_data[11])); + + rtm::vector4f segment_range_extent_xxxx = rtm::vector_set(float(segment_range_data[12]), float(segment_range_data[13]), float(segment_range_data[14]), float(segment_range_data[15])); + rtm::vector4f segment_range_extent_yyyy = rtm::vector_set(float(segment_range_data[16]), float(segment_range_data[17]), float(segment_range_data[18]), float(segment_range_data[19])); + rtm::vector4f segment_range_extent_zzzz = rtm::vector_set(float(segment_range_data[20]), float(segment_range_data[21]), float(segment_range_data[22]), float(segment_range_data[23])); + + const float normalization_value = 1.0F / 255.0F; + + segment_range_min_xxxx = rtm::vector_mul(segment_range_min_xxxx, normalization_value); + segment_range_min_yyyy = rtm::vector_mul(segment_range_min_yyyy, normalization_value); + segment_range_min_zzzz = rtm::vector_mul(segment_range_min_zzzz, normalization_value); + + segment_range_extent_xxxx = rtm::vector_mul(segment_range_extent_xxxx, normalization_value); + segment_range_extent_yyyy = rtm::vector_mul(segment_range_extent_yyyy, normalization_value); + segment_range_extent_zzzz = rtm::vector_mul(segment_range_extent_zzzz, normalization_value); +#endif + +#if defined(ACL_IMPL_PREFETCH_EARLY) + // Prefetch the next cache line even if we don't have any data left + // By the time we unpack again, it will have arrived in the CPU cache + // If our format is full precision, we have at most 4 samples per cache line + // If our format is drop W, we have at most 5.33 samples per cache line + + // If our pointer was already aligned to a cache line before we unpacked our 4 values, + // it now points to the first byte of the next cache line. Any offset between 0-63 will fetch it. + // If our pointer had some offset into a cache line, we might have spanned 2 cache lines. + // If this happens, we probably already read some data from the next cache line in which + // case we don't need to prefetch it and we can go to the next one. Any offset after the end + // of this cache line will fetch it. For safety, we prefetch 63 bytes ahead. + // Prefetch 4 samples ahead in all levels of the CPU cache + ACL_IMPL_ANIMATED_PREFETCH(segment_range_data + 64); +#endif + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + // With AVX, we must duplicate our data for the first segment in case we don't have a second segment + if (scratch_offset == 0) + { + // First segment data is duplicated + // Use 256 bit stores to avoid doing too many stores which might stall + _mm256_store_ps(reinterpret_cast(&output_scratch.segment_range_min[0]), _mm256_set_m128(segment_range_min_xxxx, segment_range_min_xxxx)); + _mm256_store_ps(reinterpret_cast(&output_scratch.segment_range_min[2]), _mm256_set_m128(segment_range_min_yyyy, segment_range_min_yyyy)); + _mm256_store_ps(reinterpret_cast(&output_scratch.segment_range_min[4]), _mm256_set_m128(segment_range_min_zzzz, segment_range_min_zzzz)); + _mm256_store_ps(reinterpret_cast(&output_scratch.segment_range_extent[0]), _mm256_set_m128(segment_range_extent_xxxx, segment_range_extent_xxxx)); + _mm256_store_ps(reinterpret_cast(&output_scratch.segment_range_extent[2]), _mm256_set_m128(segment_range_extent_yyyy, segment_range_extent_yyyy)); + _mm256_store_ps(reinterpret_cast(&output_scratch.segment_range_extent[4]), _mm256_set_m128(segment_range_extent_zzzz, segment_range_extent_zzzz)); + } + else + { + // Second segment overwrites our data + output_scratch.segment_range_min[1] = segment_range_min_xxxx; + output_scratch.segment_range_min[3] = segment_range_min_yyyy; + output_scratch.segment_range_min[5] = segment_range_min_zzzz; + output_scratch.segment_range_extent[1] = segment_range_extent_xxxx; + output_scratch.segment_range_extent[3] = segment_range_extent_yyyy; + output_scratch.segment_range_extent[5] = segment_range_extent_zzzz; + } +#else + output_scratch.segment_range_min[scratch_offset + 0] = segment_range_min_xxxx; + output_scratch.segment_range_min[scratch_offset + 2] = segment_range_min_yyyy; + output_scratch.segment_range_min[scratch_offset + 4] = segment_range_min_zzzz; + output_scratch.segment_range_extent[scratch_offset + 0] = segment_range_extent_xxxx; + output_scratch.segment_range_extent[scratch_offset + 2] = segment_range_extent_yyyy; + output_scratch.segment_range_extent[scratch_offset + 4] = segment_range_extent_zzzz; +#endif + } + + // About 19 cycles with AVX on Skylake + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL remap_segment_range_data4(const segment_animated_scratch_v0& segment_scratch, uint32_t scratch_offset, range_reduction_masks_t range_reduction_masks, + rtm::vector4f& xxxx, rtm::vector4f& yyyy, rtm::vector4f& zzzz) + { + // Load and mask out our segment range data + const rtm::vector4f one_v = rtm::vector_set(1.0F); + + rtm::vector4f segment_range_min_xxxx = segment_scratch.segment_range_min[scratch_offset + 0]; + rtm::vector4f segment_range_min_yyyy = segment_scratch.segment_range_min[scratch_offset + 2]; + rtm::vector4f segment_range_min_zzzz = segment_scratch.segment_range_min[scratch_offset + 4]; + + rtm::vector4f segment_range_extent_xxxx = segment_scratch.segment_range_extent[scratch_offset + 0]; + rtm::vector4f segment_range_extent_yyyy = segment_scratch.segment_range_extent[scratch_offset + 2]; + rtm::vector4f segment_range_extent_zzzz = segment_scratch.segment_range_extent[scratch_offset + 4]; + +#if defined(RTM_SSE2_INTRINSICS) + // Mask out the segment min we ignore + const rtm::mask4f segment_range_ignore_mask_v = _mm_castsi128_ps(_mm_unpacklo_epi16(range_reduction_masks, range_reduction_masks)); + + segment_range_min_xxxx = _mm_andnot_ps(segment_range_ignore_mask_v, segment_range_min_xxxx); + segment_range_min_yyyy = _mm_andnot_ps(segment_range_ignore_mask_v, segment_range_min_yyyy); + segment_range_min_zzzz = _mm_andnot_ps(segment_range_ignore_mask_v, segment_range_min_zzzz); +#elif defined(RTM_NEON_INTRINSICS) + // Mask out the segment min we ignore + const uint32x4_t segment_range_ignore_mask_v = vreinterpretq_u32_s32(vmovl_s16(vget_low_s16(range_reduction_masks))); + + segment_range_min_xxxx = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(segment_range_min_xxxx), segment_range_ignore_mask_v)); + segment_range_min_yyyy = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(segment_range_min_yyyy), segment_range_ignore_mask_v)); + segment_range_min_zzzz = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(segment_range_min_zzzz), segment_range_ignore_mask_v)); +#else + const rtm::vector4f zero_v = rtm::vector_zero(); + + const uint32_t segment_range_mask_u32 = uint32_t(range_reduction_masks); + const rtm::mask4f segment_range_ignore_mask_v = rtm::mask_set((segment_range_mask_u32 & 0x000000FF) != 0, (segment_range_mask_u32 & 0x0000FF00) != 0, (segment_range_mask_u32 & 0x00FF0000) != 0, (segment_range_mask_u32 & 0xFF000000) != 0); + + segment_range_min_xxxx = rtm::vector_select(segment_range_ignore_mask_v, zero_v, segment_range_min_xxxx); + segment_range_min_yyyy = rtm::vector_select(segment_range_ignore_mask_v, zero_v, segment_range_min_yyyy); + segment_range_min_zzzz = rtm::vector_select(segment_range_ignore_mask_v, zero_v, segment_range_min_zzzz); +#endif + + // Mask out the segment extent we ignore + segment_range_extent_xxxx = rtm::vector_select(segment_range_ignore_mask_v, one_v, segment_range_extent_xxxx); + segment_range_extent_yyyy = rtm::vector_select(segment_range_ignore_mask_v, one_v, segment_range_extent_yyyy); + segment_range_extent_zzzz = rtm::vector_select(segment_range_ignore_mask_v, one_v, segment_range_extent_zzzz); + + // Remap + xxxx = rtm::vector_mul_add(xxxx, segment_range_extent_xxxx, segment_range_min_xxxx); + yyyy = rtm::vector_mul_add(yyyy, segment_range_extent_yyyy, segment_range_min_yyyy); + zzzz = rtm::vector_mul_add(zzzz, segment_range_extent_zzzz, segment_range_min_zzzz); + } + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL remap_segment_range_data_avx8(const segment_animated_scratch_v0& segment_scratch, + range_reduction_masks_t range_reduction_masks0, range_reduction_masks_t range_reduction_masks1, + __m256& xxxx0_xxxx1, __m256& yyyy0_yyyy1, __m256& zzzz0_zzzz1) + { + // Load and mask out our segment range data + const __m256 one_v = _mm256_set1_ps(1.0F); + + __m256 segment_range_min_xxxx0_xxxx1 = _mm256_load_ps(reinterpret_cast(&segment_scratch.segment_range_min[0])); + __m256 segment_range_min_yyyy0_yyyy1 = _mm256_load_ps(reinterpret_cast(&segment_scratch.segment_range_min[2])); + __m256 segment_range_min_zzzz0_zzzz1 = _mm256_load_ps(reinterpret_cast(&segment_scratch.segment_range_min[4])); + + __m256 segment_range_extent_xxxx0_xxxx1 = _mm256_load_ps(reinterpret_cast(&segment_scratch.segment_range_extent[0])); + __m256 segment_range_extent_yyyy0_yyyy1 = _mm256_load_ps(reinterpret_cast(&segment_scratch.segment_range_extent[2])); + __m256 segment_range_extent_zzzz0_zzzz1 = _mm256_load_ps(reinterpret_cast(&segment_scratch.segment_range_extent[4])); + + // Mask out the segment min we ignore + const __m128 segment_range_ignore_mask_v0 = _mm_castsi128_ps(_mm_unpacklo_epi16(range_reduction_masks0, range_reduction_masks0)); + const __m128 segment_range_ignore_mask_v1 = _mm_castsi128_ps(_mm_unpacklo_epi16(range_reduction_masks1, range_reduction_masks1)); + + const __m256 segment_range_mask0_mask1 = _mm256_set_m128(segment_range_ignore_mask_v1, segment_range_ignore_mask_v0); + + segment_range_min_xxxx0_xxxx1 = _mm256_andnot_ps(segment_range_mask0_mask1, segment_range_min_xxxx0_xxxx1); + segment_range_min_yyyy0_yyyy1 = _mm256_andnot_ps(segment_range_mask0_mask1, segment_range_min_yyyy0_yyyy1); + segment_range_min_zzzz0_zzzz1 = _mm256_andnot_ps(segment_range_mask0_mask1, segment_range_min_zzzz0_zzzz1); + + segment_range_extent_xxxx0_xxxx1 = _mm256_blendv_ps(segment_range_extent_xxxx0_xxxx1, one_v, segment_range_mask0_mask1); + segment_range_extent_yyyy0_yyyy1 = _mm256_blendv_ps(segment_range_extent_yyyy0_yyyy1, one_v, segment_range_mask0_mask1); + segment_range_extent_zzzz0_zzzz1 = _mm256_blendv_ps(segment_range_extent_zzzz0_zzzz1, one_v, segment_range_mask0_mask1); + + xxxx0_xxxx1 = _mm256_add_ps(_mm256_mul_ps(xxxx0_xxxx1, segment_range_extent_xxxx0_xxxx1), segment_range_min_xxxx0_xxxx1); + yyyy0_yyyy1 = _mm256_add_ps(_mm256_mul_ps(yyyy0_yyyy1, segment_range_extent_yyyy0_yyyy1), segment_range_min_yyyy0_yyyy1); + zzzz0_zzzz1 = _mm256_add_ps(_mm256_mul_ps(zzzz0_zzzz1, segment_range_extent_zzzz0_zzzz1), segment_range_min_zzzz0_zzzz1); + } +#endif + + // About 24 cycles with AVX on Skylake + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL remap_clip_range_data4(const uint8_t* clip_range_data, uint32_t num_to_unpack, + range_reduction_masks_t range_reduction_masks0, range_reduction_masks_t range_reduction_masks1, + rtm::vector4f& xxxx0, rtm::vector4f& yyyy0, rtm::vector4f& zzzz0, + rtm::vector4f& xxxx1, rtm::vector4f& yyyy1, rtm::vector4f& zzzz1) + { + // Always load 4x rotations, we might contain garbage in a few lanes but it's fine + const uint32_t load_size = num_to_unpack * sizeof(float); + +#if defined(RTM_SSE2_INTRINSICS) + const __m128 clip_range_mask0 = _mm_castsi128_ps(_mm_unpackhi_epi16(range_reduction_masks0, range_reduction_masks0)); + const __m128 clip_range_mask1 = _mm_castsi128_ps(_mm_unpackhi_epi16(range_reduction_masks1, range_reduction_masks1)); +#elif defined(RTM_NEON_INTRINSICS) + const float32x4_t clip_range_mask0 = vreinterpretq_f32_s32(vmovl_s16(vget_high_s16(range_reduction_masks0))); + const float32x4_t clip_range_mask1 = vreinterpretq_f32_s32(vmovl_s16(vget_high_s16(range_reduction_masks1))); +#else + const uint32_t clip_range_mask_u32_0 = uint32_t(range_reduction_masks0 >> 32); + const uint32_t clip_range_mask_u32_1 = uint32_t(range_reduction_masks1 >> 32); + const rtm::mask4f clip_range_mask0 = rtm::mask_set((clip_range_mask_u32_0 & 0x000000FF) != 0, (clip_range_mask_u32_0 & 0x0000FF00) != 0, (clip_range_mask_u32_0 & 0x00FF0000) != 0, (clip_range_mask_u32_0 & 0xFF000000) != 0); + const rtm::mask4f clip_range_mask1 = rtm::mask_set((clip_range_mask_u32_1 & 0x000000FF) != 0, (clip_range_mask_u32_1 & 0x0000FF00) != 0, (clip_range_mask_u32_1 & 0x00FF0000) != 0, (clip_range_mask_u32_1 & 0xFF000000) != 0); +#endif + + const rtm::vector4f clip_range_min_xxxx = rtm::vector_load(clip_range_data + load_size * 0); + const rtm::vector4f clip_range_min_yyyy = rtm::vector_load(clip_range_data + load_size * 1); + const rtm::vector4f clip_range_min_zzzz = rtm::vector_load(clip_range_data + load_size * 2); + + const rtm::vector4f clip_range_extent_xxxx = rtm::vector_load(clip_range_data + load_size * 3); + const rtm::vector4f clip_range_extent_yyyy = rtm::vector_load(clip_range_data + load_size * 4); + const rtm::vector4f clip_range_extent_zzzz = rtm::vector_load(clip_range_data + load_size * 5); + + // Mask out the clip ranges we ignore +#if defined(RTM_SSE2_INTRINSICS) + const rtm::vector4f clip_range_min_xxxx0 = _mm_andnot_ps(clip_range_mask0, clip_range_min_xxxx); + const rtm::vector4f clip_range_min_yyyy0 = _mm_andnot_ps(clip_range_mask0, clip_range_min_yyyy); + const rtm::vector4f clip_range_min_zzzz0 = _mm_andnot_ps(clip_range_mask0, clip_range_min_zzzz); + + const rtm::vector4f clip_range_min_xxxx1 = _mm_andnot_ps(clip_range_mask1, clip_range_min_xxxx); + const rtm::vector4f clip_range_min_yyyy1 = _mm_andnot_ps(clip_range_mask1, clip_range_min_yyyy); + const rtm::vector4f clip_range_min_zzzz1 = _mm_andnot_ps(clip_range_mask1, clip_range_min_zzzz); +#elif defined(RTM_NEON_INTRINSICS) + const rtm::vector4f clip_range_min_xxxx0 = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(clip_range_min_xxxx), vreinterpretq_u32_f32(clip_range_mask0))); + const rtm::vector4f clip_range_min_yyyy0 = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(clip_range_min_yyyy), vreinterpretq_u32_f32(clip_range_mask0))); + const rtm::vector4f clip_range_min_zzzz0 = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(clip_range_min_zzzz), vreinterpretq_u32_f32(clip_range_mask0))); + + const rtm::vector4f clip_range_min_xxxx1 = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(clip_range_min_xxxx), vreinterpretq_u32_f32(clip_range_mask1))); + const rtm::vector4f clip_range_min_yyyy1 = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(clip_range_min_yyyy), vreinterpretq_u32_f32(clip_range_mask1))); + const rtm::vector4f clip_range_min_zzzz1 = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(clip_range_min_zzzz), vreinterpretq_u32_f32(clip_range_mask1))); +#else + const rtm::vector4f zero_v = rtm::vector_zero(); + + const rtm::vector4f clip_range_min_xxxx0 = rtm::vector_select(clip_range_mask0, zero_v, clip_range_min_xxxx); + const rtm::vector4f clip_range_min_yyyy0 = rtm::vector_select(clip_range_mask0, zero_v, clip_range_min_yyyy); + const rtm::vector4f clip_range_min_zzzz0 = rtm::vector_select(clip_range_mask0, zero_v, clip_range_min_zzzz); + + const rtm::vector4f clip_range_min_xxxx1 = rtm::vector_select(clip_range_mask1, zero_v, clip_range_min_xxxx); + const rtm::vector4f clip_range_min_yyyy1 = rtm::vector_select(clip_range_mask1, zero_v, clip_range_min_yyyy); + const rtm::vector4f clip_range_min_zzzz1 = rtm::vector_select(clip_range_mask1, zero_v, clip_range_min_zzzz); +#endif + + const rtm::vector4f one_v = rtm::vector_set(1.0F); + + const rtm::vector4f clip_range_extent_xxxx0 = rtm::vector_select(clip_range_mask0, one_v, clip_range_extent_xxxx); + const rtm::vector4f clip_range_extent_yyyy0 = rtm::vector_select(clip_range_mask0, one_v, clip_range_extent_yyyy); + const rtm::vector4f clip_range_extent_zzzz0 = rtm::vector_select(clip_range_mask0, one_v, clip_range_extent_zzzz); + + const rtm::vector4f clip_range_extent_xxxx1 = rtm::vector_select(clip_range_mask1, one_v, clip_range_extent_xxxx); + const rtm::vector4f clip_range_extent_yyyy1 = rtm::vector_select(clip_range_mask1, one_v, clip_range_extent_yyyy); + const rtm::vector4f clip_range_extent_zzzz1 = rtm::vector_select(clip_range_mask1, one_v, clip_range_extent_zzzz); + + xxxx0 = rtm::vector_mul_add(xxxx0, clip_range_extent_xxxx0, clip_range_min_xxxx0); + yyyy0 = rtm::vector_mul_add(yyyy0, clip_range_extent_yyyy0, clip_range_min_yyyy0); + zzzz0 = rtm::vector_mul_add(zzzz0, clip_range_extent_zzzz0, clip_range_min_zzzz0); + + xxxx1 = rtm::vector_mul_add(xxxx1, clip_range_extent_xxxx1, clip_range_min_xxxx1); + yyyy1 = rtm::vector_mul_add(yyyy1, clip_range_extent_yyyy1, clip_range_min_yyyy1); + zzzz1 = rtm::vector_mul_add(zzzz1, clip_range_extent_zzzz1, clip_range_min_zzzz1); + } + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL remap_clip_range_data_avx8(const uint8_t* clip_range_data, uint32_t num_to_unpack, + range_reduction_masks_t range_reduction_masks0, range_reduction_masks_t range_reduction_masks1, + __m256& xxxx0_xxxx1, __m256& yyyy0_yyyy1, __m256& zzzz0_zzzz1) + { + const __m256 one_v = _mm256_set1_ps(1.0F); + + // Always load 4x rotations, we might contain garbage in a few lanes but it's fine + const uint32_t load_size = num_to_unpack * sizeof(float); + + const __m128 clip_range_mask0 = _mm_castsi128_ps(_mm_unpackhi_epi16(range_reduction_masks0, range_reduction_masks0)); + const __m128 clip_range_mask1 = _mm_castsi128_ps(_mm_unpackhi_epi16(range_reduction_masks1, range_reduction_masks1)); + + const __m256 clip_range_mask0_mask1 = _mm256_set_m128(clip_range_mask1, clip_range_mask0); + + const rtm::vector4f clip_range_min_xxxx = rtm::vector_load(clip_range_data + load_size * 0); + const rtm::vector4f clip_range_min_yyyy = rtm::vector_load(clip_range_data + load_size * 1); + const rtm::vector4f clip_range_min_zzzz = rtm::vector_load(clip_range_data + load_size * 2); + + const rtm::vector4f clip_range_extent_xxxx = rtm::vector_load(clip_range_data + load_size * 3); + const rtm::vector4f clip_range_extent_yyyy = rtm::vector_load(clip_range_data + load_size * 4); + const rtm::vector4f clip_range_extent_zzzz = rtm::vector_load(clip_range_data + load_size * 5); + + __m256 clip_range_min_xxxx_xxxx = _mm256_set_m128(clip_range_min_xxxx, clip_range_min_xxxx); + __m256 clip_range_min_yyyy_yyyy = _mm256_set_m128(clip_range_min_yyyy, clip_range_min_yyyy); + __m256 clip_range_min_zzzz_zzzz = _mm256_set_m128(clip_range_min_zzzz, clip_range_min_zzzz); + + __m256 clip_range_extent_xxxx_xxxx = _mm256_set_m128(clip_range_extent_xxxx, clip_range_extent_xxxx); + __m256 clip_range_extent_yyyy_yyyy = _mm256_set_m128(clip_range_extent_yyyy, clip_range_extent_yyyy); + __m256 clip_range_extent_zzzz_zzzz = _mm256_set_m128(clip_range_extent_zzzz, clip_range_extent_zzzz); + + // Mask out the clip ranges we ignore + clip_range_min_xxxx_xxxx = _mm256_andnot_ps(clip_range_mask0_mask1, clip_range_min_xxxx_xxxx); + clip_range_min_yyyy_yyyy = _mm256_andnot_ps(clip_range_mask0_mask1, clip_range_min_yyyy_yyyy); + clip_range_min_zzzz_zzzz = _mm256_andnot_ps(clip_range_mask0_mask1, clip_range_min_zzzz_zzzz); + + clip_range_extent_xxxx_xxxx = _mm256_blendv_ps(clip_range_extent_xxxx_xxxx, one_v, clip_range_mask0_mask1); + clip_range_extent_yyyy_yyyy = _mm256_blendv_ps(clip_range_extent_yyyy_yyyy, one_v, clip_range_mask0_mask1); + clip_range_extent_zzzz_zzzz = _mm256_blendv_ps(clip_range_extent_zzzz_zzzz, one_v, clip_range_mask0_mask1); + + xxxx0_xxxx1 = _mm256_add_ps(_mm256_mul_ps(xxxx0_xxxx1, clip_range_extent_xxxx_xxxx), clip_range_min_xxxx_xxxx); + yyyy0_yyyy1 = _mm256_add_ps(_mm256_mul_ps(yyyy0_yyyy1, clip_range_extent_yyyy_yyyy), clip_range_min_yyyy_yyyy); + zzzz0_zzzz1 = _mm256_add_ps(_mm256_mul_ps(zzzz0_zzzz1, clip_range_extent_zzzz_zzzz), clip_range_min_zzzz_zzzz); + } +#endif + + // About 31 cycles with AVX on Skylake + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL quat_from_positive_w4(rtm::vector4f_arg0 xxxx, rtm::vector4f_arg1 yyyy, rtm::vector4f_arg2 zzzz) + { + const rtm::vector4f xxxx_squared = rtm::vector_mul(xxxx, xxxx); + const rtm::vector4f yyyy_squared = rtm::vector_mul(yyyy, yyyy); + const rtm::vector4f zzzz_squared = rtm::vector_mul(zzzz, zzzz); + const rtm::vector4f wwww_squared = rtm::vector_sub(rtm::vector_sub(rtm::vector_sub(rtm::vector_set(1.0F), xxxx_squared), yyyy_squared), zzzz_squared); + + // w_squared can be negative either due to rounding or due to quantization imprecision, we take the absolute value + // to ensure the resulting quaternion is always normalized with a positive W component + return rtm::vector_sqrt(rtm::vector_abs(wwww_squared)); + } + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK __m256 RTM_SIMD_CALL quat_from_positive_w_avx8(__m256 xxxx0_xxxx1, __m256 yyyy0_yyyy1, __m256 zzzz0_zzzz1) + { + const __m256 one_v = _mm256_set1_ps(1.0F); + + const __m256 xxxx0_xxxx1_squared = _mm256_mul_ps(xxxx0_xxxx1, xxxx0_xxxx1); + const __m256 yyyy0_yyyy1_squared = _mm256_mul_ps(yyyy0_yyyy1, yyyy0_yyyy1); + const __m256 zzzz0_zzzz1_squared = _mm256_mul_ps(zzzz0_zzzz1, zzzz0_zzzz1); + + const __m256 wwww0_wwww1_squared = _mm256_sub_ps(_mm256_sub_ps(_mm256_sub_ps(one_v, xxxx0_xxxx1_squared), yyyy0_yyyy1_squared), zzzz0_zzzz1_squared); + + const __m256i abs_mask = _mm256_set1_epi32(0x7FFFFFFFULL); + const __m256 wwww0_wwww1_squared_abs = _mm256_and_ps(wwww0_wwww1_squared, _mm256_castsi256_ps(abs_mask)); + + return _mm256_sqrt_ps(wwww0_wwww1_squared_abs); + } +#endif + + // About 28 cycles with AVX on Skylake + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL quat_lerp4( + rtm::vector4f_arg0 xxxx0, rtm::vector4f_arg1 yyyy0, rtm::vector4f_arg2 zzzz0, rtm::vector4f_arg3 wwww0, + rtm::vector4f_arg4 xxxx1, rtm::vector4f_arg5 yyyy1, rtm::vector4f_arg6 zzzz1, rtm::vector4f_arg7 wwww1, + float interpolation_alpha, + rtm::vector4f& interp_xxxx, rtm::vector4f& interp_yyyy, rtm::vector4f& interp_zzzz, rtm::vector4f& interp_wwww) + { + // Calculate the vector4 dot product: dot(start, end) + const rtm::vector4f xxxx_squared = rtm::vector_mul(xxxx0, xxxx1); + const rtm::vector4f yyyy_squared = rtm::vector_mul(yyyy0, yyyy1); + const rtm::vector4f zzzz_squared = rtm::vector_mul(zzzz0, zzzz1); + const rtm::vector4f wwww_squared = rtm::vector_mul(wwww0, wwww1); + + const rtm::vector4f dot4 = rtm::vector_add(rtm::vector_add(rtm::vector_add(xxxx_squared, yyyy_squared), zzzz_squared), wwww_squared); + + // Calculate the bias, if the dot product is positive or zero, there is no bias + // but if it is negative, we want to flip the 'end' rotation XYZW components + const rtm::vector4f neg_zero = rtm::vector_set(-0.0F); + const rtm::vector4f bias = rtm::vector_and(dot4, neg_zero); + + // Apply our bias to the 'end' + const rtm::vector4f xxxx1_with_bias = rtm::vector_xor(xxxx1, bias); + const rtm::vector4f yyyy1_with_bias = rtm::vector_xor(yyyy1, bias); + const rtm::vector4f zzzz1_with_bias = rtm::vector_xor(zzzz1, bias); + const rtm::vector4f wwww1_with_bias = rtm::vector_xor(wwww1, bias); + + // Lerp the rotation after applying the bias + // ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias)) + const rtm::vector4f alpha = rtm::vector_set(interpolation_alpha); + + interp_xxxx = rtm::vector_mul_add(xxxx1_with_bias, alpha, rtm::vector_neg_mul_sub(xxxx0, alpha, xxxx0)); + interp_yyyy = rtm::vector_mul_add(yyyy1_with_bias, alpha, rtm::vector_neg_mul_sub(yyyy0, alpha, yyyy0)); + interp_zzzz = rtm::vector_mul_add(zzzz1_with_bias, alpha, rtm::vector_neg_mul_sub(zzzz0, alpha, zzzz0)); + interp_wwww = rtm::vector_mul_add(wwww1_with_bias, alpha, rtm::vector_neg_mul_sub(wwww0, alpha, wwww0)); + } + + // About 9 cycles with AVX on Skylake + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL quat_normalize4(rtm::vector4f& xxxx, rtm::vector4f& yyyy, rtm::vector4f& zzzz, rtm::vector4f& wwww) + { + const rtm::vector4f xxxx_squared = rtm::vector_mul(xxxx, xxxx); + const rtm::vector4f yyyy_squared = rtm::vector_mul(yyyy, yyyy); + const rtm::vector4f zzzz_squared = rtm::vector_mul(zzzz, zzzz); + const rtm::vector4f wwww_squared = rtm::vector_mul(wwww, wwww); + + const rtm::vector4f dot4 = rtm::vector_add(rtm::vector_add(rtm::vector_add(xxxx_squared, yyyy_squared), zzzz_squared), wwww_squared); + + const rtm::vector4f len4 = rtm::vector_sqrt(dot4); + const rtm::vector4f inv_len4 = rtm::vector_div(rtm::vector_set(1.0F), len4); + + xxxx = rtm::vector_mul(xxxx, inv_len4); + yyyy = rtm::vector_mul(yyyy, inv_len4); + zzzz = rtm::vector_mul(zzzz, inv_len4); + wwww = rtm::vector_mul(wwww, inv_len4); + } + + template + inline RTM_DISABLE_SECURITY_COOKIE_CHECK range_reduction_masks_t RTM_SIMD_CALL unpack_animated_quat(const persistent_transform_decompression_context_v0& decomp_context, rtm::vector4f output_scratch[4], + uint32_t num_to_unpack, segment_animated_sampling_context_v0& segment_sampling_context) + { + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + + uint32_t segment_range_ignore_mask = 0; + uint32_t clip_range_ignore_mask = 0; + + const uint8_t* format_per_track_data = segment_sampling_context.format_per_track_data; + const uint8_t* segment_range_data = segment_sampling_context.segment_range_data; + const uint8_t* animated_track_data = segment_sampling_context.animated_track_data; + uint32_t animated_track_data_bit_offset = segment_sampling_context.animated_track_data_bit_offset; + + // For SIMD, can we load constant samples and write them to scratch? Afterwards its the same as packed on 16 bits + // We get 4 short branches (test, cmp, 6x loads, 3x ORs, 3x writes, load immediate) followed by a common code path for all 4 samples + + // Maybe we can write in SOA order directly, and we could even write the scaling value per lane, load ones multiply 3 times for xyz in SOA + + // Try inlining the unpacking functions + + for (uint32_t unpack_index = 0; unpack_index < num_to_unpack; ++unpack_index) + { + // Our decompressed rotation as a vector4 + rtm::vector4f rotation_as_vec; + + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + const uint32_t num_bits_at_bit_rate = format_per_track_data[unpack_index]; + + uint32_t sample_segment_range_ignore_mask; + uint32_t sample_clip_range_ignore_mask; + + if (num_bits_at_bit_rate == 0) // Constant bit rate + { + // Segment range is packed: min.xxxx, min.yyyy, min.zzzz, extent.xxxx, extent.yyyy, extent.zzzz + // Our constant sample value is packed 8 bits in each group in the sample's lane + // To load our sample, we need to load: (min.x[unpack_index] << 8) | min.y[unpack_index], (min.z[unpack_index] << 8) | extent.x[unpack_index], (extent.y[unpack_index] << 8) | extent.z[unpack_index] + // This is more complicated than if we were in AOS form but constant bit rates are somewhat rare while nearly every sample + // has segment range information which is a lot simpler to load in SOA form + const uint8_t* shifted_segment_range_data = segment_range_data + unpack_index; + const uint32_t x = (uint32_t(shifted_segment_range_data[0]) << 8) | shifted_segment_range_data[4]; + const uint32_t y = (uint32_t(shifted_segment_range_data[8]) << 8) | shifted_segment_range_data[12]; + const uint32_t z = (uint32_t(shifted_segment_range_data[16]) << 8) | shifted_segment_range_data[20]; + +#if defined(RTM_SSE2_INTRINSICS) + // TODO: Use SIMD for this + + // Load min.xxxx, min.yyyy, 8 bytes, offset by our sample index such that the first byte is our sample + // Unpack low and interleave xxxx, yyyy, we end up with sample.x in our first lane as uint16_t + // Unpack low to convert to uint32_t, sample.x lives in lane 0, repeat for sample.yz + // Total of 2x loads (re-use first load and interleave high for sample.y), 5x unpack + // Merge sample.xy together (1x shuffle) + // Merge sample.xyz together (1x shuffle) + // Convert to floats and normalize + __m128i xyz = _mm_setr_epi32(x, y, z, 0); + __m128 xyzf = _mm_cvtepi32_ps(xyz); + rotation_as_vec = _mm_mul_ps(xyzf, _mm_set_ps1(1.0F / 65535.0F)); +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t xyz = vcombine_u32(vcreate_u32((uint64_t(y) << 32) | x), vcreate_u32(z)); + float32x4_t xyzf = vcvtq_f32_u32(xyz); + rotation_as_vec = vmulq_n_f32(xyzf, 1.0F / 65535.0F); +#else + const rtm::vector4f xyz = rtm::vector_set(float(x), float(y), float(z), 0.0F); + rotation_as_vec = rtm::vector_mul(xyz, 1.0F / 65535.0F); +#endif + + sample_segment_range_ignore_mask = 0xFF; // Ignore segment range + sample_clip_range_ignore_mask = 0x00; + } + else if (num_bits_at_bit_rate == 32) // Raw bit rate + { + rotation_as_vec = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += 96; + sample_segment_range_ignore_mask = 0xFF; // Ignore segment range + sample_clip_range_ignore_mask = 0xFF; // Ignore clip range + } + else + { + rotation_as_vec = unpack_vector3_uXX_unsafe(num_bits_at_bit_rate, animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += num_bits_at_bit_rate * 3; + sample_segment_range_ignore_mask = 0x00; + sample_clip_range_ignore_mask = 0x00; + } + + // Masks are used in little endian format so the first sample is in the LSB end + segment_range_ignore_mask |= sample_segment_range_ignore_mask << (unpack_index * 8); + clip_range_ignore_mask |= sample_clip_range_ignore_mask << (unpack_index * 8); + } + else + { + if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + { + rotation_as_vec = unpack_vector4_128_unsafe(animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += 128; + } + else // rotation_format8::quatf_drop_w_full + { + rotation_as_vec = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += 96; + } + } + + output_scratch[unpack_index] = rotation_as_vec; + } + + // Prefetch the next cache line even if we don't have any data left + // By the time we unpack again, it will have arrived in the CPU cache + // If our format is full precision, we have at most 4 samples per cache line + // If our format is drop W, we have at most 5.33 samples per cache line + + // If our pointer was already aligned to a cache line before we unpacked our 4 values, + // it now points to the first byte of the next cache line. Any offset between 0-63 will fetch it. + // If our pointer had some offset into a cache line, we might have spanned 2 cache lines. + // If this happens, we probably already read some data from the next cache line in which + // case we don't need to prefetch it and we can go to the next one. Any offset after the end + // of this cache line will fetch it. For safety, we prefetch 63 bytes ahead. + // Prefetch 4 samples ahead in all levels of the CPU cache +#if defined(ACL_IMPL_PREFETCH_EARLY) + ACL_IMPL_ANIMATED_PREFETCH(animated_track_data + (animated_track_data_bit_offset / 8) + 63); +#endif + + // Update our pointers + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { +#if defined(ACL_IMPL_PREFETCH_EARLY) + // Prefetch the next cache line in all levels of the CPU cache + ACL_IMPL_ANIMATED_PREFETCH(format_per_track_data + 64); +#endif + + // Skip our used metadata data, all groups are padded to 4 elements + segment_sampling_context.format_per_track_data = format_per_track_data + 4; + } + + segment_sampling_context.animated_track_data_bit_offset = animated_track_data_bit_offset; + + range_reduction_masks_t range_reduction_masks; // function's return value + + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { +#if defined(RTM_SSE2_INTRINSICS) + const __m128i ignore_masks_v8 = _mm_set_epi32(0, 0, clip_range_ignore_mask, segment_range_ignore_mask); + range_reduction_masks = _mm_unpacklo_epi8(ignore_masks_v8, ignore_masks_v8); +#elif defined(RTM_NEON_INTRINSICS) + const int8x8_t ignore_masks_v8 = vcreate_s8((uint64_t(clip_range_ignore_mask) << 32) | segment_range_ignore_mask); + range_reduction_masks = vmovl_s8(ignore_masks_v8); +#else + range_reduction_masks = (uint64_t(clip_range_ignore_mask) << 32) | segment_range_ignore_mask; +#endif + + // Skip our used segment range data, all groups are padded to 4 elements + segment_range_data += 6 * 4; + + // Update our ptr + segment_sampling_context.segment_range_data = segment_range_data; + } + else + { +#if defined(RTM_SSE2_INTRINSICS) + range_reduction_masks = _mm_setzero_si128(); +#elif defined(RTM_NEON_INTRINSICS) + range_reduction_masks = vcombine_s16(vcreate_s16(0ULL), vcreate_s16(0ULL)); +#else + range_reduction_masks = 0ULL; +#endif + } + + return range_reduction_masks; + } + + template + inline RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL unpack_single_animated_quat(const persistent_transform_decompression_context_v0& decomp_context, + uint32_t unpack_index, uint32_t group_size, + const clip_animated_sampling_context_v0& clip_sampling_context, const segment_animated_sampling_context_v0& segment_sampling_context) + { + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + + uint32_t segment_range_ignore_mask = 0; + uint32_t clip_range_ignore_mask = 0; + + const uint8_t* format_per_track_data = segment_sampling_context.format_per_track_data; + const uint8_t* segment_range_data = segment_sampling_context.segment_range_data; + const uint8_t* animated_track_data = segment_sampling_context.animated_track_data; + uint32_t animated_track_data_bit_offset = segment_sampling_context.animated_track_data_bit_offset; + + // Unpack sample + rtm::vector4f rotation_as_vec; + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + // Fall-through intentional + uint32_t skip_size = 0; + switch (unpack_index) + { + default: + case 3: + skip_size += format_per_track_data[2]; + ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL; + case 2: + skip_size += format_per_track_data[1]; + ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL; + case 1: + skip_size += format_per_track_data[0]; + ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL; + case 0: + // Nothing to skip + (void)skip_size; + } + + // Skip prior samples + animated_track_data_bit_offset += skip_size * 3; + + const uint32_t num_bits_at_bit_rate = format_per_track_data[unpack_index]; + + if (num_bits_at_bit_rate == 0) // Constant bit rate + { + // Segment range is packed: min.xxxx, min.yyyy, min.zzzz, extent.xxxx, extent.yyyy, extent.zzzz + // Our constant sample value is packed 8 bits in each group in the sample's lane + // To load our sample, we need to load: (min.x[unpack_index] << 8) | min.y[unpack_index], (min.z[unpack_index] << 8) | extent.x[unpack_index], (extent.y[unpack_index] << 8) | extent.z[unpack_index] + // This is more complicated than if we were in AOS form but constant bit rates are somewhat rare while nearly every sample + // has segment range information which is a lot simpler to load in SOA form + const uint8_t* shifted_segment_range_data = segment_range_data + unpack_index; + const uint32_t x = (uint32_t(shifted_segment_range_data[0]) << 8) | shifted_segment_range_data[4]; + const uint32_t y = (uint32_t(shifted_segment_range_data[8]) << 8) | shifted_segment_range_data[12]; + const uint32_t z = (uint32_t(shifted_segment_range_data[16]) << 8) | shifted_segment_range_data[20]; + +#if defined(RTM_SSE2_INTRINSICS) + // TODO: Use SIMD for this + + // Load min.xxxx, min.yyyy, 8 bytes, offset by our sample index such that the first byte is our sample + // Unpack low and interleave xxxx, yyyy, we end up with sample.x in our first lane as uint16_t + // Unpack low to convert to uint32_t, sample.x lives in lane 0, repeat for sample.yz + // Total of 2x loads (re-use first load and interleave high for sample.y), 5x unpack + // Merge sample.xy together (1x shuffle) + // Merge sample.xyz together (1x shuffle) + // Convert to floats and normalize + __m128i xyz = _mm_setr_epi32(x, y, z, 0); + __m128 xyzf = _mm_cvtepi32_ps(xyz); + rotation_as_vec = _mm_mul_ps(xyzf, _mm_set_ps1(1.0F / 65535.0F)); +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t xyz = vcombine_u32(vcreate_u32((uint64_t(y) << 32) | x), vcreate_u32(z)); + float32x4_t xyzf = vcvtq_f32_u32(xyz); + rotation_as_vec = vmulq_n_f32(xyzf, 1.0F / 65535.0F); +#else + const rtm::vector4f xyz = rtm::vector_set(float(x), float(y), float(z), 0.0F); + rotation_as_vec = rtm::vector_mul(xyz, 1.0F / 65535.0F); +#endif + + segment_range_ignore_mask = 0xFF; // Ignore segment range + clip_range_ignore_mask = 0x00; + } + else if (num_bits_at_bit_rate == 32) // Raw bit rate + { + rotation_as_vec = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + segment_range_ignore_mask = 0xFF; // Ignore segment range + clip_range_ignore_mask = 0xFF; // Ignore clip range + } + else + { + rotation_as_vec = unpack_vector3_uXX_unsafe(num_bits_at_bit_rate, animated_track_data, animated_track_data_bit_offset); + segment_range_ignore_mask = 0x00; + clip_range_ignore_mask = 0x00; + } + } + else + { + if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + { + animated_track_data_bit_offset += unpack_index * 128; + rotation_as_vec = unpack_vector4_128_unsafe(animated_track_data, animated_track_data_bit_offset); + } + else // rotation_format8::quatf_drop_w_full + { + animated_track_data_bit_offset += unpack_index * 96; + rotation_as_vec = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + } + } + + // Remap within our ranges + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + if (decomp_context.has_segments && segment_range_ignore_mask == 0) + { + // Segment range is packed: min.xxxx, min.yyyy, min.zzzz, extent.xxxx, extent.yyyy, extent.zzzz + segment_range_data += unpack_index; // Offset to our sample + + const uint32_t min_x = segment_range_data[0]; + const uint32_t min_y = segment_range_data[4]; + const uint32_t min_z = segment_range_data[8]; + + const uint32_t extent_x = segment_range_data[12]; + const uint32_t extent_y = segment_range_data[16]; + const uint32_t extent_z = segment_range_data[20]; + +#if defined(RTM_SSE2_INTRINSICS) + __m128i min_u32 = _mm_setr_epi32(min_x, min_y, min_z, 0); + __m128i extent_u32 = _mm_setr_epi32(extent_x, extent_y, extent_z, 0); + + rtm::vector4f segment_range_min = _mm_cvtepi32_ps(min_u32); + rtm::vector4f segment_range_extent = _mm_cvtepi32_ps(extent_u32); +#elif defined(RTM_NEON_INTRINSICS) + uint32x4_t min_u32 = vcombine_u32(vcreate_u32((uint64_t(min_y) << 32) | min_x), vcreate_u32(min_z)); + uint32x4_t extent_u32 = vcombine_u32(vcreate_u32((uint64_t(extent_y) << 32) | extent_x), vcreate_u32(extent_z)); + + rtm::vector4f segment_range_min = vcvtq_f32_u32(min_u32); + rtm::vector4f segment_range_extent = vcvtq_f32_u32(extent_u32); +#else + rtm::vector4f segment_range_min = rtm::vector_set(float(min_x), float(min_y), float(min_z), 0.0F); + rtm::vector4f segment_range_extent = rtm::vector_set(float(extent_x), float(extent_y), float(extent_z), 0.0F); +#endif + + const float normalization_scale = 1.0F / 255.0F; + segment_range_min = rtm::vector_mul(segment_range_min, normalization_scale); + segment_range_extent = rtm::vector_mul(segment_range_extent, normalization_scale); + + rotation_as_vec = rtm::vector_mul_add(rotation_as_vec, segment_range_extent, segment_range_min); + } + + if (clip_range_ignore_mask == 0) + { + const float* clip_range_data = reinterpret_cast(clip_sampling_context.clip_range_data) + unpack_index; // Offset to our sample + + const float min_x = clip_range_data[group_size * 0]; + const float min_y = clip_range_data[group_size * 1]; + const float min_z = clip_range_data[group_size * 2]; + const rtm::vector4f clip_range_min = rtm::vector_set(min_x, min_y, min_z, 0.0F); + + const float extent_x = clip_range_data[group_size * 3]; + const float extent_y = clip_range_data[group_size * 4]; + const float extent_z = clip_range_data[group_size * 5]; + const rtm::vector4f clip_range_extent = rtm::vector_set(extent_x, extent_y, extent_z, 0.0F); + + rotation_as_vec = rtm::vector_mul_add(rotation_as_vec, clip_range_extent, clip_range_min); + } + } + + return rotation_as_vec; + } + + template + inline RTM_DISABLE_SECURITY_COOKIE_CHECK void unpack_animated_vector3(const persistent_transform_decompression_context_v0& decomp_context, rtm::vector4f output_scratch[4], + uint32_t num_to_unpack, + const clip_animated_sampling_context_v0& clip_sampling_context, segment_animated_sampling_context_v0& segment_sampling_context) + { + const vector_format8 format = get_vector_format(decompression_settings_adapter_type::get_vector_format(decomp_context)); + + const uint8_t* format_per_track_data = segment_sampling_context.format_per_track_data; + const uint8_t* segment_range_data = segment_sampling_context.segment_range_data; + const uint8_t* animated_track_data = segment_sampling_context.animated_track_data; + uint32_t animated_track_data_bit_offset = segment_sampling_context.animated_track_data_bit_offset; + + const uint8_t* clip_range_data = clip_sampling_context.clip_range_data; + + for (uint32_t unpack_index = 0; unpack_index < num_to_unpack; ++unpack_index) + { + // Range ignore flags are used to skip range normalization at the clip and/or segment levels + // Each sample has two bits like so: + // - 0x01 = ignore segment level + // - 0x02 = ignore clip level + uint32_t range_ignore_flags; + + rtm::vector4f sample; + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + const uint32_t num_bits_at_bit_rate = *format_per_track_data; + format_per_track_data++; + + if (num_bits_at_bit_rate == 0) // Constant bit rate + { + sample = unpack_vector3_u48_unsafe(segment_range_data); + segment_range_data += sizeof(uint16_t) * 3; + range_ignore_flags = 0x01; // Skip segment only + } + else if (num_bits_at_bit_rate == 32) // Raw bit rate + { + sample = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += 96; + segment_range_data += sizeof(uint16_t) * 3; // Raw bit rates have unused range data, skip it + range_ignore_flags = 0x03; // Skip clip and segment + } + else + { + sample = unpack_vector3_uXX_unsafe(num_bits_at_bit_rate, animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += num_bits_at_bit_rate * 3; + range_ignore_flags = 0x00; // Don't skip range reduction + } + } + else // vector_format8::vector3f_full + { + sample = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + animated_track_data_bit_offset += 96; + range_ignore_flags = 0x03; // Skip clip and segment + } + + // Remap within our ranges + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + if (decomp_context.has_segments && (range_ignore_flags & 0x01) == 0) + { + // Apply segment range remapping + const uint32_t range_entry_size = 3 * sizeof(uint8_t); + const uint8_t* segment_range_min_ptr = segment_range_data; + const uint8_t* segment_range_extent_ptr = segment_range_min_ptr + range_entry_size; + segment_range_data = segment_range_extent_ptr + range_entry_size; + + const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(segment_range_min_ptr); + const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(segment_range_extent_ptr); + + sample = rtm::vector_mul_add(sample, segment_range_extent, segment_range_min); + } + + if ((range_ignore_flags & 0x02) == 0) + { + // Apply clip range remapping + const uint32_t range_entry_size = 3 * sizeof(float); + const uint32_t sub_track_offset = range_entry_size * 2 * unpack_index; + const uint8_t* clip_range_min_ptr = clip_range_data + sub_track_offset; + const uint8_t* clip_range_extent_ptr = clip_range_min_ptr + range_entry_size; + + const rtm::vector4f clip_range_min = rtm::vector_load(clip_range_min_ptr); + const rtm::vector4f clip_range_extent = rtm::vector_load(clip_range_extent_ptr); + + sample = rtm::vector_mul_add(sample, clip_range_extent, clip_range_min); + } + } + + ACL_ASSERT(rtm::vector_is_finite3(sample), "Vector3 is not valid!"); + + // TODO: Fill in W component with something sensible? + + // Cache + output_scratch[unpack_index] = sample; + } + + // Update our pointers + segment_sampling_context.format_per_track_data = format_per_track_data; + segment_sampling_context.segment_range_data = segment_range_data; + segment_sampling_context.animated_track_data_bit_offset = animated_track_data_bit_offset; + + // Prefetch the next cache line even if we don't have any data left + // By the time we unpack again, it will have arrived in the CPU cache + // If our format is full precision, we have at most 4 samples per cache line + // If our format is drop W, we have at most 5.33 samples per cache line + + // If our pointer was already aligned to a cache line before we unpacked our 4 values, + // it now points to the first byte of the next cache line. Any offset between 0-63 will fetch it. + // If our pointer had some offset into a cache line, we might have spanned 2 cache lines. + // If this happens, we probably already read some data from the next cache line in which + // case we don't need to prefetch it and we can go to the next one. Any offset after the end + // of this cache line will fetch it. For safety, we prefetch 63 bytes ahead. + // Prefetch 4 samples ahead in all levels of the CPU cache + ACL_IMPL_ANIMATED_PREFETCH(format_per_track_data + 60); + ACL_IMPL_ANIMATED_PREFETCH(animated_track_data + (animated_track_data_bit_offset / 8) + 63); + ACL_IMPL_ANIMATED_PREFETCH(segment_range_data + 48); + } + + template + inline RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL unpack_single_animated_vector3(const persistent_transform_decompression_context_v0& decomp_context, + uint32_t unpack_index, + const clip_animated_sampling_context_v0& clip_sampling_context, const segment_animated_sampling_context_v0& segment_sampling_context) + { + const vector_format8 format = get_vector_format(decompression_settings_adapter_type::get_vector_format(decomp_context)); + + const uint8_t* format_per_track_data = segment_sampling_context.format_per_track_data; + const uint8_t* segment_range_data = segment_sampling_context.segment_range_data; + const uint8_t* animated_track_data = segment_sampling_context.animated_track_data; + uint32_t animated_track_data_bit_offset = segment_sampling_context.animated_track_data_bit_offset; + + const uint8_t* clip_range_data = clip_sampling_context.clip_range_data; + + // Range ignore flags are used to skip range normalization at the clip and/or segment levels + // Each sample has two bits like so: + // - 0x01 = ignore segment level + // - 0x02 = ignore clip level + uint32_t range_ignore_flags; + + rtm::vector4f sample; + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + // Fall-through intentional + uint32_t skip_size = 0; + switch (unpack_index) + { + default: + case 3: + skip_size += format_per_track_data[2]; + ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL; + case 2: + skip_size += format_per_track_data[1]; + ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL; + case 1: + skip_size += format_per_track_data[0]; + ACL_SWITCH_CASE_FALLTHROUGH_INTENTIONAL; + case 0: + // Nothing to skip + (void)skip_size; + } + + // Skip prior samples + animated_track_data_bit_offset += skip_size * 3; + segment_range_data += sizeof(uint8_t) * 6 * unpack_index; + clip_range_data += sizeof(rtm::float3f) * 2 * unpack_index; + + const uint32_t num_bits_at_bit_rate = format_per_track_data[unpack_index]; + + if (num_bits_at_bit_rate == 0) // Constant bit rate + { + sample = unpack_vector3_u48_unsafe(segment_range_data); + range_ignore_flags = 0x01; // Skip segment only + } + else if (num_bits_at_bit_rate == 32) // Raw bit rate + { + sample = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + range_ignore_flags = 0x03; // Skip clip and segment + } + else + { + sample = unpack_vector3_uXX_unsafe(num_bits_at_bit_rate, animated_track_data, animated_track_data_bit_offset); + range_ignore_flags = 0x00; // Don't skip range reduction + } + } + else // vector_format8::vector3f_full + { + animated_track_data_bit_offset += unpack_index * 96; + sample = unpack_vector3_96_unsafe(animated_track_data, animated_track_data_bit_offset); + range_ignore_flags = 0x03; // Skip clip and segment + } + + // Remap within our ranges + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + if (decomp_context.has_segments && (range_ignore_flags & 0x01) == 0) + { + // Apply segment range remapping + const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(segment_range_data); + const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(segment_range_data + 3 * sizeof(uint8_t)); + + sample = rtm::vector_mul_add(sample, segment_range_extent, segment_range_min); + } + + if ((range_ignore_flags & 0x02) == 0) + { + // Apply clip range remapping + const rtm::vector4f clip_range_min = rtm::vector_load(clip_range_data); + const rtm::vector4f clip_range_extent = rtm::vector_load(clip_range_data + sizeof(rtm::float3f)); + + sample = rtm::vector_mul_add(sample, clip_range_extent, clip_range_min); + } + } + + ACL_ASSERT(rtm::vector_is_finite3(sample), "Vector3 is not valid!"); + return sample; + } + + // Force inline this function, we only use it to keep the code readable + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void count_animated_group_bit_size( + const uint8_t* format_per_track_data0, const uint8_t* format_per_track_data1, uint32_t num_groups_to_skip, + uint32_t& out_group_bit_size_per_component0, uint32_t& out_group_bit_size_per_component1) + { + // TODO: Do the same with NEON +#if defined(RTM_AVX_INTRINSICS) + __m128i zero = _mm_setzero_si128(); + __m128i group_bit_size_per_component0_v = zero; + __m128i group_bit_size_per_component1_v = zero; + + // We add 4 at a time in SIMD + for (uint32_t group_index = 0; group_index < num_groups_to_skip; ++group_index) + { + const uint32_t group_offset = group_index * 4; + const __m128i group_bit_size_per_component0_u8 = _mm_loadu_si128(reinterpret_cast(format_per_track_data0 + group_offset)); + const __m128i group_bit_size_per_component1_u8 = _mm_loadu_si128(reinterpret_cast(format_per_track_data1 + group_offset)); + + group_bit_size_per_component0_v = _mm_add_epi32(group_bit_size_per_component0_v, _mm_unpacklo_epi16(_mm_unpacklo_epi8(group_bit_size_per_component0_u8, zero), zero)); + group_bit_size_per_component1_v = _mm_add_epi32(group_bit_size_per_component1_v, _mm_unpacklo_epi16(_mm_unpacklo_epi8(group_bit_size_per_component1_u8, zero), zero)); + } + + // Now we sum horizontally + group_bit_size_per_component0_v = _mm_hadd_epi32(_mm_hadd_epi32(group_bit_size_per_component0_v, group_bit_size_per_component0_v), group_bit_size_per_component0_v); + group_bit_size_per_component1_v = _mm_hadd_epi32(_mm_hadd_epi32(group_bit_size_per_component1_v, group_bit_size_per_component1_v), group_bit_size_per_component1_v); + + out_group_bit_size_per_component0 = _mm_cvtsi128_si32(group_bit_size_per_component0_v); + out_group_bit_size_per_component1 = _mm_cvtsi128_si32(group_bit_size_per_component1_v); +#else + uint32_t group_bit_size_per_component0 = 0; + uint32_t group_bit_size_per_component1 = 0; + + for (uint32_t group_index = 0; group_index < num_groups_to_skip; ++group_index) + { + group_bit_size_per_component0 += format_per_track_data0[(group_index * 4) + 0]; + group_bit_size_per_component1 += format_per_track_data1[(group_index * 4) + 0]; + + group_bit_size_per_component0 += format_per_track_data0[(group_index * 4) + 1]; + group_bit_size_per_component1 += format_per_track_data1[(group_index * 4) + 1]; + + group_bit_size_per_component0 += format_per_track_data0[(group_index * 4) + 2]; + group_bit_size_per_component1 += format_per_track_data1[(group_index * 4) + 2]; + + group_bit_size_per_component0 += format_per_track_data0[(group_index * 4) + 3]; + group_bit_size_per_component1 += format_per_track_data1[(group_index * 4) + 3]; + } + + out_group_bit_size_per_component0 = group_bit_size_per_component0; + out_group_bit_size_per_component1 = group_bit_size_per_component1; +#endif + } + + // Performance notes: + // - Using SOA after unpacking vec3 appears to be slightly slower. Full groups aren't super common + // because animated translation/scale isn't common. But even with clips with lots of full groups, + // SOA remains slightly slower. It seems the longer dependency chains offsets the gain from + // using all SIMD lanes. + // - Removing the unpack prefetches seems to harm performance, especially on mobile + // I also tried reworking them to be more optimal for single segment usage but while I could get + // a small win on desktop, mobile remained under performing. + + struct animated_track_cache_v0 + { + track_cache_quatf_v0<8> rotations; + track_cache_vector4f_v0<8> translations; + track_cache_vector4f_v0<8> scales; + + // Scratch space when we decompress our samples before we interpolate + rtm::vector4f scratch0[4]; + rtm::vector4f scratch1[4]; + + clip_animated_sampling_context_v0 clip_sampling_context_rotations; + clip_animated_sampling_context_v0 clip_sampling_context_translations; + clip_animated_sampling_context_v0 clip_sampling_context_scales; + + segment_animated_sampling_context_v0 segment_sampling_context_rotations[2]; + segment_animated_sampling_context_v0 segment_sampling_context_translations[2]; + segment_animated_sampling_context_v0 segment_sampling_context_scales[2]; + + template + void RTM_DISABLE_SECURITY_COOKIE_CHECK initialize(const persistent_transform_decompression_context_v0& decomp_context) + { + const transform_tracks_header& transform_header = get_transform_tracks_header(*decomp_context.tracks); + + const segment_header* segment0 = decomp_context.segment_offsets[0].add_to(decomp_context.tracks); + const segment_header* segment1 = decomp_context.segment_offsets[1].add_to(decomp_context.tracks); + + const uint8_t* animated_track_data0 = decomp_context.animated_track_data[0]; + const uint8_t* animated_track_data1 = decomp_context.animated_track_data[1]; + + const uint8_t* clip_range_data_rotations = transform_header.get_clip_range_data(); + clip_sampling_context_rotations.clip_range_data = clip_range_data_rotations; + + const uint8_t* format_per_track_data_rotations0 = decomp_context.format_per_track_data[0]; + const uint8_t* segment_range_data_rotations0 = decomp_context.segment_range_data[0]; + const uint32_t animated_track_data_bit_offset_rotations0 = decomp_context.key_frame_bit_offsets[0]; + segment_sampling_context_rotations[0].format_per_track_data = format_per_track_data_rotations0; + segment_sampling_context_rotations[0].segment_range_data = segment_range_data_rotations0; + segment_sampling_context_rotations[0].animated_track_data = animated_track_data0; + segment_sampling_context_rotations[0].animated_track_data_bit_offset = animated_track_data_bit_offset_rotations0; + + const uint8_t* format_per_track_data_rotations1 = decomp_context.format_per_track_data[1]; + const uint8_t* segment_range_data_rotations1 = decomp_context.segment_range_data[1]; + const uint32_t animated_track_data_bit_offset_rotations1 = decomp_context.key_frame_bit_offsets[1]; + segment_sampling_context_rotations[1].format_per_track_data = format_per_track_data_rotations1; + segment_sampling_context_rotations[1].segment_range_data = segment_range_data_rotations1; + segment_sampling_context_rotations[1].animated_track_data = animated_track_data1; + segment_sampling_context_rotations[1].animated_track_data_bit_offset = animated_track_data_bit_offset_rotations1; + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + const bool are_rotations_variable = rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable); + + const uint32_t num_animated_rotation_sub_tracks_padded = align_to(transform_header.num_animated_rotation_sub_tracks, 4); + + // Rotation range data follows translations, no padding + const uint32_t rotation_clip_range_data_size = are_rotations_variable ? (sizeof(rtm::float3f) * 2) : 0; + const uint8_t* clip_range_data_translations = clip_range_data_rotations + (transform_header.num_animated_rotation_sub_tracks * rotation_clip_range_data_size); + clip_sampling_context_translations.clip_range_data = clip_range_data_translations; + + // Rotation metadata is padded to 4 sub-tracks (1 byte each) + const uint32_t rotation_per_track_metadata_size = are_rotations_variable ? 1 : 0; + const uint8_t* format_per_track_data_translations0 = format_per_track_data_rotations0 + (num_animated_rotation_sub_tracks_padded * rotation_per_track_metadata_size); + const uint8_t* format_per_track_data_translations1 = format_per_track_data_rotations1 + (num_animated_rotation_sub_tracks_padded * rotation_per_track_metadata_size); + segment_sampling_context_translations[0].format_per_track_data = format_per_track_data_translations0; + segment_sampling_context_translations[1].format_per_track_data = format_per_track_data_translations1; + + // Rotation range data is padded to 4 sub-tracks (6 bytes each) + const uint32_t rotation_segment_range_data_size = are_rotations_variable ? 6 : 0; + const uint8_t* segment_range_data_translations0 = segment_range_data_rotations0 + (num_animated_rotation_sub_tracks_padded * rotation_segment_range_data_size); + const uint8_t* segment_range_data_translations1 = segment_range_data_rotations1 + (num_animated_rotation_sub_tracks_padded * rotation_segment_range_data_size); + segment_sampling_context_translations[0].segment_range_data = segment_range_data_translations0; + segment_sampling_context_translations[1].segment_range_data = segment_range_data_translations1; + + // Every sub-track uses the same base animated track data pointer + segment_sampling_context_translations[0].animated_track_data = animated_track_data0; + segment_sampling_context_translations[1].animated_track_data = animated_track_data1; + + const uint32_t animated_track_data_bit_offset_translations0 = animated_track_data_bit_offset_rotations0 + segment0->animated_rotation_bit_size; + const uint32_t animated_track_data_bit_offset_translations1 = animated_track_data_bit_offset_rotations1 + segment1->animated_rotation_bit_size; + segment_sampling_context_translations[0].animated_track_data_bit_offset = animated_track_data_bit_offset_translations0; + segment_sampling_context_translations[1].animated_track_data_bit_offset = animated_track_data_bit_offset_translations1; + + if (decomp_context.has_scale) + { + const vector_format8 translation_format = get_vector_format(decompression_settings_translation_adapter_type::get_vector_format(decomp_context)); + const bool are_translations_variable = translation_format == vector_format8::vector3f_variable && decompression_settings_translation_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable); + + // Scale data just follows the translation data without any extra padding + const uint32_t translation_clip_range_data_size = are_translations_variable ? (sizeof(rtm::float3f) * 2) : 0; + clip_sampling_context_scales.clip_range_data = clip_range_data_translations + (transform_header.num_animated_translation_sub_tracks * translation_clip_range_data_size); + + const uint32_t translation_per_track_metadata_size = are_translations_variable ? 1 : 0; + segment_sampling_context_scales[0].format_per_track_data = format_per_track_data_translations0 + (transform_header.num_animated_translation_sub_tracks * translation_per_track_metadata_size); + segment_sampling_context_scales[1].format_per_track_data = format_per_track_data_translations1 + (transform_header.num_animated_translation_sub_tracks * translation_per_track_metadata_size); + + const uint32_t translation_segment_range_data_size = are_translations_variable ? 6 : 0; + segment_sampling_context_scales[0].segment_range_data = segment_range_data_translations0 + (transform_header.num_animated_translation_sub_tracks * translation_segment_range_data_size); + segment_sampling_context_scales[1].segment_range_data = segment_range_data_translations1 + (transform_header.num_animated_translation_sub_tracks * translation_segment_range_data_size); + + segment_sampling_context_scales[0].animated_track_data = animated_track_data0; + segment_sampling_context_scales[1].animated_track_data = animated_track_data1; + + segment_sampling_context_scales[0].animated_track_data_bit_offset = animated_track_data_bit_offset_translations0 + segment0->animated_translation_bit_size; + segment_sampling_context_scales[1].animated_track_data_bit_offset = animated_track_data_bit_offset_translations1 + segment1->animated_translation_bit_size; + } + + rotations.num_left_to_unpack = transform_header.num_animated_rotation_sub_tracks; + translations.num_left_to_unpack = transform_header.num_animated_translation_sub_tracks; + scales.num_left_to_unpack = transform_header.num_animated_scale_sub_tracks; + } + + template + void RTM_DISABLE_SECURITY_COOKIE_CHECK unpack_rotation_group(const persistent_transform_decompression_context_v0& decomp_context) + { + const uint32_t num_left_to_unpack = rotations.num_left_to_unpack; + if (num_left_to_unpack == 0) + return; // Nothing left to do, we are done + + // If we have less than 4 cached samples, unpack 4 more and prefetch the next cache line + const uint32_t num_cached = rotations.get_num_cached(); + if (num_cached >= 4) + return; // Enough cached, nothing to do + + const uint32_t num_to_unpack = std::min(num_left_to_unpack, 4); + rotations.num_left_to_unpack = num_left_to_unpack - num_to_unpack; + + // Write index will be either 0 or 4 here since we always unpack 4 at a time + const uint32_t cache_write_index = rotations.cache_write_index % 8; + rotations.cache_write_index += num_to_unpack; + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + + segment_animated_scratch_v0 segment_scratch; + + // We start by unpacking our segment range data into our scratch memory + // We often only use a single segment to interpolate, we can avoid redundant work + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + if (decomp_context.has_segments) + { + unpack_segment_range_data(segment_sampling_context_rotations[0].segment_range_data, 0, segment_scratch); + + // We are interpolating between two segments (rare) + if (!decomp_context.uses_single_segment) + unpack_segment_range_data(segment_sampling_context_rotations[1].segment_range_data, 1, segment_scratch); + +#if !defined(ACL_IMPL_PREFETCH_EARLY) + // Our segment range data takes 24 bytes per group (4 samples, 6 bytes each), each cache line fits 2.67 groups + // Prefetch every time while alternating between both segments + ACL_IMPL_ANIMATED_PREFETCH(segment_sampling_context_rotations[cache_write_index % 2].segment_range_data + 64); +#endif + } + } + + const range_reduction_masks_t range_reduction_masks0 = unpack_animated_quat(decomp_context, scratch0, num_to_unpack, segment_sampling_context_rotations[0]); + const range_reduction_masks_t range_reduction_masks1 = unpack_animated_quat(decomp_context, scratch1, num_to_unpack, segment_sampling_context_rotations[1]); + + // Swizzle our samples into SOA form + rtm::vector4f scratch0_xxxx; + rtm::vector4f scratch0_yyyy; + rtm::vector4f scratch0_zzzz; + rtm::vector4f scratch0_wwww; + RTM_MATRIXF_TRANSPOSE_4X4(scratch0[0], scratch0[1], scratch0[2], scratch0[3], scratch0_xxxx, scratch0_yyyy, scratch0_zzzz, scratch0_wwww); + + rtm::vector4f scratch1_xxxx; + rtm::vector4f scratch1_yyyy; + rtm::vector4f scratch1_zzzz; + rtm::vector4f scratch1_wwww; + RTM_MATRIXF_TRANSPOSE_4X4(scratch1[0], scratch1[1], scratch1[2], scratch1[3], scratch1_xxxx, scratch1_yyyy, scratch1_zzzz, scratch1_wwww); + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + __m256 scratch_xxxx0_xxxx1 = _mm256_set_m128(scratch1_xxxx, scratch0_xxxx); + __m256 scratch_yyyy0_yyyy1 = _mm256_set_m128(scratch1_yyyy, scratch0_yyyy); + __m256 scratch_zzzz0_zzzz1 = _mm256_set_m128(scratch1_zzzz, scratch0_zzzz); +#endif + + // If we have a variable bit rate, we perform range reduction, skip the data we used + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + if (decomp_context.has_segments) + { +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + remap_segment_range_data_avx8(segment_scratch, range_reduction_masks0, range_reduction_masks1, scratch_xxxx0_xxxx1, scratch_yyyy0_yyyy1, scratch_zzzz0_zzzz1); +#else + remap_segment_range_data4(segment_scratch, 0, range_reduction_masks0, scratch0_xxxx, scratch0_yyyy, scratch0_zzzz); + remap_segment_range_data4(segment_scratch, uint32_t(!decomp_context.uses_single_segment), range_reduction_masks1, scratch1_xxxx, scratch1_yyyy, scratch1_zzzz); +#endif + } + + const uint8_t* clip_range_data = clip_sampling_context_rotations.clip_range_data; + +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + remap_clip_range_data_avx8(clip_range_data, num_to_unpack, range_reduction_masks0, range_reduction_masks1, scratch_xxxx0_xxxx1, scratch_yyyy0_yyyy1, scratch_zzzz0_zzzz1); +#else + remap_clip_range_data4(clip_range_data, num_to_unpack, range_reduction_masks0, range_reduction_masks1, scratch0_xxxx, scratch0_yyyy, scratch0_zzzz, scratch1_xxxx, scratch1_yyyy, scratch1_zzzz); +#endif + + // Skip our data + clip_range_data += num_to_unpack * sizeof(rtm::float3f) * 2; + clip_sampling_context_rotations.clip_range_data = clip_range_data; + +#if defined(ACL_IMPL_PREFETCH_EARLY) + // Clip range data is 24 bytes per sub-track and as such we need to prefetch two cache lines ahead to process 4 sub-tracks + ACL_IMPL_ANIMATED_PREFETCH(clip_range_data + 64); + ACL_IMPL_ANIMATED_PREFETCH(clip_range_data + 128); +#endif + } + + // Reconstruct our quaternion W component in SOA + if (rotation_format != rotation_format8::quatf_full || !decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + { +#if defined(ACL_IMPL_USE_AVX_8_WIDE_DECOMP) + const __m256 scratch_wwww0_wwww1 = quat_from_positive_w_avx8(scratch_xxxx0_xxxx1, scratch_yyyy0_yyyy1, scratch_zzzz0_zzzz1); + + // This is the last AVX step, unpack everything + scratch0_xxxx = _mm256_extractf128_ps(scratch_xxxx0_xxxx1, 0); + scratch1_xxxx = _mm256_extractf128_ps(scratch_xxxx0_xxxx1, 1); + scratch0_yyyy = _mm256_extractf128_ps(scratch_yyyy0_yyyy1, 0); + scratch1_yyyy = _mm256_extractf128_ps(scratch_yyyy0_yyyy1, 1); + scratch0_zzzz = _mm256_extractf128_ps(scratch_zzzz0_zzzz1, 0); + scratch1_zzzz = _mm256_extractf128_ps(scratch_zzzz0_zzzz1, 1); + scratch0_wwww = _mm256_extractf128_ps(scratch_wwww0_wwww1, 0); + scratch1_wwww = _mm256_extractf128_ps(scratch_wwww0_wwww1, 1); +#else + scratch0_wwww = quat_from_positive_w4(scratch0_xxxx, scratch0_yyyy, scratch0_zzzz); + +#if !defined(ACL_IMPL_PREFETCH_EARLY) + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + // Our segment per track metadata takes 4 bytes per group (4 samples, 1 byte each), each cache line fits 16 groups + // Prefetch every other 8th group + // We prefetch here because we have a square-root in quat_from_positive_w4(..) that we'll wait after + // This allows us to insert the prefetch basically for free in its shadow + // Branching is faster than prefetching every time and alternating between the two + if (cache_write_index == 0) + ACL_IMPL_ANIMATED_PREFETCH(segment_sampling_context_rotations[0].format_per_track_data + 64); + else if (cache_write_index == 4) + ACL_IMPL_ANIMATED_PREFETCH(segment_sampling_context_rotations[1].format_per_track_data + 64); + } +#endif + + scratch1_wwww = quat_from_positive_w4(scratch1_xxxx, scratch1_yyyy, scratch1_zzzz); + +#if !defined(ACL_IMPL_PREFETCH_EARLY) + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + // Our clip range data is 24 bytes per sub-track and as such we need to prefetch two cache lines ahead to process 4 sub-tracks + // Each group is 96 bytes (4 samples, 24 bytes each), each cache line fits 0.67 groups + // We prefetch here because we have a square-root in quat_from_positive_w4(..) that we'll wait after + // This allows us to insert the prefetch basically for free in its shadow + ACL_IMPL_ANIMATED_PREFETCH(clip_sampling_context_rotations.clip_range_data + 64); + ACL_IMPL_ANIMATED_PREFETCH(clip_sampling_context_rotations.clip_range_data + 128); + } +#endif +#endif + } + + // Interpolate linearly and store our rotations in SOA + { + rtm::vector4f interp_xxxx; + rtm::vector4f interp_yyyy; + rtm::vector4f interp_zzzz; + rtm::vector4f interp_wwww; + + const bool should_interpolate = should_interpolate_samples(rotation_format, decomp_context.interpolation_alpha); + if (should_interpolate) + { + // Interpolate our quaternions without normalizing just yet + quat_lerp4(scratch0_xxxx, scratch0_yyyy, scratch0_zzzz, scratch0_wwww, + scratch1_xxxx, scratch1_yyyy, scratch1_zzzz, scratch1_wwww, + decomp_context.interpolation_alpha, + interp_xxxx, interp_yyyy, interp_zzzz, interp_wwww); + + // Due to the interpolation, the result might not be anywhere near normalized! + // Make sure to normalize afterwards if we need to + constexpr bool normalize_rotations = decompression_settings_type::normalize_rotations(); + if (normalize_rotations) + quat_normalize4(interp_xxxx, interp_yyyy, interp_zzzz, interp_wwww); + } + else + { + // If we don't interpolate, just pick the sample we need, it is already normalized after reconstructing + // the W component or it was raw to begin with + const rtm::mask4f use_sample0 = rtm::vector_less_equal(rtm::vector_set(decomp_context.interpolation_alpha), rtm::vector_zero()); + + interp_xxxx = rtm::vector_select(use_sample0, scratch0_xxxx, scratch1_xxxx); + interp_yyyy = rtm::vector_select(use_sample0, scratch0_yyyy, scratch1_yyyy); + interp_zzzz = rtm::vector_select(use_sample0, scratch0_zzzz, scratch1_zzzz); + interp_wwww = rtm::vector_select(use_sample0, scratch0_wwww, scratch1_wwww); + } + +#if !defined(ACL_IMPL_PREFETCH_EARLY) + { + // Our animated variable bit packed data uses at most 32 bits per component + // When we use raw data, that means each group uses 64 bytes (4 bytes per component, 4 components, 4 samples in group), we have 1 group per cache line + // When we use variable data, the highest bit rate uses 32 bits per component and thus our upper bound is 48 bytes per group (4 bytes per component, 3 components, 4 samples in group), we have 1.33 group per cache line + // In practice, the highest bit rate is rare and the second higher uses 19 bits per component which brings us to 28.5 bytes per group, leading to 2.24 group per cache line + // We prefetch both key frames every time to help hide TLB miss latency in large clips + // We prefetch here because we have a square-root and division in quat_normalize4(..) that we'll wait after + // This allows us to insert the prefetch basically for free in their shadow + const uint8_t* animated_track_data = segment_sampling_context_rotations[0].animated_track_data + 64; // One cache line ahead + const uint32_t animated_bit_offset0 = segment_sampling_context_rotations[0].animated_track_data_bit_offset; + const uint32_t animated_bit_offset1 = segment_sampling_context_rotations[1].animated_track_data_bit_offset; + ACL_IMPL_ANIMATED_PREFETCH(animated_track_data + (animated_bit_offset0 / 8)); + ACL_IMPL_ANIMATED_PREFETCH(animated_track_data + (animated_bit_offset1 / 8)); + } +#endif + + // Swizzle out our 4 samples + rtm::vector4f sample0; + rtm::vector4f sample1; + rtm::vector4f sample2; + rtm::vector4f sample3; + RTM_MATRIXF_TRANSPOSE_4X4(interp_xxxx, interp_yyyy, interp_zzzz, interp_wwww, sample0, sample1, sample2, sample3); + + rtm::quatf* cache_ptr = &rotations.cached_samples[cache_write_index]; + + cache_ptr[0] = rtm::vector_to_quat(sample0); + cache_ptr[1] = rtm::vector_to_quat(sample1); + cache_ptr[2] = rtm::vector_to_quat(sample2); + cache_ptr[3] = rtm::vector_to_quat(sample3); + } + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void skip_rotation_groups(const persistent_transform_decompression_context_v0& decomp_context, uint32_t num_groups_to_skip) + { + const uint32_t num_left_to_unpack = rotations.num_left_to_unpack; + const uint32_t num_to_skip = num_groups_to_skip * 4; + ACL_ASSERT(num_to_skip < num_left_to_unpack, "Cannot skip rotations that aren't present"); + + rotations.num_left_to_unpack = num_left_to_unpack - num_to_skip; + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)) + { + const uint8_t* format_per_track_data0 = segment_sampling_context_rotations[0].format_per_track_data; + const uint8_t* format_per_track_data1 = segment_sampling_context_rotations[1].format_per_track_data; + + uint32_t group_bit_size_per_component0; + uint32_t group_bit_size_per_component1; + count_animated_group_bit_size(format_per_track_data0, format_per_track_data1, num_groups_to_skip, group_bit_size_per_component0, group_bit_size_per_component1); + + const uint32_t format_per_track_data_skip_size = num_groups_to_skip * 4; + const uint32_t segment_range_data_skip_size = num_groups_to_skip * 6 * 4; + + segment_sampling_context_rotations[0].format_per_track_data = format_per_track_data0 + format_per_track_data_skip_size; + segment_sampling_context_rotations[0].segment_range_data += segment_range_data_skip_size; + segment_sampling_context_rotations[0].animated_track_data_bit_offset += group_bit_size_per_component0 * 3; + + segment_sampling_context_rotations[1].format_per_track_data = format_per_track_data1 + format_per_track_data_skip_size; + segment_sampling_context_rotations[1].segment_range_data += segment_range_data_skip_size; + segment_sampling_context_rotations[1].animated_track_data_bit_offset += group_bit_size_per_component1 * 3; + + clip_sampling_context_rotations.clip_range_data += sizeof(rtm::float3f) * 2 * 4 * num_groups_to_skip; + } + else + { + uint32_t group_bit_size; + if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + group_bit_size = 32 * 4 * 4 * num_groups_to_skip; + else // drop w full + group_bit_size = 32 * 3 * 4 * num_groups_to_skip; + + segment_sampling_context_rotations[0].animated_track_data_bit_offset += group_bit_size; + segment_sampling_context_rotations[1].animated_track_data_bit_offset += group_bit_size; + } + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::quatf RTM_SIMD_CALL unpack_rotation_within_group(const persistent_transform_decompression_context_v0& decomp_context, uint32_t unpack_index) + { + ACL_ASSERT(unpack_index < rotations.num_left_to_unpack && unpack_index < 4, "Cannot unpack sample that isn't present"); + + const uint32_t group_size = std::min(rotations.num_left_to_unpack, 4); + + const rtm::vector4f sample_as_vec0 = unpack_single_animated_quat(decomp_context, unpack_index, group_size, clip_sampling_context_rotations, segment_sampling_context_rotations[0]); + const rtm::vector4f sample_as_vec1 = unpack_single_animated_quat(decomp_context, unpack_index, group_size, clip_sampling_context_rotations, segment_sampling_context_rotations[1]); + + rtm::quatf sample0; + rtm::quatf sample1; + + // Reconstruct our quaternion W component + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + if (rotation_format != rotation_format8::quatf_full || !decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + { + sample0 = rtm::quat_from_positive_w(sample_as_vec0); + sample1 = rtm::quat_from_positive_w(sample_as_vec1); + } + else + { + sample0 = rtm::vector_to_quat(sample_as_vec0); + sample1 = rtm::vector_to_quat(sample_as_vec1); + } + + rtm::quatf result; + + const bool should_interpolate = should_interpolate_samples(rotation_format, decomp_context.interpolation_alpha); + if (should_interpolate) + { + // Due to the interpolation, the result might not be anywhere near normalized! + // Make sure to normalize afterwards before using + constexpr bool normalize_rotations = decompression_settings_type::normalize_rotations(); + if (normalize_rotations) + result = rtm::quat_lerp(sample0, sample1, decomp_context.interpolation_alpha); + else + result = quat_lerp_no_normalization(sample0, sample1, decomp_context.interpolation_alpha); + } + else + { + // If we don't interpolate, just pick the sample we need, it is already normalized after reconstructing + // the W component or it was raw to begin with + result = decomp_context.interpolation_alpha <= 0.0F ? sample0 : sample1; + } + + return result; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK const rtm::quatf& consume_rotation() + { + ACL_ASSERT(rotations.cache_read_index < rotations.cache_write_index, "Attempting to consume an animated sample that isn't cached"); + const uint32_t cache_read_index = rotations.cache_read_index++; + return rotations.cached_samples[cache_read_index % 8]; + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void unpack_translation_group(const persistent_transform_decompression_context_v0& decomp_context) + { + const uint32_t num_left_to_unpack = translations.num_left_to_unpack; + if (num_left_to_unpack == 0) + return; // Nothing left to do, we are done + + // If we have less than 4 cached samples, unpack 4 more and prefetch the next cache line + const uint32_t num_cached = translations.get_num_cached(); + if (num_cached >= 4) + return; // Enough cached, nothing to do + + const uint32_t num_to_unpack = std::min(num_left_to_unpack, 4); + translations.num_left_to_unpack = num_left_to_unpack - num_to_unpack; + + // Write index will be either 0 or 4 here since we always unpack 4 at a time + const uint32_t cache_write_index = translations.cache_write_index % 8; + translations.cache_write_index += num_to_unpack; + + unpack_animated_vector3(decomp_context, scratch0, num_to_unpack, clip_sampling_context_translations, segment_sampling_context_translations[0]); + unpack_animated_vector3(decomp_context, scratch1, num_to_unpack, clip_sampling_context_translations, segment_sampling_context_translations[1]); + + const rtm::vector4f interpolation_alpha = rtm::vector_set(decomp_context.interpolation_alpha); + rtm::vector4f* cache_ptr = &translations.cached_samples[cache_write_index]; + for (uint32_t unpack_index = 0; unpack_index < num_to_unpack; ++unpack_index) + { + const rtm::vector4f sample0 = scratch0[unpack_index]; + const rtm::vector4f sample1 = scratch1[unpack_index]; + + const rtm::vector4f sample = rtm::vector_lerp(sample0, sample1, interpolation_alpha); + + cache_ptr[unpack_index] = sample; + } + + // If we have clip range data, skip it + const vector_format8 format = get_vector_format(decompression_settings_adapter_type::get_vector_format(decomp_context)); + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + clip_sampling_context_translations.clip_range_data += num_to_unpack * sizeof(rtm::float3f) * 2; + + // Clip range data is 24 bytes per sub-track and as such we need to prefetch two cache lines ahead to process 4 sub-tracks + ACL_IMPL_ANIMATED_PREFETCH(clip_sampling_context_translations.clip_range_data + 64); + ACL_IMPL_ANIMATED_PREFETCH(clip_sampling_context_translations.clip_range_data + 128); + } + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void skip_translation_groups(const persistent_transform_decompression_context_v0& decomp_context, uint32_t num_groups_to_skip) + { + const uint32_t num_left_to_unpack = translations.num_left_to_unpack; + const uint32_t num_to_skip = num_groups_to_skip * 4; + ACL_ASSERT(num_to_skip < num_left_to_unpack, "Cannot skip translations that aren't present"); + + translations.num_left_to_unpack = num_left_to_unpack - num_to_skip; + + const vector_format8 format = get_vector_format(decompression_settings_adapter_type::get_vector_format(decomp_context)); + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + const uint8_t* format_per_track_data0 = segment_sampling_context_translations[0].format_per_track_data; + const uint8_t* format_per_track_data1 = segment_sampling_context_translations[1].format_per_track_data; + + uint32_t group_bit_size_per_component0; + uint32_t group_bit_size_per_component1; + count_animated_group_bit_size(format_per_track_data0, format_per_track_data1, num_groups_to_skip, group_bit_size_per_component0, group_bit_size_per_component1); + + const uint32_t format_per_track_data_skip_size = num_groups_to_skip * 4; + const uint32_t segment_range_data_skip_size = num_groups_to_skip * 6 * 4; + + segment_sampling_context_translations[0].format_per_track_data = format_per_track_data0 + format_per_track_data_skip_size; + segment_sampling_context_translations[0].segment_range_data += segment_range_data_skip_size; + segment_sampling_context_translations[0].animated_track_data_bit_offset += group_bit_size_per_component0 * 3; + + segment_sampling_context_translations[1].format_per_track_data = format_per_track_data1 + format_per_track_data_skip_size; + segment_sampling_context_translations[1].segment_range_data += segment_range_data_skip_size; + segment_sampling_context_translations[1].animated_track_data_bit_offset += group_bit_size_per_component1 * 3; + + clip_sampling_context_translations.clip_range_data += sizeof(rtm::float3f) * 2 * 4 * num_groups_to_skip; + } + else + { + const uint32_t group_bit_size = 32 * 3 * 4 * num_groups_to_skip; + segment_sampling_context_translations[0].animated_track_data_bit_offset += group_bit_size; + segment_sampling_context_translations[1].animated_track_data_bit_offset += group_bit_size; + } + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL unpack_translation_within_group(const persistent_transform_decompression_context_v0& decomp_context, uint32_t unpack_index) + { + ACL_ASSERT(unpack_index < translations.num_left_to_unpack && unpack_index < 4, "Cannot unpack sample that isn't present"); + + const rtm::vector4f sample0 = unpack_single_animated_vector3(decomp_context, unpack_index, clip_sampling_context_translations, segment_sampling_context_translations[0]); + const rtm::vector4f sample1 = unpack_single_animated_vector3(decomp_context, unpack_index, clip_sampling_context_translations, segment_sampling_context_translations[1]); + + return rtm::vector_lerp(sample0, sample1, decomp_context.interpolation_alpha); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK const rtm::vector4f& consume_translation() + { + ACL_ASSERT(translations.cache_read_index < translations.cache_write_index, "Attempting to consume an animated sample that isn't cached"); + const uint32_t cache_read_index = translations.cache_read_index++; + return translations.cached_samples[cache_read_index % 8]; + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void unpack_scale_group(const persistent_transform_decompression_context_v0& decomp_context) + { + const uint32_t num_left_to_unpack = scales.num_left_to_unpack; + if (num_left_to_unpack == 0) + return; // Nothing left to do, we are done + + // If we have less than 4 cached samples, unpack 4 more and prefetch the next cache line + const uint32_t num_cached = scales.get_num_cached(); + if (num_cached >= 4) + return; // Enough cached, nothing to do + + const uint32_t num_to_unpack = std::min(num_left_to_unpack, 4); + scales.num_left_to_unpack = num_left_to_unpack - num_to_unpack; + + // Write index will be either 0 or 4 here since we always unpack 4 at a time + const uint32_t cache_write_index = scales.cache_write_index % 8; + scales.cache_write_index += num_to_unpack; + + unpack_animated_vector3(decomp_context, scratch0, num_to_unpack, clip_sampling_context_scales, segment_sampling_context_scales[0]); + unpack_animated_vector3(decomp_context, scratch1, num_to_unpack, clip_sampling_context_scales, segment_sampling_context_scales[1]); + + const rtm::vector4f interpolation_alpha = rtm::vector_set(decomp_context.interpolation_alpha); + rtm::vector4f* cache_ptr = &scales.cached_samples[cache_write_index]; + for (uint32_t unpack_index = 0; unpack_index < num_to_unpack; ++unpack_index) + { + const rtm::vector4f sample0 = scratch0[unpack_index]; + const rtm::vector4f sample1 = scratch1[unpack_index]; + + const rtm::vector4f sample = rtm::vector_lerp(sample0, sample1, interpolation_alpha); + + cache_ptr[unpack_index] = sample; + } + + // If we have clip range data, skip it + const vector_format8 format = get_vector_format(decompression_settings_adapter_type::get_vector_format(decomp_context)); + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + clip_sampling_context_scales.clip_range_data += num_to_unpack * sizeof(rtm::float3f) * 2; + + // Clip range data is 24 bytes per sub-track and as such we need to prefetch two cache lines ahead to process 4 sub-tracks + ACL_IMPL_ANIMATED_PREFETCH(clip_sampling_context_scales.clip_range_data + 64); + ACL_IMPL_ANIMATED_PREFETCH(clip_sampling_context_scales.clip_range_data + 128); + } + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void skip_scale_groups(const persistent_transform_decompression_context_v0& decomp_context, uint32_t num_groups_to_skip) + { + const uint32_t num_left_to_unpack = scales.num_left_to_unpack; + const uint32_t num_to_skip = num_groups_to_skip * 4; + ACL_ASSERT(num_to_skip < num_left_to_unpack, "Cannot skip scales that aren't present"); + + scales.num_left_to_unpack = num_left_to_unpack - num_to_skip; + + const vector_format8 format = get_vector_format(decompression_settings_adapter_type::get_vector_format(decomp_context)); + if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)) + { + const uint8_t* format_per_track_data0 = segment_sampling_context_scales[0].format_per_track_data; + const uint8_t* format_per_track_data1 = segment_sampling_context_scales[1].format_per_track_data; + + uint32_t group_bit_size_per_component0; + uint32_t group_bit_size_per_component1; + count_animated_group_bit_size(format_per_track_data0, format_per_track_data1, num_groups_to_skip, group_bit_size_per_component0, group_bit_size_per_component1); + + const uint32_t format_per_track_data_skip_size = num_groups_to_skip * 4; + const uint32_t segment_range_data_skip_size = num_groups_to_skip * 6 * 4; + + segment_sampling_context_scales[0].format_per_track_data = format_per_track_data0 + format_per_track_data_skip_size; + segment_sampling_context_scales[0].segment_range_data += segment_range_data_skip_size; + segment_sampling_context_scales[0].animated_track_data_bit_offset += group_bit_size_per_component0 * 3; + + segment_sampling_context_scales[1].format_per_track_data = format_per_track_data1 + format_per_track_data_skip_size; + segment_sampling_context_scales[1].segment_range_data += segment_range_data_skip_size; + segment_sampling_context_scales[1].animated_track_data_bit_offset += group_bit_size_per_component1 * 3; + + clip_sampling_context_scales.clip_range_data += sizeof(rtm::float3f) * 2 * 4 * num_groups_to_skip; + } + else + { + const uint32_t group_bit_size = 32 * 3 * 4 * num_groups_to_skip; + segment_sampling_context_scales[0].animated_track_data_bit_offset += group_bit_size; + segment_sampling_context_scales[1].animated_track_data_bit_offset += group_bit_size; + } + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL unpack_scale_within_group(const persistent_transform_decompression_context_v0& decomp_context, uint32_t unpack_index) + { + ACL_ASSERT(unpack_index < scales.num_left_to_unpack && unpack_index < 4, "Cannot unpack sample that isn't present"); + + const rtm::vector4f sample0 = unpack_single_animated_vector3(decomp_context, unpack_index, clip_sampling_context_scales, segment_sampling_context_scales[0]); + const rtm::vector4f sample1 = unpack_single_animated_vector3(decomp_context, unpack_index, clip_sampling_context_scales, segment_sampling_context_scales[1]); + + return rtm::vector_lerp(sample0, sample1, decomp_context.interpolation_alpha); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK const rtm::vector4f& consume_scale() + { + ACL_ASSERT(scales.cache_read_index < scales.cache_write_index, "Attempting to consume an animated sample that isn't cached"); + const uint32_t cache_read_index = scales.cache_read_index++; + return scales.cached_samples[cache_read_index % 8]; + } + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_constant_track_cache.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_constant_track_cache.h new file mode 100644 index 00000000..fd6821bb --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_constant_track_cache.h @@ -0,0 +1,341 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/track_formats.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/impl/track_cache.h" +#include "acl/decompression/impl/transform_decompression_context.h" +#include "acl/math/quat_packing.h" + +#include +#include + +#include + +#define ACL_IMPL_USE_CONSTANT_PREFETCH + +ACL_IMPL_FILE_PRAGMA_PUSH + +// We only initialize some variables when we need them which prompts the compiler to complain +// The usage is perfectly safe and because this code is VERY hot and needs to be as fast as possible, +// we disable the warning to avoid zeroing out things we don't need +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) + // warning C26451: Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow (io.2). + // We can't overflow because compressed clips cannot contain more than 4 GB worth of data + #pragma warning(disable : 26451) + // warning C6385: Reading invalid data from '...': the readable size is '512' bytes, but '528' bytes may be read. + // We properly handle overflow, this is a false positive + #pragma warning(disable : 6385) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + +#if defined(ACL_IMPL_USE_CONSTANT_PREFETCH) +#define ACL_IMPL_CONSTANT_PREFETCH(ptr) memory_prefetch(ptr) +#else +#define ACL_IMPL_CONSTANT_PREFETCH(ptr) (void)(ptr) +#endif + + namespace acl_impl + { + struct constant_track_cache_v0 + { + // Our constant rotation samples are packed in groups of 4 samples (16 floats, 64 bytes) in AOS form when we have full precision + // and with SOA form otherwise (12 floats, 48 bytes). To avoid stalling, we unpack 16 samples at a time and cache the result in AOS form. + // This means we need 3-4 cache lines to unpack. If we cache miss, we'll be able to queue up as many uops as we can before we stall. + // We can also queue up enough independent uops to avoid stalling on the square-root (6-21 cycles depending on the platform). + // Constant rotation samples are fairly common, see here: https://nfrechette.github.io/2020/08/09/animation_data_numbers/ + // CMU has 64.41%, Paragon has 47.69%, and Fortnite has 62.84%. + // Following these numbers, it is common for clips to have at least 10 constant rotation samples to unpack. + + track_cache_quatf_v0<32> rotations; + + // Points to our packed sub-track data + const uint8_t* constant_data_rotations; + const uint8_t* constant_data_translations; + const uint8_t* constant_data_scales; + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void initialize(const persistent_transform_decompression_context_v0& decomp_context) + { + const transform_tracks_header& transform_header = get_transform_tracks_header(*decomp_context.tracks); + + rotations.num_left_to_unpack = transform_header.num_constant_rotation_samples; + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + const rotation_format8 packed_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format; + const uint32_t packed_rotation_size = get_packed_rotation_size(packed_format); + const uint32_t packed_translation_size = get_packed_vector_size(vector_format8::vector3f_full); + + constant_data_rotations = transform_header.get_constant_track_data(); + constant_data_translations = constant_data_rotations + packed_rotation_size * transform_header.num_constant_rotation_samples; + constant_data_scales = constant_data_translations + packed_translation_size * transform_header.num_constant_translation_samples; + } + + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void unpack_rotation_group(const persistent_transform_decompression_context_v0& decomp_context) + { + const uint32_t num_left_to_unpack = rotations.num_left_to_unpack; + if (num_left_to_unpack == 0) + return; // Nothing left to do, we are done + + // If we have less than half our cache filled with samples, unpack some more + const uint32_t num_cached = rotations.get_num_cached(); + if (num_cached >= 16) + return; // Enough cached, nothing to do + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + + uint32_t num_to_unpack = std::min(num_left_to_unpack, 16); + rotations.num_left_to_unpack = num_left_to_unpack - num_to_unpack; + + // Write index will be either 0 or 16 here since we always unpack 16 at a time + const uint32_t cache_write_index = rotations.cache_write_index % 32; + rotations.cache_write_index += num_to_unpack; + + const uint8_t* constant_track_data = constant_data_rotations; + rtm::quatf* cache_ptr = &rotations.cached_samples[cache_write_index]; + + if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + { + for (uint32_t unpack_index = num_to_unpack; unpack_index != 0; --unpack_index) + { + // Unpack + const rtm::quatf sample = unpack_quat_128(constant_track_data); + + // Cache + *cache_ptr = sample; + + // Update our pointers + constant_track_data += sizeof(rtm::float4f); + cache_ptr++; + } + } + else + { + while (num_to_unpack != 0) + { + const uint32_t unpack_count = std::min(num_to_unpack, 4); + num_to_unpack -= unpack_count; + + // Unpack + // Always load 4x rotations, we might contain garbage in a few lanes but it's fine + // The last group contains no padding so we have to make to align our reads properly + const uint32_t load_size = unpack_count * sizeof(float); + + const rtm::vector4f xxxx = rtm::vector_load(reinterpret_cast(constant_track_data + load_size * 0)); + const rtm::vector4f yyyy = rtm::vector_load(reinterpret_cast(constant_track_data + load_size * 1)); + const rtm::vector4f zzzz = rtm::vector_load(reinterpret_cast(constant_track_data + load_size * 2)); + + // Update our read ptr + constant_track_data += load_size * 3; + + // quat_from_positive_w_soa + const rtm::vector4f wwww_squared = rtm::vector_sub(rtm::vector_sub(rtm::vector_sub(rtm::vector_set(1.0F), rtm::vector_mul(xxxx, xxxx)), rtm::vector_mul(yyyy, yyyy)), rtm::vector_mul(zzzz, zzzz)); + + // w_squared can be negative either due to rounding or due to quantization imprecision, we take the absolute value + // to ensure the resulting quaternion is always normalized with a positive W component + const rtm::vector4f wwww = rtm::vector_sqrt(rtm::vector_abs(wwww_squared)); + + rtm::vector4f sample0; + rtm::vector4f sample1; + rtm::vector4f sample2; + rtm::vector4f sample3; + RTM_MATRIXF_TRANSPOSE_4X4(xxxx, yyyy, zzzz, wwww, sample0, sample1, sample2, sample3); + + // Cache + cache_ptr[0] = rtm::vector_to_quat(sample0); + cache_ptr[1] = rtm::vector_to_quat(sample1); + cache_ptr[2] = rtm::vector_to_quat(sample2); + cache_ptr[3] = rtm::vector_to_quat(sample3); + cache_ptr += 4; + } + } + +#if defined(ACL_HAS_ASSERT_CHECKS) + num_to_unpack = std::min(num_left_to_unpack, 16); + for (uint32_t unpack_index = 0; unpack_index < num_to_unpack; ++unpack_index) + { + const rtm::quatf rotation = rotations.cached_samples[cache_write_index + unpack_index]; + + ACL_ASSERT(rtm::quat_is_finite(rotation), "Rotation is not valid!"); + ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation is not normalized!"); + } +#endif + + // Update our pointer + constant_data_rotations = constant_track_data; + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK void skip_rotation_groups(const persistent_transform_decompression_context_v0& decomp_context, uint32_t num_groups_to_skip) + { + // We only support skipping full groups + const uint32_t num_left_to_unpack = rotations.num_left_to_unpack; + const uint32_t num_to_skip = num_groups_to_skip * 4; + ACL_ASSERT(num_to_skip < num_left_to_unpack, "Cannot skip rotations that aren't present"); + + rotations.num_left_to_unpack = num_left_to_unpack - num_to_skip; + + const uint8_t* constant_track_data = constant_data_rotations; + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + constant_track_data += num_to_skip * sizeof(rtm::float4f); + else + constant_track_data += num_to_skip * sizeof(rtm::float3f); + + constant_data_rotations = constant_track_data; + + // Prefetch our group + ACL_IMPL_CONSTANT_PREFETCH(constant_track_data); + } + + template + RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::quatf RTM_SIMD_CALL unpack_rotation_within_group(const persistent_transform_decompression_context_v0& decomp_context, uint32_t unpack_index) const + { + ACL_ASSERT(unpack_index < rotations.num_left_to_unpack && unpack_index < 4, "Cannot unpack sample that isn't present"); + + rtm::quatf sample; + + const rotation_format8 rotation_format = get_rotation_format(decomp_context.rotation_format); + if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + { + const uint8_t* constant_track_data = constant_data_rotations + (unpack_index * sizeof(rtm::float4f)); + sample = unpack_quat_128(constant_track_data); + } + else + { + // Data is in SOA form + const uint32_t group_size = std::min(rotations.num_left_to_unpack, 4); + const float* constant_track_data = reinterpret_cast(constant_data_rotations) + unpack_index; + const float x = constant_track_data[group_size * 0]; + const float y = constant_track_data[group_size * 1]; + const float z = constant_track_data[group_size * 2]; + const rtm::vector4f sample_v = rtm::vector_set(x, y, z, 0.0F); + sample = rtm::quat_from_positive_w(sample_v); + } + + ACL_ASSERT(rtm::quat_is_finite(sample), "Sample is not valid!"); + ACL_ASSERT(rtm::quat_is_normalized(sample), "Sample is not normalized!"); + return sample; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK const rtm::quatf& RTM_SIMD_CALL consume_rotation() + { + ACL_ASSERT(rotations.cache_read_index < rotations.cache_write_index, "Attempting to consume a constant sample that isn't cached"); + const uint32_t cache_read_index = rotations.cache_read_index++; + return rotations.cached_samples[cache_read_index % 32]; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK void skip_translation_groups(uint32_t num_groups_to_skip) + { + const uint8_t* constant_track_data = constant_data_translations; + + // We only support skipping full groups + const uint32_t num_to_skip = num_groups_to_skip * 4; + constant_track_data += num_to_skip * sizeof(rtm::float3f); + + constant_data_translations = constant_track_data; + + // Prefetch our group + ACL_IMPL_CONSTANT_PREFETCH(constant_track_data); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL unpack_translation_within_group(uint32_t unpack_index) const + { + ACL_ASSERT(unpack_index < 4, "Cannot unpack sample that isn't present"); + + const uint8_t* constant_track_data = constant_data_translations + (unpack_index * sizeof(rtm::float3f)); + const rtm::vector4f sample = rtm::vector_load(constant_track_data); + ACL_ASSERT(rtm::vector_is_finite3(sample), "Sample is not valid!"); + return sample; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK const uint8_t* consume_translation() + { + const uint8_t* sample_ptr = constant_data_translations; + constant_data_translations += sizeof(rtm::float3f); + return sample_ptr; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK void skip_scale_groups(uint32_t num_groups_to_skip) + { + const uint8_t* constant_track_data = constant_data_scales; + + // We only support skipping full groups + const uint32_t num_to_skip = num_groups_to_skip * 4; + constant_track_data += num_to_skip * sizeof(rtm::float3f); + + constant_data_scales = constant_track_data; + + // Prefetch our group + ACL_IMPL_CONSTANT_PREFETCH(constant_track_data); + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK rtm::vector4f RTM_SIMD_CALL unpack_scale_within_group(uint32_t unpack_index) const + { + ACL_ASSERT(unpack_index < 4, "Cannot unpack sample that isn't present"); + + const uint8_t* constant_track_data = constant_data_scales + (unpack_index * sizeof(rtm::float3f)); + const rtm::vector4f sample = rtm::vector_load(constant_track_data); + ACL_ASSERT(rtm::vector_is_finite3(sample), "Sample is not valid!"); + return sample; + } + + RTM_DISABLE_SECURITY_COOKIE_CHECK const uint8_t* consume_scale() + { + const uint8_t* sample_ptr = constant_data_scales; + constant_data_scales += sizeof(rtm::float3f); + return sample_ptr; + } + }; + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +#if defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_decompression_context.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_decompression_context.h new file mode 100644 index 00000000..c5428efe --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_decompression_context.h @@ -0,0 +1,193 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/bitset.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/range_reduction_types.h" +#include "acl/core/track_formats.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/impl/database_context.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + struct alignas(64) persistent_transform_decompression_context_v0 + { + // Clip related data // offsets + // Only member used to detect if we are initialized, must be first + const compressed_tracks* tracks; // 0 | 0 + + // Database context, optional + const database_context_v0* db; // 4 | 8 + + uint32_t clip_hash; // 8 | 16 + + float clip_duration; // 12 | 20 + + rotation_format8 rotation_format; // 16 | 24 + vector_format8 translation_format; // 17 | 25 + vector_format8 scale_format; // 18 | 26 + + uint8_t has_scale; // 19 | 27 + uint8_t has_segments; // 20 | 28 + + uint8_t padding0[22]; // 21 | 29 + + // Seeking related data + uint8_t uses_single_segment; // 43 | 51 + + float sample_time; // 44 | 52 + + // Offsets relative to the 'tracks' pointer + ptr_offset32 segment_offsets[2]; // 48 | 56 + + const uint8_t* format_per_track_data[2]; // 56 | 64 + const uint8_t* segment_range_data[2]; // 64 | 80 + const uint8_t* animated_track_data[2]; // 72 | 96 + + // Offsets relative to the 'animated_track_data' pointers + uint32_t key_frame_bit_offsets[2]; // 80 | 112 + + float interpolation_alpha; // 88 | 120 + + uint8_t padding1[sizeof(void*) == 4 ? 36 : 4]; // 92 | 124 + + // Total size: 128 | 128 + + ////////////////////////////////////////////////////////////////////////// + + const compressed_tracks* get_compressed_tracks() const { return tracks; } + compressed_tracks_version16 get_version() const { return tracks->get_version(); } + bool is_initialized() const { return tracks != nullptr; } + void reset() { tracks = nullptr; } + }; + + static_assert(sizeof(persistent_transform_decompression_context_v0) == 128, "Unexpected size"); + + // We use adapters to wrap the decompression_settings + // This allows us to re-use the code for skipping and decompressing Vector3 samples + // Code generation will generate specialized code for each specialization + template + struct translation_decompression_settings_adapter + { + // Forward to our decompression settings + static constexpr range_reduction_flags8 get_range_reduction_flag() { return range_reduction_flags8::translations; } + static constexpr vector_format8 get_vector_format(const persistent_transform_decompression_context_v0& context) { return context.translation_format; } + static constexpr bool is_vector_format_supported(vector_format8 format) { return decompression_settings_type::is_translation_format_supported(format); } + }; + + template + struct scale_decompression_settings_adapter + { + // Forward to our decompression settings + static constexpr range_reduction_flags8 get_range_reduction_flag() { return range_reduction_flags8::scales; } + static constexpr vector_format8 get_vector_format(const persistent_transform_decompression_context_v0& context) { return context.scale_format; } + static constexpr bool is_vector_format_supported(vector_format8 format) { return decompression_settings_type::is_scale_format_supported(format); } + }; + + // Returns the statically known number of rotation formats supported by the decompression settings + template + constexpr int32_t num_supported_rotation_formats() + { + return int32_t(decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full)) + + int32_t(decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_full)) + + int32_t(decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable)); + } + + // Returns the statically known rotation format supported if we only support one, otherwise we return the input value + // which might not be known statically + template + constexpr rotation_format8 get_rotation_format(rotation_format8 format) + { + return num_supported_rotation_formats() > 1 + // More than one format is supported, return the input value, whatever it may be + ? format + // Only one format is supported, figure out statically which one it is and return it + : (decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full) ? rotation_format8::quatf_full + : (decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_full) ? rotation_format8::quatf_drop_w_full + : rotation_format8::quatf_drop_w_variable)); + } + + // Returns the statically known number of vector formats supported by the decompression settings + template + constexpr int32_t num_supported_vector_formats() + { + return int32_t(decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_full)) + + int32_t(decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable)); + } + + // Returns the statically known vector format supported if we only support one, otherwise we return the input value + // which might not be known statically + template + constexpr vector_format8 get_vector_format(vector_format8 format) + { + return num_supported_vector_formats() > 1 + // More than one format is supported, return the input value, whatever it may be + ? format + // Only one format is supported, figure out statically which one it is and return it + : (decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_full) ? vector_format8::vector3f_full + : vector_format8::vector3f_variable); + } + + // Returns whether or not we should interpolate our samples + // If we support more than one format, we'll assume that we always need to interpolate + // and if we support only the raw format, we'll check if we need to based on the interpolation alpha + template + constexpr bool should_interpolate_samples(rotation_format8 format, float interpolation_alpha) + { + return num_supported_rotation_formats() > 1 + // More than one format is supported, always interpolate + ? true + // If our format is raw, only interpolate if our alpha isn't <= 0.0 or >= 1.0 + // otherwise we always interpolate + : format == rotation_format8::quatf_full ? (interpolation_alpha > 0.0F && interpolation_alpha < 1.0F) : true; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_track_decompression.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_track_decompression.h new file mode 100644 index 00000000..dd7edf60 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/transform_track_decompression.h @@ -0,0 +1,1655 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/bit_manip_utils.h" +#include "acl/core/bitset.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/range_reduction_types.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_writer.h" +#include "acl/core/variable_bit_rates.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database.h" +#include "acl/decompression/impl/transform_animated_track_cache.h" +#include "acl/decompression/impl/transform_constant_track_cache.h" +#include "acl/decompression/impl/transform_decompression_context.h" +#include "acl/math/quatf.h" +#include "acl/math/quat_packing.h" +#include "acl/math/vector4f.h" + +#include +#include +#include + +#include +#include + +#define ACL_IMPL_USE_SEEK_PREFETCH + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C4127: conditional expression is constant + // This is fine, the optimizer will strip the code away when it can, but it isn't always constant in practice + #pragma warning(disable : 4127) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { +#if defined(ACL_IMPL_USE_SEEK_PREFETCH) +#define ACL_IMPL_SEEK_PREFETCH(ptr) memory_prefetch(ptr) +#else +#define ACL_IMPL_SEEK_PREFETCH(ptr) (void)(ptr) +#endif + + template + constexpr bool is_database_supported_impl() + { + return decompression_settings_type::database_settings_type::version_supported() != compressed_tracks_version16::none; + } + + template + inline bool initialize_v0(persistent_transform_decompression_context_v0& context, const compressed_tracks& tracks, const database_context* database) + { + ACL_ASSERT(tracks.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(tracks.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled)); + + using translation_adapter = acl_impl::translation_decompression_settings_adapter; + using scale_adapter = acl_impl::scale_decompression_settings_adapter; + + const tracks_header& header = get_tracks_header(tracks); + const transform_tracks_header& transform_header = get_transform_tracks_header(tracks); + + const rotation_format8 packed_rotation_format = header.get_rotation_format(); + const vector_format8 packed_translation_format = header.get_translation_format(); + const vector_format8 packed_scale_format = header.get_scale_format(); + const rotation_format8 rotation_format = get_rotation_format(packed_rotation_format); + const vector_format8 translation_format = get_vector_format(packed_translation_format); + const vector_format8 scale_format = get_vector_format(packed_scale_format); + + ACL_ASSERT(rotation_format == packed_rotation_format, "Statically compiled rotation format (%s) differs from the compressed rotation format (%s)!", get_rotation_format_name(rotation_format), get_rotation_format_name(packed_rotation_format)); + ACL_ASSERT(translation_format == packed_translation_format, "Statically compiled translation format (%s) differs from the compressed translation format (%s)!", get_vector_format_name(translation_format), get_vector_format_name(packed_translation_format)); + ACL_ASSERT(scale_format == packed_scale_format, "Statically compiled scale format (%s) differs from the compressed scale format (%s)!", get_vector_format_name(scale_format), get_vector_format_name(packed_scale_format)); + + context.tracks = &tracks; + context.db = reinterpret_cast(database); // Context is always the first member and versions should always match + context.clip_hash = tracks.get_hash(); + context.clip_duration = calculate_finite_duration(header.num_samples, header.sample_rate); + context.sample_time = -1.0F; + + context.rotation_format = rotation_format; + context.translation_format = translation_format; + context.scale_format = scale_format; + context.has_scale = header.get_has_scale(); + context.has_segments = transform_header.has_multiple_segments(); + + return true; + } + + inline bool is_dirty_v0(const persistent_transform_decompression_context_v0& context, const compressed_tracks& tracks) + { + if (context.tracks != &tracks) + return true; + + if (context.clip_hash != tracks.get_hash()) + return true; + + return false; + } + + template + inline void seek_v0(persistent_transform_decompression_context_v0& context, float sample_time, sample_rounding_policy rounding_policy) + { + const tracks_header& header = get_tracks_header(*context.tracks); + if (header.num_tracks == 0) + return; // Empty track list + + // Clamp for safety, the caller should normally handle this but in practice, it often isn't the case + if (decompression_settings_type::clamp_sample_time()) + sample_time = rtm::scalar_clamp(sample_time, 0.0F, context.clip_duration); + + if (context.sample_time == sample_time) + return; + + const transform_tracks_header& transform_header = get_transform_tracks_header(*context.tracks); + + // Prefetch our sub-track types, we'll need them soon when we start decompressing + // Most clips will have their sub-track types fit into 1 or 2 cache lines, we'll prefetch 2 + // to be safe + { + const uint8_t* sub_track_types = reinterpret_cast(transform_header.get_sub_track_types()); + + ACL_IMPL_SEEK_PREFETCH(sub_track_types); + ACL_IMPL_SEEK_PREFETCH(sub_track_types + 64); + } + + context.sample_time = sample_time; + + uint32_t key_frame0; + uint32_t key_frame1; + find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, context.interpolation_alpha); + + uint32_t segment_key_frame0; + uint32_t segment_key_frame1; + + const segment_header* segment_header0; + const segment_header* segment_header1; + + const uint8_t* db_animated_track_data0 = nullptr; + const uint8_t* db_animated_track_data1 = nullptr; + + // These two pointers are the same, the compiler should optimize one out, only here for type safety later + const segment_header* segment_headers = transform_header.get_segment_headers(); + const segment_tier0_header* segment_tier0_headers = transform_header.get_segment_tier0_headers(); + + const uint32_t num_segments = transform_header.num_segments; + + constexpr bool is_database_supported = is_database_supported_impl(); + ACL_ASSERT(is_database_supported || !context.tracks->has_database(), "Cannot have a database when it isn't supported"); + + const bool has_database = is_database_supported && context.tracks->has_database(); + const database_context_v0* db = context.db; + + if (num_segments == 1) + { + // Key frame 0 and 1 are in the only segment present + // This is a really common case and when it happens, we don't store the segment start index (zero) + + if (is_database_supported && has_database) + { + const segment_tier0_header* segment_tier0_header0 = segment_tier0_headers; + + // This will cache miss + uint32_t sample_indices0 = segment_tier0_header0->sample_indices; + + // Calculate our clip relative sample index, we'll remap it later relative to the samples we'll use + const float sample_index = context.interpolation_alpha + float(key_frame0); + + // When we load our sample indices and offsets from the database, there can be another thread writing + // to those memory locations at the same time (e.g. streaming in/out). + // To ensure thread safety, we atomically load the offset and sample indices. + uint64_t medium_importance_tier_metadata0 = 0; + uint64_t low_importance_tier_metadata0 = 0; + + // Combine all our loaded samples into a single bit set to find which samples we need to interpolate + if (db != nullptr) + { + // Possible cache miss for the clip header offset + // Cache miss for the db clip segment headers pointer + const tracks_database_header* tracks_db_header = transform_header.get_database_header(); + const database_runtime_clip_header* db_clip_header = tracks_db_header->get_clip_header(db->clip_segment_headers); + const database_runtime_segment_header* db_segment_headers = db_clip_header->get_segment_headers(); + + // Cache miss for the db segment headers + const database_runtime_segment_header* db_segment_header0 = db_segment_headers; + medium_importance_tier_metadata0 = db_segment_header0->tier_metadata[0].load(std::memory_order::memory_order_relaxed); + low_importance_tier_metadata0 = db_segment_header0->tier_metadata[1].load(std::memory_order::memory_order_relaxed); + + sample_indices0 |= uint32_t(medium_importance_tier_metadata0); + sample_indices0 |= uint32_t(low_importance_tier_metadata0); + } + + // Find the closest loaded samples + // Mask all trailing samples to find the first sample by counting trailing zeros + const uint32_t candidate_indices0 = sample_indices0 & (0xFFFFFFFFU << (31 - key_frame0)); + key_frame0 = 31 - count_trailing_zeros(candidate_indices0); + + // Mask all leading samples to find the second sample by counting leading zeros + const uint32_t candidate_indices1 = sample_indices0 & (0xFFFFFFFFU >> key_frame1); + key_frame1 = count_leading_zeros(candidate_indices1); + + // Calculate our new interpolation alpha + // We used the rounding policy above to snap to the correct key frame earlier but we might need to interpolate now + // if key frames have been removed + context.interpolation_alpha = find_linear_interpolation_alpha(sample_index, key_frame0, key_frame1, sample_rounding_policy::none); + + // Find where our data lives (clip or database tier X) + sample_indices0 = segment_tier0_header0->sample_indices; + uint32_t sample_indices1 = sample_indices0; // Identical + + if (db != nullptr) + { + const uint64_t sample_index0 = uint64_t(1) << (31 - key_frame0); + const uint64_t sample_index1 = uint64_t(1) << (31 - key_frame1); + + const uint8_t* bulk_data_medium = db->bulk_data[0]; // Might be nullptr if we haven't streamed in yet + const uint8_t* bulk_data_low = db->bulk_data[1]; // Might be nullptr if we haven't streamed in yet + if ((medium_importance_tier_metadata0 & sample_index0) != 0) + { + sample_indices0 = uint32_t(medium_importance_tier_metadata0); + db_animated_track_data0 = bulk_data_medium + uint32_t(medium_importance_tier_metadata0 >> 32); + } + else if ((low_importance_tier_metadata0 & sample_index0) != 0) + { + sample_indices0 = uint32_t(low_importance_tier_metadata0); + db_animated_track_data0 = bulk_data_low + uint32_t(low_importance_tier_metadata0 >> 32); + } + + // Only one segment, our metadata is the same for our second key frame + if ((medium_importance_tier_metadata0 & sample_index1) != 0) + { + sample_indices1 = uint32_t(medium_importance_tier_metadata0); + db_animated_track_data1 = bulk_data_medium + uint32_t(medium_importance_tier_metadata0 >> 32); + } + else if ((low_importance_tier_metadata0 & sample_index1) != 0) + { + sample_indices1 = uint32_t(low_importance_tier_metadata0); + db_animated_track_data1 = bulk_data_low + uint32_t(low_importance_tier_metadata0 >> 32); + } + } + + // Remap our sample indices within the ones actually stored (e.g. index 3 might be the second frame stored) + segment_key_frame0 = count_set_bits(and_not(0xFFFFFFFFU >> key_frame0, sample_indices0)); + segment_key_frame1 = count_set_bits(and_not(0xFFFFFFFFU >> key_frame1, sample_indices1)); + + // Nasty but safe since they have the same layout + segment_header0 = reinterpret_cast(segment_tier0_header0); + segment_header1 = reinterpret_cast(segment_tier0_header0); + } + else + { + segment_header0 = segment_headers; + segment_header1 = segment_headers; + + segment_key_frame0 = key_frame0; + segment_key_frame1 = key_frame1; + } + } + else + { + const uint32_t* segment_start_indices = transform_header.get_segment_start_indices(); + + // See segment_streams(..) for implementation details. This implementation is directly tied to it. + const uint32_t approx_num_samples_per_segment = header.num_samples / num_segments; // TODO: Store in header? + const uint32_t approx_segment_index = key_frame0 / approx_num_samples_per_segment; + + uint32_t segment_index0 = 0; + uint32_t segment_index1 = 0; + + // Our approximate segment guess is just that, a guess. The actual segments we need could be just before or after. + // We start looking one segment earlier and up to 2 after. If we have too few segments after, we will hit the + // sentinel value of 0xFFFFFFFF and exit the loop. + // TODO: Can we do this with SIMD? Load all 4 values, set key_frame0, compare, move mask, count leading zeroes + const uint32_t start_segment_index = approx_segment_index > 0 ? (approx_segment_index - 1) : 0; + const uint32_t end_segment_index = start_segment_index + 4; + + for (uint32_t segment_index = start_segment_index; segment_index < end_segment_index; ++segment_index) + { + if (key_frame0 < segment_start_indices[segment_index]) + { + // We went too far, use previous segment + ACL_ASSERT(segment_index > 0, "Invalid segment index: %u", segment_index); + segment_index0 = segment_index - 1; + segment_index1 = key_frame1 < segment_start_indices[segment_index] ? segment_index0 : segment_index; + break; + } + } + + segment_key_frame0 = key_frame0 - segment_start_indices[segment_index0]; + segment_key_frame1 = key_frame1 - segment_start_indices[segment_index1]; + + if (is_database_supported && has_database) + { + const segment_tier0_header* segment_tier0_header0 = segment_tier0_headers + segment_index0; + const segment_tier0_header* segment_tier0_header1 = segment_tier0_headers + segment_index1; + + // This will cache miss + uint32_t sample_indices0 = segment_tier0_header0->sample_indices; + uint32_t sample_indices1 = segment_tier0_header1->sample_indices; + + // Calculate our clip relative sample index, we'll remap it later relative to the samples we'll use + const float sample_index = context.interpolation_alpha + float(key_frame0); + + // When we load our sample indices and offsets from the database, there can be another thread writing + // to those memory locations at the same time (e.g. streaming in/out). + // To ensure thread safety, we atomically load the offset and sample indices. + uint64_t medium_importance_tier_metadata0 = 0; + uint64_t medium_importance_tier_metadata1 = 0; + uint64_t low_importance_tier_metadata0 = 0; + uint64_t low_importance_tier_metadata1 = 0; + + // Combine all our loaded samples into a single bit set to find which samples we need to interpolate + if (db != nullptr) + { + // Possible cache miss for the clip header offset + // Cache miss for the db clip segment headers pointer + const tracks_database_header* tracks_db_header = transform_header.get_database_header(); + const database_runtime_clip_header* db_clip_header = tracks_db_header->get_clip_header(db->clip_segment_headers); + const database_runtime_segment_header* db_segment_headers = db_clip_header->get_segment_headers(); + + // Cache miss for the db segment headers + const database_runtime_segment_header* db_segment_header0 = db_segment_headers + segment_index0; + medium_importance_tier_metadata0 = db_segment_header0->tier_metadata[0].load(std::memory_order::memory_order_relaxed); + low_importance_tier_metadata0 = db_segment_header0->tier_metadata[1].load(std::memory_order::memory_order_relaxed); + + sample_indices0 |= uint32_t(medium_importance_tier_metadata0); + sample_indices0 |= uint32_t(low_importance_tier_metadata0); + + const database_runtime_segment_header* db_segment_header1 = db_segment_headers + segment_index1; + medium_importance_tier_metadata1 = db_segment_header1->tier_metadata[0].load(std::memory_order::memory_order_relaxed); + low_importance_tier_metadata1 = db_segment_header1->tier_metadata[1].load(std::memory_order::memory_order_relaxed); + + sample_indices1 |= uint32_t(medium_importance_tier_metadata1); + sample_indices1 |= uint32_t(low_importance_tier_metadata1); + } + + // Find the closest loaded samples + // Mask all trailing samples to find the first sample by counting trailing zeros + const uint32_t candidate_indices0 = sample_indices0 & (0xFFFFFFFFU << (31 - segment_key_frame0)); + segment_key_frame0 = 31 - count_trailing_zeros(candidate_indices0); + + // Mask all leading samples to find the second sample by counting leading zeros + const uint32_t candidate_indices1 = sample_indices1 & (0xFFFFFFFFU >> segment_key_frame1); + segment_key_frame1 = count_leading_zeros(candidate_indices1); + + // Calculate our clip relative sample indices + const uint32_t clip_key_frame0 = segment_start_indices[segment_index0] + segment_key_frame0; + const uint32_t clip_key_frame1 = segment_start_indices[segment_index1] + segment_key_frame1; + + // Calculate our new interpolation alpha + // We used the rounding policy above to snap to the correct key frame earlier but we might need to interpolate now + // if key frames have been removed + context.interpolation_alpha = find_linear_interpolation_alpha(sample_index, clip_key_frame0, clip_key_frame1, sample_rounding_policy::none); + + // Find where our data lives (clip or database tier X) + sample_indices0 = segment_tier0_header0->sample_indices; + sample_indices1 = segment_tier0_header1->sample_indices; + + if (db != nullptr) + { + const uint64_t sample_index0 = uint64_t(1) << (31 - segment_key_frame0); + const uint64_t sample_index1 = uint64_t(1) << (31 - segment_key_frame1); + + const uint8_t* bulk_data_medium = db->bulk_data[0]; // Might be nullptr if we haven't streamed in yet + const uint8_t* bulk_data_low = db->bulk_data[1]; // Might be nullptr if we haven't streamed in yet + if ((medium_importance_tier_metadata0 & sample_index0) != 0) + { + sample_indices0 = uint32_t(medium_importance_tier_metadata0); + db_animated_track_data0 = bulk_data_medium + uint32_t(medium_importance_tier_metadata0 >> 32); + } + else if ((low_importance_tier_metadata0 & sample_index0) != 0) + { + sample_indices0 = uint32_t(low_importance_tier_metadata0); + db_animated_track_data0 = bulk_data_low + uint32_t(low_importance_tier_metadata0 >> 32); + } + + if ((medium_importance_tier_metadata1 & sample_index1) != 0) + { + sample_indices1 = uint32_t(medium_importance_tier_metadata1); + db_animated_track_data1 = bulk_data_medium + uint32_t(medium_importance_tier_metadata1 >> 32); + } + else if ((low_importance_tier_metadata1 & sample_index1) != 0) + { + sample_indices1 = uint32_t(low_importance_tier_metadata1); + db_animated_track_data1 = bulk_data_low + uint32_t(low_importance_tier_metadata1 >> 32); + } + } + + // Remap our sample indices within the ones actually stored (e.g. index 3 might be the second frame stored) + segment_key_frame0 = count_set_bits(and_not(0xFFFFFFFFU >> segment_key_frame0, sample_indices0)); + segment_key_frame1 = count_set_bits(and_not(0xFFFFFFFFU >> segment_key_frame1, sample_indices1)); + + // Nasty but safe since they have the same layout + segment_header0 = reinterpret_cast(segment_tier0_header0); + segment_header1 = reinterpret_cast(segment_tier0_header1); + } + else + { + segment_header0 = segment_headers + segment_index0; + segment_header1 = segment_headers + segment_index1; + } + } + + { + // Prefetch our constant rotation data, we'll need it soon when we start decompressing and we are about to cache miss on the segment headers + const uint8_t* constant_data_rotations = transform_header.get_constant_track_data(); + ACL_IMPL_SEEK_PREFETCH(constant_data_rotations); + ACL_IMPL_SEEK_PREFETCH(constant_data_rotations + 64); + } + + const bool uses_single_segment = segment_header0 == segment_header1; + context.uses_single_segment = uses_single_segment; + + // Cache miss if we don't access the db data + transform_header.get_segment_data(*segment_header0, context.format_per_track_data[0], context.segment_range_data[0], context.animated_track_data[0]); + + // More often than not the two segments are identical, when this is the case, just copy our pointers + if (!uses_single_segment) + { + transform_header.get_segment_data(*segment_header1, context.format_per_track_data[1], context.segment_range_data[1], context.animated_track_data[1]); + } + else + { + context.format_per_track_data[1] = context.format_per_track_data[0]; + context.segment_range_data[1] = context.segment_range_data[0]; + context.animated_track_data[1] = context.animated_track_data[0]; + } + + if (is_database_supported && has_database) + { + // Update our pointers if the data lives within the database + if (db_animated_track_data0 != nullptr) + context.animated_track_data[0] = db_animated_track_data0; + + if (db_animated_track_data1 != nullptr) + context.animated_track_data[1] = db_animated_track_data1; + } + + context.key_frame_bit_offsets[0] = segment_key_frame0 * segment_header0->animated_pose_bit_size; + context.key_frame_bit_offsets[1] = segment_key_frame1 * segment_header1->animated_pose_bit_size; + + context.segment_offsets[0] = ptr_offset32(context.tracks, segment_header0); + context.segment_offsets[1] = ptr_offset32(context.tracks, segment_header1); + } + + + // TODO: Merge the per track format and segment range info into a single buffer? Less to prefetch and used together + // TODO: Remove segment data alignment, no longer required? + + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_default_rotation_sub_tracks( + const packed_sub_track_types* rotation_sub_track_types, uint32_t last_entry_index, uint32_t padding_mask, + rtm::quatf_arg0 default_rotation, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + uint32_t packed_entry = rotation_sub_track_types[entry_index].types; + + // Mask out everything but default sub-tracks, this way we can early out when we iterate + // Each sub-track is either 0 (default), 1 (constant), or 2 (animated) + // By flipping the bits with logical NOT, 0 becomes 3, 1 becomes 2, and 2 becomes 1 + // We then subtract 1 from every group so 3 becomes 2, 2 becomes 1, and 1 becomes 0 + // Finally, we mask out everything but the second bit for each sub-track + // After this, our original default tracks are equal to 2, our constant tracks are equal to 1, and our animated tracks are equal to 0 + // Testing for default tracks can be done by testing the second bit of each group (same as animated track testing) + packed_entry = (~packed_entry - 0x55555555) & 0xAAAAAAAA; + + // Because our last entry might have padding with 0 (default), we have to strip any padding we might have + const uint32_t entry_padding_mask = (entry_index == last_entry_index) ? padding_mask : 0xFFFFFFFF; + packed_entry &= entry_padding_mask; + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0xAA000000) == 0) + continue; // This group contains no default sub-tracks, skip it + + if ((packed_group & 0x80000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index0)) + writer.write_rotation(track_index0, default_rotation); + } + + if ((packed_group & 0x20000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index1)) + writer.write_rotation(track_index1, default_rotation); + } + + if ((packed_group & 0x08000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index2)) + writer.write_rotation(track_index2, default_rotation); + } + + if ((packed_group & 0x02000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index3)) + writer.write_rotation(track_index3, default_rotation); + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_constant_rotation_sub_tracks( + const packed_sub_track_types* rotation_sub_track_types, uint32_t last_entry_index, + const persistent_transform_decompression_context_v0& context, + constant_track_cache_v0& constant_track_cache, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + // Mask out everything but constant sub-tracks, this way we can early out when we iterate + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + uint32_t packed_entry = and_not(~0x55555555U, rotation_sub_track_types[entry_index].types); + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Unpack our next 16 tracks + constant_track_cache.unpack_rotation_group(context); + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0x55000000) == 0) + continue; // This group contains no constant sub-tracks, skip it + + if ((packed_group & 0x40000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + const rtm::quatf& rotation = constant_track_cache.consume_rotation(); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index0)) + writer.write_rotation(track_index0, rotation); + } + + if ((packed_group & 0x10000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + const rtm::quatf& rotation = constant_track_cache.consume_rotation(); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index1)) + writer.write_rotation(track_index1, rotation); + } + + if ((packed_group & 0x04000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + const rtm::quatf& rotation = constant_track_cache.consume_rotation(); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index2)) + writer.write_rotation(track_index2, rotation); + } + + if ((packed_group & 0x01000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + const rtm::quatf& rotation = constant_track_cache.consume_rotation(); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index3)) + writer.write_rotation(track_index3, rotation); + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_animated_rotation_sub_tracks( + const packed_sub_track_types* rotation_sub_track_types, uint32_t last_entry_index, + const persistent_transform_decompression_context_v0& context, + animated_track_cache_v0& animated_track_cache, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + // Mask out everything but animated sub-tracks, this way we can early out when we iterate + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + uint32_t packed_entry = and_not(~0xAAAAAAAAU, rotation_sub_track_types[entry_index].types); + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0xAA000000) == 0) + continue; // This group contains no animated sub-tracks, skip it + + // Unpack our next 4 tracks + animated_track_cache.unpack_rotation_group(context); + + if ((packed_group & 0x80000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + const rtm::quatf& rotation = animated_track_cache.consume_rotation(); + + ACL_ASSERT(rtm::quat_is_finite(rotation), "Rotation is not valid!"); + ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation is not normalized!"); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index0)) + writer.write_rotation(track_index0, rotation); + } + + if ((packed_group & 0x20000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + const rtm::quatf& rotation = animated_track_cache.consume_rotation(); + + ACL_ASSERT(rtm::quat_is_finite(rotation), "Rotation is not valid!"); + ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation is not normalized!"); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index1)) + writer.write_rotation(track_index1, rotation); + } + + if ((packed_group & 0x08000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + const rtm::quatf& rotation = animated_track_cache.consume_rotation(); + + ACL_ASSERT(rtm::quat_is_finite(rotation), "Rotation is not valid!"); + ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation is not normalized!"); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index2)) + writer.write_rotation(track_index2, rotation); + } + + if ((packed_group & 0x02000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + const rtm::quatf& rotation = animated_track_cache.consume_rotation(); + + ACL_ASSERT(rtm::quat_is_finite(rotation), "Rotation is not valid!"); + ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation is not normalized!"); + + if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index3)) + writer.write_rotation(track_index3, rotation); + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_default_translation_sub_tracks( + const packed_sub_track_types* translation_sub_track_types, uint32_t last_entry_index, uint32_t padding_mask, + rtm::vector4f_arg0 default_translation, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + uint32_t packed_entry = translation_sub_track_types[entry_index].types; + + // Mask out everything but default sub-tracks, this way we can early out when we iterate + // Each sub-track is either 0 (default), 1 (constant), or 2 (animated) + // By flipping the bits with logical NOT, 0 becomes 3, 1 becomes 2, and 2 becomes 1 + // We then subtract 1 from every group so 3 becomes 2, 2 becomes 1, and 1 becomes 0 + // Finally, we mask out everything but the second bit for each sub-track + // After this, our original default tracks are equal to 2, our constant tracks are equal to 1, and our animated tracks are equal to 0 + // Testing for default tracks can be done by testing the second bit of each group (same as animated track testing) + packed_entry = (~packed_entry - 0x55555555) & 0xAAAAAAAA; + + // Because our last entry might have padding with 0 (default), we have to strip any padding we might have + const uint32_t entry_padding_mask = (entry_index == last_entry_index) ? padding_mask : 0xFFFFFFFF; + packed_entry &= entry_padding_mask; + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0xAA000000) == 0) + continue; // This group contains no default sub-tracks, skip it + + if ((packed_group & 0x80000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index0)) + writer.write_translation(track_index0, default_translation); + } + + if ((packed_group & 0x20000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index1)) + writer.write_translation(track_index1, default_translation); + } + + if ((packed_group & 0x08000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index2)) + writer.write_translation(track_index2, default_translation); + } + + if ((packed_group & 0x02000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index3)) + writer.write_translation(track_index3, default_translation); + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_constant_translation_sub_tracks( + const packed_sub_track_types* translation_sub_track_types, uint32_t last_entry_index, + constant_track_cache_v0& constant_track_cache, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + // Mask out everything but constant sub-tracks, this way we can early out when we iterate + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + uint32_t packed_entry = and_not(~0x55555555U, translation_sub_track_types[entry_index].types); + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0x55000000) == 0) + continue; // This group contains no constant sub-tracks, skip it + + if ((packed_group & 0x40000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + const uint8_t* translation_ptr = constant_track_cache.consume_translation(); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index0)) + { + const rtm::vector4f translation = rtm::vector_load(translation_ptr); + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + writer.write_translation(track_index0, translation); + } + } + + if ((packed_group & 0x10000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + const uint8_t* translation_ptr = constant_track_cache.consume_translation(); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index1)) + { + const rtm::vector4f translation = rtm::vector_load(translation_ptr); + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + writer.write_translation(track_index1, translation); + } + } + + if ((packed_group & 0x04000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + const uint8_t* translation_ptr = constant_track_cache.consume_translation(); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index2)) + { + const rtm::vector4f translation = rtm::vector_load(translation_ptr); + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + writer.write_translation(track_index2, translation); + } + } + + if ((packed_group & 0x01000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + const uint8_t* translation_ptr = constant_track_cache.consume_translation(); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index3)) + { + const rtm::vector4f translation = rtm::vector_load(translation_ptr); + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + writer.write_translation(track_index3, translation); + } + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_animated_translation_sub_tracks( + const packed_sub_track_types* translation_sub_track_types, uint32_t last_entry_index, + const persistent_transform_decompression_context_v0& context, + animated_track_cache_v0& animated_track_cache, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + // Mask out everything but animated sub-tracks, this way we can early out when we iterate + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + uint32_t packed_entry = and_not(~0xAAAAAAAAU, translation_sub_track_types[entry_index].types); + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0xAA000000) == 0) + continue; // This group contains no animated sub-tracks, skip it + + // Unpack our next 4 tracks + animated_track_cache.unpack_translation_group(context); + + if ((packed_group & 0x80000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + const rtm::vector4f& translation = animated_track_cache.consume_translation(); + + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index0)) + writer.write_translation(track_index0, translation); + } + + if ((packed_group & 0x20000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + const rtm::vector4f& translation = animated_track_cache.consume_translation(); + + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index1)) + writer.write_translation(track_index1, translation); + } + + if ((packed_group & 0x08000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + const rtm::vector4f& translation = animated_track_cache.consume_translation(); + + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index2)) + writer.write_translation(track_index2, translation); + } + + if ((packed_group & 0x02000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + const rtm::vector4f& translation = animated_track_cache.consume_translation(); + + ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!"); + + if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index3)) + writer.write_translation(track_index3, translation); + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_default_scale_sub_tracks( + const packed_sub_track_types* scale_sub_track_types, uint32_t last_entry_index, uint32_t padding_mask, + rtm::vector4f_arg0 default_scale, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + uint32_t packed_entry = scale_sub_track_types[entry_index].types; + + // Mask out everything but default sub-tracks, this way we can early out when we iterate + // Each sub-track is either 0 (default), 1 (constant), or 2 (animated) + // By flipping the bits with logical NOT, 0 becomes 3, 1 becomes 2, and 2 becomes 1 + // We then subtract 1 from every group so 3 becomes 2, 2 becomes 1, and 1 becomes 0 + // Finally, we mask out everything but the second bit for each sub-track + // After this, our original default tracks are equal to 2, our constant tracks are equal to 1, and our animated tracks are equal to 0 + // Testing for default tracks can be done by testing the second bit of each group (same as animated track testing) + packed_entry = (~packed_entry - 0x55555555) & 0xAAAAAAAA; + + // Because our last entry might have padding with 0 (default), we have to strip any padding we might have + const uint32_t entry_padding_mask = (entry_index == last_entry_index) ? padding_mask : 0xFFFFFFFF; + packed_entry &= entry_padding_mask; + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0xAA000000) == 0) + continue; // This group contains no default sub-tracks, skip it + + if ((packed_group & 0x80000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index0)) + writer.write_scale(track_index0, default_scale); + } + + if ((packed_group & 0x20000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index1)) + writer.write_scale(track_index1, default_scale); + } + + if ((packed_group & 0x08000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index2)) + writer.write_scale(track_index2, default_scale); + } + + if ((packed_group & 0x02000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index3)) + writer.write_scale(track_index3, default_scale); + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_constant_scale_sub_tracks( + const packed_sub_track_types* scale_sub_track_types, uint32_t last_entry_index, + constant_track_cache_v0& constant_track_cache, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + // Mask out everything but constant sub-tracks, this way we can early out when we iterate + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + uint32_t packed_entry = and_not(~0x55555555U, scale_sub_track_types[entry_index].types); + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0x55000000) == 0) + continue; // This group contains no constant sub-tracks, skip it + + if ((packed_group & 0x40000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + const uint8_t* scale_ptr = constant_track_cache.consume_scale(); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index0)) + { + const rtm::vector4f scale = rtm::vector_load(scale_ptr); + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + writer.write_scale(track_index0, scale); + } + } + + if ((packed_group & 0x10000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + const uint8_t* scale_ptr = constant_track_cache.consume_scale(); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index1)) + { + const rtm::vector4f scale = rtm::vector_load(scale_ptr); + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + writer.write_scale(track_index1, scale); + } + } + + if ((packed_group & 0x04000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + const uint8_t* scale_ptr = constant_track_cache.consume_scale(); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index2)) + { + const rtm::vector4f scale = rtm::vector_load(scale_ptr); + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + writer.write_scale(track_index2, scale); + } + } + + if ((packed_group & 0x01000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + const uint8_t* scale_ptr = constant_track_cache.consume_scale(); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index3)) + { + const rtm::vector4f scale = rtm::vector_load(scale_ptr); + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + writer.write_scale(track_index3, scale); + } + } + } + } + } + + // Force inline this function, we only use it to keep the code readable + template + RTM_FORCE_INLINE RTM_DISABLE_SECURITY_COOKIE_CHECK void RTM_SIMD_CALL unpack_animated_scale_sub_tracks( + const packed_sub_track_types* scale_sub_track_types, uint32_t last_entry_index, + const persistent_transform_decompression_context_v0& context, + animated_track_cache_v0& animated_track_cache, track_writer_type& writer) + { + for (uint32_t entry_index = 0, track_index = 0; entry_index <= last_entry_index; ++entry_index) + { + // Mask out everything but animated sub-tracks, this way we can early out when we iterate + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + uint32_t packed_entry = and_not(~0xAAAAAAAAU, scale_sub_track_types[entry_index].types); + + uint32_t curr_entry_track_index = track_index; + + // We might early out below, always skip 16 tracks + track_index += 16; + + // Process 4 sub-tracks at a time + while (packed_entry != 0) + { + const uint32_t packed_group = packed_entry; + const uint32_t curr_group_track_index = curr_entry_track_index; + + // Move to the next group + packed_entry <<= 8; + curr_entry_track_index += 4; + + if ((packed_group & 0xAA000000) == 0) + continue; // This group contains no animated sub-tracks, skip it + + // Unpack our next 4 tracks + animated_track_cache.unpack_scale_group(context); + + if ((packed_group & 0x80000000) != 0) + { + const uint32_t track_index0 = curr_group_track_index + 0; + const rtm::vector4f& scale = animated_track_cache.consume_scale(); + + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index0)) + writer.write_scale(track_index0, scale); + } + + if ((packed_group & 0x20000000) != 0) + { + const uint32_t track_index1 = curr_group_track_index + 1; + const rtm::vector4f& scale = animated_track_cache.consume_scale(); + + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index1)) + writer.write_scale(track_index1, scale); + } + + if ((packed_group & 0x08000000) != 0) + { + const uint32_t track_index2 = curr_group_track_index + 2; + const rtm::vector4f& scale = animated_track_cache.consume_scale(); + + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index2)) + writer.write_scale(track_index2, scale); + } + + if ((packed_group & 0x02000000) != 0) + { + const uint32_t track_index3 = curr_group_track_index + 3; + const rtm::vector4f& scale = animated_track_cache.consume_scale(); + + ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!"); + + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index3)) + writer.write_scale(track_index3, scale); + } + } + } + } + + template + inline void decompress_tracks_v0(const persistent_transform_decompression_context_v0& context, track_writer_type& writer) + { + const tracks_header& header = get_tracks_header(*context.tracks); + const uint32_t num_tracks = header.num_tracks; + if (num_tracks == 0) + return; // Empty track list + + ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time"); + if (context.sample_time < 0.0F) + return; // Invalid sample time, we didn't seek yet + + // Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used. + // Disable floating point exceptions to avoid issues. + fp_environment fp_env; + if (decompression_settings_type::disable_fp_exeptions()) + disable_fp_exceptions(fp_env); + + using translation_adapter = acl_impl::translation_decompression_settings_adapter; + using scale_adapter = acl_impl::scale_decompression_settings_adapter; + + const rtm::quatf default_rotation = rtm::quat_identity(); + const rtm::vector4f default_translation = rtm::vector_zero(); + const rtm::vector4f default_scale = rtm::vector_set(float(header.get_default_scale())); + const uint32_t has_scale = context.has_scale; + + const packed_sub_track_types* sub_track_types = get_transform_tracks_header(*context.tracks).get_sub_track_types(); + const uint32_t num_sub_track_entries = (num_tracks + k_num_sub_tracks_per_packed_entry - 1) / k_num_sub_tracks_per_packed_entry; + const uint32_t num_padded_sub_tracks = (num_sub_track_entries * k_num_sub_tracks_per_packed_entry) - num_tracks; + const uint32_t last_entry_index = num_sub_track_entries - 1; + + // Build a mask to strip the extra sub-tracks we don't need that live in the padding + // They are set to 0 which means they would be 'default' sub-tracks but they don't really exist + // If we have no padding, we retain every sub-track + // Sub-tracks that are kept have their bits set to 1 to mask them with logical AND later + const uint32_t padding_mask = num_padded_sub_tracks != 0 ? ~(0xFFFFFFFF >> ((k_num_sub_tracks_per_packed_entry - num_padded_sub_tracks) * 2)) : 0xFFFFFFFF; + + const packed_sub_track_types* rotation_sub_track_types = sub_track_types; + const packed_sub_track_types* translation_sub_track_types = rotation_sub_track_types + num_sub_track_entries; + const packed_sub_track_types* scale_sub_track_types = translation_sub_track_types + num_sub_track_entries; + + constant_track_cache_v0 constant_track_cache; + constant_track_cache.initialize(context); + + { + // By now, our bit sets (1-2 cache lines) constant rotations (2 cache lines) have landed in the L2 + // We prefetched them ahead in the seek(..) function call and due to cache misses when seeking, + // their latency should be fully hidden. + // Prefetch our 3rd constant rotation cache line to prime the hardware prefetcher and do the same for constant translations + + ACL_IMPL_SEEK_PREFETCH(constant_track_cache.constant_data_rotations + 128); + ACL_IMPL_SEEK_PREFETCH(constant_track_cache.constant_data_translations); + ACL_IMPL_SEEK_PREFETCH(constant_track_cache.constant_data_translations + 64); + ACL_IMPL_SEEK_PREFETCH(constant_track_cache.constant_data_translations + 128); + } + + animated_track_cache_v0 animated_track_cache; + animated_track_cache.initialize(context); + + { + // Start prefetching the per track metadata of both segments + // They might live in a different memory page than the clip's header and constant data + // and we need to prime VMEM translation and the TLB + + ACL_IMPL_SEEK_PREFETCH(context.format_per_track_data[0]); + ACL_IMPL_SEEK_PREFETCH(context.format_per_track_data[1]); + } + + // TODO: The first time we iterate over the sub-track types, unpack it into our output pose as a temporary buffer + // We can build a linked list + // Store on the stack the first animated rot/trans/scale + // For its rot/trans/scale, write instead the index of the next animated rot/trans/scale + // We can even unpack it first on its own + // Writer can expose this with something like write_rotation_index/read_rotation_index + // The writer can then allocate a separate buffer for this or re-use the pose buffer + // When the time comes to write our animated samples, we can unpack 4, grab the next 4 entries from the linked + // list and write our samples. We can do this until all samples are written which should be faster than iterating a bit set + // since it'll allow us to quickly skip entries we don't care about. The same scheme can be used for constant/default tracks. + // When we unpack our bitset, we can also count the number of entries for each type to help iterate + + // Unpack our default rotation sub-tracks + // Default rotation sub-tracks are uncommon, this shouldn't take much more than 50 cycles + unpack_default_rotation_sub_tracks(rotation_sub_track_types, last_entry_index, padding_mask, default_rotation, writer); + + // Unpack our constant rotation sub-tracks + // Constant rotation sub-tracks are very common, this should take at least 200 cycles + unpack_constant_rotation_sub_tracks(rotation_sub_track_types, last_entry_index, context, constant_track_cache, writer); + + // By now, our constant translations (3 cache lines) have landed in L2 after our prefetching has completed + // We typically will do enough work above to hide the latency + // We do not prefetch our constant scales because scale is fairly rare + // Instead, we prefetch our segment range and animated data + // The second key frame of animated data might not live in the same memory page even if we use a single segment + // so this allows us to prime the TLB as well + { + const uint8_t* segment_range_data0 = animated_track_cache.segment_sampling_context_rotations[0].segment_range_data; + const uint8_t* segment_range_data1 = animated_track_cache.segment_sampling_context_rotations[1].segment_range_data; + const uint8_t* animated_data0 = animated_track_cache.segment_sampling_context_rotations[0].animated_track_data; + const uint8_t* animated_data1 = animated_track_cache.segment_sampling_context_rotations[1].animated_track_data; + const uint8_t* frame_animated_data0 = animated_data0 + (animated_track_cache.segment_sampling_context_rotations[0].animated_track_data_bit_offset / 8); + const uint8_t* frame_animated_data1 = animated_data1 + (animated_track_cache.segment_sampling_context_rotations[1].animated_track_data_bit_offset / 8); + + ACL_IMPL_SEEK_PREFETCH(segment_range_data0); + ACL_IMPL_SEEK_PREFETCH(segment_range_data0 + 64); + ACL_IMPL_SEEK_PREFETCH(segment_range_data1); + ACL_IMPL_SEEK_PREFETCH(segment_range_data1 + 64); + ACL_IMPL_SEEK_PREFETCH(frame_animated_data0); + ACL_IMPL_SEEK_PREFETCH(frame_animated_data1); + } + + // Unpack our default translation sub-tracks + // Default translation sub-tracks are rare, this shouldn't take much more than 50 cycles + unpack_default_translation_sub_tracks(translation_sub_track_types, last_entry_index, padding_mask, default_translation, writer); + + // Unpack our constant translation sub-tracks + // Constant translation sub-tracks are very common, this should take at least 200 cycles + unpack_constant_translation_sub_tracks(translation_sub_track_types, last_entry_index, constant_track_cache, writer); + + if (has_scale) + { + // Unpack our default scale sub-tracks + // Scale sub-tracks are almost always default, this should take at least 200 cycles + unpack_default_scale_sub_tracks(scale_sub_track_types, last_entry_index, padding_mask, default_scale, writer); + + // Unpack our constant scale sub-tracks + // Constant scale sub-tracks are very rare, this shouldn't take much more than 50 cycles + unpack_constant_scale_sub_tracks(scale_sub_track_types, last_entry_index, constant_track_cache, writer); + } + else + { + // No scale present, everything is just the default value + // This shouldn't take much more than 50 cycles + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index)) + writer.write_scale(track_index, default_scale); + } + } + + { + // By now the first few cache lines of our segment data has landed in the L2 + // Prefetch ahead some more to prime the hardware prefetcher + // We also start prefetching the clip range data since we'll need it soon and we need to prime the TLB + // and the hardware prefetcher + + const uint8_t* per_track_metadata0 = animated_track_cache.segment_sampling_context_rotations[0].format_per_track_data; + const uint8_t* per_track_metadata1 = animated_track_cache.segment_sampling_context_rotations[1].format_per_track_data; + const uint8_t* animated_data0 = animated_track_cache.segment_sampling_context_rotations[0].animated_track_data; + const uint8_t* animated_data1 = animated_track_cache.segment_sampling_context_rotations[1].animated_track_data; + const uint8_t* frame_animated_data0 = animated_data0 + (animated_track_cache.segment_sampling_context_rotations[0].animated_track_data_bit_offset / 8); + const uint8_t* frame_animated_data1 = animated_data1 + (animated_track_cache.segment_sampling_context_rotations[1].animated_track_data_bit_offset / 8); + + ACL_IMPL_SEEK_PREFETCH(per_track_metadata0 + 64); + ACL_IMPL_SEEK_PREFETCH(per_track_metadata1 + 64); + ACL_IMPL_SEEK_PREFETCH(frame_animated_data0 + 64); + ACL_IMPL_SEEK_PREFETCH(frame_animated_data1 + 64); + ACL_IMPL_SEEK_PREFETCH(animated_track_cache.clip_sampling_context_rotations.clip_range_data); + ACL_IMPL_SEEK_PREFETCH(animated_track_cache.clip_sampling_context_rotations.clip_range_data + 64); + + // TODO: Can we prefetch the translation data ahead instead to prime the TLB? + } + + // Unpack our variable sub-tracks + // Sub-track data is sorted by type: rotations ... translations ... scales ... + // We process everything linearly in order, this should help the hardware prefetcher work with us + // We use quite a few memory streams: + // - segment per track metadata: 1 per segment + // - segment range data: 1 per segment + // - animated frame data: 2 (might be in different segments or in database) + // - clip range data: 1 + // We thus need between 5 and 7 memory streams which is a lot. + // This is why the unpacking code uses manual software prefetching to ensure prefetching happens. + // Removing the manual prefetching slows down the execution on Zen2 and a Pixel 3. + // Quite a few of these memory streams might live in separate memory pages if the clip is large + // and might thus require TLB misses + + // TODO: Unpack 4, then iterate over tracks to write? + // Can we keep the rotations in registers? Does it matter? + + // Unpack rotations first + // Animated rotation sub-tracks are very common, this should take at least 400 cycles + unpack_animated_rotation_sub_tracks(rotation_sub_track_types, last_entry_index, context, animated_track_cache, writer); + + // Unpack translations second + // Animated translation sub-tracks are common, this should take at least 200 cycles + unpack_animated_translation_sub_tracks(translation_sub_track_types, last_entry_index, context, animated_track_cache, writer); + + // Unpack scales last + // Animated scale sub-tracks are very rare, this shouldn't take much more than 100 cycles + if (has_scale) + unpack_animated_scale_sub_tracks(scale_sub_track_types, last_entry_index, context, animated_track_cache, writer); + + if (decompression_settings_type::disable_fp_exeptions()) + restore_fp_exceptions(fp_env); + } + + // We only initialize some variables when we need them which prompts the compiler to complain + // The usage is perfectly safe and because this code is VERY hot and needs to be as fast as possible, + // we disable the warning to avoid zeroing out things we don't need +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C4701: potentially uninitialized local variable + #pragma warning(disable : 4701) + // warning C6001: Using uninitialized memory '...'. + #pragma warning(disable : 6001) +#elif defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + + template + inline void decompress_track_v0(const persistent_transform_decompression_context_v0& context, uint32_t track_index, track_writer_type& writer) + { + const tracks_header& tracks_header_ = get_tracks_header(*context.tracks); + const uint32_t num_tracks = tracks_header_.num_tracks; + if (num_tracks == 0) + return; // Empty track list + + ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time"); + if (context.sample_time < 0.0F) + return; // Invalid sample time, we didn't seek yet + + ACL_ASSERT(track_index < num_tracks, "Invalid track index"); + if (track_index >= num_tracks) + return; // Invalid track index + + // Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used. + // Disable floating point exceptions to avoid issues. + fp_environment fp_env; + if (decompression_settings_type::disable_fp_exeptions()) + disable_fp_exceptions(fp_env); + + using translation_adapter = acl_impl::translation_decompression_settings_adapter; + using scale_adapter = acl_impl::scale_decompression_settings_adapter; + + const rtm::quatf default_rotation = rtm::quat_identity(); + const rtm::vector4f default_translation = rtm::vector_zero(); + const rtm::vector4f default_scale = rtm::vector_set(float(tracks_header_.get_default_scale())); + const uint32_t has_scale = context.has_scale; + + const packed_sub_track_types* sub_track_types = get_transform_tracks_header(*context.tracks).get_sub_track_types(); + const uint32_t num_sub_track_entries = (num_tracks + k_num_sub_tracks_per_packed_entry - 1) / k_num_sub_tracks_per_packed_entry; + + const packed_sub_track_types* rotation_sub_track_types = sub_track_types; + const packed_sub_track_types* translation_sub_track_types = rotation_sub_track_types + num_sub_track_entries; + + // If we have no scale, we'll load the rotation sub-track types and mask it out to avoid branching, forcing it to be the default value + const packed_sub_track_types* scale_sub_track_types = has_scale ? (translation_sub_track_types + num_sub_track_entries) : sub_track_types; + + // Build a mask to strip out the scale sub-track types if we have no scale present + // has_scale is either 0 or 1, negating yields 0 (0x00000000) or -1 (0xFFFFFFFF) + // Equivalent to: has_scale ? 0xFFFFFFFF : 0x00000000 + const uint32_t scale_sub_track_mask = -int32_t(has_scale); + + const uint32_t sub_track_entry_index = track_index / 16; + const uint32_t packed_index = track_index % 16; + + // Shift our sub-track types so that the sub-track we care about ends up in the LSB position + const uint32_t packed_shift = (15 - packed_index) * 2; + + const uint32_t rotation_sub_track_type = (rotation_sub_track_types[sub_track_entry_index].types >> packed_shift) & 0x3; + const uint32_t translation_sub_track_type = (translation_sub_track_types[sub_track_entry_index].types >> packed_shift) & 0x3; + const uint32_t scale_sub_track_type = scale_sub_track_mask & (scale_sub_track_types[sub_track_entry_index].types >> packed_shift) & 0x3; + + // Combine all three so we can quickly test if all are default and if any are constant/animated + const uint32_t combined_sub_track_type = rotation_sub_track_type | translation_sub_track_type | scale_sub_track_type; + + if (combined_sub_track_type == 0) + { + // Everything is default + writer.write_rotation(track_index, default_rotation); + writer.write_translation(track_index, default_translation); + writer.write_scale(track_index, default_scale); + return; + } + + uint32_t num_constant_rotations = 0; + uint32_t num_constant_translations = 0; + uint32_t num_constant_scales = 0; + uint32_t num_animated_rotations = 0; + uint32_t num_animated_translations = 0; + uint32_t num_animated_scales = 0; + + const uint32_t last_entry_index = track_index / k_num_sub_tracks_per_packed_entry; + const uint32_t num_padded_sub_tracks = ((last_entry_index + 1) * k_num_sub_tracks_per_packed_entry) - track_index; + + // Build a mask to strip the extra sub-tracks we don't need that live in the padding + // They are set to 0 which means they would be 'default' sub-tracks but they don't really exist + // If we have no padding, we retain every sub-track + // Sub-tracks that are kept have their bits set to 0 to mask them with logical ANDNOT later + const uint32_t padding_mask = num_padded_sub_tracks != 0 ? (0xFFFFFFFF >> ((k_num_sub_tracks_per_packed_entry - num_padded_sub_tracks) * 2)) : 0x00000000; + + for (uint32_t sub_track_entry_index_ = 0; sub_track_entry_index_ <= last_entry_index; ++sub_track_entry_index_) + { + // Our last entry might contain more information than we need so we strip the padding we don't need + const uint32_t entry_padding_mask = (sub_track_entry_index_ == last_entry_index) ? padding_mask : 0x00000000; + + // Use and_not(..) to load our sub-track types directly from memory on x64 with BMI + const uint32_t rotation_sub_track_type_ = and_not(entry_padding_mask, rotation_sub_track_types[sub_track_entry_index_].types); + const uint32_t translation_sub_track_type_ = and_not(entry_padding_mask, translation_sub_track_types[sub_track_entry_index_].types); + const uint32_t scale_sub_track_type_ = scale_sub_track_mask & and_not(entry_padding_mask, scale_sub_track_types[sub_track_entry_index_].types); + + num_constant_rotations += count_set_bits(rotation_sub_track_type_ & 0x55555555); + num_animated_rotations += count_set_bits(rotation_sub_track_type_ & 0xAAAAAAAA); + + num_constant_translations += count_set_bits(translation_sub_track_type_ & 0x55555555); + num_animated_translations += count_set_bits(translation_sub_track_type_ & 0xAAAAAAAA); + + num_constant_scales += count_set_bits(scale_sub_track_type_ & 0x55555555); + num_animated_scales += count_set_bits(scale_sub_track_type_ & 0xAAAAAAAA); + } + + uint32_t rotation_group_sample_index; + uint32_t translation_group_sample_index; + uint32_t scale_group_sample_index; + + constant_track_cache_v0 constant_track_cache; + + // Skip the constant track data + if ((combined_sub_track_type & 1) != 0) + { + // TODO: Can we init just what we need? + constant_track_cache.initialize(context); + + // Calculate how many constant groups of each sub-track type we need to skip + // Constant groups are easy to skip since they are contiguous in memory, we can just skip N trivially + + // Unpack the groups we need and skip the tracks before us + if (rotation_sub_track_type & 1) + { + rotation_group_sample_index = num_constant_rotations % 4; + + const uint32_t num_rotation_constant_groups_to_skip = num_constant_rotations / 4; + if (num_rotation_constant_groups_to_skip != 0) + constant_track_cache.skip_rotation_groups(context, num_rotation_constant_groups_to_skip); + } + + if (translation_sub_track_type & 1) + { + translation_group_sample_index = num_constant_translations % 4; + + const uint32_t num_translation_constant_groups_to_skip = num_constant_translations / 4; + if (num_translation_constant_groups_to_skip != 0) + constant_track_cache.skip_translation_groups(num_translation_constant_groups_to_skip); + } + + if (scale_sub_track_type & 1) + { + scale_group_sample_index = num_constant_scales % 4; + + const uint32_t num_scale_constant_groups_to_skip = num_constant_scales / 4; + if (num_scale_constant_groups_to_skip != 0) + constant_track_cache.skip_scale_groups(num_scale_constant_groups_to_skip); + } + } + + animated_track_cache_v0 animated_track_cache; + + // Skip the animated track data + if ((combined_sub_track_type & 2) != 0) + { + // TODO: Can we init just what we need? + animated_track_cache.initialize(context); + + if (rotation_sub_track_type & 2) + { + rotation_group_sample_index = num_animated_rotations % 4; + + const uint32_t num_groups_to_skip = num_animated_rotations / 4; + if (num_groups_to_skip != 0) + animated_track_cache.skip_rotation_groups(context, num_groups_to_skip); + } + + if (translation_sub_track_type & 2) + { + translation_group_sample_index = num_animated_translations % 4; + + const uint32_t num_groups_to_skip = num_animated_translations / 4; + if (num_groups_to_skip != 0) + animated_track_cache.skip_translation_groups(context, num_groups_to_skip); + } + + if (scale_sub_track_type & 2) + { + scale_group_sample_index = num_animated_scales % 4; + + const uint32_t num_groups_to_skip = num_animated_scales / 4; + if (num_groups_to_skip != 0) + animated_track_cache.skip_scale_groups(context, num_groups_to_skip); + } + } + + // Finally reached our desired track, unpack it + + { + rtm::quatf rotation; + if (rotation_sub_track_type == 0) + rotation = default_rotation; + else if (rotation_sub_track_type & 1) + rotation = constant_track_cache.unpack_rotation_within_group(context, rotation_group_sample_index); + else + rotation = animated_track_cache.unpack_rotation_within_group(context, rotation_group_sample_index); + + writer.write_rotation(track_index, rotation); + } + + { + rtm::vector4f translation; + if (translation_sub_track_type == 0) + translation = default_translation; + else if (translation_sub_track_type & 1) + translation = constant_track_cache.unpack_translation_within_group(translation_group_sample_index); + else + translation = animated_track_cache.unpack_translation_within_group(context, translation_group_sample_index); + + writer.write_translation(track_index, translation); + } + + { + rtm::vector4f scale; + if (scale_sub_track_type == 0) + scale = default_scale; + else if (scale_sub_track_type & 1) + scale = constant_track_cache.unpack_scale_within_group(scale_group_sample_index); + else + scale = animated_track_cache.unpack_scale_within_group(context, scale_group_sample_index); + + writer.write_scale(track_index, scale); + } + + if (decompression_settings_type::disable_fp_exeptions()) + restore_fp_exceptions(fp_env); + } + + // Restore our warnings +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#elif defined(RTM_COMPILER_GCC) + #pragma GCC diagnostic pop +#endif + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/universal_track_decompression.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/universal_track_decompression.h new file mode 100644 index 00000000..c2b67002 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/decompression/impl/universal_track_decompression.h @@ -0,0 +1,193 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/compressed_tracks_version.h" +#include "acl/core/interpolation_utils.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/decompression/database/database.h" +#include "acl/decompression/impl/scalar_track_decompression.h" +#include "acl/decompression/impl/transform_track_decompression.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C4582: 'union': constructor is not implicitly called (/Wall) + // This is fine because a context is empty until it is constructed with a valid clip. + // Afterwards, access is typesafe. + #pragma warning(disable : 4582) + // warning C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6). + // We explicitly control initialization + #pragma warning(disable : 26495) +#endif + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + union persistent_universal_decompression_context + { + persistent_scalar_decompression_context_v0 scalar; + persistent_transform_decompression_context_v0 transform; + + ////////////////////////////////////////////////////////////////////////// + + persistent_universal_decompression_context() noexcept {} + const compressed_tracks* get_compressed_tracks() const { return scalar.tracks; } + compressed_tracks_version16 get_version() const { return scalar.tracks->get_version(); } + bool is_initialized() const { return scalar.is_initialized(); } + void reset() { scalar.tracks = nullptr; } + }; + + template + inline bool initialize_v0(persistent_universal_decompression_context& context, const compressed_tracks& tracks, const database_context* database) + { + const track_type8 track_type = tracks.get_track_type(); + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + return initialize_v0(context.scalar, tracks, database); + case track_type8::qvvf: + return initialize_v0(context.transform, tracks, database); + default: + ACL_ASSERT(false, "Invalid track type"); + return false; + } + } + + inline bool is_dirty_v0(const persistent_universal_decompression_context& context, const compressed_tracks& tracks) + { + if (!context.is_initialized()) + return true; // Always dirty if we are not initialized + + const track_type8 track_type = context.scalar.tracks->get_track_type(); + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + return is_dirty_v0(context.scalar, tracks); + case track_type8::qvvf: + return is_dirty_v0(context.transform, tracks); + default: + ACL_ASSERT(false, "Invalid track type"); + return true; + } + } + + template + inline void seek_v0(persistent_universal_decompression_context& context, float sample_time, sample_rounding_policy rounding_policy) + { + ACL_ASSERT(context.is_initialized(), "Context is not initialized"); + + const track_type8 track_type = context.scalar.tracks->get_track_type(); + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + seek_v0(context.scalar, sample_time, rounding_policy); + break; + case track_type8::qvvf: + seek_v0(context.transform, sample_time, rounding_policy); + break; + default: + ACL_ASSERT(false, "Invalid track type"); + break; + } + } + + template + inline void decompress_tracks_v0(const persistent_universal_decompression_context& context, track_writer_type& writer) + { + ACL_ASSERT(context.is_initialized(), "Context is not initialized"); + + const track_type8 track_type = context.scalar.tracks->get_track_type(); + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + decompress_tracks_v0(context.scalar, writer); + break; + case track_type8::qvvf: + decompress_tracks_v0(context.transform, writer); + break; + default: + ACL_ASSERT(false, "Invalid track type"); + break; + } + } + + template + inline void decompress_track_v0(const persistent_universal_decompression_context& context, uint32_t track_index, track_writer_type& writer) + { + ACL_ASSERT(context.is_initialized(), "Context is not initialized"); + + const track_type8 track_type = context.scalar.tracks->get_track_type(); + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + decompress_track_v0(context.scalar, track_index, writer); + break; + case track_type8::qvvf: + decompress_track_v0(context.transform, track_index, writer); + break; + default: + ACL_ASSERT(false, "Invalid track type"); + break; + } + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/fwd.h new file mode 100644 index 00000000..ad3ce5ea --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/fwd.h @@ -0,0 +1,37 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public ACL types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/compression/fwd.h" +#include "acl/core/fwd.h" +#include "acl/decompression/fwd.h" +#include "acl/io/fwd.h" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_reader.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_reader.h new file mode 100644 index 00000000..223732a6 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_reader.h @@ -0,0 +1,1378 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SJSON_CPP_PARSER) + +#include "acl/version.h" +#include "acl/io/clip_reader_error.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/track_array.h" +#include "acl/core/algorithm_types.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/string.h" +#include "acl/core/track_desc.h" +#include "acl/core/unique_ptr.h" +#include "acl/math/quatf.h" + +#include +#include +#include +#include +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // Enum to describe each type of raw content that an SJSON ACL file might contain. + enum class sjson_file_type + { + unknown, + raw_clip, + raw_track_list, + }; + + ////////////////////////////////////////////////////////////////////////// + // Raw transform tracks + struct sjson_raw_clip + { + track_array_qvvf track_list; + + track_array_qvvf additive_base_track_list; + additive_clip_format8 additive_format = additive_clip_format8::none; + + track_qvvf bind_pose; + + bool has_settings = false; + compression_settings settings; + }; + + ////////////////////////////////////////////////////////////////////////// + // Raw scalar tracks + struct sjson_raw_track_list + { + track_array track_list; + + track_qvvf bind_pose; + + bool has_settings = false; + compression_settings settings; + }; + + ////////////////////////////////////////////////////////////////////////// + // An SJSON ACL file reader. + class clip_reader + { + public: + clip_reader(iallocator& allocator, const char* sjson_input, size_t input_length) + : m_allocator(allocator) + , m_parser(sjson_input, input_length) + { + } + + ~clip_reader() + { + deallocate_type_array(m_allocator, m_bone_names, m_num_bones); + } + + sjson_file_type get_file_type() + { + reset_state(); + + if (!read_version()) + return sjson_file_type::unknown; + + if (m_parser.object_begins("clip")) + return sjson_file_type::raw_clip; + + if (m_parser.object_begins("track_list")) + return sjson_file_type::raw_track_list; + + return sjson_file_type::unknown; + } + + bool read_raw_clip(sjson_raw_clip& out_data) + { + reset_state(); + + if (!read_version()) + return false; + + if (!read_raw_clip_header()) + return false; + + if (!read_settings(&out_data.has_settings, &out_data.settings)) + return false; + + if (!create_skeleton(out_data.track_list, out_data.bind_pose)) + return false; + + if (!read_tracks(out_data.track_list, out_data.additive_base_track_list)) + return false; + + out_data.additive_format = !out_data.additive_base_track_list.is_empty() ? m_additive_format : additive_clip_format8::none; + + return nothing_follows(); + } + + bool read_raw_track_list(sjson_raw_track_list& out_data) + { + reset_state(); + + if (!read_version()) + return false; + + if (!read_raw_track_list_header()) + return false; + + if (!read_settings(&out_data.has_settings, &out_data.settings)) + return false; + + if (!create_track_list(out_data.track_list, out_data.bind_pose)) + return false; + + return nothing_follows(); + } + + clip_reader_error get_error() const { return m_error; } + + private: + clip_reader(const clip_reader&) = delete; + clip_reader& operator=(const clip_reader&) = delete; + + iallocator& m_allocator; + sjson::Parser m_parser; + clip_reader_error m_error; + + uint32_t m_version = 0; + uint32_t m_num_samples = 0; + float m_sample_rate = 0.0F; + sjson::StringView m_clip_name; + bool m_is_binary_exact = false; + additive_clip_format8 m_additive_format = additive_clip_format8::none; + sjson::StringView m_additive_base_name; + uint32_t m_additive_base_num_samples = 0; + float m_additive_base_sample_rate = 0.0F; + + bool m_has_settings = false; + float m_constant_rotation_threshold_angle = 0.0F; + float m_constant_translation_threshold = 0.0F; + float m_constant_scale_threshold = 0.0F; + float m_error_threshold = 0.0F; + + sjson::StringView* m_bone_names = nullptr; + uint32_t m_num_bones = 0; + + string convert_string(const sjson::StringView& str) const + { + return string(m_allocator, str.c_str(), str.size()); + } + + void reset_state() + { + m_parser.reset_state(); + set_error(clip_reader_error::None); + } + + bool read_version() + { + if (!m_parser.read("version", m_version)) + { + m_error = m_parser.get_error(); + return false; + } + + if (m_version > 5) + { + set_error(clip_reader_error::UnsupportedVersion); + return false; + } + + return true; + } + + bool read_raw_clip_header() + { + sjson::StringView additive_format; + + if (!m_parser.object_begins("clip")) + goto error; + + m_parser.try_read("name", m_clip_name, ""); + + double num_samples; + if (!m_parser.read("num_samples", num_samples)) + goto error; + + m_num_samples = static_cast(num_samples); + if (static_cast(m_num_samples) != num_samples) + { + set_error(clip_reader_error::UnsignedIntegerExpected); + return false; + } + + double sample_rate; + if (!m_parser.read("sample_rate", sample_rate)) + goto error; + + m_sample_rate = static_cast(sample_rate); + if (m_sample_rate <= 0.0F) + { + set_error(clip_reader_error::PositiveValueExpected); + return false; + } + + // Version 1 had an error_threshold field, skip it + double error_threshold; + if (m_version == 1 && !m_parser.read("error_threshold", error_threshold)) + goto error; + + // Optional value + m_parser.try_read("is_binary_exact", m_is_binary_exact, false); + + // Optional value + m_parser.try_read("additive_format", additive_format, "none"); + if (!get_additive_clip_format(additive_format.c_str(), m_additive_format)) + { + set_error(clip_reader_error::InvalidAdditiveClipFormat); + return false; + } + + m_parser.try_read("additive_base_name", m_additive_base_name, ""); + m_parser.try_read("additive_base_num_samples", num_samples, 1.0); + m_additive_base_num_samples = static_cast(num_samples); + if (static_cast(m_additive_base_num_samples) != num_samples || m_additive_base_num_samples == 0) + { + set_error(clip_reader_error::UnsignedIntegerExpected); + return false; + } + m_parser.try_read("additive_base_sample_rate", sample_rate, 30.0); + m_additive_base_sample_rate = static_cast(sample_rate); + if (m_additive_base_sample_rate <= 0.0F) + { + set_error(clip_reader_error::PositiveValueExpected); + return false; + } + + if (!m_parser.object_ends()) + goto error; + + return true; + + error: + m_error = m_parser.get_error(); + return false; + } + + bool read_raw_track_list_header() + { + if (!m_parser.object_begins("track_list")) + goto error; + + m_parser.try_read("name", m_clip_name, ""); + + double num_samples; + if (!m_parser.read("num_samples", num_samples)) + goto error; + + m_num_samples = static_cast(num_samples); + if (static_cast(m_num_samples) != num_samples) + { + set_error(clip_reader_error::UnsignedIntegerExpected); + return false; + } + + double sample_rate; + if (!m_parser.read("sample_rate", sample_rate)) + goto error; + + m_sample_rate = static_cast(sample_rate); + if (m_sample_rate <= 0.0F) + { + set_error(clip_reader_error::PositiveValueExpected); + return false; + } + + // Optional value + m_parser.try_read("is_binary_exact", m_is_binary_exact, false); + + if (!m_parser.object_ends()) + goto error; + + return true; + + error: + m_error = m_parser.get_error(); + return false; + } + + bool read_settings(bool* out_has_settings, compression_settings* out_settings) + { + m_has_settings = false; + + if (!m_parser.try_object_begins("settings")) + { + if (out_has_settings != nullptr) + *out_has_settings = false; + + // Settings are optional, all good + return true; + } + + compression_settings default_settings; + + sjson::StringView algorithm_name; + sjson::StringView compression_level; + sjson::StringView rotation_format; + sjson::StringView translation_format; + sjson::StringView scale_format; + bool rotation_range_reduction; + bool translation_range_reduction; + bool scale_range_reduction; + double constant_rotation_threshold_angle; + double constant_translation_threshold; + double constant_scale_threshold; + double error_threshold; + + double segmenting_ideal_num_samples = 0.0; // Legacy, no longer used + double segmenting_max_num_samples = 0.0; // Legacy, no longer used + + m_parser.try_read("algorithm_name", algorithm_name, get_algorithm_name(algorithm_type8::uniformly_sampled)); + m_parser.try_read("level", compression_level, get_compression_level_name(default_settings.level)); + m_parser.try_read("rotation_format", rotation_format, get_rotation_format_name(default_settings.rotation_format)); + m_parser.try_read("translation_format", translation_format, get_vector_format_name(default_settings.translation_format)); + m_parser.try_read("scale_format", scale_format, get_vector_format_name(default_settings.scale_format)); + m_parser.try_read("rotation_range_reduction", rotation_range_reduction, false); // Legacy, no longer used + m_parser.try_read("translation_range_reduction", translation_range_reduction, false); // Legacy, no longer used + m_parser.try_read("scale_range_reduction", scale_range_reduction, false); // Legacy, no longer used + + if (m_parser.try_object_begins("segmenting")) + { + bool segmenting_enabled; + bool segmenting_rotation_range_reduction; + bool segmenting_translation_range_reduction; + bool segmenting_scale_range_reduction; + m_parser.try_read("enabled", segmenting_enabled, false); // Legacy, no longer used + m_parser.try_read("ideal_num_samples", segmenting_ideal_num_samples, 0.0); // Legacy, no longer used + m_parser.try_read("max_num_samples", segmenting_max_num_samples, 0.0); // Legacy, no longer used + m_parser.try_read("rotation_range_reduction", segmenting_rotation_range_reduction, false); // Legacy, no longer used + m_parser.try_read("translation_range_reduction", segmenting_translation_range_reduction, false); // Legacy, no longer used + m_parser.try_read("scale_range_reduction", segmenting_scale_range_reduction, false); // Legacy, no longer used + + if (!m_parser.is_valid() || !m_parser.object_ends()) + goto parsing_error; + } + + // Skip deprecated values + m_parser.try_read("constant_rotation_threshold_angle", constant_rotation_threshold_angle, 0.00284714461); + m_parser.try_read("constant_translation_threshold", constant_translation_threshold, 0.001); + m_parser.try_read("constant_scale_threshold", constant_scale_threshold, 0.00001); + m_parser.try_read("error_threshold", error_threshold, 0.01); + + if (!m_parser.is_valid() || !m_parser.object_ends()) + goto parsing_error; + + if (out_has_settings != nullptr) + { + *out_has_settings = true; + + if (!get_compression_level(compression_level.c_str(), out_settings->level)) + goto invalid_value_error; + + if (!get_rotation_format(rotation_format.c_str(), out_settings->rotation_format)) + goto invalid_value_error; + + if (!get_vector_format(translation_format.c_str(), out_settings->translation_format)) + goto invalid_value_error; + + if (!get_vector_format(scale_format.c_str(), out_settings->scale_format)) + goto invalid_value_error; + + m_constant_rotation_threshold_angle = float(constant_rotation_threshold_angle); + m_constant_translation_threshold = float(constant_translation_threshold); + m_constant_scale_threshold = float(constant_scale_threshold); + m_error_threshold = float(error_threshold); + } + + m_has_settings = true; + return true; + + parsing_error: + m_error = m_parser.get_error(); + return false; + + invalid_value_error: + m_parser.get_position(m_error.line, m_error.column); + m_error.error = clip_reader_error::InvalidCompressionSetting; + return false; + } + + bool create_skeleton(track_array_qvvf& track_list, track_qvvf& bind_pose) + { + sjson::ParserState before_bones = m_parser.save_state(); + + uint32_t num_bones; + if (!process_each_bone(nullptr, nullptr, num_bones)) + return false; + + m_parser.restore_state(before_bones); + + m_num_bones = num_bones; + m_bone_names = allocate_type_array(m_allocator, num_bones); + + track_list = track_array_qvvf(m_allocator, num_bones); + track_list.set_name(convert_string(m_clip_name)); + bind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_bones, 30.0F); // 1 sample per track + bind_pose.set_name(string(m_allocator, "bind pose")); + + const uint32_t num_allocated_bones = num_bones; + + if (!process_each_bone(&track_list, &bind_pose, num_bones)) + return false; + + (void)num_allocated_bones; + ACL_ASSERT(num_bones == num_allocated_bones, "Number of bones read mismatch"); + + return true; + } + + static double hex_to_double(const sjson::StringView& value) + { + union UInt64ToDouble + { + uint64_t u64; + double dbl; + + constexpr explicit UInt64ToDouble(uint64_t u64_value) : u64(u64_value) {} + }; + + ACL_ASSERT(value.size() <= 16, "Invalid binary exact double value"); + uint64_t value_u64 = acl_impl::strtoull(value.c_str(), nullptr, 16); + return UInt64ToDouble(value_u64).dbl; + } + + static float hex_to_float(const sjson::StringView& value) + { + union UInt32ToFloat + { + uint32_t u32; + float flt; + + constexpr explicit UInt32ToFloat(uint32_t u32_value) : u32(u32_value) {} + }; + + ACL_ASSERT(value.size() <= 8, "Invalid binary exact float value"); + uint32_t value_u32 = safe_static_cast(std::strtoul(value.c_str(), nullptr, 16)); + return UInt32ToFloat(value_u32).flt; + } + + static rtm::quatd hex_to_quat(const sjson::StringView values[4]) + { + return rtm::quat_set(hex_to_double(values[0]), hex_to_double(values[1]), hex_to_double(values[2]), hex_to_double(values[3])); + } + + static rtm::vector4d hex_to_vector3(const sjson::StringView values[3]) + { + return rtm::vector_set(hex_to_double(values[0]), hex_to_double(values[1]), hex_to_double(values[2])); + } + + static rtm::float4f hex_to_float4f(const sjson::StringView values[4], uint32_t num_components) + { + ACL_ASSERT(num_components <= 4, "Invalid number of components"); + + rtm::float4f result = { 0.0F, 0.0F, 0.0F, 0.0F }; + float* result_ptr = &result.x; + + for (uint32_t component_index = 0; component_index < num_components; ++component_index) + result_ptr[component_index] = hex_to_float(values[component_index]); + + return result; + } + + bool process_each_bone(track_array_qvvf* tracks, track_qvvf* bind_pose, uint32_t& num_bones) + { + bool counting = tracks == nullptr; + num_bones = 0; + + if (!m_parser.array_begins("bones")) + goto error; + + for (uint32_t i = 0; !m_parser.try_array_ends(); ++i) + { + if (!m_parser.object_begins()) + goto error; + + sjson::StringView name; + if (!m_parser.read("name", name)) + goto error; + + if (tracks != nullptr) + m_bone_names[i] = name; + + sjson::StringView parent; + if (!m_parser.read("parent", parent)) + goto error; + + uint32_t parent_index = k_invalid_track_index; + if (tracks != nullptr && !parent.empty()) + { + parent_index = find_bone(parent); + if (parent_index == k_invalid_track_index) + { + set_error(clip_reader_error::NoParentBoneWithThatName); + return false; + } + } + + float vertex_distance; + if (!m_parser.read("vertex_distance", vertex_distance)) + goto error; + + rtm::qvvd bind_transform = rtm::qvv_identity(); + if (m_is_binary_exact) + { + sjson::StringView rotation[4]; + if (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting) + bind_transform.rotation = hex_to_quat(rotation); + + sjson::StringView translation[3]; + if (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting) + bind_transform.translation = hex_to_vector3(translation); + + sjson::StringView scale[3]; + if (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting) + bind_transform.scale = hex_to_vector3(scale); + } + else + { + double rotation[4] = { 0.0, 0.0, 0.0, 0.0 }; + if (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting) + bind_transform.rotation = rtm::quat_load(&rotation[0]); + + double translation[3] = { 0.0, 0.0, 0.0 }; + if (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting) + bind_transform.translation = rtm::vector_load3(&translation[0]); + + double scale[3] = { 0.0, 0.0, 0.0 }; + if (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting) + bind_transform.scale = rtm::vector_load3(&scale[0]); + } + + if (tracks != nullptr) + { + track_desc_transformf desc; + desc.parent_index = parent_index; + desc.shell_distance = vertex_distance; + + if (m_has_settings) + { + desc.precision = m_error_threshold; + desc.constant_rotation_threshold_angle = m_constant_rotation_threshold_angle; + desc.constant_translation_threshold = m_constant_translation_threshold; + desc.constant_scale_threshold = m_constant_scale_threshold; + } + + // Create a dummy track for now to hold our arguments + (*tracks)[i] = track_qvvf::make_ref(desc, nullptr, 0, 30.0F); + (*tracks)[i].set_name(convert_string(name)); + + rtm::qvvf bind_transform_ = rtm::qvv_cast(bind_transform); + bind_transform_.rotation = rtm::quat_normalize(bind_transform_.rotation); + + (*bind_pose)[i] = bind_transform_; + } + + if (!m_parser.object_ends()) + goto error; + + ++num_bones; + } + + return true; + + error: + m_error = m_parser.get_error(); + return false; + } + + uint32_t find_bone(const sjson::StringView& name) const + { + for (uint32_t i = 0; i < m_num_bones; ++i) + { + if (name == m_bone_names[i]) + return i; + } + + return k_invalid_track_index; + } + + float read_optional_float(const char* key, float default_value) + { + float value = default_value; + if (m_is_binary_exact) + { + sjson::StringView value_str; + if (m_parser.try_read(key, value_str, "")) + value = hex_to_float(value_str); + } + else + m_parser.try_read(key, value, default_value); + + return value; + } + + bool read_qvv_sample(rtm::qvvf& out_sample) + { + rtm::qvvf sample = rtm::qvv_identity(); + sjson::StringView values_str[4]; + double values[4] = { 0.0, 0.0, 0.0, 0.0 }; + + { + if (!m_parser.array_begins()) + return false; + + rtm::float4f value; + if (m_is_binary_exact) + { + if (!m_parser.read(values_str, 4)) + return false; + + value = hex_to_float4f(values_str, 4); + } + else + { + if (!m_parser.read(values, 4)) + return false; + + value = { static_cast(values[0]), static_cast(values[1]), static_cast(values[2]), static_cast(values[3])}; + } + + sample.rotation = rtm::quat_load(&value); + if (!rtm::quat_is_finite(sample.rotation)) + return false; + + if (!m_parser.array_ends()) + return false; + } + + if (!m_parser.read_comma()) + return false; + + { + if (!m_parser.array_begins()) + return false; + + rtm::float4f value; + if (m_is_binary_exact) + { + if (!m_parser.read(values_str, 3)) + return false; + + value = hex_to_float4f(values_str, 4); + } + else + { + if (!m_parser.read(values, 3)) + return false; + + value = { static_cast(values[0]), static_cast(values[1]), static_cast(values[2]), 0.0F }; + } + + sample.translation = vector_load(&value); + if (!rtm::vector_is_finite3(sample.translation)) + return false; + + if (!m_parser.array_ends()) + return false; + } + + if (!m_parser.read_comma()) + return false; + + { + if (!m_parser.array_begins()) + return false; + + rtm::float4f value; + if (m_is_binary_exact) + { + if (!m_parser.read(values_str, 3)) + return false; + + value = hex_to_float4f(values_str, 4); + } + else + { + if (!m_parser.read(values, 3)) + return false; + + value = { static_cast(values[0]), static_cast(values[1]), static_cast(values[2]), 0.0F }; + } + + sample.scale = vector_load(&value); + if (!rtm::vector_is_finite3(sample.scale)) + return false; + + if (!m_parser.array_ends()) + return false; + } + + out_sample = sample; + return true; + } + + bool process_track_list(track* tracks, track_qvvf* bind_pose, uint32_t& num_tracks) + { + const bool counting = tracks == nullptr; + track dummy; + track_type8 track_list_type = track_type8::float1f; + + num_tracks = 0; + + if (!m_parser.array_begins("tracks")) + goto error; + + for (uint32_t i = 0; !m_parser.try_array_ends(); ++i) + { + track& track_ = counting ? dummy : tracks[i]; + + if (!m_parser.object_begins()) + goto error; + + sjson::StringView name; + m_parser.try_read("name", name, ""); + + sjson::StringView type; + if (!m_parser.read("type", type)) + goto error; + + track_type8 track_type; + if (!get_track_type(type.c_str(), track_type)) + { + m_error.error = clip_reader_error::InvalidTrackType; + return false; + } + + if (num_tracks == 0) + { + // Found our first track, this is the type we'll use for every track + track_list_type = track_type; + } + else if (track_type != track_list_type) + { + // Track type doesn't match the type of the first track + m_error.error = clip_reader_error::InvalidTrackType; + return false; + } + + const uint32_t num_components = get_track_num_sample_elements(track_type); + ACL_ASSERT((num_components > 0 && num_components <= 4) || num_components == 12, "Must have between 1 and 4 components or 12"); + + track_desc_scalarf scalar_desc; + track_desc_transformf transform_desc; + + float precision = read_optional_float("precision", -1.0F); + + // Deprecated, no longer used + read_optional_float("constant_threshold", -1.0F); + + uint32_t output_index; + m_parser.try_read("output_index", output_index, i); + + m_parser.try_read("parent_index", transform_desc.parent_index, k_invalid_track_index); + + transform_desc.shell_distance = read_optional_float("shell_distance", transform_desc.shell_distance); + transform_desc.constant_rotation_threshold_angle = read_optional_float("constant_rotation_threshold_angle", transform_desc.constant_rotation_threshold_angle); + transform_desc.constant_translation_threshold = read_optional_float("constant_translation_threshold", transform_desc.constant_translation_threshold); + transform_desc.constant_scale_threshold = read_optional_float("constant_scale_threshold", transform_desc.constant_scale_threshold); + + scalar_desc.output_index = output_index; + transform_desc.output_index = output_index; + + if (track_type == track_type8::qvvf) + transform_desc.precision = precision < 0.0F ? transform_desc.precision : precision; + else + scalar_desc.precision = precision < 0.0F ? scalar_desc.precision : precision; + + rtm::qvvf bind_transform = rtm::qvv_identity(); + if (m_is_binary_exact) + { + sjson::StringView rotation[4]; + if (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting) + { + const rtm::float4f value = hex_to_float4f(rotation, 4); + bind_transform.rotation = rtm::quat_load(&value); + } + + sjson::StringView translation[3]; + if (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting) + { + const rtm::float4f value = hex_to_float4f(translation, 3); + bind_transform.translation = rtm::vector_load(&value); + } + + sjson::StringView scale[3]; + if (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting) + { + const rtm::float4f value = hex_to_float4f(scale, 3); + bind_transform.scale = rtm::vector_load(&value); + } + } + else + { + double rotation[4] = { 0.0, 0.0, 0.0, 0.0 }; + if (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting) + bind_transform.rotation = rtm::quat_normalize(rtm::quat_cast(rtm::quat_load(&rotation[0]))); + + double translation[3] = { 0.0, 0.0, 0.0 }; + if (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting) + bind_transform.translation = rtm::vector_cast(rtm::vector_load3(&translation[0])); + + double scale[3] = { 0.0, 0.0, 0.0 }; + if (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting) + bind_transform.scale = rtm::vector_cast(rtm::vector_load3(&scale[0])); + } + + if (bind_pose != nullptr) + (*bind_pose)[i] = bind_transform; + + if (!m_parser.array_begins("data")) + goto error; + + union track_samples_ptr_union + { + void* any; + float* float1f; + rtm::float2f* float2f; + rtm::float3f* float3f; + rtm::float4f* float4f; + rtm::vector4f* vector4f; + rtm::qvvf* qvvf; + }; + track_samples_ptr_union track_samples_typed = { nullptr }; + + switch (track_type) + { + case track_type8::float1f: + track_samples_typed.float1f = allocate_type_array(m_allocator, m_num_samples); + break; + case track_type8::float2f: + track_samples_typed.float2f = allocate_type_array(m_allocator, m_num_samples); + break; + case track_type8::float3f: + track_samples_typed.float3f = allocate_type_array(m_allocator, m_num_samples); + break; + case track_type8::float4f: + track_samples_typed.float4f = allocate_type_array(m_allocator, m_num_samples); + break; + case track_type8::vector4f: + track_samples_typed.vector4f = allocate_type_array(m_allocator, m_num_samples); + break; + case track_type8::qvvf: + track_samples_typed.qvvf = allocate_type_array(m_allocator, m_num_samples); + break; + default: + ACL_ASSERT(false, "Unsupported track type"); + // Initialize our value to itself, we'll never use it + // This silences an MSVC static analysis warning + track_samples_typed.any = &track_samples_typed; + break; + } + + bool has_error = false; + for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index) + { + if (!m_parser.array_begins()) + { + has_error = true; + break; + } + + if (m_is_binary_exact) + { + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + { + sjson::StringView values[4]; + if (m_parser.read(values, num_components)) + { + const rtm::float4f value = hex_to_float4f(values, num_components); + std::memcpy(track_samples_typed.float1f + (size_t(sample_index) * num_components), &value, sizeof(float) * num_components); + } + else + has_error = true; + break; + } + case track_type8::qvvf: + { + rtm::qvvf sample; + if (!read_qvv_sample(sample)) + has_error = true; + else + std::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf)); + break; + } + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + + if (has_error) + break; + } + else + { + switch (track_type) + { + case track_type8::float1f: + case track_type8::float2f: + case track_type8::float3f: + case track_type8::float4f: + case track_type8::vector4f: + { + double values[4] = { 0.0, 0.0, 0.0, 0.0 }; + if (m_parser.read(values, num_components)) + { + const rtm::float4f value = { static_cast(values[0]), static_cast(values[1]), static_cast(values[2]), static_cast(values[3]) }; + std::memcpy(track_samples_typed.float1f + (size_t(sample_index) * num_components), &value, sizeof(float) * num_components); + } + else + has_error = true; + break; + } + case track_type8::qvvf: + { + rtm::qvvf sample; + if (!read_qvv_sample(sample)) + has_error = true; + else + std::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf)); + break; + } + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + + if (has_error) + break; + } + + if (!has_error && !m_parser.array_ends()) + { + has_error = true; + break; + } + } + + if (!has_error && !m_parser.array_ends()) + { + has_error = true; + break; + } + + if (!has_error && !m_parser.object_ends()) + { + has_error = true; + break; + } + + if (has_error) + { + switch (track_type) + { + case track_type8::float1f: + deallocate_type_array(m_allocator, track_samples_typed.float1f, m_num_samples); + break; + case track_type8::float2f: + deallocate_type_array(m_allocator, track_samples_typed.float2f, m_num_samples); + break; + case track_type8::float3f: + deallocate_type_array(m_allocator, track_samples_typed.float3f, m_num_samples); + break; + case track_type8::float4f: + deallocate_type_array(m_allocator, track_samples_typed.float4f, m_num_samples); + break; + case track_type8::vector4f: + deallocate_type_array(m_allocator, track_samples_typed.vector4f, m_num_samples); + break; + case track_type8::qvvf: + deallocate_type_array(m_allocator, track_samples_typed.qvvf, m_num_samples); + break; + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + } + else + { + switch (track_type) + { + case track_type8::float1f: + track_ = track_float1f::make_owner(scalar_desc, m_allocator, track_samples_typed.float1f, m_num_samples, m_sample_rate); + break; + case track_type8::float2f: + track_ = track_float2f::make_owner(scalar_desc, m_allocator, track_samples_typed.float2f, m_num_samples, m_sample_rate); + break; + case track_type8::float3f: + track_ = track_float3f::make_owner(scalar_desc, m_allocator, track_samples_typed.float3f, m_num_samples, m_sample_rate); + break; + case track_type8::float4f: + track_ = track_float4f::make_owner(scalar_desc, m_allocator, track_samples_typed.float4f, m_num_samples, m_sample_rate); + break; + case track_type8::vector4f: + track_ = track_vector4f::make_owner(scalar_desc, m_allocator, track_samples_typed.vector4f, m_num_samples, m_sample_rate); + break; + case track_type8::qvvf: + track_ = track_qvvf::make_owner(transform_desc, m_allocator, track_samples_typed.qvvf, m_num_samples, m_sample_rate); + break; + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + } + + track_.set_name(convert_string(name)); + + num_tracks++; + } + + return true; + + error: + m_error = m_parser.get_error(); + return false; + } + + bool create_track_list(track_array& track_list, track_qvvf& bind_pose) + { + const sjson::ParserState before_tracks = m_parser.save_state(); + + uint32_t num_tracks; + if (!process_track_list(nullptr, nullptr, num_tracks)) + return false; + + m_parser.restore_state(before_tracks); + + track_list = track_array(m_allocator, num_tracks); + track_list.set_name(convert_string(m_clip_name)); + bind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_tracks, 30.0F); // 1 sample per track + bind_pose.set_name(string(m_allocator, "bind pose")); + + if (!process_track_list(track_list.begin(), &bind_pose, num_tracks)) + return false; + + ACL_ASSERT(num_tracks == track_list.get_num_tracks(), "Number of tracks read mismatch"); + + return true; + } + + bool read_tracks(track_array_qvvf& track_list, track_array_qvvf& additive_base_track_list) + { + const uint32_t num_transforms = track_list.get_num_tracks(); + + if (m_parser.try_array_begins("base_tracks")) + { + // Copy our metadata from the actual clip + additive_base_track_list = track_array_qvvf(m_allocator, num_transforms); + additive_base_track_list.set_name(string(m_allocator, "additive base")); + for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index) + additive_base_track_list[transform_index].get_description() = track_list[transform_index].get_description(); + + while (!m_parser.try_array_ends()) + { + if (!m_parser.object_begins()) + goto error; + + sjson::StringView name; + if (!m_parser.read("name", name)) + goto error; + + const uint32_t bone_index = find_bone(name); + if (bone_index == k_invalid_track_index) + { + set_error(clip_reader_error::NoBoneWithThatName); + return false; + } + + track_desc_transformf desc = additive_base_track_list[bone_index].get_description(); // Copy our description + desc.output_index = bone_index; + + track_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_additive_base_num_samples, m_additive_base_sample_rate); + track.set_name(convert_string(name)); + + if (m_parser.try_array_begins("rotations")) + { + if (!read_track_rotations(track, m_additive_base_num_samples) || !m_parser.array_ends()) + goto error; + } + else + { + for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index) + track[sample_index].rotation = rtm::quat_identity(); + } + + if (m_parser.try_array_begins("translations")) + { + if (!read_track_translations(track, m_additive_base_num_samples) || !m_parser.array_ends()) + goto error; + } + else + { + for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index) + track[sample_index].translation = rtm::vector_zero(); + } + + if (m_parser.try_array_begins("scales")) + { + if (!read_track_scales(track, m_additive_base_num_samples) || !m_parser.array_ends()) + goto error; + } + else + { + for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index) + track[sample_index].scale = rtm::vector_set(1.0F); + } + + additive_base_track_list[bone_index] = std::move(track); + + if (!m_parser.object_ends()) + goto error; + } + } + + if (!m_parser.array_begins("tracks")) + goto error; + + while (!m_parser.try_array_ends()) + { + if (!m_parser.object_begins()) + goto error; + + sjson::StringView name; + if (!m_parser.read("name", name)) + goto error; + + const uint32_t bone_index = find_bone(name); + if (bone_index == k_invalid_track_index) + { + set_error(clip_reader_error::NoBoneWithThatName); + return false; + } + + track_desc_transformf desc = track_list[bone_index].get_description(); // Copy our description + desc.output_index = bone_index; + + track_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate); + track.set_name(convert_string(name)); + + if (m_parser.try_array_begins("rotations")) + { + if (!read_track_rotations(track, m_num_samples) || !m_parser.array_ends()) + goto error; + } + else + { + for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index) + track[sample_index].rotation = rtm::quat_identity(); + } + + if (m_parser.try_array_begins("translations")) + { + if (!read_track_translations(track, m_num_samples) || !m_parser.array_ends()) + goto error; + } + else + { + for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index) + track[sample_index].translation = rtm::vector_zero(); + } + + if (m_parser.try_array_begins("scales")) + { + if (!read_track_scales(track, m_num_samples) || !m_parser.array_ends()) + goto error; + } + else + { + for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index) + track[sample_index].scale = rtm::vector_set(1.0F); + } + + track_list[bone_index] = std::move(track); + + if (!m_parser.object_ends()) + goto error; + } + + return true; + + error: + m_error = m_parser.get_error(); + return false; + } + + bool read_track_rotations(track_qvvf& track, uint32_t num_samples_expected) + { + for (uint32_t i = 0; i < num_samples_expected; ++i) + { + if (!m_parser.array_begins()) + return false; + + rtm::quatd rotation; + + if (m_is_binary_exact) + { + sjson::StringView values[4]; + if (!m_parser.read(values, 4)) + return false; + + rotation = hex_to_quat(values); + } + else + { + double values[4] = { 0.0, 0.0, 0.0, 0.0 }; + if (!m_parser.read(values, 4)) + return false; + + rotation = rtm::quat_load(values); + } + + if (!m_parser.array_ends()) + return false; + + track[i].rotation = rtm::quat_normalize(rtm::quat_cast(rotation)); + } + + return true; + } + + bool read_track_translations(track_qvvf& track, uint32_t num_samples_expected) + { + for (uint32_t i = 0; i < num_samples_expected; ++i) + { + if (!m_parser.array_begins()) + return false; + + rtm::vector4d translation; + + if (m_is_binary_exact) + { + sjson::StringView values[3]; + if (!m_parser.read(values, 3)) + return false; + + translation = hex_to_vector3(values); + } + else + { + double values[3]; + if (!m_parser.read(values, 3)) + return false; + + translation = rtm::vector_load3(values); + } + + if (!m_parser.array_ends()) + return false; + + track[i].translation = rtm::vector_cast(translation); + } + + return true; + } + + bool read_track_scales(track_qvvf& track, uint32_t num_samples_expected) + { + for (uint32_t i = 0; i < num_samples_expected; ++i) + { + if (!m_parser.array_begins()) + return false; + + rtm::vector4d scale; + + if (m_is_binary_exact) + { + sjson::StringView values[3]; + if (!m_parser.read(values, 3)) + return false; + + scale = hex_to_vector3(values); + } + else + { + double values[3]; + if (!m_parser.read(values, 3)) + return false; + + scale = rtm::vector_load3(values); + } + + if (!m_parser.array_ends()) + return false; + + track[i].scale = rtm::vector_cast(scale); + } + + return true; + } + + bool nothing_follows() + { + if (!m_parser.remainder_is_comments_and_whitespace()) + { + m_error = m_parser.get_error(); + return false; + } + + return true; + } + + void set_error(uint32_t reason) + { + m_parser.get_position(m_error.line, m_error.column); + m_error.error = reason; + } + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP + +#endif // #if defined(SJSON_CPP_PARSER) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_reader_error.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_reader_error.h new file mode 100644 index 00000000..a739fcbc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_reader_error.h @@ -0,0 +1,94 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SJSON_CPP_PARSER) + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + struct clip_reader_error : sjson::ParserError + { + enum : uint32_t + { + UnsupportedVersion = sjson::ParserError::Last, + NoParentBoneWithThatName, + NoBoneWithThatName, + UnsignedIntegerExpected, + InvalidCompressionSetting, + InvalidAdditiveClipFormat, + PositiveValueExpected, + InvalidTrackType, + }; + + clip_reader_error() noexcept = default; + + clip_reader_error(const sjson::ParserError& e) + { + error = e.error; + line = e.line; + column = e.column; + } + + virtual const char* get_description() const override + { + switch (error) + { + case UnsupportedVersion: + return "This library does not support this version of animation file"; + case NoParentBoneWithThatName: + return "There is no parent bone with this name"; + case NoBoneWithThatName: + return "The skeleton does not define a bone with this name"; + case UnsignedIntegerExpected: + return "An unsigned integer is expected here"; + case InvalidCompressionSetting: + return "Invalid compression settings provided"; + case InvalidAdditiveClipFormat: + return "Invalid additive clip format provided"; + case PositiveValueExpected: + return "A positive value is expected here"; + case InvalidTrackType: + return "Invalid raw track type"; + default: + return sjson::ParserError::get_description(); + } + } + }; + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP + +#endif // #if defined(SJSON_CPP_PARSER) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_writer.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_writer.h new file mode 100644 index 00000000..cad5bfe8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/clip_writer.h @@ -0,0 +1,379 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SJSON_CPP_WRITER) + +#include "acl/version.h" +#include "acl/compression/compression_settings.h" +#include "acl/compression/track_array.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/iallocator.h" +#include "acl/core/error.h" +#include "acl/core/track_desc.h" + +#include +#include + +#include +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + namespace acl_impl + { + inline const char* format_hex_float(float value, char* buffer, size_t buffer_size) + { + union FloatToUInt32 + { + uint32_t u32; + float flt; + + constexpr explicit FloatToUInt32(float flt_value) : flt(flt_value) {} + }; + + snprintf(buffer, buffer_size, "%" PRIX32, FloatToUInt32(value).u32); + + return buffer; + }; + + inline void write_sjson_settings(const compression_settings& settings, sjson::Writer& writer) + { + writer["settings"] = [&](sjson::ObjectWriter& settings_writer) + { + settings_writer["level"] = get_compression_level_name(settings.level); + settings_writer["rotation_format"] = get_rotation_format_name(settings.rotation_format); + settings_writer["translation_format"] = get_vector_format_name(settings.translation_format); + settings_writer["scale_format"] = get_vector_format_name(settings.scale_format); + }; + writer.insert_newline(); + } + + inline void write_sjson_header(const track_array& track_list, sjson::Writer& writer) + { + writer["version"] = 5; + writer.insert_newline(); + + writer["track_list"] = [&](sjson::ObjectWriter& header_writer) + { + header_writer["name"] = track_list.get_name().c_str(); + header_writer["num_samples"] = track_list.get_num_samples_per_track(); + header_writer["sample_rate"] = track_list.get_sample_rate(); + header_writer["is_binary_exact"] = true; + +#if 0 // TODO + clip_writer["additive_format"] = get_additive_clip_format_name(clip.get_additive_format()); + + const AnimationClip* base_clip = clip.get_additive_base(); + if (base_clip != nullptr) + { + clip_writer["additive_base_name"] = base_clip->get_name().c_str(); + clip_writer["additive_base_num_samples"] = base_clip->get_num_samples(); + clip_writer["additive_base_sample_rate"] = base_clip->get_sample_rate(); + } +#endif + }; + writer.insert_newline(); + } + + inline void write_sjson_scalar_desc(const track_desc_scalarf& desc, sjson::ObjectWriter& writer) + { + char buffer[32] = { 0 }; + + writer["precision"] = format_hex_float(desc.precision, buffer, sizeof(buffer)); + writer["output_index"] = desc.output_index; + } + + inline void write_sjson_transform_desc(const track_desc_transformf& desc, sjson::ObjectWriter& writer) + { + char buffer[32] = { 0 }; + + writer["precision"] = format_hex_float(desc.precision, buffer, sizeof(buffer)); + writer["output_index"] = desc.output_index; + writer["parent_index"] = desc.parent_index; + writer["shell_distance"] = format_hex_float(desc.shell_distance, buffer, sizeof(buffer)); + writer["constant_rotation_threshold_angle"] = format_hex_float(desc.constant_rotation_threshold_angle, buffer, sizeof(buffer)); + writer["constant_translation_threshold"] = format_hex_float(desc.constant_translation_threshold, buffer, sizeof(buffer)); + writer["constant_scale_threshold"] = format_hex_float(desc.constant_scale_threshold, buffer, sizeof(buffer)); + } + + inline void write_sjson_tracks(const track_array& track_list, sjson::Writer& writer) + { + char buffer[32] = { 0 }; + + writer["tracks"] = [&](sjson::ArrayWriter& tracks_writer) + { + const uint32_t num_tracks = track_list.get_num_tracks(); + if (num_tracks > 0) + tracks_writer.push_newline(); + + for (const track& track_ : track_list) + { + tracks_writer.push([&](sjson::ObjectWriter& track_writer) + { + track_writer["name"] = track_.get_name().c_str(); + track_writer["type"] = get_track_type_name(track_.get_type()); + + switch (track_.get_type()) + { + case track_type8::float1f: + { + const track_float1f& track__ = track_cast(track_); + write_sjson_scalar_desc(track__.get_description(), track_writer); + + track_writer["data"] = [&](sjson::ArrayWriter& data_writer) + { + const uint32_t num_samples = track__.get_num_samples(); + if (num_samples > 0) + data_writer.push_newline(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + data_writer.push([&](sjson::ArrayWriter& sample_writer) + { + const float sample = track__[sample_index]; + sample_writer.push(acl_impl::format_hex_float(sample, buffer, sizeof(buffer))); + }); + data_writer.push_newline(); + } + }; + break; + } + case track_type8::float2f: + { + const track_float2f& track__ = track_cast(track_); + write_sjson_scalar_desc(track__.get_description(), track_writer); + + track_writer["data"] = [&](sjson::ArrayWriter& data_writer) + { + const uint32_t num_samples = track__.get_num_samples(); + if (num_samples > 0) + data_writer.push_newline(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + data_writer.push([&](sjson::ArrayWriter& sample_writer) + { + const rtm::float2f& sample = track__[sample_index]; + sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer))); + }); + data_writer.push_newline(); + } + }; + break; + } + case track_type8::float3f: + { + const track_float3f& track__ = track_cast(track_); + write_sjson_scalar_desc(track__.get_description(), track_writer); + + track_writer["data"] = [&](sjson::ArrayWriter& data_writer) + { + const uint32_t num_samples = track__.get_num_samples(); + if (num_samples > 0) + data_writer.push_newline(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + data_writer.push([&](sjson::ArrayWriter& sample_writer) + { + const rtm::float3f& sample = track__[sample_index]; + sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(sample.z, buffer, sizeof(buffer))); + }); + data_writer.push_newline(); + } + }; + break; + } + case track_type8::float4f: + { + const track_float4f& track__ = track_cast(track_); + write_sjson_scalar_desc(track__.get_description(), track_writer); + + track_writer["data"] = [&](sjson::ArrayWriter& data_writer) + { + const uint32_t num_samples = track__.get_num_samples(); + if (num_samples > 0) + data_writer.push_newline(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + data_writer.push([&](sjson::ArrayWriter& sample_writer) + { + const rtm::float4f& sample = track__[sample_index]; + sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(sample.z, buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(sample.w, buffer, sizeof(buffer))); + }); + data_writer.push_newline(); + } + }; + break; + } + case track_type8::vector4f: + { + const track_vector4f& track__ = track_cast(track_); + write_sjson_scalar_desc(track__.get_description(), track_writer); + + track_writer["data"] = [&](sjson::ArrayWriter& data_writer) + { + const uint32_t num_samples = track__.get_num_samples(); + if (num_samples > 0) + data_writer.push_newline(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + data_writer.push([&](sjson::ArrayWriter& sample_writer) + { + const rtm::vector4f& sample = track__[sample_index]; + sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample), buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample), buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample), buffer, sizeof(buffer))); + sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_w(sample), buffer, sizeof(buffer))); + }); + data_writer.push_newline(); + } + }; + break; + } + case track_type8::qvvf: + { + const track_qvvf& track__ = track_cast(track_); + write_sjson_transform_desc(track__.get_description(), track_writer); + + track_writer["data"] = [&](sjson::ArrayWriter& data_writer) + { + const uint32_t num_samples = track__.get_num_samples(); + if (num_samples > 0) + data_writer.push_newline(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + data_writer.push([&](sjson::ArrayWriter& sample_writer) + { + const rtm::qvvf& sample = track__[sample_index]; + sample_writer.push([&](sjson::ArrayWriter& rotation_writer) + { + rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_x(sample.rotation), buffer, sizeof(buffer))); + rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_y(sample.rotation), buffer, sizeof(buffer))); + rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_z(sample.rotation), buffer, sizeof(buffer))); + rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_w(sample.rotation), buffer, sizeof(buffer))); + }); + sample_writer.push([&](sjson::ArrayWriter& translation_writer) + { + translation_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample.translation), buffer, sizeof(buffer))); + translation_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample.translation), buffer, sizeof(buffer))); + translation_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample.translation), buffer, sizeof(buffer))); + }); + sample_writer.push([&](sjson::ArrayWriter& scale_writer) + { + scale_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample.scale), buffer, sizeof(buffer))); + scale_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample.scale), buffer, sizeof(buffer))); + scale_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample.scale), buffer, sizeof(buffer))); + }); + }); + data_writer.push_newline(); + } + }; + break; + } + default: + ACL_ASSERT(false, "Unknown track type"); + break; + } + }); + } + }; + writer.insert_newline(); + } + + inline error_result write_track_list(const track_array& track_list, const compression_settings* settings, const char* acl_filename) + { + if (acl_filename == nullptr) + return error_result("'acl_filename' cannot be NULL!"); + + const size_t filename_len = std::strlen(acl_filename); + if (filename_len < 10 || strncmp(acl_filename + filename_len - 10, ".acl.sjson", 10) != 0) + return error_result("'acl_filename' file must be an ACL SJSON file of the form: *.acl.sjson"); + + std::FILE* file = nullptr; + +#ifdef _WIN32 + char path[64 * 1024] = { 0 }; + snprintf(path, get_array_size(path), "\\\\?\\%s", acl_filename); + fopen_s(&file, path, "w"); +#else + file = fopen(acl_filename, "w"); +#endif + + if (file == nullptr) + return error_result("Failed to open ACL file for writing"); + + sjson::FileStreamWriter stream_writer(file); + sjson::Writer writer(stream_writer); + + write_sjson_header(track_list, writer); + + if (settings != nullptr) + write_sjson_settings(*settings, writer); + + write_sjson_tracks(track_list, writer); + + std::fclose(file); + return error_result(); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Write out an SJSON ACL track list file. + ////////////////////////////////////////////////////////////////////////// + inline error_result write_track_list(const track_array& track_list, const char* acl_filename) + { + return acl_impl::write_track_list(track_list, nullptr, acl_filename); + } + + ////////////////////////////////////////////////////////////////////////// + // Write out an SJSON ACL track list file. + ////////////////////////////////////////////////////////////////////////// + inline error_result write_track_list(const track_array& track_list, const compression_settings& settings, const char* acl_filename) + { + return acl_impl::write_track_list(track_list, &settings, acl_filename); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP + +#endif // #if defined(SJSON_CPP_WRITER) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/fwd.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/fwd.h new file mode 100644 index 00000000..08c8df13 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/io/fwd.h @@ -0,0 +1,47 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" + +//////////////////////////////////////////////////////////////////////////////// +// This header provides forward declarations for all public ACL types. +// Forward declaring symbols from a 3rd party library is a bad idea, use this +// header instead. +// See also: https://blog.libtorrent.org/2017/12/forward-declarations-and-abi/ +//////////////////////////////////////////////////////////////////////////////// + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + struct clip_reader_error; + enum class sjson_file_type; + struct sjson_raw_clip; + struct sjson_raw_track_list; + class clip_reader; + + ACL_IMPL_VERSION_NAMESPACE_END +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/quat_packing.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/quat_packing.h new file mode 100644 index 00000000..28a005f2 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/quat_packing.h @@ -0,0 +1,110 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/memory_utils.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/math/scalar_packing.h" +#include "acl/math/vector4_packing.h" + +#include +#include +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline void RTM_SIMD_CALL pack_quat_128(rtm::quatf_arg0 rotation, uint8_t* out_rotation_data) + { + pack_vector4_128(rtm::quat_to_vector(rotation), out_rotation_data); + } + + inline rtm::quatf RTM_SIMD_CALL unpack_quat_128(const uint8_t* data_ptr) + { + return rtm::vector_to_quat(unpack_vector4_128(data_ptr)); + } + + inline void RTM_SIMD_CALL pack_quat_96(rtm::quatf_arg0 rotation, uint8_t* out_rotation_data) + { + rtm::vector4f rotation_xyz = rtm::quat_to_vector(rtm::quat_ensure_positive_w(rotation)); + pack_vector3_96(rotation_xyz, out_rotation_data); + } + + // Assumes the 'data_ptr' is padded in order to load up to 16 bytes from it + inline rtm::quatf RTM_SIMD_CALL unpack_quat_96_unsafe(const uint8_t* data_ptr) + { + rtm::vector4f rotation_xyz = unpack_vector3_96_unsafe(data_ptr); + return rtm::quat_from_positive_w(rotation_xyz); + } + + inline void RTM_SIMD_CALL pack_quat_48(rtm::quatf_arg0 rotation, uint8_t* out_rotation_data) + { + rtm::vector4f rotation_xyz = rtm::quat_to_vector(rtm::quat_ensure_positive_w(rotation)); + pack_vector3_s48_unsafe(rotation_xyz, out_rotation_data); + } + + inline rtm::quatf RTM_SIMD_CALL unpack_quat_48(const uint8_t* data_ptr) + { + rtm::vector4f rotation_xyz = unpack_vector3_s48_unsafe(data_ptr); + return rtm::quat_from_positive_w(rotation_xyz); + } + + inline void RTM_SIMD_CALL pack_quat_32(rtm::quatf_arg0 rotation, uint8_t* out_rotation_data) + { + rtm::vector4f rotation_xyz = rtm::quat_to_vector(rtm::quat_ensure_positive_w(rotation)); + pack_vector3_32(rotation_xyz, 11, 11, 10, false, out_rotation_data); + } + + inline rtm::quatf RTM_SIMD_CALL unpack_quat_32(const uint8_t* data_ptr) + { + rtm::vector4f rotation_xyz = unpack_vector3_32(11, 11, 10, false, data_ptr); + return rtm::quat_from_positive_w(rotation_xyz); + } + + ////////////////////////////////////////////////////////////////////////// + + constexpr uint32_t get_packed_rotation_size(rotation_format8 format) + { + return format == rotation_format8::quatf_full ? (sizeof(float) * 4) : (sizeof(float) * 3); + } + + constexpr uint32_t get_range_reduction_rotation_size(rotation_format8 format) + { + return format == rotation_format8::quatf_full ? (sizeof(float) * 8) : (sizeof(float) * 6); + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/quatf.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/quatf.h new file mode 100644 index 00000000..02745b3e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/quatf.h @@ -0,0 +1,134 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + RTM_DISABLE_SECURITY_COOKIE_CHECK inline rtm::quatf RTM_SIMD_CALL quat_lerp_no_normalization(rtm::quatf_arg0 start, rtm::quatf_arg1 end, float alpha) RTM_NO_EXCEPT + { + using namespace rtm; + +#if defined(RTM_SSE2_INTRINSICS) + // Calculate the vector4 dot product: dot(start, end) + __m128 dot; +#if defined(RTM_SSE4_INTRINSICS) + // The dpps instruction isn't as accurate but we don't care here, we only need the sign of the + // dot product. If both rotations are on opposite ends of the hypersphere, the result will be + // very negative. If we are on the edge, the rotations are nearly opposite but not quite which + // means that the linear interpolation here will have terrible accuracy to begin with. It is designed + // for interpolating rotations that are reasonably close together. The bias check is mainly necessary + // because the W component is often kept positive which flips the sign. + // Using the dpps instruction reduces the number of registers that we need and helps the function get + // inlined. + dot = _mm_dp_ps(start, end, 0xFF); +#else + { + __m128 x2_y2_z2_w2 = _mm_mul_ps(start, end); + __m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2)); + __m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0); + __m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1)); + __m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0); + // Shuffle the dot product to all SIMD lanes, there is no _mm_and_ss and loading + // the constant from memory with the 'and' instruction is faster, it uses fewer registers + // and fewer instructions + dot = _mm_shuffle_ps(x2y2z2w2_0_0_0, x2y2z2w2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0)); + } +#endif + + // Calculate the bias, if the dot product is positive or zero, there is no bias + // but if it is negative, we want to flip the 'end' rotation XYZW components + __m128 bias = _mm_and_ps(dot, _mm_set_ps1(-0.0F)); + + // Lerp the rotation after applying the bias + // ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias)) + __m128 alpha_ = _mm_set_ps1(alpha); + __m128 interpolated_rotation = _mm_add_ps(_mm_sub_ps(start, _mm_mul_ps(alpha_, start)), _mm_mul_ps(alpha_, _mm_xor_ps(end, bias))); + + // Due to the interpolation, the result might not be anywhere near normalized! + // Make sure to normalize afterwards before using + return interpolated_rotation; +#elif defined (RTM_NEON64_INTRINSICS) + // On ARM64 with NEON, we load 1.0 once and use it twice which is faster than + // using a AND/XOR with the bias (same number of instructions) + float dot = vector_dot(start, end); + float bias = dot >= 0.0F ? 1.0F : -1.0F; + + // ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias)) + vector4f interpolated_rotation = vector_mul_add(vector_mul(end, bias), alpha, vector_neg_mul_sub(start, alpha, start)); + + // Due to the interpolation, the result might not be anywhere near normalized! + // Make sure to normalize afterwards before using + return interpolated_rotation; +#elif defined(RTM_NEON_INTRINSICS) + // Calculate the vector4 dot product: dot(start, end) + float32x4_t x2_y2_z2_w2 = vmulq_f32(start, end); + float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2); + float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2); + float32x2_t x2z2_y2w2 = vadd_f32(x2_y2, z2_w2); + float32x2_t x2y2z2w2 = vpadd_f32(x2z2_y2w2, x2z2_y2w2); + + // Calculate the bias, if the dot product is positive or zero, there is no bias + // but if it is negative, we want to flip the 'end' rotation XYZW components + // On ARM-v7-A, the AND/XOR trick is faster than the cmp/fsel + uint32x2_t bias = vand_u32(vreinterpret_u32_f32(x2y2z2w2), vdup_n_u32(0x80000000)); + + // Lerp the rotation after applying the bias + // ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias)) + float32x4_t end_biased = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(end), vcombine_u32(bias, bias))); + float32x4_t interpolated_rotation = vmlaq_n_f32(vmlsq_n_f32(start, start, alpha), end_biased, alpha); + + // Due to the interpolation, the result might not be anywhere near normalized! + // Make sure to normalize afterwards before using + return interpolated_rotation; +#else + // To ensure we take the shortest path, we apply a bias if the dot product is negative + vector4f start_vector = quat_to_vector(start); + vector4f end_vector = quat_to_vector(end); + float dot = vector_dot(start_vector, end_vector); + float bias = dot >= 0.0F ? 1.0F : -1.0F; + // ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias)) + vector4f interpolated_rotation = vector_mul_add(vector_mul(end_vector, bias), alpha, vector_neg_mul_sub(start_vector, alpha, start_vector)); + + // Due to the interpolation, the result might not be anywhere near normalized! + // Make sure to normalize afterwards before using + return vector_to_quat(interpolated_rotation); +#endif + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/scalar_packing.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/scalar_packing.h new file mode 100644 index 00000000..43304073 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/scalar_packing.h @@ -0,0 +1,180 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/memory_utils.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + inline uint32_t pack_scalar_unsigned(float input, uint32_t num_bits) + { + ACL_ASSERT(num_bits < 31, "Attempting to pack on too many bits"); + ACL_ASSERT(input >= 0.0F && input <= 1.0F, "Expected normalized unsigned input value: %f", input); + const uint32_t max_value = (1 << num_bits) - 1; + return static_cast(rtm::scalar_round_symmetric(input * rtm::scalar_safe_to_float(max_value))); + } + + inline float unpack_scalar_unsigned(uint32_t input, uint32_t num_bits) + { + ACL_ASSERT(num_bits < 31, "Attempting to unpack from too many bits"); + const uint32_t max_value = (1 << num_bits) - 1; + ACL_ASSERT(input <= max_value, "Input value too large: %ull", input); + // For performance reasons, unpacking is faster when multiplying with the reciprocal + const float inv_max_value = 1.0F / rtm::scalar_safe_to_float(max_value); + return rtm::scalar_safe_to_float(input) * inv_max_value; + } + + inline uint32_t pack_scalar_signed(float input, uint32_t num_bits) + { + return pack_scalar_unsigned((input * 0.5F) + 0.5F, num_bits); + } + + inline float unpack_scalar_signed(uint32_t input, uint32_t num_bits) + { + return (unpack_scalar_unsigned(input, num_bits) * 2.0F) - 1.0F; + } + + // Assumes the 'vector_data' is in big-endian order and is padded in order to load up to 8 bytes from it + inline rtm::scalarf RTM_SIMD_CALL unpack_scalarf_32_unsafe(const uint8_t* vector_data, uint32_t bit_offset) + { +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t x32 = uint32_t(vector_u64); + + return rtm::scalarf{ _mm_castsi128_ps(_mm_set1_epi32(x32)) }; +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + const uint32x2_t xy = vcreate_u32(x64); + return vget_lane_f32(vreinterpret_f32_u32(xy), 0); +#else + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + const float x = aligned_load(&x64); + + return rtm::scalar_set(x); +#endif + } + + // Assumes the 'vector_data' is in big-endian order and padded in order to load up to 8 bytes from it + inline rtm::scalarf RTM_SIMD_CALL unpack_scalarf_uXX_unsafe(uint32_t num_bits, const uint8_t* vector_data, uint32_t bit_offset) + { + ACL_ASSERT(num_bits <= 19, "This function does not support reading more than 19 bits per component"); + + struct PackedTableEntry + { + explicit constexpr PackedTableEntry(uint8_t num_bits_) + : max_value(num_bits_ == 0 ? 1.0F : (1.0F / float((1 << num_bits_) - 1))) + , mask((1 << num_bits_) - 1) + {} + + float max_value; + uint32_t mask; + }; + + // TODO: We technically don't need the first 3 entries, which could save a few bytes + alignas(64) static constexpr PackedTableEntry k_packed_constants[20] = + { + PackedTableEntry(0), PackedTableEntry(1), PackedTableEntry(2), PackedTableEntry(3), + PackedTableEntry(4), PackedTableEntry(5), PackedTableEntry(6), PackedTableEntry(7), + PackedTableEntry(8), PackedTableEntry(9), PackedTableEntry(10), PackedTableEntry(11), + PackedTableEntry(12), PackedTableEntry(13), PackedTableEntry(14), PackedTableEntry(15), + PackedTableEntry(16), PackedTableEntry(17), PackedTableEntry(18), PackedTableEntry(19), + }; + +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + const uint32_t mask = k_packed_constants[num_bits].mask; + const __m128 inv_max_value = _mm_load_ps1(&k_packed_constants[num_bits].max_value); + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + const __m128 value = _mm_cvtsi32_ss(inv_max_value, x32 & mask); + return rtm::scalarf{ _mm_mul_ss(value, inv_max_value) }; +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + const uint32_t mask = k_packed_constants[num_bits].mask; + const float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + const int32_t value_u32 = x32 & mask; + const float value_f32 = static_cast(value_u32); + return value_f32 * inv_max_value; +#else + const uint32_t bit_shift = 32 - num_bits; + const uint32_t mask = k_packed_constants[num_bits].mask; + const float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + return rtm::scalar_set(static_cast(x32) * inv_max_value); +#endif + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/vector4_packing.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/vector4_packing.h new file mode 100644 index 00000000..f979d83b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/vector4_packing.h @@ -0,0 +1,1105 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" +#include "acl/core/error.h" +#include "acl/core/memory_utils.h" +#include "acl/core/track_formats.h" +#include "acl/core/track_types.h" +#include "acl/math/scalar_packing.h" + +#include + +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + ////////////////////////////////////////////////////////////////////////// + // vector4 packing and decay + + inline void RTM_SIMD_CALL pack_vector4_128(rtm::vector4f_arg0 vector, uint8_t* out_vector_data) + { + rtm::vector_store(vector, out_vector_data); + } + + inline rtm::vector4f RTM_SIMD_CALL unpack_vector4_128(const uint8_t* vector_data) + { + return rtm::vector_load(vector_data); + } + + // Assumes the 'vector_data' is in big-endian order and is padded in order to load up to 23 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector4_128_unsafe(const uint8_t* vector_data, uint32_t bit_offset) + { +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t x32 = uint32_t(vector_u64); + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t y32 = uint32_t(vector_u64); + + vector_u64 = unaligned_load(vector_data + byte_offset + 8); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t z32 = uint32_t(vector_u64); + + vector_u64 = unaligned_load(vector_data + byte_offset + 12); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t w32 = uint32_t(vector_u64); + + return _mm_castsi128_ps(_mm_set_epi32(w32, z32, y32, x32)); +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + + const uint64_t y64 = vector_u64 & uint64_t(0xFFFFFFFF00000000ULL); + + vector_u64 = unaligned_load(vector_data + byte_offset + 8); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t z64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 12); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + + const uint64_t w64 = vector_u64 & uint64_t(0xFFFFFFFF00000000ULL); + + const uint32x2_t xy = vcreate_u32(x64 | y64); + const uint32x2_t zw = vcreate_u32(z64 | w64); + const uint32x4_t value_u32 = vcombine_u32(xy, zw); + return vreinterpretq_f32_u32(value_u32); +#else + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t y64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 8); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t z64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 12); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t w64 = vector_u64; + + const float x = aligned_load(&x64); + const float y = aligned_load(&y64); + const float z = aligned_load(&z64); + const float w = aligned_load(&w64); + + return rtm::vector_set(x, y, z, w); +#endif + } + + inline void RTM_SIMD_CALL pack_vector4_64(rtm::vector4f_arg0 vector, bool is_unsigned, uint8_t* out_vector_data) + { + uint32_t vector_x = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_x(vector), 16) : pack_scalar_signed(rtm::vector_get_x(vector), 16); + uint32_t vector_y = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_y(vector), 16) : pack_scalar_signed(rtm::vector_get_y(vector), 16); + uint32_t vector_z = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_z(vector), 16) : pack_scalar_signed(rtm::vector_get_z(vector), 16); + uint32_t vector_w = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_w(vector), 16) : pack_scalar_signed(rtm::vector_get_w(vector), 16); + + uint16_t* data = safe_ptr_cast(out_vector_data); + data[0] = safe_static_cast(vector_x); + data[1] = safe_static_cast(vector_y); + data[2] = safe_static_cast(vector_z); + data[3] = safe_static_cast(vector_w); + } + + inline rtm::vector4f RTM_SIMD_CALL unpack_vector4_64(const uint8_t* vector_data, bool is_unsigned) + { + const uint16_t* data_ptr_u16 = safe_ptr_cast(vector_data); + uint16_t x16 = data_ptr_u16[0]; + uint16_t y16 = data_ptr_u16[1]; + uint16_t z16 = data_ptr_u16[2]; + uint16_t w16 = data_ptr_u16[3]; + float x = is_unsigned ? unpack_scalar_unsigned(x16, 16) : unpack_scalar_signed(x16, 16); + float y = is_unsigned ? unpack_scalar_unsigned(y16, 16) : unpack_scalar_signed(y16, 16); + float z = is_unsigned ? unpack_scalar_unsigned(z16, 16) : unpack_scalar_signed(z16, 16); + float w = is_unsigned ? unpack_scalar_unsigned(w16, 16) : unpack_scalar_signed(w16, 16); + return rtm::vector_set(x, y, z, w); + } + + inline void RTM_SIMD_CALL pack_vector4_32(rtm::vector4f_arg0 vector, bool is_unsigned, uint8_t* out_vector_data) + { + uint32_t vector_x = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_x(vector), 8) : pack_scalar_signed(rtm::vector_get_x(vector), 8); + uint32_t vector_y = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_y(vector), 8) : pack_scalar_signed(rtm::vector_get_y(vector), 8); + uint32_t vector_z = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_z(vector), 8) : pack_scalar_signed(rtm::vector_get_z(vector), 8); + uint32_t vector_w = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_w(vector), 8) : pack_scalar_signed(rtm::vector_get_w(vector), 8); + + out_vector_data[0] = safe_static_cast(vector_x); + out_vector_data[1] = safe_static_cast(vector_y); + out_vector_data[2] = safe_static_cast(vector_z); + out_vector_data[3] = safe_static_cast(vector_w); + } + + inline rtm::vector4f RTM_SIMD_CALL unpack_vector4_32(const uint8_t* vector_data, bool is_unsigned) + { + uint8_t x8 = vector_data[0]; + uint8_t y8 = vector_data[1]; + uint8_t z8 = vector_data[2]; + uint8_t w8 = vector_data[3]; + float x = is_unsigned ? unpack_scalar_unsigned(x8, 8) : unpack_scalar_signed(x8, 8); + float y = is_unsigned ? unpack_scalar_unsigned(y8, 8) : unpack_scalar_signed(y8, 8); + float z = is_unsigned ? unpack_scalar_unsigned(z8, 8) : unpack_scalar_signed(z8, 8); + float w = is_unsigned ? unpack_scalar_unsigned(w8, 8) : unpack_scalar_signed(w8, 8); + return rtm::vector_set(x, y, z, w); + } + + // Packs data in big-endian order and assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector4_uXX_unsafe(rtm::vector4f_arg0 vector, uint32_t num_bits, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_unsigned(rtm::vector_get_x(vector), num_bits); + uint32_t vector_y = pack_scalar_unsigned(rtm::vector_get_y(vector), num_bits); + uint32_t vector_z = pack_scalar_unsigned(rtm::vector_get_z(vector), num_bits); + uint32_t vector_w = pack_scalar_unsigned(rtm::vector_get_w(vector), num_bits); + + uint64_t vector_u64 = static_cast(vector_x) << (64 - num_bits * 1); + vector_u64 |= static_cast(vector_y) << (64 - num_bits * 2); + vector_u64 |= static_cast(vector_z) << (64 - num_bits * 3); + vector_u64 = byte_swap(vector_u64); + + unaligned_write(vector_u64, out_vector_data); + + uint32_t vector_u32 = vector_w << (32 - num_bits); + vector_u32 = byte_swap(vector_u32); + + const uint32_t bit_offset = num_bits * 3; + memcpy_bits(out_vector_data, bit_offset, &vector_u32, 0, num_bits); + } + + // Assumes the 'vector_data' is in big-endian order and padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector4_uXX_unsafe(uint32_t num_bits, const uint8_t* vector_data, uint32_t bit_offset) + { + ACL_ASSERT(num_bits <= 19, "This function does not support reading more than 19 bits per component"); + + struct PackedTableEntry + { + explicit constexpr PackedTableEntry(uint8_t num_bits_) + : max_value(num_bits_ == 0 ? 1.0F : (1.0F / float((1 << num_bits_) - 1))) + , mask((1 << num_bits_) - 1) + {} + + float max_value; + uint32_t mask; + }; + + // TODO: We technically don't need the first 3 entries, which could save a few bytes + alignas(64) static constexpr PackedTableEntry k_packed_constants[20] = + { + PackedTableEntry(0), PackedTableEntry(1), PackedTableEntry(2), PackedTableEntry(3), + PackedTableEntry(4), PackedTableEntry(5), PackedTableEntry(6), PackedTableEntry(7), + PackedTableEntry(8), PackedTableEntry(9), PackedTableEntry(10), PackedTableEntry(11), + PackedTableEntry(12), PackedTableEntry(13), PackedTableEntry(14), PackedTableEntry(15), + PackedTableEntry(16), PackedTableEntry(17), PackedTableEntry(18), PackedTableEntry(19), + }; + +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + const __m128i mask = _mm_castps_si128(_mm_load_ps1((const float*)&k_packed_constants[num_bits].mask)); + const __m128 inv_max_value = _mm_load_ps1(&k_packed_constants[num_bits].max_value); + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t z32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t w32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + __m128i int_value = _mm_set_epi32(w32, z32, y32, x32); + int_value = _mm_and_si128(int_value, mask); + const __m128 value = _mm_cvtepi32_ps(int_value); + return _mm_mul_ps(value, inv_max_value); +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + uint32x4_t mask = vdupq_n_u32(k_packed_constants[num_bits].mask); + float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t z32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t w32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + uint32x2_t xy = vcreate_u32(uint64_t(x32) | (uint64_t(y32) << 32)); + uint32x2_t zw = vcreate_u32(uint64_t(z32) | (uint64_t(w32) << 32)); + uint32x4_t value_u32 = vcombine_u32(xy, zw); + value_u32 = vandq_u32(value_u32, mask); + float32x4_t value_f32 = vcvtq_f32_u32(value_u32); + return vmulq_n_f32(value_f32, inv_max_value); +#else + const uint32_t bit_shift = 32 - num_bits; + const uint32_t mask = k_packed_constants[num_bits].mask; + const float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t z32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t w32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + return rtm::vector_mul(rtm::vector_set(float(x32), float(y32), float(z32), float(w32)), inv_max_value); +#endif + } + + // Assumes the 'vector_data' is in big-endian order and is padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector2_64_unsafe(const uint8_t* vector_data, uint32_t bit_offset) + { +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t x32 = uint32_t(vector_u64); + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t y32 = uint32_t(vector_u64); + + // TODO: Convert to u64 first before set1_epi64 or equivalent? + return _mm_castsi128_ps(_mm_set_epi32(y32, x32, y32, x32)); +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + + const uint64_t y64 = vector_u64 & uint64_t(0xFFFFFFFF00000000ULL); + + const uint32x2_t xy = vcreate_u32(x64 | y64); + const uint32x4_t value_u32 = vcombine_u32(xy, xy); + return vreinterpretq_f32_u32(value_u32); +#else + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t y64 = vector_u64; + + const float x = aligned_load(&x64); + const float y = aligned_load(&y64); + + return rtm::vector_set(x, y, x, y); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + // vector3 packing and decay + + inline void RTM_SIMD_CALL pack_vector3_96(rtm::vector4f_arg0 vector, uint8_t* out_vector_data) + { + rtm::vector_store3(vector, out_vector_data); + } + + // Assumes the 'vector_data' is padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_96_unsafe(const uint8_t* vector_data) + { + return rtm::vector_load(vector_data); + } + + // Assumes the 'vector_data' is in big-endian order and is padded in order to load up to 19 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_96_unsafe(const uint8_t* vector_data, uint32_t bit_offset) + { +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t x32 = uint32_t(vector_u64); + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t y32 = uint32_t(vector_u64); + + vector_u64 = unaligned_load(vector_data + byte_offset + 8); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint32_t z32 = uint32_t(vector_u64); + + return _mm_castsi128_ps(_mm_set_epi32(x32, z32, y32, x32)); +#elif defined(RTM_NEON64_INTRINSICS) && defined(__clang__) && __clang_major__ == 3 && __clang_minor__ == 8 + // Clang 3.8 has a bug in its codegen and we have to use a slightly slower impl to avoid it + // This is a pretty old version but UE 4.23 still uses it on android + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + + uint8x16_t x64y64_u8 = vrev64q_u8(vld1q_u8(vector_data + byte_offset + 0)); + uint64x2_t x64_tmp = vreinterpretq_u64_u8(x64y64_u8); + uint64x2_t tmp_y64 = vreinterpretq_u64_u8(vextq_u8(x64y64_u8, x64y64_u8, 4)); + + const uint64x2_t shift_offset64 = vdupq_n_u64(shift_offset); + x64_tmp = vshlq_u64(x64_tmp, shift_offset64); + tmp_y64 = vshlq_u64(tmp_y64, shift_offset64); + uint32x2_t xy32 = vreinterpret_u32_u64(vsri_n_u64(vget_high_u32(tmp_y64), vget_low_u64(x64_tmp), 32)); + + uint8x8_t z64_u8 = vrev64_u8(vld1_u8(vector_data + byte_offset + 8)); + uint64x1_t z64 = vreinterpret_u64_u8(z64_u8); + z64 = vshl_u64(z64, vdup_n_u64(shift_offset - 32)); + + const uint32x4_t xyz32 = vcombine_u32(xy32, vreinterpret_u32_u64(z64)); + return vreinterpretq_f32_u32(xyz32); +#elif defined(RTM_NEON64_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + + const uint64_t x64 = (vector_u64 >> (32 - shift_offset)) & uint64_t(0x00000000FFFFFFFFULL); + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + + const uint64_t y64 = vector_u64 & uint64_t(0xFFFFFFFF00000000ULL); + + vector_u64 = unaligned_load(vector_data + byte_offset + 8); + vector_u64 = byte_swap(vector_u64); + + const uint64_t z64 = vector_u64 >> (32 - shift_offset); + + const uint32x2_t xy = vcreate_u32(x64 | y64); + const uint32x2_t z = vcreate_u32(z64); + const uint32x4_t value_u32 = vcombine_u32(xy, z); + return vreinterpretq_f32_u32(value_u32); +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + + uint8x16_t x64y64_u8 = vrev64q_u8(vld1q_u8(vector_data + byte_offset + 0)); + uint64x2_t x64_tmp = vreinterpretq_u64_u8(x64y64_u8); + uint64x2_t tmp_y64 = vreinterpretq_u64_u8(vextq_u8(x64y64_u8, x64y64_u8, 4)); + + const uint64x2_t shift_offset64 = vdupq_n_u64(shift_offset); + x64_tmp = vshlq_u64(x64_tmp, shift_offset64); + tmp_y64 = vshlq_u64(tmp_y64, shift_offset64); + uint32x2_t xy32 = vreinterpret_u32_u64(vsri_n_u64(vget_high_u32(tmp_y64), vget_low_u64(x64_tmp), 32)); + + uint8x8_t z64_u8 = vrev64_u8(vld1_u8(vector_data + byte_offset + 8)); + uint64x1_t z64 = vreinterpret_u64_u8(z64_u8); + z64 = vshl_u64(z64, vdup_n_u64(shift_offset)); + + const uint32x4_t xyz32 = vcombine_u32(xy32, vrev64_u32(vreinterpret_u32_u64(z64))); + return vreinterpretq_f32_u32(xyz32); +#else + const uint32_t byte_offset = bit_offset / 8; + const uint32_t shift_offset = bit_offset % 8; + uint64_t vector_u64 = unaligned_load(vector_data + byte_offset + 0); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t x64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 4); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t y64 = vector_u64; + + vector_u64 = unaligned_load(vector_data + byte_offset + 8); + vector_u64 = byte_swap(vector_u64); + vector_u64 <<= shift_offset; + vector_u64 >>= 32; + + const uint64_t z64 = vector_u64; + + const float x = aligned_load(&x64); + const float y = aligned_load(&y64); + const float z = aligned_load(&z64); + + return rtm::vector_set(x, y, z); +#endif + } + + // Assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector3_u48_unsafe(rtm::vector4f_arg0 vector, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_unsigned(rtm::vector_get_x(vector), 16); + uint32_t vector_y = pack_scalar_unsigned(rtm::vector_get_y(vector), 16); + uint32_t vector_z = pack_scalar_unsigned(rtm::vector_get_z(vector), 16); + + uint16_t* data = safe_ptr_cast(out_vector_data); + data[0] = safe_static_cast(vector_x); + data[1] = safe_static_cast(vector_y); + data[2] = safe_static_cast(vector_z); + } + + // Assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector3_s48_unsafe(rtm::vector4f_arg0 vector, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_signed(rtm::vector_get_x(vector), 16); + uint32_t vector_y = pack_scalar_signed(rtm::vector_get_y(vector), 16); + uint32_t vector_z = pack_scalar_signed(rtm::vector_get_z(vector), 16); + + uint16_t* data = safe_ptr_cast(out_vector_data); + data[0] = safe_static_cast(vector_x); + data[1] = safe_static_cast(vector_y); + data[2] = safe_static_cast(vector_z); + } + + // Assumes the 'vector_data' is padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_u48_unsafe(const uint8_t* vector_data) + { +#if defined(RTM_SSE2_INTRINSICS) + __m128i zero = _mm_setzero_si128(); + __m128i x16y16z16 = _mm_loadu_si128((const __m128i*)vector_data); + __m128i x32y32z32 = _mm_unpacklo_epi16(x16y16z16, zero); + __m128 value = _mm_cvtepi32_ps(x32y32z32); + return _mm_mul_ps(value, _mm_set_ps1(1.0F / 65535.0F)); +#elif defined(RTM_NEON_INTRINSICS) + uint8x8_t x8y8z8 = vld1_u8(vector_data); + uint16x4_t x16y16z16 = vreinterpret_u16_u8(x8y8z8); + uint32x4_t x32y32z32 = vmovl_u16(x16y16z16); + + float32x4_t value = vcvtq_f32_u32(x32y32z32); + return vmulq_n_f32(value, 1.0F / 65535.0F); +#else + const uint16_t* data_ptr_u16 = safe_ptr_cast(vector_data); + uint16_t x16 = data_ptr_u16[0]; + uint16_t y16 = data_ptr_u16[1]; + uint16_t z16 = data_ptr_u16[2]; + float x = unpack_scalar_unsigned(x16, 16); + float y = unpack_scalar_unsigned(y16, 16); + float z = unpack_scalar_unsigned(z16, 16); + return rtm::vector_set(x, y, z); +#endif + } + + // Assumes the 'vector_data' is padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_s48_unsafe(const uint8_t* vector_data) + { + const rtm::vector4f unsigned_value = unpack_vector3_u48_unsafe(vector_data); + return rtm::vector_neg_mul_sub(unsigned_value, -2.0F, rtm::vector_set(-1.0F)); + } + + inline rtm::vector4f RTM_SIMD_CALL decay_vector3_u48(rtm::vector4f_arg0 input) + { + ACL_ASSERT(rtm::vector_all_greater_equal3(input, rtm::vector_zero()) && rtm::vector_all_less_equal3(input, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f", (float)rtm::vector_get_x(input), (float)rtm::vector_get_y(input), (float)rtm::vector_get_z(input)); + + const float max_value = float((1 << 16) - 1); + const float inv_max_value = 1.0F / max_value; + + const rtm::vector4f packed = rtm::vector_round_symmetric(rtm::vector_mul(input, max_value)); + const rtm::vector4f decayed = rtm::vector_mul(packed, inv_max_value); + return decayed; + } + + inline rtm::vector4f RTM_SIMD_CALL decay_vector3_s48(rtm::vector4f_arg0 input) + { + const rtm::vector4f half = rtm::vector_set(0.5F); + const rtm::vector4f unsigned_input = rtm::vector_mul_add(input, half, half); + + ACL_ASSERT(rtm::vector_all_greater_equal3(unsigned_input, rtm::vector_zero()) && rtm::vector_all_less_equal3(unsigned_input, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f", (float)rtm::vector_get_x(unsigned_input), (float)rtm::vector_get_y(unsigned_input), (float)rtm::vector_get_z(unsigned_input)); + + const float max_value = rtm::scalar_safe_to_float((1 << 16) - 1); + const float inv_max_value = 1.0F / max_value; + + const rtm::vector4f packed = rtm::vector_round_symmetric(rtm::vector_mul(unsigned_input, max_value)); + const rtm::vector4f decayed = rtm::vector_mul(packed, inv_max_value); + return rtm::vector_neg_mul_sub(decayed, -2.0F, rtm::vector_set(-1.0F)); + } + + inline void RTM_SIMD_CALL pack_vector3_32(rtm::vector4f_arg0 vector, uint32_t XBits, uint32_t YBits, uint32_t ZBits, bool is_unsigned, uint8_t* out_vector_data) + { + ACL_ASSERT(XBits + YBits + ZBits == 32, "Sum of XYZ bits does not equal 32!"); + + uint32_t vector_x = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_x(vector), XBits) : pack_scalar_signed(rtm::vector_get_x(vector), XBits); + uint32_t vector_y = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_y(vector), YBits) : pack_scalar_signed(rtm::vector_get_y(vector), YBits); + uint32_t vector_z = is_unsigned ? pack_scalar_unsigned(rtm::vector_get_z(vector), ZBits) : pack_scalar_signed(rtm::vector_get_z(vector), ZBits); + + uint32_t vector_u32 = (vector_x << (YBits + ZBits)) | (vector_y << ZBits) | vector_z; + + // Written 2 bytes at a time to ensure safe alignment + uint16_t* data = safe_ptr_cast(out_vector_data); + data[0] = safe_static_cast(vector_u32 >> 16); + data[1] = safe_static_cast(vector_u32 & 0xFFFF); + } + + inline rtm::vector4f RTM_SIMD_CALL decay_vector3_u32(rtm::vector4f_arg0 input, uint32_t XBits, uint32_t YBits, uint32_t ZBits) + { + ACL_ASSERT(XBits + YBits + ZBits == 32, "Sum of XYZ bits does not equal 32!"); + ACL_ASSERT(rtm::vector_all_greater_equal3(input, rtm::vector_zero()) && rtm::vector_all_less_equal(input, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f", (float)rtm::vector_get_x(input), (float)rtm::vector_get_y(input), (float)rtm::vector_get_z(input)); + + const float max_value_x = float((1 << XBits) - 1); + const float max_value_y = float((1 << YBits) - 1); + const float max_value_z = float((1 << ZBits) - 1); + const rtm::vector4f max_value = rtm::vector_set(max_value_x, max_value_y, max_value_z, max_value_z); + const rtm::vector4f inv_max_value = rtm::vector_reciprocal(max_value); + + const rtm::vector4f packed = rtm::vector_round_symmetric(rtm::vector_mul(input, max_value)); + const rtm::vector4f decayed = rtm::vector_mul(packed, inv_max_value); + return decayed; + } + + inline rtm::vector4f RTM_SIMD_CALL decay_vector3_s32(rtm::vector4f_arg0 input, uint32_t XBits, uint32_t YBits, uint32_t ZBits) + { + const rtm::vector4f half = rtm::vector_set(0.5F); + const rtm::vector4f unsigned_input = rtm::vector_mul_add(input, half, half); + + ACL_ASSERT(XBits + YBits + ZBits == 32, "Sum of XYZ bits does not equal 32!"); + ACL_ASSERT(rtm::vector_all_greater_equal3(unsigned_input, rtm::vector_zero()) && rtm::vector_all_less_equal(unsigned_input, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f", (float)rtm::vector_get_x(unsigned_input), (float)rtm::vector_get_y(unsigned_input), (float)rtm::vector_get_z(unsigned_input)); + + const float max_value_x = float((1 << XBits) - 1); + const float max_value_y = float((1 << YBits) - 1); + const float max_value_z = float((1 << ZBits) - 1); + const rtm::vector4f max_value = rtm::vector_set(max_value_x, max_value_y, max_value_z, max_value_z); + const rtm::vector4f inv_max_value = rtm::vector_reciprocal(max_value); + + const rtm::vector4f packed = rtm::vector_round_symmetric(rtm::vector_mul(unsigned_input, max_value)); + const rtm::vector4f decayed = rtm::vector_mul(packed, inv_max_value); + return rtm::vector_neg_mul_sub(decayed, -2.0F, rtm::vector_set(-1.0F)); + } + + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_32(uint32_t XBits, uint32_t YBits, uint32_t ZBits, bool is_unsigned, const uint8_t* vector_data) + { + ACL_ASSERT(XBits + YBits + ZBits == 32, "Sum of XYZ bits does not equal 32!"); + + // Read 2 bytes at a time to ensure safe alignment + const uint16_t* data_ptr_u16 = safe_ptr_cast(vector_data); + uint32_t vector_u32 = (safe_static_cast(data_ptr_u16[0]) << 16) | safe_static_cast(data_ptr_u16[1]); + uint32_t x32 = vector_u32 >> (YBits + ZBits); + uint32_t y32 = (vector_u32 >> ZBits) & ((1 << YBits) - 1); + uint32_t z32 = vector_u32 & ((1 << ZBits) - 1); + float x = is_unsigned ? unpack_scalar_unsigned(x32, XBits) : unpack_scalar_signed(x32, XBits); + float y = is_unsigned ? unpack_scalar_unsigned(y32, YBits) : unpack_scalar_signed(y32, YBits); + float z = is_unsigned ? unpack_scalar_unsigned(z32, ZBits) : unpack_scalar_signed(z32, ZBits); + return rtm::vector_set(x, y, z); + } + + // Assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector3_u24_unsafe(rtm::vector4f_arg0 vector, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_unsigned(rtm::vector_get_x(vector), 8); + uint32_t vector_y = pack_scalar_unsigned(rtm::vector_get_y(vector), 8); + uint32_t vector_z = pack_scalar_unsigned(rtm::vector_get_z(vector), 8); + + out_vector_data[0] = safe_static_cast(vector_x); + out_vector_data[1] = safe_static_cast(vector_y); + out_vector_data[2] = safe_static_cast(vector_z); + } + + // Assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector3_s24_unsafe(rtm::vector4f_arg0 vector, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_signed(rtm::vector_get_x(vector), 8); + uint32_t vector_y = pack_scalar_signed(rtm::vector_get_y(vector), 8); + uint32_t vector_z = pack_scalar_signed(rtm::vector_get_z(vector), 8); + + out_vector_data[0] = safe_static_cast(vector_x); + out_vector_data[1] = safe_static_cast(vector_y); + out_vector_data[2] = safe_static_cast(vector_z); + } + + // Assumes the 'vector_data' is padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_u24_unsafe(const uint8_t* vector_data) + { +#if defined(RTM_SSE2_INTRINSICS) && 0 + // This implementation leverages fast fixed point coercion, it relies on the + // input being positive and normalized as well as fixed point (division by 256, not 255) + // TODO: Enable this, it's a bit faster but requires compensating with the clip range to avoid losing precision + __m128i zero = _mm_setzero_si128(); + __m128i exponent = _mm_set1_epi32(0x3f800000); + + __m128i x8y8z8 = _mm_loadu_si128((const __m128i*)vector_data); + __m128i x16y16z16 = _mm_unpacklo_epi8(x8y8z8, zero); + __m128i x32y32z32 = _mm_unpacklo_epi16(x16y16z16, zero); + __m128i segment_extent_i32 = _mm_or_si128(_mm_slli_epi32(x32y32z32, 23 - 8), exponent); + return _mm_sub_ps(_mm_castsi128_ps(segment_extent_i32), _mm_castsi128_ps(exponent)); +#elif defined(RTM_SSE2_INTRINSICS) + __m128i zero = _mm_setzero_si128(); + __m128i x8y8z8 = _mm_loadu_si128((const __m128i*)vector_data); + __m128i x16y16z16 = _mm_unpacklo_epi8(x8y8z8, zero); + __m128i x32y32z32 = _mm_unpacklo_epi16(x16y16z16, zero); + __m128 value = _mm_cvtepi32_ps(x32y32z32); + return _mm_mul_ps(value, _mm_set_ps1(1.0F / 255.0F)); +#elif defined(RTM_NEON_INTRINSICS) + uint8x8_t x8y8z8 = vld1_u8(vector_data); + uint16x8_t x16y16z16 = vmovl_u8(x8y8z8); + uint32x4_t x32y32z32 = vmovl_u16(vget_low_u16(x16y16z16)); + + float32x4_t value = vcvtq_f32_u32(x32y32z32); + return vmulq_n_f32(value, 1.0F / 255.0F); +#else + uint8_t x8 = vector_data[0]; + uint8_t y8 = vector_data[1]; + uint8_t z8 = vector_data[2]; + float x = unpack_scalar_unsigned(x8, 8); + float y = unpack_scalar_unsigned(y8, 8); + float z = unpack_scalar_unsigned(z8, 8); + return rtm::vector_set(x, y, z); +#endif + } + + // Assumes the 'vector_data' is padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_s24_unsafe(const uint8_t* vector_data) + { + const rtm::vector4f unsigned_value = unpack_vector3_u24_unsafe(vector_data); + return rtm::vector_neg_mul_sub(unsigned_value, -2.0F, rtm::vector_set(-1.0F)); + } + + // Packs data in big-endian order and assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector3_uXX_unsafe(rtm::vector4f_arg0 vector, uint32_t num_bits, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_unsigned(rtm::vector_get_x(vector), num_bits); + uint32_t vector_y = pack_scalar_unsigned(rtm::vector_get_y(vector), num_bits); + uint32_t vector_z = pack_scalar_unsigned(rtm::vector_get_z(vector), num_bits); + + uint64_t vector_u64 = static_cast(vector_x) << (64 - num_bits * 1); + vector_u64 |= static_cast(vector_y) << (64 - num_bits * 2); + vector_u64 |= static_cast(vector_z) << (64 - num_bits * 3); + vector_u64 = byte_swap(vector_u64); + + unaligned_write(vector_u64, out_vector_data); + } + + // Packs data in big-endian order and assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector3_sXX_unsafe(rtm::vector4f_arg0 vector, uint32_t num_bits, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_signed(rtm::vector_get_x(vector), num_bits); + uint32_t vector_y = pack_scalar_signed(rtm::vector_get_y(vector), num_bits); + uint32_t vector_z = pack_scalar_signed(rtm::vector_get_z(vector), num_bits); + + uint64_t vector_u64 = static_cast(vector_x) << (64 - num_bits * 1); + vector_u64 |= static_cast(vector_y) << (64 - num_bits * 2); + vector_u64 |= static_cast(vector_z) << (64 - num_bits * 3); + vector_u64 = byte_swap(vector_u64); + + unaligned_write(vector_u64, out_vector_data); + } + + inline rtm::vector4f RTM_SIMD_CALL decay_vector3_uXX(rtm::vector4f_arg0 input, uint32_t num_bits) + { + ACL_ASSERT(rtm::vector_all_greater_equal3(input, rtm::vector_zero()) && rtm::vector_all_less_equal3(input, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f", (float)rtm::vector_get_x(input), (float)rtm::vector_get_y(input), (float)rtm::vector_get_z(input)); + + const float max_value = rtm::scalar_safe_to_float((1 << num_bits) - 1); + const float inv_max_value = 1.0F / max_value; + + const rtm::vector4f packed = rtm::vector_round_symmetric(rtm::vector_mul(input, max_value)); + const rtm::vector4f decayed = rtm::vector_mul(packed, inv_max_value); + return decayed; + } + + inline rtm::vector4f RTM_SIMD_CALL decay_vector3_sXX(rtm::vector4f_arg0 input, uint32_t num_bits) + { + const rtm::vector4f half = rtm::vector_set(0.5F); + const rtm::vector4f unsigned_input = rtm::vector_mul_add(input, half, half); + + ACL_ASSERT(rtm::vector_all_greater_equal3(unsigned_input, rtm::vector_zero()) && rtm::vector_all_less_equal3(unsigned_input, rtm::vector_set(1.0F)), "Expected normalized unsigned input value: %f, %f, %f", (float)rtm::vector_get_x(unsigned_input), (float)rtm::vector_get_y(unsigned_input), (float)rtm::vector_get_z(unsigned_input)); + + const float max_value = rtm::scalar_safe_to_float((1 << num_bits) - 1); + const float inv_max_value = 1.0F / max_value; + + const rtm::vector4f packed = rtm::vector_round_symmetric(rtm::vector_mul(unsigned_input, max_value)); + const rtm::vector4f decayed = rtm::vector_mul(packed, inv_max_value); + return rtm::vector_neg_mul_sub(decayed, -2.0F, rtm::vector_set(-1.0F)); + } + + // Assumes the 'vector_data' is in big-endian order and padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_uXX_unsafe(uint32_t num_bits, const uint8_t* vector_data, uint32_t bit_offset) + { + ACL_ASSERT(num_bits <= 19, "This function does not support reading more than 19 bits per component"); + + struct PackedTableEntry + { + explicit constexpr PackedTableEntry(uint8_t num_bits_) + : max_value(num_bits_ == 0 ? 1.0F : (1.0F / float((1 << num_bits_) - 1))) + , mask((1 << num_bits_) - 1) + {} + + float max_value; + uint32_t mask; + }; + + // TODO: We technically don't need the first 3 entries, which could save a few bytes + alignas(64) static constexpr PackedTableEntry k_packed_constants[20] = + { + PackedTableEntry(0), PackedTableEntry(1), PackedTableEntry(2), PackedTableEntry(3), + PackedTableEntry(4), PackedTableEntry(5), PackedTableEntry(6), PackedTableEntry(7), + PackedTableEntry(8), PackedTableEntry(9), PackedTableEntry(10), PackedTableEntry(11), + PackedTableEntry(12), PackedTableEntry(13), PackedTableEntry(14), PackedTableEntry(15), + PackedTableEntry(16), PackedTableEntry(17), PackedTableEntry(18), PackedTableEntry(19), + }; + +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + const __m128i mask = _mm_castps_si128(_mm_load_ps1((const float*)&k_packed_constants[num_bits].mask)); + const __m128 inv_max_value = _mm_load_ps1(&k_packed_constants[num_bits].max_value); + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t z32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + __m128i int_value = _mm_set_epi32(x32, z32, y32, x32); + int_value = _mm_and_si128(int_value, mask); + const __m128 value = _mm_cvtepi32_ps(int_value); + return _mm_mul_ps(value, inv_max_value); +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + uint32x4_t mask = vdupq_n_u32(k_packed_constants[num_bits].mask); + float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t z32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + uint32x2_t xy = vcreate_u32(uint64_t(x32) | (uint64_t(y32) << 32)); + uint32x2_t z = vcreate_u32(uint64_t(z32)); + uint32x4_t value_u32 = vcombine_u32(xy, z); + value_u32 = vandq_u32(value_u32, mask); + float32x4_t value_f32 = vcvtq_f32_u32(value_u32); + return vmulq_n_f32(value_f32, inv_max_value); +#else + const uint32_t bit_shift = 32 - num_bits; + const uint32_t mask = k_packed_constants[num_bits].mask; + const float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t z32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + return rtm::vector_mul(rtm::vector_set(float(x32), float(y32), float(z32)), inv_max_value); +#endif + } + + // Assumes the 'vector_data' is in big-endian order and padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector3_sXX_unsafe(uint32_t num_bits, const uint8_t* vector_data, uint32_t bit_offset) + { + ACL_ASSERT(num_bits * 3 <= 64, "Attempting to read too many bits"); + + const rtm::vector4f unsigned_value = unpack_vector3_uXX_unsafe(num_bits, vector_data, bit_offset); + return rtm::vector_neg_mul_sub(unsigned_value, -2.0F, rtm::vector_set(-1.0F)); + } + + ////////////////////////////////////////////////////////////////////////// + // vector2 packing and decay + + // Packs data in big-endian order and assumes the 'out_vector_data' is padded in order to write up to 16 bytes to it + inline void RTM_SIMD_CALL pack_vector2_uXX_unsafe(rtm::vector4f_arg0 vector, uint32_t num_bits, uint8_t* out_vector_data) + { + uint32_t vector_x = pack_scalar_unsigned(rtm::vector_get_x(vector), num_bits); + uint32_t vector_y = pack_scalar_unsigned(rtm::vector_get_y(vector), num_bits); + + uint64_t vector_u64 = static_cast(vector_x) << (64 - num_bits * 1); + vector_u64 |= static_cast(vector_y) << (64 - num_bits * 2); + vector_u64 = byte_swap(vector_u64); + + unaligned_write(vector_u64, out_vector_data); + } + + // Assumes the 'vector_data' is in big-endian order and padded in order to load up to 16 bytes from it + inline rtm::vector4f RTM_SIMD_CALL unpack_vector2_uXX_unsafe(uint32_t num_bits, const uint8_t* vector_data, uint32_t bit_offset) + { + ACL_ASSERT(num_bits <= 19, "This function does not support reading more than 19 bits per component"); + + struct PackedTableEntry + { + explicit constexpr PackedTableEntry(uint8_t num_bits_) + : max_value(num_bits_ == 0 ? 1.0F : (1.0F / float((1 << num_bits_) - 1))) + , mask((1 << num_bits_) - 1) + {} + + float max_value; + uint32_t mask; + }; + + // TODO: We technically don't need the first 3 entries, which could save a few bytes + alignas(64) static constexpr PackedTableEntry k_packed_constants[20] = + { + PackedTableEntry(0), PackedTableEntry(1), PackedTableEntry(2), PackedTableEntry(3), + PackedTableEntry(4), PackedTableEntry(5), PackedTableEntry(6), PackedTableEntry(7), + PackedTableEntry(8), PackedTableEntry(9), PackedTableEntry(10), PackedTableEntry(11), + PackedTableEntry(12), PackedTableEntry(13), PackedTableEntry(14), PackedTableEntry(15), + PackedTableEntry(16), PackedTableEntry(17), PackedTableEntry(18), PackedTableEntry(19), + }; + +#if defined(RTM_SSE2_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + const __m128i mask = _mm_castps_si128(_mm_load_ps1((const float*)&k_packed_constants[num_bits].mask)); + const __m128 inv_max_value = _mm_load_ps1(&k_packed_constants[num_bits].max_value); + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + __m128i int_value = _mm_set_epi32(y32, x32, y32, x32); + int_value = _mm_and_si128(int_value, mask); + const __m128 value = _mm_cvtepi32_ps(int_value); + return _mm_mul_ps(value, inv_max_value); +#elif defined(RTM_NEON_INTRINSICS) + const uint32_t bit_shift = 32 - num_bits; + uint32x2_t mask = vdup_n_u32(k_packed_constants[num_bits].mask); + float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))); + + uint32x2_t xy = vcreate_u32(uint64_t(x32) | (uint64_t(y32) << 32)); + xy = vand_u32(xy, mask); + float32x2_t value_f32 = vcvt_f32_u32(xy); + float32x2_t result = vmul_n_f32(value_f32, inv_max_value); + return vcombine_f32(result, result); +#else + const uint32_t bit_shift = 32 - num_bits; + const uint32_t mask = k_packed_constants[num_bits].mask; + const float inv_max_value = k_packed_constants[num_bits].max_value; + + uint32_t byte_offset = bit_offset / 8; + uint32_t vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t x32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + bit_offset += num_bits; + + byte_offset = bit_offset / 8; + vector_u32 = unaligned_load(vector_data + byte_offset); + vector_u32 = byte_swap(vector_u32); + const uint32_t y32 = (vector_u32 >> (bit_shift - (bit_offset % 8))) & mask; + + return rtm::vector_mul(rtm::vector_set(float(x32), float(y32), 0.0F, 0.0F), inv_max_value); +#endif + } + + ////////////////////////////////////////////////////////////////////////// + + // TODO: constexpr + inline uint32_t get_packed_vector_size(vector_format8 format) + { + switch (format) + { + case vector_format8::vector3f_full: return sizeof(float) * 3; + case vector_format8::vector3f_variable: + default: + ACL_ASSERT(false, "Invalid or unsupported vector format: %s", get_vector_format_name(format)); + return 0; + } + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/vector4f.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/vector4f.h new file mode 100644 index 00000000..8480ff2b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/math/vector4f.h @@ -0,0 +1,49 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl/version.h" +#include "acl/core/impl/compiler_utils.h" + +#include + +#include +#include + +ACL_IMPL_FILE_PRAGMA_PUSH + +namespace acl +{ + ACL_IMPL_VERSION_NAMESPACE_BEGIN + + // Temporary put here until they are included in RTM + namespace acl_impl + { + } + + ACL_IMPL_VERSION_NAMESPACE_END +} + +ACL_IMPL_FILE_PRAGMA_POP diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/version.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/version.h new file mode 100644 index 00000000..d2049962 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/includes/acl/version.h @@ -0,0 +1,90 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +//////////////////////////////////////////////////////////////////////////////// +// Macros to detect the ACL version +//////////////////////////////////////////////////////////////////////////////// + +#define ACL_VERSION_MAJOR 2 +#define ACL_VERSION_MINOR 0 +#define ACL_VERSION_PATCH 4 + +//////////////////////////////////////////////////////////////////////////////// +// In order to allow multiple versions of this library to coexist side by side +// within the same executable/library, the symbols have to be unique per version. +// We achieve this by using a versioned namespace that we optionally inline. +// To disable namespace inlining, define ACL_NO_INLINE_NAMESPACE before including +// any ACL header. To disable the versioned namespace altogether, +// define ACL_NO_VERSION_NAMESPACE before including any ACL header. +//////////////////////////////////////////////////////////////////////////////// + +#if !defined(ACL_NO_VERSION_NAMESPACE) + #if defined(RTM_COMPILER_MSVC) && RTM_COMPILER_MSVC == RTM_COMPILER_MSVC_2015 + // VS2015 struggles with type resolution when inline namespaces are used + // For that reason, we disable it explicitly + #define ACL_NO_VERSION_NAMESPACE + #endif +#endif + +// Name of the namespace, e.g. v20 +#define ACL_IMPL_VERSION_NAMESPACE_NAME v ## ACL_VERSION_MAJOR ## ACL_VERSION_MINOR + +// Because this is being introduced in a patch release, as caution, it is disabled +// by default. It does break ABI if host runtimes forward declare types but that +// is something they shouldn't do with a 3rd party library. Now, we offer forward +// declaration headers to help prepare the migration in the next minor release. +#if defined(ACL_NO_VERSION_NAMESPACE) || !defined(ACL_ENABLE_VERSION_NAMESPACE) + // Namespace is inlined, its usage does not need to be qualified with the + // full version everywhere + #define ACL_IMPL_NAMESPACE acl + + #define ACL_IMPL_VERSION_NAMESPACE_BEGIN + #define ACL_IMPL_VERSION_NAMESPACE_END +#elif defined(ACL_NO_INLINE_NAMESPACE) + // Namespace won't be inlined, its usage will have to be qualified with the + // full version everywhere + #define ACL_IMPL_NAMESPACE acl::ACL_IMPL_VERSION_NAMESPACE_NAME + + #define ACL_IMPL_VERSION_NAMESPACE_BEGIN \ + namespace ACL_IMPL_VERSION_NAMESPACE_NAME \ + { + + #define ACL_IMPL_VERSION_NAMESPACE_END \ + } +#else + // Namespace is inlined, its usage does not need to be qualified with the + // full version everywhere + #define ACL_IMPL_NAMESPACE acl + + #define ACL_IMPL_VERSION_NAMESPACE_BEGIN \ + inline namespace ACL_IMPL_VERSION_NAMESPACE_NAME \ + { + + #define ACL_IMPL_VERSION_NAMESPACE_END \ + } +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/make.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/make.py new file mode 100644 index 00000000..00c9974c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/make.py @@ -0,0 +1,896 @@ +# coding: utf-8 + +from __future__ import print_function + +import argparse +import multiprocessing +import os +import platform +import re +import shutil +import subprocess +import sys +import threading +import time +import zipfile + +# The current test/decompression data version in use +current_test_data = 'test_data_v5' +current_decomp_data = 'decomp_data_v7' + +def parse_argv(): + parser = argparse.ArgumentParser(add_help=False) + + actions = parser.add_argument_group(title='Actions', description='If no action is specified, on Windows, OS X, and Linux the solution/make files are generated. Multiple actions can be used simultaneously.') + actions.add_argument('-build', action='store_true') + actions.add_argument('-clean', action='store_true') + actions.add_argument('-clean_only', action='store_true') + actions.add_argument('-unit_test', action='store_true') + actions.add_argument('-regression_test', action='store_true') + actions.add_argument('-bench', action='store_true') + actions.add_argument('-run_bench', action='store_true') + actions.add_argument('-pull_bench', action='store_true') # Android only + actions.add_argument('-convert', help='Input/Output directory to convert') + + target = parser.add_argument_group(title='Target') + target.add_argument('-compiler', choices=['vs2015', 'vs2017', 'vs2019', 'vs2019-clang', 'android', 'clang4', 'clang5', 'clang6', 'clang7', 'clang8', 'clang9', 'clang10', 'clang11', 'gcc5', 'gcc6', 'gcc7', 'gcc8', 'gcc9', 'gcc10', 'osx', 'ios', 'emscripten'], help='Defaults to the host system\'s default compiler') + target.add_argument('-config', choices=['Debug', 'Release'], type=str.capitalize) + target.add_argument('-cpu', choices=['x86', 'x64', 'armv7', 'arm64', 'wasm'], help='Defaults to the host system\'s architecture') + + misc = parser.add_argument_group(title='Miscellaneous') + misc.add_argument('-avx', dest='use_avx', action='store_true', help='Compile using AVX instructions on Windows, OS X, and Linux') + misc.add_argument('-pop', dest='use_popcnt', action='store_true', help='Compile using the POPCNT instruction') + misc.add_argument('-nosimd', dest='use_simd', action='store_false', help='Compile without SIMD instructions') + misc.add_argument('-nosjson', dest='use_sjson', action='store_false', help='Compile without SJSON support') + misc.add_argument('-num_threads', help='No. to use while compiling and regressing') + misc.add_argument('-tests_matching', help='Only run tests whose names match this regex') + misc.add_argument('-help', action='help', help='Display this usage information') + + num_threads = multiprocessing.cpu_count() + if platform.system() == 'Linux' and sys.version_info >= (3, 4): + num_threads = len(os.sched_getaffinity(0)) + if not num_threads or num_threads == 0: + num_threads = 4 + + parser.set_defaults(build=False, clean=False, clean_only=False, unit_test=False, regression_test=False, bench=False, run_bench=False, pull_bench=False, + compiler=None, config='Release', cpu=None, use_avx=False, use_popcnt=False, use_simd=True, use_sjson=True, + num_threads=num_threads, tests_matching='') + + args = parser.parse_args() + + # Sanitize and validate our options + if args.use_avx and not args.use_simd: + print('SIMD is disabled; AVX cannot be used') + args.use_avx = False + + if args.compiler == 'android': + if not args.cpu: + args.cpu = 'arm64' + + if not platform.system() == 'Windows': + print('Android is only supported on Windows') + sys.exit(1) + + if args.use_avx: + print('AVX is not supported on Android') + sys.exit(1) + + if not args.cpu in ['armv7', 'arm64']: + print('{} cpu architecture not in supported list [armv7, arm64] for Android'.format(args.cpu)) + sys.exit(1) + elif args.compiler == 'ios': + if not args.cpu: + args.cpu = 'arm64' + + if not platform.system() == 'Darwin': + print('iOS is only supported on OS X') + sys.exit(1) + + if args.use_avx: + print('AVX is not supported on iOS') + sys.exit(1) + + if args.unit_test: + print('Unit tests cannot run from the command line on iOS') + sys.exit(1) + + if not args.cpu in ['arm64']: + print('{} cpu architecture not in supported list [arm64] for iOS'.format(args.cpu)) + sys.exit(1) + elif args.compiler == 'emscripten': + if not args.cpu: + args.cpu = 'wasm' + + if not platform.system() == 'Darwin' and not platform.system() == 'Linux': + print('Emscripten is only supported on OS X and Linux') + sys.exit(1) + + if args.use_avx: + print('AVX is not supported with Emscripten') + sys.exit(1) + + if not args.cpu in ['wasm']: + print('{} cpu architecture not in supported list [wasm] for Emscripten'.format(args.cpu)) + sys.exit(1) + else: + if not args.cpu: + args.cpu = 'x64' + + if args.cpu == 'arm64': + if not args.compiler in ['vs2017', 'vs2019', 'ios', 'android']: + print('arm64 is only supported with VS2017, VS2019, Android, and iOS') + sys.exit(1) + elif args.cpu == 'armv7': + if not args.compiler == 'android': + print('armv7 is only supported with Android') + sys.exit(1) + elif args.cpu == 'wasm': + if not args.compiler == 'emscripten': + print('wasm is only supported with Emscripten') + sys.exit(1) + + if platform.system() == 'Darwin' and args.cpu == 'x86': + result = subprocess.check_output(['xcodebuild', '-version']).decode("utf-8") + if 'Xcode 11' in result: + print('Versions of Xcode 11 and up no longer support x86') + sys.exit(1) + + return args + +def get_generator(compiler, cpu): + if compiler == None: + return None + + if platform.system() == 'Windows': + if compiler == 'vs2015': + if cpu == 'x86': + return 'Visual Studio 14' + elif cpu == 'x64': + return 'Visual Studio 14 Win64' + elif compiler == 'vs2017': + if cpu == 'x86': + return 'Visual Studio 15' + elif cpu == 'x64': + return 'Visual Studio 15 Win64' + elif cpu == 'arm64': + # VS2017 ARM/ARM64 support only works with cmake 3.13 and up and the architecture must be specified with + # the -A cmake switch + return 'Visual Studio 15 2017' + elif compiler == 'vs2019' or compiler == 'vs2019-clang': + return 'Visual Studio 16 2019' + elif compiler == 'android': + # For Android, we use the default generator since we don't build with CMake + return None + elif platform.system() == 'Darwin': + if compiler == 'osx' or compiler == 'ios': + return 'Xcode' + elif compiler == 'emscripten': + # Emscripten uses the default generator + return None + elif platform.system() == 'Linux': + if compiler == 'emscripten': + # Emscripten uses the default generator + return None + + return 'Unix Makefiles' + + print('Unknown compiler: {}'.format(compiler)) + print('See help with: python make.py -help') + sys.exit(1) + +def get_architecture(compiler, cpu): + if compiler == None: + return None + + if platform.system() == 'Windows': + if compiler == 'vs2017': + if cpu == 'arm64': + return 'ARM64' + elif compiler == 'vs2019' or compiler == 'vs2019-clang': + if cpu == 'x86': + return 'Win32' + else: + return cpu + + # This compiler/cpu pair does not need the architecture switch + return None + +def get_toolchain(compiler, cmake_script_dir): + if platform.system() == 'Windows' and compiler == 'android': + return os.path.join(cmake_script_dir, 'Toolchain-Android.cmake') + elif platform.system() == 'Darwin' and compiler == 'ios': + return os.path.join(cmake_script_dir, 'Toolchain-iOS.cmake') + + # No toolchain + return None + +def set_compiler_env(compiler, args): + if platform.system() == 'Linux': + os.environ['MAKEFLAGS'] = '-j{}'.format(args.num_threads) + if compiler == 'clang4': + os.environ['CC'] = 'clang-4.0' + os.environ['CXX'] = 'clang++-4.0' + elif compiler == 'clang5': + os.environ['CC'] = 'clang-5.0' + os.environ['CXX'] = 'clang++-5.0' + elif compiler == 'clang6': + os.environ['CC'] = 'clang-6.0' + os.environ['CXX'] = 'clang++-6.0' + elif compiler == 'clang7': + os.environ['CC'] = 'clang-7' + os.environ['CXX'] = 'clang++-7' + elif compiler == 'clang8': + os.environ['CC'] = 'clang-8' + os.environ['CXX'] = 'clang++-8' + elif compiler == 'clang9': + os.environ['CC'] = 'clang-9' + os.environ['CXX'] = 'clang++-9' + elif compiler == 'clang10': + os.environ['CC'] = 'clang-10' + os.environ['CXX'] = 'clang++-10' + elif compiler == 'clang11': + os.environ['CC'] = 'clang-11' + os.environ['CXX'] = 'clang++-11' + elif compiler == 'gcc5': + os.environ['CC'] = 'gcc-5' + os.environ['CXX'] = 'g++-5' + elif compiler == 'gcc6': + os.environ['CC'] = 'gcc-6' + os.environ['CXX'] = 'g++-6' + elif compiler == 'gcc7': + os.environ['CC'] = 'gcc-7' + os.environ['CXX'] = 'g++-7' + elif compiler == 'gcc8': + os.environ['CC'] = 'gcc-8' + os.environ['CXX'] = 'g++-8' + elif compiler == 'gcc9': + os.environ['CC'] = 'gcc-9' + os.environ['CXX'] = 'g++-9' + elif compiler == 'gcc10': + os.environ['CC'] = 'gcc-10' + os.environ['CXX'] = 'g++-10' + elif compiler == 'emscripten': + # Nothing to do for Emscripten + return + else: + print('Unknown compiler: {}'.format(compiler)) + print('See help with: python make.py -help') + sys.exit(1) + +def do_generate_solution(build_dir, cmake_script_dir, test_data_dir, decomp_data_dir, args): + compiler = args.compiler + cpu = args.cpu + config = args.config + + if compiler: + set_compiler_env(compiler, args) + + extra_switches = ['--no-warn-unused-cli'] + extra_switches.append('-DCPU_INSTRUCTION_SET:STRING={}'.format(cpu)) + + if args.use_avx: + print('Enabling AVX usage') + extra_switches.append('-DUSE_AVX_INSTRUCTIONS:BOOL=true') + + if args.use_popcnt: + print('Enabling POPCOUNT usage') + extra_switches.append('-DUSE_POPCNT_INSTRUCTIONS:BOOL=true') + + if not args.use_simd: + print('Disabling SIMD instruction usage') + extra_switches.append('-DUSE_SIMD_INSTRUCTIONS:BOOL=false') + + if not args.use_sjson: + print('Disabling SJSON support') + extra_switches.append('-DUSE_SJSON:BOOL=false') + + if args.bench: + extra_switches.append('-DBUILD_BENCHMARK_EXE:BOOL=true') + + if not platform.system() == 'Windows': + extra_switches.append('-DCMAKE_BUILD_TYPE={}'.format(config.upper())) + + toolchain = get_toolchain(compiler, cmake_script_dir) + if toolchain: + extra_switches.append('-DCMAKE_TOOLCHAIN_FILE={}'.format(toolchain)) + + if test_data_dir: + extra_switches.append('-DTEST_DATA_DIR:STRING="{}"'.format(test_data_dir)) + + if decomp_data_dir: + extra_switches.append('-DDECOMP_DATA_DIR:STRING="{}"'.format(decomp_data_dir)) + + # Generate IDE solution + print('Generating build files ...') + if compiler == 'emscripten': + cmake_cmd = 'emcmake cmake .. -DCMAKE_INSTALL_PREFIX="{}" {}'.format(build_dir, ' '.join(extra_switches)) + else: + cmake_generator = get_generator(compiler, cpu) + if not cmake_generator: + print('Using default generator') + else: + generator_suffix = '' + if compiler == 'vs2019-clang': + extra_switches.append('-T ClangCL') + generator_suffix = 'Clang CL' + + print('Using generator: {} {}'.format(cmake_generator, generator_suffix)) + extra_switches.append('-G "{}"'.format(cmake_generator)) + + cmake_arch = get_architecture(compiler, cpu) + if cmake_arch: + print('Using architecture: {}'.format(cmake_arch)) + extra_switches.append('-A {}'.format(cmake_arch)) + + cmake_cmd = 'cmake .. -DCMAKE_INSTALL_PREFIX="{}" {}'.format(build_dir, ' '.join(extra_switches)) + + result = subprocess.call(cmake_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_build(args): + config = args.config + + print('Building ...') + cmake_cmd = 'cmake --build .' + if platform.system() == 'Windows': + if args.compiler == 'android': + cmake_cmd += ' --config {}'.format(config) + else: + cmake_cmd += ' --config {} --target INSTALL'.format(config) + elif platform.system() == 'Darwin': + if args.compiler == 'ios': + cmake_cmd += ' --config {}'.format(config) + else: + cmake_cmd += ' --config {} --target install'.format(config) + else: + cmake_cmd += ' --target install' + + result = subprocess.call(cmake_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_convert(test_data_dir, args): + if sys.version_info < (3, 4): + print('Python 3.4 or higher needed to run conversion') + sys.exit(1) + + if not os.path.exists(args.convert): + print('Input/Output conversion directory not found: {}'.format(args.convert)) + sys.exit(1) + + # Validate that our regression testing tool is present + if args.compiler == 'emscripten': + compressor_exe_path = './bin/acl_compressor.js' + elif platform.system() == 'Windows': + compressor_exe_path = './bin/acl_compressor.exe' + else: + compressor_exe_path = './bin/acl_compressor' + + compressor_exe_path = os.path.abspath(compressor_exe_path) + if not os.path.exists(compressor_exe_path): + print('Compressor exe not found: {}'.format(compressor_exe_path)) + sys.exit(1) + + # Grab all the test clips + conversion_clips = [] + for (dirpath, dirnames, filenames) in os.walk(args.convert): + for filename in filenames: + if not filename.endswith('.acl.sjson'): + continue + + clip_filename = os.path.join(dirpath, filename) + conversion_clips.append(clip_filename) + + # Grab the raw config + config_dir = os.path.join(test_data_dir, 'configs') + config_filename = os.path.join(config_dir, 'uniformly_sampled_raw.config.sjson') + + print('Converting SJSON clips in {} ...'.format(args.convert)) + conversion_failed = False + for clip_filename in conversion_clips: + output_filename = clip_filename.replace('.acl.sjson', '.acl') + + if args.compiler == 'emscripten': + cmd = 'node "{}" -acl="{}" -config="{}" -out="{}"'.format(compressor_exe_path, clip_filename, config_filename, output_filename) + else: + cmd = '"{}" -acl="{}" -config="{}" -out="{}"'.format(compressor_exe_path, clip_filename, config_filename, output_filename) + + if platform.system() == 'Windows': + cmd = cmd.replace('/', '\\') + + result = subprocess.call(cmd, shell=True) + + if result != 0: + print('Failed to run conversion for clip: {}'.format(clip_filename)) + print(cmd) + conversion_failed = True + + print('Done!') + + if conversion_failed: + sys.exit(1) + +def do_tests_android(build_dir, args): + # Switch our working directory to where we built everything + working_dir = os.path.join(build_dir, 'tests', 'main_android') + os.chdir(working_dir) + + gradlew_exe = os.path.join(working_dir, 'gradlew.bat') + + # We uninstall first and then install + if args.config == 'Debug': + install_cmd = 'uninstallAll installDebug' + elif args.config == 'Release': + install_cmd = 'uninstallAll installRelease' + + # Install our app + test_cmd = '"{}" {}'.format(gradlew_exe, install_cmd) + result = subprocess.call(test_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Execute through ADB + run_cmd = 'adb shell am start -n "com.acl.unit_tests/com.acl.unit_tests.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER' + result = subprocess.call(run_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Restore working directory + os.chdir(build_dir) + +def do_tests_cmake(args): + ctest_cmd = 'ctest --output-on-failure --parallel {}'.format(args.num_threads) + + if platform.system() == 'Windows' or platform.system() == 'Darwin': + ctest_cmd += ' -C {}'.format(args.config) + if args.tests_matching: + ctest_cmd += ' --tests-regex {}'.format(args.tests_matching) + + result = subprocess.call(ctest_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_tests(build_dir, args): + print('Running unit tests ...') + + if args.compiler == 'android': + do_tests_android(build_dir, args) + else: + do_tests_cmake(args) + +def format_elapsed_time(elapsed_time): + hours, rem = divmod(elapsed_time, 3600) + minutes, seconds = divmod(rem, 60) + return '{:0>2}h {:0>2}m {:05.2f}s'.format(int(hours), int(minutes), seconds) + +def print_progress(iteration, total, prefix='', suffix='', decimals = 1, bar_length = 40): + # Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console + # With minor tweaks + """ + Call in a loop to create terminal progress bar + @params: + iteration - Required : current iteration (Int) + total - Required : total iterations (Int) + prefix - Optional : prefix string (Str) + suffix - Optional : suffix string (Str) + decimals - Optional : positive number of decimals in percent complete (Int) + bar_length - Optional : character length of bar (Int) + """ + str_format = "{0:." + str(decimals) + "f}" + percents = str_format.format(100 * (iteration / float(total))) + filled_length = int(round(bar_length * iteration / float(total))) + bar = '█' * filled_length + '-' * (bar_length - filled_length) + + # We need to clear any previous line we might have to ensure we have no visual artifacts + # Note that if this function is called too quickly, the text might flicker + terminal_width = 80 + sys.stdout.write('{}\r'.format(' ' * terminal_width)) + sys.stdout.flush() + + sys.stdout.write('%s |%s| %s%s %s\r' % (prefix, bar, percents, '%', suffix)), + sys.stdout.flush() + + if iteration == total: + sys.stdout.write('\n') + +def do_prepare_regression_test_data(test_data_dir, args): + print('Preparing regression test data ...') + + current_test_data_zip = os.path.join(test_data_dir, '{}.zip'.format(current_test_data)) + + # Validate that our regression test data is present + if not os.path.exists(current_test_data_zip): + print('Regression test data not found: {}'.format(current_test_data_zip)) + return + + # If it hasn't been decompressed yet, do so now + current_test_data_dir = os.path.join(test_data_dir, current_test_data) + needs_decompression = not os.path.exists(current_test_data_dir) + if needs_decompression: + print('Decompressing {} ...'.format(current_test_data_zip)) + with zipfile.ZipFile(current_test_data_zip, 'r') as zip_ref: + zip_ref.extractall(test_data_dir) + + # Grab all the test clips + regression_clips = [] + for (dirpath, dirnames, filenames) in os.walk(current_test_data_dir): + for filename in filenames: + if not filename.endswith('.acl'): + continue + + clip_filename = os.path.join(dirpath, filename) + regression_clips.append((clip_filename, os.path.getsize(clip_filename))) + + if len(regression_clips) == 0: + print('No regression clips found') + sys.exit(1) + + print('Found {} regression clips'.format(len(regression_clips))) + + # Grab all the test configurations + test_configs = [] + test_config_dir = os.path.join(test_data_dir, 'configs') + if os.path.exists(test_config_dir): + for (dirpath, dirnames, filenames) in os.walk(test_config_dir): + for filename in filenames: + if not filename.endswith('.config.sjson'): + continue + + config_filename = os.path.join(dirpath, filename) + test_configs.append((config_filename, filename)) + + if len(test_configs) == 0: + print('No regression configurations found') + sys.exit(1) + + print('Found {} regression configurations'.format(len(test_configs))) + + # Sort the configs by name for consistency + test_configs.sort(key=lambda entry: entry[1]) + + # Sort clips by size to test larger clips first, it parallelizes better + regression_clips.sort(key=lambda entry: entry[1], reverse=True) + + # Write our metadata file + with open(os.path.join(current_test_data_dir, 'metadata.sjson'), 'w') as metadata_file: + print('configs = [', file = metadata_file) + for config_filename, _ in test_configs: + print('\t"{}"'.format(os.path.relpath(config_filename, test_config_dir)), file = metadata_file) + print(']', file = metadata_file) + print('', file = metadata_file) + print('clips = [', file = metadata_file) + for clip_filename, _ in regression_clips: + print('\t"{}"'.format(os.path.relpath(clip_filename, current_test_data_dir)), file = metadata_file) + print(']', file = metadata_file) + print('', file = metadata_file) + + return current_test_data_dir + +def do_prepare_decompression_test_data(test_data_dir, args): + print('Preparing decompression test data ...') + + current_data_zip = os.path.join(test_data_dir, '{}.zip'.format(current_decomp_data)) + + # Validate that our regression test data is present + if not os.path.exists(current_data_zip): + print('Decompression test data not found: {}'.format(current_data_zip)) + return + + # If it hasn't been decompressed yet, do so now + current_data_dir = os.path.join(test_data_dir, current_decomp_data) + needs_decompression = not os.path.exists(current_data_dir) + if needs_decompression: + print('Decompressing {} ...'.format(current_data_zip)) + with zipfile.ZipFile(current_data_zip, 'r') as zip_ref: + zip_ref.extractall(test_data_dir) + + # Grab all the test clips + clips = [] + for (dirpath, dirnames, filenames) in os.walk(current_data_dir): + for filename in filenames: + if not filename.endswith('.acl'): + continue + + clip_filename = os.path.join(dirpath, filename) + clips.append(clip_filename) + + if len(clips) == 0: + print('No decompression clips found') + sys.exit(1) + + print('Found {} decompression clips'.format(len(clips))) + + # Write our metadata file + with open(os.path.join(current_data_dir, 'metadata.sjson'), 'w') as metadata_file: + print('clip_dir = "{}"'.format(current_data_dir), file = metadata_file) + print('', file = metadata_file) + print('clips = [', file = metadata_file) + for clip_filename in clips: + print('\t"{}"'.format(os.path.relpath(clip_filename, current_data_dir)), file = metadata_file) + print(']', file = metadata_file) + print('', file = metadata_file) + + return current_data_dir + +def do_regression_tests_android(build_dir, args): + # Switch our working directory to where we built everything + working_dir = os.path.join(build_dir, 'tools', 'regression_tester_android') + os.chdir(working_dir) + + gradlew_exe = os.path.join(working_dir, 'gradlew.bat') + + # We uninstall first and then install + if args.config == 'Debug': + install_cmd = 'uninstallAll installDebug' + elif args.config == 'Release': + install_cmd = 'uninstallAll installRelease' + + # Install our app + test_cmd = '"{}" {}'.format(gradlew_exe, install_cmd) + result = subprocess.call(test_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Execute through ADB + run_cmd = 'adb shell am start -n "com.acl.regression_tests/com.acl.regression_tests.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER' + result = subprocess.call(run_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Restore working directory + os.chdir(build_dir) + +def do_regression_tests_cmake(test_data_dir, args): + if sys.version_info < (3, 4): + print('Python 3.4 or higher needed to run regression tests') + sys.exit(1) + + import queue + + # Validate that our regression testing tool is present + if args.compiler == 'emscripten': + compressor_exe_path = './bin/acl_compressor.js' + elif platform.system() == 'Windows': + compressor_exe_path = './bin/acl_compressor.exe' + else: + compressor_exe_path = './bin/acl_compressor' + + compressor_exe_path = os.path.abspath(compressor_exe_path) + if not os.path.exists(compressor_exe_path): + print('Compressor exe not found: {}'.format(compressor_exe_path)) + sys.exit(1) + + # Grab all the test clips + regression_clips = [] + current_test_data_dir = os.path.join(test_data_dir, current_test_data) + for (dirpath, dirnames, filenames) in os.walk(current_test_data_dir): + for filename in filenames: + if not filename.endswith('.acl'): + continue + + clip_filename = os.path.join(dirpath, filename) + regression_clips.append((clip_filename, os.path.getsize(clip_filename))) + + # Grab all the test configurations + test_configs = [] + test_config_dir = os.path.join(test_data_dir, 'configs') + if os.path.exists(test_config_dir): + for (dirpath, dirnames, filenames) in os.walk(test_config_dir): + for filename in filenames: + if not filename.endswith('.config.sjson'): + continue + + config_filename = os.path.join(dirpath, filename) + test_configs.append((config_filename, filename)) + + # Sort the configs by name for consistency + test_configs.sort(key=lambda entry: entry[1]) + + # Sort clips by size to test larger clips first, it parallelizes better + regression_clips.sort(key=lambda entry: entry[1], reverse=True) + + # Iterate over every clip and configuration and perform the regression testing + for config_filename, _ in test_configs: + print('Performing regression tests for configuration: {}'.format(os.path.basename(config_filename))) + regression_start_time = time.perf_counter() + + cmd_queue = queue.Queue() + completed_queue = queue.Queue() + failed_queue = queue.Queue() + failure_lock = threading.Lock() + for clip_filename, _ in regression_clips: + if args.compiler == 'emscripten': + cmd = 'node "{}" -acl="{}" -test -config="{}"'.format(compressor_exe_path, clip_filename, config_filename) + else: + cmd = '"{}" -acl="{}" -test -config="{}"'.format(compressor_exe_path, clip_filename, config_filename) + + if platform.system() == 'Windows': + cmd = cmd.replace('/', '\\') + + cmd_queue.put((clip_filename, cmd)) + + # Add a marker to terminate the threads + for i in range(args.num_threads): + cmd_queue.put(None) + + def run_clip_regression_test(cmd_queue, completed_queue, failed_queue, failure_lock): + while True: + entry = cmd_queue.get() + if entry is None: + return + + (clip_filename, cmd) = entry + + result = subprocess.call(cmd, shell=True) + + if result != 0: + failed_queue.put((clip_filename, cmd)) + failure_lock.acquire() + print('Failed to run regression test for clip: {}'.format(clip_filename)) + print(cmd) + failure_lock.release() + + completed_queue.put(clip_filename) + + threads = [ threading.Thread(target = run_clip_regression_test, args = (cmd_queue, completed_queue, failed_queue, failure_lock)) for _i in range(args.num_threads) ] + for thread in threads: + thread.daemon = True + thread.start() + + print_progress(0, len(regression_clips), 'Testing clips:', '{} / {}'.format(0, len(regression_clips))) + try: + while True: + for thread in threads: + thread.join(1.0) + + num_processed = completed_queue.qsize() + print_progress(num_processed, len(regression_clips), 'Testing clips:', '{} / {}'.format(num_processed, len(regression_clips))) + + all_threads_done = True + for thread in threads: + if thread.is_alive(): + all_threads_done = False + + if all_threads_done: + break + except KeyboardInterrupt: + sys.exit(1) + + regression_testing_failed = not failed_queue.empty() + + regression_end_time = time.perf_counter() + print('Done in {}'.format(format_elapsed_time(regression_end_time - regression_start_time))) + + if regression_testing_failed: + sys.exit(1) + +def do_regression_tests(build_dir, test_data_dir, args): + print('Running regression tests ...') + + if args.compiler == 'android': + do_regression_tests_android(build_dir, args) + else: + do_regression_tests_cmake(test_data_dir, args) + +def do_run_bench_android(build_dir, args): + # Switch our working directory to where we built everything + working_dir = os.path.join(build_dir, 'tools', 'acl_decompressor', 'main_android') + os.chdir(working_dir) + + gradlew_exe = os.path.join(working_dir, 'gradlew.bat') + + # We uninstall first and then install + if args.config == 'Debug': + install_cmd = 'uninstallAll installDebug' + elif args.config == 'Release': + install_cmd = 'uninstallAll installRelease' + + # Install our app + test_cmd = '"{}" {}'.format(gradlew_exe, install_cmd) + result = subprocess.call(test_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Execute through ADB + run_cmd = 'adb shell am start -n "com.acl.decompressor/com.acl.decompressor.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER' + result = subprocess.call(run_cmd, shell=True) + if result != 0: + sys.exit(result) + + # Restore working directory + os.chdir(build_dir) + +def do_pull_bench_android(build_dir): + # Grab the android directory we wrote the results to + output = str(subprocess.check_output('adb logcat -s acl -e "Benchmark results will be written to:" -m 1 -d')) + matches = re.search('Benchmark results will be written to: ([/\.\w]+)', output) + if matches == None: + print('Failed to find Android source directory from ADB') + android_src_dir = '/storage/emulated/0/Android/data/com.acl.decompressor/files' + print('{} will be used instead'.format(android_src_dir)) + else: + android_src_dir = matches.group(1) + + # Grab the benchmark results from the android device + dst_filename = os.path.join(build_dir, 'benchmark_results.json') + src_filename = '{}/benchmark_results.json'.format(android_src_dir) + cmd = 'adb pull "{}" "{}"'.format(src_filename, dst_filename) + os.system(cmd) + +def do_run_bench_native(build_dir, test_data_dir): + if platform.system() == 'Windows': + bench_exe = os.path.join(os.getcwd(), 'bin/acl_decompressor.exe') + else: + bench_exe = os.path.join(os.getcwd(), 'bin/acl_decompressor') + + current_data_dir = os.path.join(test_data_dir, current_decomp_data) + metadata_filename = os.path.join(current_data_dir, 'metadata.sjson') + benchmark_output_filename = os.path.join(build_dir, 'benchmark_results.json') + bench_cmd = '{} -metadata="{}" --benchmark_out={} --benchmark_out_format=json'.format(bench_exe, metadata_filename, benchmark_output_filename) + + result = subprocess.call(bench_cmd, shell=True) + if result != 0: + sys.exit(result) + +def do_run_bench(build_dir, test_data_dir, args): + if args.compiler == 'ios': + return # Not supported on iOS + + print('Running benchmark ...') + + if args.compiler == 'android': + do_run_bench_android(build_dir, args) + else: + do_run_bench_native(build_dir, test_data_dir) + +if __name__ == "__main__": + args = parse_argv() + + build_dir = os.path.join(os.getcwd(), 'build') + test_data_dir = os.path.join(os.getcwd(), 'test_data') + cmake_script_dir = os.path.join(os.getcwd(), 'cmake') + + is_clean_requested = args.clean or args.clean_only + if is_clean_requested and os.path.exists(build_dir): + print('Cleaning previous build ...') + shutil.rmtree(build_dir) + + if args.clean_only: + sys.exit(0) + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + + os.chdir(build_dir) + + print('Using config: {}'.format(args.config)) + print('Using cpu: {}'.format(args.cpu)) + if args.compiler: + print('Using compiler: {}'.format(args.compiler)) + print('Using {} threads'.format(args.num_threads)) + + regression_data_dir = do_prepare_regression_test_data(test_data_dir, args) + decomp_data_dir = do_prepare_decompression_test_data(test_data_dir, args) + + do_generate_solution(build_dir, cmake_script_dir, regression_data_dir, decomp_data_dir, args) + + if args.build: + do_build(args) + + if args.convert: + do_convert(test_data_dir, args) + + if args.unit_test: + do_tests(build_dir, args) + + if args.regression_test and not args.compiler == 'ios': + do_regression_tests(build_dir, test_data_dir, args) + + if args.run_bench: + do_run_bench(build_dir, test_data_dir, args) + + if args.pull_bench: + do_pull_bench_android(build_dir) + + sys.exit(0) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/sonar-project.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/sonar-project.properties new file mode 100644 index 00000000..b29b513c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.organization=nfrechette-github + +sonar.projectKey=nfrechette_acl +sonar.projectName=Animation Compression Library +sonar.projectVersion=2.0.4 + +sonar.sources=includes,tests/sources,tests/main_generic,tools/acl_compressor/sources,tools/acl_compressor/main_generic,tools/acl_decompressor/main_generic + +sonar.cfamily.build-wrapper-output=bw_output +sonar.cfamily.threads=2 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/README.md new file mode 100644 index 00000000..101c39e4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/README.md @@ -0,0 +1,27 @@ +# Regression testing + +This directory is to contain all the relevant data to perform regression testing. Sadly, the setup is manual for now and as a result the continuous integration builds do not run regression testing yet. + +## Setup + +Find the latest test data zip file located on Google Drive and save it into this directory. If your data ever becomes stale, the python script will indicate as such and you will need to perform this step again. The zip file contains a number of clips from the [Carnegie-Mellon University](../docs/cmu_performance.md) database that were hand selected. A readme file within the zip file details this. + +* **v1** Test data [link](https://drive.google.com/open?id=1psNO0riJ6RlD5_vsvPh2vPsgEBvLN3Hr) +* **v2** Test data [link](https://drive.google.com/open?id=192CWjNRwlskgdqakNI-k8dv-EefXwxtU) +* **v3** Test data [link](https://drive.google.com/file/d/1ZxQp1-q_stN2MIgyQm6v6FP2zg6GmNPk/view?usp=sharing) +* **v4** Test data [link](https://drive.google.com/file/d/10TQGU15uk2P3QY2DFxuOj5ilCCUjToxj/view?usp=sharing) +* **v5** Test data [link](https://drive.google.com/file/d/1rcvd6Uf4p2enZ4hSrP2WUuwGlUWnE0Gs/view?usp=sharing) (**Latest**) + +## Running the tests + +Using **Python 3**, run [make.py](../make.py) with the `-regression_test` command line switch. + +## Test configurations + +The [configs](./configs) directory contains a number of test configuration files. Each file specifies the compression settings to use and the corresponding expected error threshold. A reference configuration file can be found [here](./reference.config.sjson). + +All clips are tested against every test configuration. + +## Debugging a test failure + +If a regression test fails, the python script will output the clip that failed along with the command line used to reproduce the failure. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database.config.sjson new file mode 100644 index 00000000..f4d82c17 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database.config.sjson @@ -0,0 +1,12 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 +split_into_database = true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_4kb.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_4kb.config.sjson new file mode 100644 index 00000000..80259428 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_4kb.config.sjson @@ -0,0 +1,13 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 +split_into_database = true +database_max_chunk_size = 4096 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_4kb_mixed.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_4kb_mixed.config.sjson new file mode 100644 index 00000000..2678f612 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_4kb_mixed.config.sjson @@ -0,0 +1,16 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 + +split_into_database = true +database_max_chunk_size = 4096 +medium_importance_tier = 0.3 +low_importance_tier = 0.4 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_mixed.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_mixed.config.sjson new file mode 100644 index 00000000..ad142788 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_database_mixed.config.sjson @@ -0,0 +1,15 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 + +split_into_database = true +medium_importance_tier = 0.3 +low_importance_tier = 0.4 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_mixed_var_0.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_mixed_var_0.config.sjson new file mode 100644 index 00000000..7159d6f4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_mixed_var_0.config.sjson @@ -0,0 +1,11 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_full" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_mixed_var_1.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_mixed_var_1.config.sjson new file mode 100644 index 00000000..d4ab1b02 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_mixed_var_1.config.sjson @@ -0,0 +1,11 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_full" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_bind_relative.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_bind_relative.config.sjson new file mode 100644 index 00000000..525a9212 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_bind_relative.config.sjson @@ -0,0 +1,12 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 +is_bind_pose_relative = true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_high.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_high.config.sjson new file mode 100644 index 00000000..f4606202 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_high.config.sjson @@ -0,0 +1,11 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "High" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_highest.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_highest.config.sjson new file mode 100644 index 00000000..a7e40b12 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_highest.config.sjson @@ -0,0 +1,11 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Highest" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_medium.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_medium.config.sjson new file mode 100644 index 00000000..1271c1d7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_medium.config.sjson @@ -0,0 +1,11 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_mtx_error.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_mtx_error.config.sjson new file mode 100644 index 00000000..492beeef --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_quant_mtx_error.config.sjson @@ -0,0 +1,12 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +level = "Medium" + +rotation_format = "quatf_drop_w_variable" +translation_format = "vector3f_variable" +scale_format = "vector3f_variable" + +regression_error_threshold = 0.075 +use_matrix_error_metric = true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_raw.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_raw.config.sjson new file mode 100644 index 00000000..47a78485 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/configs/uniformly_sampled_raw.config.sjson @@ -0,0 +1,9 @@ +version = 2 + +algorithm_name = "uniformly_sampled" + +rotation_format = "quatf_full" +translation_format = "vector3f_full" +scale_format = "vector3f_full" + +regression_error_threshold = 0.001 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/reference.config.sjson b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/reference.config.sjson new file mode 100644 index 00000000..06163943 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/test_data/reference.config.sjson @@ -0,0 +1,19 @@ +// A version identifier, make sure to use the latest supported number +//version = 1 // initial version +version = 2 // cleanup with 2.0 + +// The name of the algorithm to use. See function get_algorithm_type(..) +// Required +algorithm_name = "uniformly_sampled" + +// The rotation, translation, and scale formats to use. See functions get_rotation_format(..) and get_vector_format(..) +// Defaults to raw: quatf_full and vector3f_full +rotation_format = "quatf_full" +translation_format = "vector3f_full" +scale_format = "vector3f_full" + +// The error threshold used when performing regression testing +// We will sample the clip at various positions and compare the raw and decompressed poses +// and fail if the error is above or equal to this threshold +// Defaults to '0.0' +regression_error_threshold = 0.0 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/CMakeLists.txt new file mode 100644 index 00000000..beac71c7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_unit_tests_root NONE) + +if(PLATFORM_ANDROID) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_android") +elseif(PLATFORM_IOS) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_ios") +elseif(PLATFORM_EMSCRIPTEN) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_emscripten") +else() + add_subdirectory("${PROJECT_SOURCE_DIR}/main_generic") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/CMakeLists.txt new file mode 100644 index 00000000..217aee59 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_unit_tests_gradle_shim NONE) + +# Set our project root since our gradle files used to build live in the binary output directory +# but the actual source files live in the source/current directory. +file(RELATIVE_PATH ACL_PROJECT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/app ${PROJECT_SOURCE_DIR}/app) + +# Setup our state +set(ACL_ANDROID_PROJECT_NAME "ACL Unit Tests") +set(ACL_ANDROID_PROJECT_PACKAGE_NAME "com.acl.unit_tests") + +# Configure gradle for our build configuration +configure_file(${ACL_ANDROID_MISC_DIR}/app/build.gradle.in app/build.gradle @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/app/src/main/AndroidManifest.xml.in app/src/main/AndroidManifest.xml @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/app/src/main/res/values/strings.xml.in app/src/main/res/values/strings.xml @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/settings.gradle.in settings.gradle @ONLY) + +# Copy gradle related files +file(COPY + ${ACL_ANDROID_MISC_DIR}/build.gradle + ${ACL_ANDROID_MISC_DIR}/gradle.properties + ${ACL_ANDROID_MISC_DIR}/gradle + ${ACL_ANDROID_MISC_DIR}/gradlew + ${ACL_ANDROID_MISC_DIR}/gradlew.bat + DESTINATION .) + +file(COPY + ${ACL_ANDROID_MISC_DIR}/app/proguard-rules.pro + DESTINATION app) + +add_custom_target(${PROJECT_NAME} ALL + COMMAND gradlew.bat + # Decide whether we should build Debug or Release + $<$:assembleDebug> + $<$:assembleRelease>) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..c249b6c8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Project root is \tests\main_android +set(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") + +include_directories("${PROJECT_ROOT_DIR}/../../includes") +include_directories("${PROJECT_ROOT_DIR}/../../external/rtm/includes") +include_directories("${PROJECT_ROOT_DIR}/../../external/catch2/single_include") + +if(USE_SJSON) + include_directories("${PROJECT_ROOT_DIR}/../../external/sjson-cpp/includes") +endif() + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_ROOT_DIR}/../sources/*.h + ${PROJECT_ROOT_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_library(${PROJECT_NAME} SHARED ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Enable exceptions +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) + +# Enable debug symbols +target_compile_options(${PROJECT_NAME} PRIVATE -g) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DACL_ON_ASSERT_THROW) +add_definitions(-DRTM_ON_ASSERT_THROW) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +# Enable SJSON when needed +if(USE_SJSON) + add_definitions(-DACL_USE_SJSON) + add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) +endif() + +target_include_directories(${PROJECT_NAME} PUBLIC jni) + +target_link_libraries(${PROJECT_NAME} m log) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/cpp/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/cpp/main.cpp new file mode 100644 index 00000000..9d38e3ed --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/cpp/main.cpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +#include + +extern "C" jint Java_com_acl_unit_1tests_MainActivity_getNumUnitTestCases(JNIEnv* env, jobject caller) +{ + return Catch::getRegistryHub().getTestCaseRegistry().getAllTests().size(); +} + +extern "C" jint Java_com_acl_unit_1tests_MainActivity_runUnitTests(JNIEnv* env, jobject caller) +{ + int result = Catch::Session().run(); + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/java/com/acl/unit_tests/MainActivity.java b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/java/com/acl/unit_tests/MainActivity.java new file mode 100644 index 00000000..1753b0bc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_android/app/src/main/java/com/acl/unit_tests/MainActivity.java @@ -0,0 +1,31 @@ +package com.acl.unit_tests; + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class MainActivity extends Activity { + static { + System.loadLibrary("acl_unit_tests"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView resultTextView = new TextView(this); + + int numUnitTestCases = getNumUnitTestCases(); + int numFailed = runUnitTests(); + + if (numFailed == 0) + resultTextView.setText("All " + numUnitTestCases + " test cases ran successfully!"); + else + resultTextView.setText(numFailed + " test cases failed!"); + + setContentView(resultTextView); + } + + public native int getNumUnitTestCases(); + public native int runUnitTests(); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_emscripten/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_emscripten/CMakeLists.txt new file mode 100644 index 00000000..efce385c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_emscripten/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/sjson-cpp/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_executable(${PROJECT_NAME} ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DACL_ON_ASSERT_THROW) +add_definitions(-DRTM_ON_ASSERT_THROW) +add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) + +add_definitions(-DACL_USE_SJSON) + +target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) # Enable all warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow) # Enable shadowing warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Werror) # Treat warnings as errors + +# Exceptions are not enabled by default, enable them +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) +target_link_libraries(${PROJECT_NAME} PRIVATE "-s DISABLE_EXCEPTION_CATCHING=0") + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s NODERAWFS=1") # Enable the raw node file system +target_link_libraries(${PROJECT_NAME} PRIVATE -lnodefs.js) # Link the node file system + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s ENVIRONMENT=node") # Force the environment to node + +target_link_libraries(${PROJECT_NAME} PRIVATE "-s ALLOW_MEMORY_GROWTH=1") # Allow dynamic memory allocation + +# Setup Catch2 so we can find and execute the unit tests with CTest +set(OptionalCatchTestLauncher node) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../external/catch2/contrib") +include(CTest) +include(ParseAndAddCatchTests) +ParseAndAddCatchTests(${PROJECT_NAME}) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.js + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wasm + DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_emscripten/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_emscripten/main.cpp new file mode 100644 index 00000000..bacb1069 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_emscripten/main.cpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_generic/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_generic/CMakeLists.txt new file mode 100644 index 00000000..b60deab7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_generic/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_unit_tests CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/sjson-cpp/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_TEST_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../external/catch2/contrib") +include(CTest) +include(Catch) +catch_discover_tests(${PROJECT_NAME}) + +setup_default_compiler_flags(${PROJECT_NAME}) + +if(MSVC) + if(CPU_INSTRUCTION_SET MATCHES "arm64") + # Exceptions are not enabled by default for ARM targets, enable them + target_compile_options(${PROJECT_NAME} PRIVATE /EHsc) + endif() +endif() + +# Throw on failure to allow us to catch them and recover +add_definitions(-DACL_ON_ASSERT_THROW) +add_definitions(-DRTM_ON_ASSERT_THROW) +add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) + +add_definitions(-DACL_USE_SJSON) + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_generic/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_generic/main.cpp new file mode 100644 index 00000000..243d5c0c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_generic/main.cpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +#ifdef _WIN32 +#include +#endif + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + +#ifdef _WIN32 + if (IsDebuggerPresent()) + { + printf("Press any key to continue...\n"); + while (_kbhit() == 0); + } +#endif + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_ios/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_ios/CMakeLists.txt new file mode 100644 index 00000000..0dbc4b63 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_ios/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_unit_tests) + +# iOS cmake toolchain does not support CMAKE_CXX_STANDARD +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Force enable debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + +# Enable optimizations in Release +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +set(MACOSX_BUNDLE_EXECUTABLE_NAME ${PROJECT_NAME}) +set(MACOSX_BUNDLE_INFO_STRING "com.acl.acl-unit-tests") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.acl.acl-unit-tests") +set(MACOSX_BUNDLE_BUNDLE_NAME "acl-unit-tests") + +include_directories("${PROJECT_SOURCE_DIR}/../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../external/catch2/single_include") + +if(USE_SJSON) + include_directories("${PROJECT_SOURCE_DIR}/../../external/sjson-cpp/includes") +endif() + +# Grab all of our test source files +file(GLOB_RECURSE ALL_TEST_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_TEST_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ALL_TEST_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Throw on failure to allow us to catch them and recover +add_definitions(-DACL_ON_ASSERT_THROW) +add_definitions(-DRTM_ON_ASSERT_THROW) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +# Enable SJSON when needed +if(USE_SJSON) + add_definitions(-DACL_USE_SJSON) + add_definitions(-DSJSON_CPP_ON_ASSERT_THROW) +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_ios/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_ios/main.cpp new file mode 100644 index 00000000..e71b4d84 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/main_ios/main.cpp @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#define CATCH_CONFIG_RUNNER +#include + +int main(int argc, char* argv[]) +{ + int result = Catch::Session().run(argc, argv); + + return (result < 0xff ? result : 0xff); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_ansi_allocator.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_ansi_allocator.cpp new file mode 100644 index 00000000..37099b9e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_ansi_allocator.cpp @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +// Enable allocation tracking +#define ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS +#define ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS + +#include +#include + +using namespace acl; + +TEST_CASE("ANSI allocator", "[core][memory]") +{ + ansi_allocator allocator; + CHECK(allocator.get_allocation_count() == 0); + + void* ptr0 = allocator.allocate(32); + CHECK(allocator.get_allocation_count() == 1); + + void* ptr1 = allocator.allocate(48, 256); + CHECK(allocator.get_allocation_count() == 2); + CHECK(is_aligned_to(ptr1, 256)); + allocator.deallocate(ptr1, 48); + CHECK(allocator.get_allocation_count() == 1); + + allocator.deallocate(ptr0, 32); + CHECK(allocator.get_allocation_count() == 0); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_bit_manip_utils.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_bit_manip_utils.cpp new file mode 100644 index 00000000..8af8222c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_bit_manip_utils.cpp @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace acl; + +TEST_CASE("bit_manip_utils", "[core][utils]") +{ + CHECK(count_set_bits(uint8_t(0x00)) == 0); + CHECK(count_set_bits(uint8_t(0x01)) == 1); + CHECK(count_set_bits(uint8_t(0x10)) == 1); + CHECK(count_set_bits(uint8_t(0xFF)) == 8); + + CHECK(count_set_bits(uint16_t(0x0000)) == 0); + CHECK(count_set_bits(uint16_t(0x0001)) == 1); + CHECK(count_set_bits(uint16_t(0x1000)) == 1); + CHECK(count_set_bits(uint16_t(0x1001)) == 2); + CHECK(count_set_bits(uint16_t(0xFFFF)) == 16); + + CHECK(count_set_bits(uint32_t(0x00000000)) == 0); + CHECK(count_set_bits(uint32_t(0x00000001)) == 1); + CHECK(count_set_bits(uint32_t(0x10000000)) == 1); + CHECK(count_set_bits(uint32_t(0x10101001)) == 4); + CHECK(count_set_bits(uint32_t(0xFFFFFFFF)) == 32); + + CHECK(count_set_bits(uint64_t(0x0000000000000000ULL)) == 0); + CHECK(count_set_bits(uint64_t(0x0000000000000001ULL)) == 1); + CHECK(count_set_bits(uint64_t(0x1000000000000000ULL)) == 1); + CHECK(count_set_bits(uint64_t(0x1000100001010101ULL)) == 6); + CHECK(count_set_bits(uint64_t(0xFFFFFFFFFFFFFFFFULL)) == 64); + + CHECK(count_leading_zeros(uint32_t(0x00000000)) == 32); + CHECK(count_leading_zeros(uint32_t(0x00000001)) == 31); + CHECK(count_leading_zeros(uint32_t(0x00000002)) == 30); + CHECK(count_leading_zeros(uint32_t(0x80000000)) == 0); + CHECK(count_leading_zeros(uint32_t(0x40000000)) == 1); + + CHECK(count_trailing_zeros(uint32_t(0x00000000)) == 32); + CHECK(count_trailing_zeros(uint32_t(0x00000001)) == 0); + CHECK(count_trailing_zeros(uint32_t(0x00000002)) == 1); + CHECK(count_trailing_zeros(uint32_t(0x80000000)) == 31); + CHECK(count_trailing_zeros(uint32_t(0x40000000)) == 30); + + CHECK(rotate_bits_left(0x00000010, 0) == 0x00000010); + CHECK(rotate_bits_left(0x10000010, 1) == 0x20000020); + CHECK(rotate_bits_left(0x10000010, 2) == 0x40000040); + CHECK(rotate_bits_left(0x10000010, 3) == 0x80000080); + CHECK(rotate_bits_left(0x10000010, 4) == 0x00000101); + + CHECK(and_not(0x00000010, 0x10101011) == 0x10101001); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_bitset.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_bitset.cpp new file mode 100644 index 00000000..24f5e144 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_bitset.cpp @@ -0,0 +1,130 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include + +using namespace acl; + +TEST_CASE("bitset", "[core][utils]") +{ + CHECK(bitset_description::make_from_num_bits(0).get_size() == 0); + CHECK(bitset_description::make_from_num_bits(1).get_size() == 1); + CHECK(bitset_description::make_from_num_bits(31).get_size() == 1); + CHECK(bitset_description::make_from_num_bits(32).get_size() == 1); + CHECK(bitset_description::make_from_num_bits(33).get_size() == 2); + CHECK(bitset_description::make_from_num_bits(64).get_size() == 2); + CHECK(bitset_description::make_from_num_bits(65).get_size() == 3); + + CHECK(bitset_description::make_from_num_bits(0).get_num_bits() == 0); + CHECK(bitset_description::make_from_num_bits(1).get_num_bits() == 32); + CHECK(bitset_description::make_from_num_bits(31).get_num_bits() == 32); + CHECK(bitset_description::make_from_num_bits(32).get_num_bits() == 32); + CHECK(bitset_description::make_from_num_bits(33).get_num_bits() == 64); + CHECK(bitset_description::make_from_num_bits(64).get_num_bits() == 64); + CHECK(bitset_description::make_from_num_bits(65).get_num_bits() == 96); + + constexpr bitset_description desc = bitset_description::make_from_num_bits<64>(); + CHECK(desc.get_size() == 2); + CHECK(desc.get_size() == bitset_description::make_from_num_bits(64).get_size()); + + uint32_t bitset_data[desc.get_size() + 1]; // Add padding + std::memset(&bitset_data[0], 0, sizeof(bitset_data)); + + bitset_reset(&bitset_data[0], desc, true); + CHECK(bitset_data[0] == 0xFFFFFFFF); + CHECK(bitset_data[1] == 0xFFFFFFFF); + CHECK(bitset_data[2] == 0); + + bitset_data[2] = 0xFFFFFFFF; + bitset_reset(&bitset_data[0], desc, false); + CHECK(bitset_data[0] == 0); + CHECK(bitset_data[1] == 0); + CHECK(bitset_data[2] == 0xFFFFFFFF); + + bitset_data[2] = 0; + bitset_set(&bitset_data[0], desc, 0, false); + CHECK(bitset_data[0] == 0); + CHECK(bitset_data[1] == 0); + CHECK(bitset_data[2] == 0); + + bitset_set(&bitset_data[0], desc, 0, true); + CHECK(bitset_data[0] == 0x80000000); + CHECK(bitset_data[1] == 0); + CHECK(bitset_data[2] == 0); + + bitset_set(&bitset_data[0], desc, 31, true); + CHECK(bitset_data[0] == 0x80000001); + CHECK(bitset_data[1] == 0); + CHECK(bitset_data[2] == 0); + + bitset_set(&bitset_data[0], desc, 31, false); + CHECK(bitset_data[0] == 0x80000000); + CHECK(bitset_data[1] == 0); + CHECK(bitset_data[2] == 0); + + bitset_set(&bitset_data[0], desc, 32, true); + CHECK(bitset_data[0] == 0x80000000); + CHECK(bitset_data[1] == 0x80000000); + CHECK(bitset_data[2] == 0); + + bitset_set_range(&bitset_data[0], desc, 8, 4, true); + CHECK(bitset_data[0] == 0x80F00000); + CHECK(bitset_data[1] == 0x80000000); + CHECK(bitset_data[2] == 0); + + bitset_set_range(&bitset_data[0], desc, 10, 2, false); + CHECK(bitset_data[0] == 0x80C00000); + CHECK(bitset_data[1] == 0x80000000); + CHECK(bitset_data[2] == 0); + + CHECK(bitset_test(&bitset_data[0], desc, 0) == true); + CHECK(bitset_test(&bitset_data[0], desc, 1) == false); + CHECK(bitset_test(&bitset_data[0], desc, 2) == false); + CHECK(bitset_test(&bitset_data[0], desc, 3) == false); + CHECK(bitset_test(&bitset_data[0], desc, 4) == false); + CHECK(bitset_test(&bitset_data[0], desc, 5) == false); + CHECK(bitset_test(&bitset_data[0], desc, 6) == false); + CHECK(bitset_test(&bitset_data[0], desc, 7) == false); + CHECK(bitset_test(&bitset_data[0], desc, 8) == true); + CHECK(bitset_test(&bitset_data[0], desc, 9) == true); + CHECK(bitset_test(&bitset_data[0], desc, 10) == false); + CHECK(bitset_test(&bitset_data[0], desc, 11) == false); + + bitset_data[2] = 0xFFFFFFFF; + CHECK(bitset_count_set_bits(&bitset_data[0], desc) == 4); + + uint32_t bitset_data1[desc.get_size() + 1]; // Add padding + bitset_data[0] = 0x00000010; + bitset_data[1] = 0x00100000; + + bitset_data1[0] = 0x10101011; + bitset_data1[1] = 0x10101011; + bitset_and_not(bitset_data, bitset_data, bitset_data1, desc); + CHECK(bitset_data[0] == 0x10101001); + CHECK(bitset_data[1] == 0x10001011); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_enum_utils.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_enum_utils.cpp new file mode 100644 index 00000000..cd5022fc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_enum_utils.cpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include + +using namespace acl; + +enum class TestEnum : uint32_t +{ + Empty = 0x0000, + One = 0x0001, + Two = 0x0002, + Four = 0x0004, + All = 0x0007, +}; + +ACL_IMPL_ENUM_FLAGS_OPERATORS(TestEnum) + +TEST_CASE("enum utils", "[core][utils]") +{ + CHECK(are_any_enum_flags_set(TestEnum::One | TestEnum::Two, TestEnum::All) == true); + CHECK(are_any_enum_flags_set(TestEnum::One | TestEnum::Two, TestEnum::One) == true); + CHECK(are_any_enum_flags_set(TestEnum::All, TestEnum::One | TestEnum::Two) == true); + CHECK(are_any_enum_flags_set(TestEnum::One | TestEnum::Two, TestEnum::Four) == false); + + CHECK(are_all_enum_flags_set(TestEnum::One | TestEnum::Two, TestEnum::One | TestEnum::Two) == true); + CHECK(are_all_enum_flags_set(TestEnum::One, TestEnum::One | TestEnum::Two) == false); + CHECK(are_all_enum_flags_set(TestEnum::One | TestEnum::Two, TestEnum::One) == true); + CHECK(are_all_enum_flags_set(TestEnum::One | TestEnum::Two, TestEnum::All) == false); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_error_result.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_error_result.cpp new file mode 100644 index 00000000..90b042d8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_error_result.cpp @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include +#include + +using namespace acl; + +TEST_CASE("error_result", "[core][error]") +{ + CHECK(error_result().any() == false); + CHECK(error_result().empty() == true); + CHECK(std::strlen(error_result().c_str()) == 0); + + CHECK(error_result("failed").any() == true); + CHECK(error_result("failed").empty() == false); + CHECK(std::strcmp(error_result("failed").c_str(), "failed") == 0); + + error_result tmp("failed"); + CHECK(tmp.any() == true); + tmp.reset(); + CHECK(tmp.any() == false); + CHECK(tmp.empty() == true); + CHECK(std::strlen(tmp.c_str()) == 0); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_interpolation_utils.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_interpolation_utils.cpp new file mode 100644 index 00000000..e2e205f0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_interpolation_utils.cpp @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +using namespace acl; +using namespace rtm; + +TEST_CASE("interpolation utils", "[core][utils]") +{ + const float error_threshold = 1.0E-6F; + + uint32_t key0; + uint32_t key1; + float alpha; + find_linear_interpolation_samples_with_duration(31, 1.0F, 0.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 0); + CHECK(key1 == 1); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 1.0F / 30.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 1); + CHECK(key1 == 2); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 2.5F / 30.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 0.5F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 1.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 30); + CHECK(key1 == 30); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 2.5F / 30.0F, sample_rounding_policy::floor, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 2.5F / 30.0F, sample_rounding_policy::ceil, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 1.0F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 2.4F / 30.0F, sample_rounding_policy::nearest, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_duration(31, 1.0F, 2.6F / 30.0F, sample_rounding_policy::nearest, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 1.0F, error_threshold)); + + ////////////////////////////////////////////////////////////////////////// + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 0.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 0); + CHECK(key1 == 1); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 1.0F / 30.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 1); + CHECK(key1 == 2); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 2.5F / 30.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 0.5F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 1.0F, sample_rounding_policy::none, key0, key1, alpha); + + CHECK(key0 == 30); + CHECK(key1 == 30); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 2.5F / 30.0F, sample_rounding_policy::floor, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 2.5F / 30.0F, sample_rounding_policy::ceil, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 1.0F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 2.4F / 30.0F, sample_rounding_policy::nearest, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 0.0F, error_threshold)); + + find_linear_interpolation_samples_with_sample_rate(31, 30.0F, 2.6F / 30.0F, sample_rounding_policy::nearest, key0, key1, alpha); + + CHECK(key0 == 2); + CHECK(key1 == 3); + CHECK(scalar_near_equal(alpha, 1.0F, error_threshold)); + + ////////////////////////////////////////////////////////////////////////// + + CHECK(scalar_near_equal(find_linear_interpolation_alpha(0.0F, 1, 1, sample_rounding_policy::none), 0.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 2, sample_rounding_policy::none), 0.5F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 2, sample_rounding_policy::none), 0.75F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 3, sample_rounding_policy::none), 0.5F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 4, sample_rounding_policy::none), 0.16666667F, error_threshold)); + + CHECK(scalar_near_equal(find_linear_interpolation_alpha(0.0F, 1, 1, sample_rounding_policy::floor), 0.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 2, sample_rounding_policy::floor), 0.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 2, sample_rounding_policy::floor), 0.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 3, sample_rounding_policy::floor), 0.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 4, sample_rounding_policy::floor), 0.0F, error_threshold)); + + CHECK(scalar_near_equal(find_linear_interpolation_alpha(0.0F, 1, 1, sample_rounding_policy::ceil), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 2, sample_rounding_policy::ceil), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 2, sample_rounding_policy::ceil), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 3, sample_rounding_policy::ceil), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 4, sample_rounding_policy::ceil), 1.0F, error_threshold)); + + CHECK(scalar_near_equal(find_linear_interpolation_alpha(0.0F, 1, 1, sample_rounding_policy::nearest), 0.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 2, sample_rounding_policy::nearest), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 2, sample_rounding_policy::nearest), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 0, 3, sample_rounding_policy::nearest), 1.0F, error_threshold)); + CHECK(scalar_near_equal(find_linear_interpolation_alpha(1.5F, 1, 4, sample_rounding_policy::nearest), 0.0F, error_threshold)); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_iterator.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_iterator.cpp new file mode 100644 index 00000000..791d246e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_iterator.cpp @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include + +using namespace acl; + +TEST_CASE("iterator", "[core][iterator]") +{ + constexpr uint32_t num_items = 3; + uint32_t items[num_items]; + + auto i = iterator(items, num_items); + + SECTION("mutable returns correct type") + { + CHECK(std::is_same::value); + CHECK(std::is_same::value); + } + + SECTION("const returns correct type") + { + auto ci = const_iterator(items, num_items); + + CHECK(std::is_same::value); + CHECK(std::is_same::value); + } + + SECTION("bounds are correct") + { + CHECK(i.begin() == items + 0); + CHECK(i.end() == items + num_items); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_memory_utils.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_memory_utils.cpp new file mode 100644 index 00000000..e7de1145 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_memory_utils.cpp @@ -0,0 +1,290 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include +#include +#include + +using namespace acl; + +TEST_CASE("misc tests", "[core][memory]") +{ + size_t num_powers_of_two = 0; + for (size_t i = 0; i <= 65536; ++i) + { + if (is_power_of_two(i)) + num_powers_of_two++; + } + + CHECK(num_powers_of_two == 17); + CHECK(is_power_of_two(1) == true); + CHECK(is_power_of_two(2) == true); + CHECK(is_power_of_two(4) == true); + CHECK(is_power_of_two(8) == true); + CHECK(is_power_of_two(16) == true); + CHECK(is_power_of_two(32) == true); + CHECK(is_power_of_two(64) == true); + CHECK(is_power_of_two(128) == true); + CHECK(is_power_of_two(256) == true); + CHECK(is_power_of_two(512) == true); + CHECK(is_power_of_two(1024) == true); + CHECK(is_power_of_two(2048) == true); + CHECK(is_power_of_two(4096) == true); + CHECK(is_power_of_two(8192) == true); + CHECK(is_power_of_two(16384) == true); + CHECK(is_power_of_two(32768) == true); + CHECK(is_power_of_two(65536) == true); + + CHECK(is_alignment_valid(0) == false); + CHECK(is_alignment_valid(4) == true); + CHECK(is_alignment_valid(8) == true); + CHECK(is_alignment_valid(2) == false); + CHECK(is_alignment_valid(5) == false); + CHECK(is_alignment_valid(8) == true); + CHECK(is_alignment_valid(16) == true); + + struct alignas(8) Tmp + { + int32_t padding; // Aligned to 8 bytes + int32_t value; // Aligned to 4 bytes + }; + Tmp tmp; + CHECK(is_aligned_to(&tmp.padding, 8) == true); + CHECK(is_aligned_to(&tmp.value, 4) == true); + CHECK(is_aligned_to(&tmp.value, 2) == true); + CHECK(is_aligned_to(&tmp.value, 1) == true); + CHECK(is_aligned_to(&tmp.value, 8) == false); + + CHECK(is_aligned_to(4, 4) == true); + CHECK(is_aligned_to(4, 2) == true); + CHECK(is_aligned_to(4, 1) == true); + CHECK(is_aligned_to(4, 8) == false); + CHECK(is_aligned_to(6, 4) == false); + CHECK(is_aligned_to(6, 2) == true); + CHECK(is_aligned_to(6, 1) == true); + + CHECK(is_aligned_to(align_to(5, 4), 4) == true); + CHECK(align_to(5, 4) == 8); + CHECK(is_aligned_to(align_to(8, 4), 4) == true); + CHECK(align_to(8, 4) == 8); + + void* ptr = (void*)0x00000000; + CHECK(align_to(ptr, 4) == (void*)0x00000000); + CHECK(align_to(ptr, 8) == (void*)0x00000000); + ptr = (void*)0x00000001; + CHECK(align_to(ptr, 4) == (void*)0x00000004); + CHECK(align_to(ptr, 8) == (void*)0x00000008); + ptr = (void*)0x00000004; + CHECK(align_to(ptr, 4) == (void*)0x00000004); + CHECK(align_to(ptr, 8) == (void*)0x00000008); + + struct alignas(8) Align8 + { + float tmp[2]; + }; + + constexpr size_t padding0 = get_required_padding(); + constexpr size_t padding1 = get_required_padding(); + CHECK(padding0 == 4); + CHECK(padding1 == 7); + + int32_t array[8]; + CHECK(get_array_size(array) == (sizeof(array) / sizeof(array[0]))); +} + +TEST_CASE("raw memory support", "[core][memory]") +{ + uint8_t buffer[1024]; + uint8_t* ptr = &buffer[32]; + CHECK(add_offset_to_ptr(ptr, 23) == ptr + 23); + CHECK(add_offset_to_ptr(ptr, 64) == ptr + 64); + + uint16_t value16 = 0x04FE; + CHECK(byte_swap(value16) == 0xFE04); + + uint32_t value32 = 0x04FE78AB; + CHECK(byte_swap(value32) == 0xAB78FE04); + + uint64_t value64 = uint64_t(0x04FE78AB0098DC56ULL); + CHECK(byte_swap(value64) == uint64_t(0x56DC9800AB78FE04ULL)); + + uint8_t unaligned_value_buffer[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; + std::memcpy(&unaligned_value_buffer[1], &value32, sizeof(uint32_t)); + CHECK(unaligned_load(&unaligned_value_buffer[1]) == value32); +} + +TEST_CASE("memcpy_bits", "[core][memory]") +{ + uint64_t dest = uint64_t(~0ULL); + uint64_t src = byte_swap(uint64_t(0x5555555555555555ULL)); + memcpy_bits(&dest, 1, &src, 0, 64 - 3); + CHECK(dest == byte_swap(uint64_t(0xAAAAAAAAAAAAAAABULL))); + + dest = byte_swap(uint64_t(0x0F00FF0000000000ULL)); + src = byte_swap(uint64_t(0x3800000000000000ULL)); + memcpy_bits(&dest, 0, &src, 2, 5); + CHECK(dest == byte_swap(uint64_t(0xE700FF0000000000ULL))); + + dest = byte_swap(uint64_t(0x0F00FF0000000000ULL)); + src = byte_swap(uint64_t(0x3800000000000000ULL)); + memcpy_bits(&dest, 1, &src, 2, 5); + CHECK(dest == byte_swap(uint64_t(0x7300FF0000000000ULL))); + + dest = 0; + src = uint64_t(~0ULL); + memcpy_bits(&dest, 1, &src, 0, 7); + CHECK(dest == byte_swap(uint64_t(0x7F00000000000000ULL))); + + memcpy_bits(&dest, 8, &src, 0, 8); + CHECK(dest == byte_swap(uint64_t(0x7FFF000000000000ULL))); + + memcpy_bits(&dest, 0, &src, 0, 64); + CHECK(dest == uint64_t(~0ULL)); + + dest = 0; + src = uint64_t(~0ULL); + memcpy_bits(&dest, 0, &src, 0, 0); + CHECK(dest == 0); + + memcpy_bits(&dest, 0, nullptr, 0, 0); + CHECK(dest == 0); + + memcpy_bits(nullptr, 0, &src, 0, 0); + CHECK(dest == 0); +} + +enum class UnsignedEnum : uint32_t +{ + ZERO = 0, + U16_MAX = std::numeric_limits::max(), + U32_MAX = std::numeric_limits::max(), +}; + +enum class SignedEnum : int32_t +{ + I32_MIN = std::numeric_limits::min(), + I16_MIN = std::numeric_limits::min(), + I16_MAX = std::numeric_limits::max(), + I32_MAX = std::numeric_limits::max(), +}; + +TEST_CASE("safe_static_cast from unsigned enum", "[core][memory]") +{ + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK(safe_static_cast(UnsignedEnum::U16_MAX) == std::numeric_limits::max()); + CHECK(safe_static_cast(UnsignedEnum::U32_MAX) == std::numeric_limits::max()); + + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK(safe_static_cast(UnsignedEnum::U16_MAX)); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U32_MAX)); + + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK(safe_static_cast(UnsignedEnum::U16_MAX) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U32_MAX)); + + CHECK(safe_static_cast(UnsignedEnum::ZERO) == 0); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U16_MAX)); + CHECK_THROWS(safe_static_cast(UnsignedEnum::U32_MAX)); +} + +TEST_CASE("safe_static_cast from signed enum", "[core][memory]") +{ + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MIN)); + CHECK_THROWS(safe_static_cast(SignedEnum::I16_MIN)); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == safe_static_cast(std::numeric_limits::max())); + CHECK(safe_static_cast(SignedEnum::I32_MAX) == safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(SignedEnum::I32_MIN) == std::numeric_limits::min()); + CHECK(safe_static_cast(SignedEnum::I16_MIN) == std::numeric_limits::min()); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == std::numeric_limits::max()); + CHECK(safe_static_cast(SignedEnum::I32_MAX) == std::numeric_limits::max()); + + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MIN)); + CHECK_THROWS(safe_static_cast(SignedEnum::I16_MIN)); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MAX)); + + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MIN)); + CHECK(safe_static_cast(SignedEnum::I16_MIN) == std::numeric_limits::min()); + CHECK(safe_static_cast(SignedEnum::I16_MAX) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(SignedEnum::I32_MAX)); +} + +TEST_CASE("safe_static_cast from signed int", "[core][memory]") +{ + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK(safe_static_cast(std::numeric_limits::max()) == safe_static_cast(std::numeric_limits::max())); + CHECK(safe_static_cast(std::numeric_limits::max()) == safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(std::numeric_limits::min()) == std::numeric_limits::min()); + CHECK(safe_static_cast(std::numeric_limits::min()) == std::numeric_limits::min()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + + CHECK_THROWS(safe_static_cast(std::numeric_limits::min())); + CHECK(safe_static_cast(std::numeric_limits::min()) == std::numeric_limits::min()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); +} + +TEST_CASE("safe_static_cast from unsigned int", "[core][memory]") +{ + CHECK(safe_static_cast(0U) == 0); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + + CHECK(safe_static_cast(0U) == 0); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(0U) == 0); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + + CHECK(safe_static_cast(0U) == 0); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); + CHECK_THROWS(safe_static_cast(std::numeric_limits::max())); +} + +TEST_CASE("safe_static_cast from double", "[core][memory]") +{ + // Use volatile to avoid a constant being truncated warning + volatile double value = -std::numeric_limits::max(); + CHECK_THROWS(safe_static_cast(value)); + value = std::numeric_limits::max(); + CHECK_THROWS(safe_static_cast(value)); + + CHECK(safe_static_cast(-std::numeric_limits::max()) == -std::numeric_limits::max()); + CHECK(safe_static_cast(std::numeric_limits::max()) == std::numeric_limits::max()); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_ptr_offset.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_ptr_offset.cpp new file mode 100644 index 00000000..d705021f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_ptr_offset.cpp @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace acl; + +TEST_CASE("ptr_offset", "[core][memory]") +{ + CHECK(ptr_offset32(invalid_ptr_offset()).is_valid() == false); + + ptr_offset32 offset(32); + CHECK(offset.is_valid() == true); + + uint8_t* ptr = nullptr; + CHECK(offset.add_to(ptr) == (ptr + 32)); + CHECK(offset.add_to(ptr) == offset.safe_add_to(ptr)); + CHECK(uint32_t(offset) == 32); + + CHECK(ptr_offset32(ptr, ptr + 31) == 31); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_string.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_string.cpp new file mode 100644 index 00000000..4ab2487f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_string.cpp @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +// Enable allocation tracking +#define ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS +#define ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS + +#include +#include + +#include + +using namespace acl; + +TEST_CASE("string", "[core][string]") +{ + ansi_allocator allocator; + ansi_allocator allocator2; + + CHECK(string().get_allocator() == nullptr); + CHECK(string().size() == 0); + CHECK(string().c_str() != nullptr); + CHECK(string().get_copy().size() == 0); + CHECK(string().get_copy().get_allocator() == nullptr); + CHECK(string().get_copy(allocator).get_allocator() == nullptr); + CHECK(string(allocator, "").size() == 0); + CHECK(string(allocator, "").c_str() != nullptr); + + const char* str0 = "this is a test string"; + const char* str1 = "this is not a test string"; + const char* str2 = "this is a test asset!"; + + CHECK(string(allocator, str0) == str0); + CHECK(string(allocator, str0) != str1); + CHECK(string(allocator, str0) != str2); + CHECK(string(allocator, str0) == string(allocator, str0)); + CHECK(string(allocator, str0) != string(allocator, str1)); + CHECK(string(allocator, str0) != string(allocator, str2)); + CHECK(string(allocator, str0).get_allocator() == &allocator); + CHECK(string(allocator, str0).c_str() != str0); + CHECK(string(allocator, str0).size() == std::strlen(str0)); + CHECK(string(allocator, str0).get_copy().size() == std::strlen(str0)); + CHECK(string(allocator, str0).get_copy().get_allocator() == &allocator); + CHECK(string(allocator, str0).get_copy() == string(allocator, str0)); + CHECK(string(allocator, str0).get_copy(allocator2).size() == std::strlen(str0)); + CHECK(string(allocator, str0).get_copy(allocator2).get_allocator() == &allocator2); + CHECK(string(allocator, str0).get_copy(allocator2) == string(allocator, str0)); + CHECK(string(allocator, str0, 4) == string(allocator, str1, 4)); + CHECK(string(allocator, str0, 4) == "this"); + + CHECK(string().empty() == true); + CHECK(string(allocator, "").empty() == true); + CHECK(string(allocator, str0).empty() == false); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_utils.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_utils.cpp new file mode 100644 index 00000000..90e98d53 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/core/test_utils.cpp @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include + +using namespace acl; +using namespace rtm; + +TEST_CASE("misc utils", "[core][utils]") +{ + CHECK(calculate_num_samples(0.0F, 30.0F) == 0); + CHECK(calculate_num_samples(1.0F, 30.0F) == 31); + CHECK(calculate_num_samples(1.0F, 24.0F) == 25); + CHECK(calculate_num_samples(1.0F / 30.F, 30.0F) == 2); + CHECK(calculate_num_samples(std::numeric_limits::infinity(), 30.0F) == 1); + + CHECK(calculate_duration(0, 30.0F) == 0.0F); + CHECK(calculate_duration(1, 30.0F) == std::numeric_limits::infinity()); + CHECK(calculate_duration(1, 8.0F) == std::numeric_limits::infinity()); + CHECK(scalar_near_equal(calculate_duration(31, 30.0F), 1.0F, 1.0E-8F)); + CHECK(scalar_near_equal(calculate_duration(9, 8.0F), 1.0F, 1.0E-8F)); + + CHECK(calculate_finite_duration(0, 30.0F) == 0.0F); + CHECK(calculate_finite_duration(1, 30.0F) == 0.0F); + CHECK(calculate_finite_duration(1, 8.0F) == 0.0F); + CHECK(scalar_near_equal(calculate_finite_duration(31, 30.0F), 1.0F, 1.0E-8F)); + CHECK(scalar_near_equal(calculate_finite_duration(9, 8.0F), 1.0F, 1.0E-8F)); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/io/test_reader_writer.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/io/test_reader_writer.cpp new file mode 100644 index 00000000..74d88b4d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/io/test_reader_writer.cpp @@ -0,0 +1,840 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +// Enable allocation tracking +#define ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS +#define ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS + +#if defined(ACL_USE_SJSON) + #include + #include +#endif + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#if defined(_WIN32) + // The below excludes some other unused services from the windows headers -- see windows.h for details. + #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ + #define NOVIRTUALKEYCODES // VK_* + #define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* + #define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* + #define NOSYSMETRICS // SM_* + #define NOMENUS // MF_* + #define NOICONS // IDI_* + #define NOKEYSTATES // MK_* + #define NOSYSCOMMANDS // SC_* + #define NORASTEROPS // Binary and Tertiary raster ops + #define NOSHOWWINDOW // SW_* + #define OEMRESOURCE // OEM Resource values + #define NOATOM // Atom Manager routines + #define NOCLIPBOARD // Clipboard routines + #define NOCOLOR // Screen colors + #define NOCTLMGR // Control and Dialog routines + #define NODRAWTEXT // DrawText() and DT_* + #define NOGDI // All GDI #defines and routines + #define NOKERNEL // All KERNEL #defines and routines + #define NOUSER // All USER #defines and routines + #define NONLS // All NLS #defines and routines + #define NOMB // MB_* and MessageBox() + #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines + #define NOMETAFILE // typedef METAFILEPICT + #define NOMINMAX // Macros min(a,b) and max(a,b) + #define NOMSG // typedef MSG and associated routines + #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* + #define NOSCROLL // SB_* and scrolling routines + #define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. + #define NOSOUND // Sound driver routines + #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines + #define NOWH // SetWindowsHook and WH_* + #define NOWINOFFSETS // GWL_*, GCL_*, associated routines + #define NOCOMM // COMM driver routines + #define NOKANJI // Kanji support stuff. + #define NOHELP // Help engine interface. + #define NOPROFILER // Profiler interface. + #define NODEFERWINDOWPOS // DeferWindowPos routines + #define NOMCX // Modem Configuration Extensions + #define NOCRYPT + #define NOTAPE + #define NOIMAGE + #define NOPROXYSTUB + #define NORPC + + #include + #include +#endif // _WIN32 + +using namespace acl; + +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) +#ifdef _WIN32 + constexpr uint32_t k_max_filename_size = MAX_PATH; +#else + constexpr uint32_t k_max_filename_size = 1024; +#endif + +static void get_temporary_filename(char* filename, uint32_t filename_size, const char* prefix) +{ +#ifdef _WIN32 + DWORD result = GetTempPathA(filename_size, filename); + REQUIRE(result != 0); + + char id[1024]; + snprintf(id, get_array_size(id), "%u", std::rand()); + + strcat_s(filename, filename_size, prefix); + strcat_s(filename, filename_size, id); + strcat_s(filename, filename_size, ".acl.sjson"); +#else + (void)filename_size; + + char id[1024]; + snprintf(id, get_array_size(id), "%u", std::rand()); + + std::strcat(filename, "/tmp/"); + std::strcat(filename, prefix); + std::strcat(filename, id); + std::strcat(filename, ".acl.sjson"); +#endif +} +#endif + +TEST_CASE("sjson_clip_reader_writer", "[io]") +{ + // Only test the reader/writer on non-mobile platforms +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) + ansi_allocator allocator; + + const uint32_t num_tracks = 3; + const uint32_t num_samples = 4; + track_array_qvvf track_list(allocator, num_tracks); + + track_desc_transformf desc0; + desc0.output_index = 0; + desc0.precision = 0.001F; + track_qvvf track0 = track_qvvf::make_reserve(desc0, allocator, num_samples, 32.0F); + track0[0].rotation = rtm::quat_from_euler(0.1F, 0.5F, 1.2F); + track0[0].translation = rtm::vector_set(0.0F, 0.6F, 2.3F); + track0[0].scale = rtm::vector_set(1.4F, 2.1F, 0.2F); + for (uint32_t i = 1; i < num_samples; ++i) + { + track0[i].rotation = rtm::quat_lerp(rtm::quat_identity(), track0[0].rotation, 0.1F * float(i)); + track0[i].translation = rtm::vector_lerp(rtm::vector_zero(), track0[0].translation, 0.1F * float(i)); + track0[i].scale = rtm::vector_lerp(rtm::vector_zero(), track0[0].scale, 0.1F * float(i)); + } + track0.set_name(acl::string(allocator, "track 0")); + track_list[0] = track0.get_ref(); + + track_desc_transformf desc1; + desc1.output_index = 0; + desc1.precision = 0.001F; + desc1.parent_index = 0; + desc1.shell_distance = 0.1241F; + desc1.constant_rotation_threshold_angle = 21.0F; + desc1.constant_translation_threshold = 0.11F; + desc1.constant_scale_threshold = 12.0F; + track_qvvf track1 = track_qvvf::make_reserve(desc1, allocator, num_samples, 32.0F); + track1[0].rotation = rtm::quat_from_euler(1.1F, 1.5F, 1.7F); + track1[0].translation = rtm::vector_set(0.0221F, 10.6F, 22.3F); + track1[0].scale = rtm::vector_set(1.451F, 24.1F, 10.2F); + for (uint32_t i = 1; i < num_samples; ++i) + { + track1[i].rotation = rtm::quat_lerp(rtm::quat_identity(), track1[0].rotation, 0.1F * float(i)); + track1[i].translation = rtm::vector_lerp(rtm::vector_zero(), track1[0].translation, 0.1F * float(i)); + track1[i].scale = rtm::vector_lerp(rtm::vector_zero(), track1[0].scale, 0.1F * float(i)); + } + track1.set_name(acl::string(allocator, "track 1")); + track_list[1] = track1.get_ref(); + + track_qvvf track2 = track_qvvf::make_reserve(desc1, allocator, num_samples, 32.0F); + track2[0].rotation = rtm::quat_from_euler(1.11F, 1.5333F, 0.17F); + track2[0].translation = rtm::vector_set(30.0221F, 101.6F, 22.3214F); + track2[0].scale = rtm::vector_set(21.451F, 244.1F, 100.2F); + for (uint32_t i = 1; i < num_samples; ++i) + { + track2[i].rotation = rtm::quat_lerp(rtm::quat_identity(), track2[0].rotation, 0.1F * float(i)); + track2[i].translation = rtm::vector_lerp(rtm::vector_zero(), track2[0].translation, 0.1F * float(i)); + track2[i].scale = rtm::vector_lerp(rtm::vector_zero(), track2[0].scale, 0.1F * float(i)); + } + track_list[2] = track2.get_ref(); + + track_list.set_name(acl::string(allocator, "some track list")); + + compression_settings settings; + settings.level = compression_level8::high; + settings.rotation_format = rotation_format8::quatf_drop_w_variable; + settings.scale_format = vector_format8::vector3f_variable; + settings.translation_format = vector_format8::vector3f_variable; + + const uint32_t filename_size = k_max_filename_size; + char filename[filename_size] = { 0 }; + + error_result error; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { + get_temporary_filename(filename, filename_size, "clip_"); + + // Write the clip to a temporary file + error = write_track_list(track_list, settings, filename); + + if (error.empty()) + break; // Everything worked, stop trying + } + REQUIRE(error.empty()); + + std::FILE* file = nullptr; + + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (file != nullptr) + break; // File is open, all good + + // Sleep a bit before tring again + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + REQUIRE(file != nullptr); + + char sjson_file_buffer[256 * 1024]; + const size_t clip_size = fread(sjson_file_buffer, 1, get_array_size(sjson_file_buffer), file); + fclose(file); + + std::remove(filename); + + // Read back the clip + clip_reader reader(allocator, sjson_file_buffer, clip_size - 1); + + REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list); + + sjson_raw_track_list file_clip; + const bool success = reader.read_raw_track_list(file_clip); + REQUIRE(success); + + CHECK(file_clip.has_settings); + CHECK(file_clip.settings.get_hash() == settings.get_hash()); + + CHECK(file_clip.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track()); + CHECK(file_clip.track_list.get_sample_rate() == track_list.get_sample_rate()); + CHECK(file_clip.track_list.get_num_tracks() == track_list.get_num_tracks()); + CHECK(rtm::scalar_near_equal(file_clip.track_list.get_duration(), track_list.get_duration(), 1.0E-8F)); + CHECK(file_clip.track_list.get_track_type() == track_list.get_track_type()); + CHECK(file_clip.track_list.get_track_category() == track_list.get_track_category()); + CHECK(file_clip.track_list.get_name() == track_list.get_name()); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_qvvf& ref_track = track_cast(track_list[track_index]); + const track_qvvf& file_track = track_cast(file_clip.track_list[track_index]); + + CHECK(file_track.get_description().output_index == ref_track.get_description().output_index); + CHECK(file_track.get_description().parent_index == ref_track.get_description().parent_index); + CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F)); + CHECK(rtm::scalar_near_equal(file_track.get_description().shell_distance, ref_track.get_description().shell_distance, 0.0F)); + CHECK(rtm::scalar_near_equal(file_track.get_description().constant_rotation_threshold_angle, ref_track.get_description().constant_rotation_threshold_angle, 0.0F)); + CHECK(rtm::scalar_near_equal(file_track.get_description().constant_translation_threshold, ref_track.get_description().constant_translation_threshold, 0.0F)); + CHECK(rtm::scalar_near_equal(file_track.get_description().constant_scale_threshold, ref_track.get_description().constant_scale_threshold, 0.0F)); + CHECK(file_track.get_num_samples() == ref_track.get_num_samples()); + CHECK(file_track.get_output_index() == ref_track.get_output_index()); + CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate()); + CHECK(file_track.get_type() == ref_track.get_type()); + CHECK(file_track.get_category() == ref_track.get_category()); + CHECK(file_track.get_name() == ref_track.get_name()); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::qvvf& ref_sample = ref_track[sample_index]; + const rtm::qvvf& file_sample = file_track[sample_index]; + CHECK(rtm::quat_near_equal(ref_sample.rotation, file_sample.rotation, 0.0F)); + CHECK(rtm::vector_all_near_equal3(ref_sample.translation, file_sample.translation, 0.0F)); + CHECK(rtm::vector_all_near_equal3(ref_sample.scale, file_sample.scale, 0.0F)); + } + } +#endif +} + +TEST_CASE("sjson_track_list_reader_writer float1f", "[io]") +{ + // Only test the reader/writer on non-mobile platforms +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) + ansi_allocator allocator; + + const uint32_t num_tracks = 3; + const uint32_t num_samples = 4; + track_array_float1f track_list(allocator, num_tracks); + + track_desc_scalarf desc0; + desc0.output_index = 0; + desc0.precision = 0.001F; + track_float1f track0 = track_float1f::make_reserve(desc0, allocator, num_samples, 32.0F); + track0[0] = 1.0F; + track0[1] = 2.333F; + track0[2] = 3.123F; + track0[3] = 4.5F; + track_list[0] = track0.get_ref(); + track_float1f track1 = track_float1f::make_reserve(desc0, allocator, num_samples, 32.0F); + track1[0] = 12.0F; + track1[1] = 21.1231F; + track1[2] = 3.1444123F; + track1[3] = 421.5156F; + track_list[1] = track1.get_ref(); + track_float1f track2 = track_float1f::make_reserve(desc0, allocator, num_samples, 32.0F); + track2[0] = 11.61F; + track2[1] = 23313.367F; + track2[2] = 313.7876F; + track2[3] = 4441.514F; + track_list[2] = track2.get_ref(); + + const uint32_t filename_size = k_max_filename_size; + char filename[filename_size] = { 0 }; + + error_result error; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { + get_temporary_filename(filename, filename_size, "list_float1f_"); + + // Write the clip to a temporary file + error = write_track_list(track_list, filename); + + if (error.empty()) + break; // Everything worked, stop trying + } + REQUIRE(error.empty()); + + std::FILE* file = nullptr; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (file != nullptr) + break; // File is open, all good + + // Sleep a bit before tring again + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + REQUIRE(file != nullptr); + + char sjson_file_buffer[256 * 1024]; + const size_t buffer_size = fread(sjson_file_buffer, 1, get_array_size(sjson_file_buffer), file); + fclose(file); + + std::remove(filename); + + // Read back the clip + clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1); + + REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list); + + sjson_raw_track_list file_track_list; + const bool success = reader.read_raw_track_list(file_track_list); + REQUIRE(success); + + CHECK(file_track_list.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track()); + CHECK(file_track_list.track_list.get_sample_rate() == track_list.get_sample_rate()); + CHECK(file_track_list.track_list.get_num_tracks() == track_list.get_num_tracks()); + CHECK(rtm::scalar_near_equal(file_track_list.track_list.get_duration(), track_list.get_duration(), 1.0E-8F)); + CHECK(file_track_list.track_list.get_track_type() == track_list.get_track_type()); + CHECK(file_track_list.track_list.get_track_category() == track_list.get_track_category()); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_float1f& ref_track = track_cast(track_list[track_index]); + const track_float1f& file_track = track_cast(file_track_list.track_list[track_index]); + + CHECK(file_track.get_description().output_index == ref_track.get_description().output_index); + CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F)); + CHECK(file_track.get_num_samples() == ref_track.get_num_samples()); + CHECK(file_track.get_output_index() == ref_track.get_output_index()); + CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate()); + CHECK(file_track.get_type() == ref_track.get_type()); + CHECK(file_track.get_category() == ref_track.get_category()); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float ref_sample = ref_track[sample_index]; + const float file_sample = file_track[sample_index]; + CHECK(rtm::scalar_near_equal(ref_sample, file_sample, 0.0F)); + } + } +#endif +} + +TEST_CASE("sjson_track_list_reader_writer float2f", "[io]") +{ + // Only test the reader/writer on non-mobile platforms +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) + ansi_allocator allocator; + + const uint32_t num_tracks = 3; + const uint32_t num_samples = 4; + track_array_float2f track_list(allocator, num_tracks); + + track_desc_scalarf desc0; + desc0.output_index = 0; + desc0.precision = 0.001F; + track_float2f track0 = track_float2f::make_reserve(desc0, allocator, num_samples, 32.0F); + track0[0] = rtm::float2f{ 1.0F, 3123.0F }; + track0[1] = rtm::float2f{ 2.333F, 321.13F }; + track0[2] = rtm::float2f{ 3.123F, 81.0F }; + track0[3] = rtm::float2f{ 4.5F, 91.13F }; + track_list[0] = track0.get_ref(); + track_float2f track1 = track_float2f::make_reserve(desc0, allocator, num_samples, 32.0F); + track1[0] = rtm::float2f{ 12.0F, 91.013F }; + track1[1] = rtm::float2f{ 21.1231F, 911.14F }; + track1[2] = rtm::float2f{ 3.1444123F, 113.44F }; + track1[3] = rtm::float2f{ 421.5156F, 913901.0F }; + track_list[1] = track1.get_ref(); + track_float2f track2 = track_float2f::make_reserve(desc0, allocator, num_samples, 32.0F); + track2[0] = rtm::float2f{ 11.61F, 90.13F }; + track2[1] = rtm::float2f{ 23313.367F, 13.3F }; + track2[2] = rtm::float2f{ 313.7876F, 931.2F }; + track2[3] = rtm::float2f{ 4441.514F, 913.56F }; + track_list[2] = track2.get_ref(); + + const uint32_t filename_size = k_max_filename_size; + char filename[filename_size] = { 0 }; + + error_result error; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { + get_temporary_filename(filename, filename_size, "list_float2f_"); + + // Write the clip to a temporary file + error = write_track_list(track_list, filename); + + if (error.empty()) + break; // Everything worked, stop trying + } + REQUIRE(error.empty()); + + std::FILE* file = nullptr; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (file != nullptr) + break; // File is open, all good + + // Sleep a bit before tring again + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + REQUIRE(file != nullptr); + + char sjson_file_buffer[256 * 1024]; + const size_t buffer_size = fread(sjson_file_buffer, 1, get_array_size(sjson_file_buffer), file); + fclose(file); + + std::remove(filename); + + // Read back the clip + clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1); + + REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list); + + sjson_raw_track_list file_track_list; + const bool success = reader.read_raw_track_list(file_track_list); + REQUIRE(success); + + CHECK(file_track_list.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track()); + CHECK(file_track_list.track_list.get_sample_rate() == track_list.get_sample_rate()); + CHECK(file_track_list.track_list.get_num_tracks() == track_list.get_num_tracks()); + CHECK(rtm::scalar_near_equal(file_track_list.track_list.get_duration(), track_list.get_duration(), 1.0E-8F)); + CHECK(file_track_list.track_list.get_track_type() == track_list.get_track_type()); + CHECK(file_track_list.track_list.get_track_category() == track_list.get_track_category()); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_float2f& ref_track = track_cast(track_list[track_index]); + const track_float2f& file_track = track_cast(file_track_list.track_list[track_index]); + + CHECK(file_track.get_description().output_index == ref_track.get_description().output_index); + CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F)); + CHECK(file_track.get_num_samples() == ref_track.get_num_samples()); + CHECK(file_track.get_output_index() == ref_track.get_output_index()); + CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate()); + CHECK(file_track.get_type() == ref_track.get_type()); + CHECK(file_track.get_category() == ref_track.get_category()); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::float2f& ref_sample = ref_track[sample_index]; + const rtm::float2f& file_sample = file_track[sample_index]; + CHECK(rtm::vector_all_near_equal2(rtm::vector_load2(&ref_sample), rtm::vector_load2(&file_sample), 0.0F)); + } + } +#endif +} + +TEST_CASE("sjson_track_list_reader_writer float3f", "[io]") +{ + // Only test the reader/writer on non-mobile platforms +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) + ansi_allocator allocator; + + const uint32_t num_tracks = 3; + const uint32_t num_samples = 4; + track_array_float3f track_list(allocator, num_tracks); + + track_desc_scalarf desc0; + desc0.output_index = 0; + desc0.precision = 0.001F; + track_float3f track0 = track_float3f::make_reserve(desc0, allocator, num_samples, 32.0F); + track0[0] = rtm::float3f{ 1.0F, 3123.0F, 315.13F }; + track0[1] = rtm::float3f{ 2.333F, 321.13F, 31.66F }; + track0[2] = rtm::float3f{ 3.123F, 81.0F, 913.13F }; + track0[3] = rtm::float3f{ 4.5F, 91.13F, 41.135F }; + track_list[0] = track0.get_ref(); + track_float3f track1 = track_float3f::make_reserve(desc0, allocator, num_samples, 32.0F); + track1[0] = rtm::float3f{ 12.0F, 91.013F, 9991.13F }; + track1[1] = rtm::float3f{ 21.1231F, 911.14F, 825.12351F }; + track1[2] = rtm::float3f{ 3.1444123F, 113.44F, 913.51F }; + track1[3] = rtm::float3f{ 421.5156F, 913901.0F, 184.6981F }; + track_list[1] = track1.get_ref(); + track_float3f track2 = track_float3f::make_reserve(desc0, allocator, num_samples, 32.0F); + track2[0] = rtm::float3f{ 11.61F, 90.13F, 918.011F }; + track2[1] = rtm::float3f{ 23313.367F, 13.3F, 913.813F }; + track2[2] = rtm::float3f{ 313.7876F, 931.2F, 8123.123F }; + track2[3] = rtm::float3f{ 4441.514F, 913.56F, 813.61F }; + track_list[2] = track2.get_ref(); + + const uint32_t filename_size = k_max_filename_size; + char filename[filename_size] = { 0 }; + + error_result error; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { + get_temporary_filename(filename, filename_size, "list_float3f_"); + + // Write the clip to a temporary file + error = write_track_list(track_list, filename); + + if (error.empty()) + break; // Everything worked, stop trying + } + REQUIRE(error.empty()); + + std::FILE* file = nullptr; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (file != nullptr) + break; // File is open, all good + + // Sleep a bit before tring again + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + REQUIRE(file != nullptr); + + char sjson_file_buffer[256 * 1024]; + const size_t buffer_size = fread(sjson_file_buffer, 1, get_array_size(sjson_file_buffer), file); + fclose(file); + + std::remove(filename); + + // Read back the clip + clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1); + + REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list); + + sjson_raw_track_list file_track_list; + const bool success = reader.read_raw_track_list(file_track_list); + REQUIRE(success); + + CHECK(file_track_list.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track()); + CHECK(file_track_list.track_list.get_sample_rate() == track_list.get_sample_rate()); + CHECK(file_track_list.track_list.get_num_tracks() == track_list.get_num_tracks()); + CHECK(rtm::scalar_near_equal(file_track_list.track_list.get_duration(), track_list.get_duration(), 1.0E-8F)); + CHECK(file_track_list.track_list.get_track_type() == track_list.get_track_type()); + CHECK(file_track_list.track_list.get_track_category() == track_list.get_track_category()); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_float3f& ref_track = track_cast(track_list[track_index]); + const track_float3f& file_track = track_cast(file_track_list.track_list[track_index]); + + CHECK(file_track.get_description().output_index == ref_track.get_description().output_index); + CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F)); + CHECK(file_track.get_num_samples() == ref_track.get_num_samples()); + CHECK(file_track.get_output_index() == ref_track.get_output_index()); + CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate()); + CHECK(file_track.get_type() == ref_track.get_type()); + CHECK(file_track.get_category() == ref_track.get_category()); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::float3f& ref_sample = ref_track[sample_index]; + const rtm::float3f& file_sample = file_track[sample_index]; + CHECK(rtm::vector_all_near_equal3(rtm::vector_load3(&ref_sample), rtm::vector_load3(&file_sample), 0.0F)); + } + } +#endif +} + +TEST_CASE("sjson_track_list_reader_writer float4f", "[io]") +{ + // Only test the reader/writer on non-mobile platforms +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) + ansi_allocator allocator; + + const uint32_t num_tracks = 3; + const uint32_t num_samples = 4; + track_array_float4f track_list(allocator, num_tracks); + + track_desc_scalarf desc0; + desc0.output_index = 0; + desc0.precision = 0.001F; + track_float4f track0 = track_float4f::make_reserve(desc0, allocator, num_samples, 32.0F); + track0[0] = rtm::float4f{ 1.0F, 3123.0F, 315.13F, 123.31F }; + track0[1] = rtm::float4f{ 2.333F, 321.13F, 31.66F, 7154.1F }; + track0[2] = rtm::float4f{ 3.123F, 81.0F, 913.13F, 9817.8135F }; + track0[3] = rtm::float4f{ 4.5F, 91.13F, 41.135F, 755.12345F }; + track_list[0] = track0.get_ref(); + track_float4f track1 = track_float4f::make_reserve(desc0, allocator, num_samples, 32.0F); + track1[0] = rtm::float4f{ 12.0F, 91.013F, 9991.13F, 813.97F }; + track1[1] = rtm::float4f{ 21.1231F, 911.14F, 825.12351F, 321.517F }; + track1[2] = rtm::float4f{ 3.1444123F, 113.44F, 913.51F, 6136.613F }; + track1[3] = rtm::float4f{ 421.5156F, 913901.0F, 184.6981F, 41.1254F }; + track_list[1] = track1.get_ref(); + track_float4f track2 = track_float4f::make_reserve(desc0, allocator, num_samples, 32.0F); + track2[0] = rtm::float4f{ 11.61F, 90.13F, 918.011F, 31.13F }; + track2[1] = rtm::float4f{ 23313.367F, 13.3F, 913.813F, 8997.1F }; + track2[2] = rtm::float4f{ 313.7876F, 931.2F, 8123.123F, 813.76F }; + track2[3] = rtm::float4f{ 4441.514F, 913.56F, 813.61F, 873.612F }; + track_list[2] = track2.get_ref(); + + const uint32_t filename_size = k_max_filename_size; + char filename[filename_size] = { 0 }; + + error_result error; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { + get_temporary_filename(filename, filename_size, "list_float4f_"); + + // Write the clip to a temporary file + error = write_track_list(track_list, filename); + + if (error.empty()) + break; // Everything worked, stop trying + } + REQUIRE(error.empty()); + + std::FILE* file = nullptr; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (file != nullptr) + break; // File is open, all good + + // Sleep a bit before tring again + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + REQUIRE(file != nullptr); + + char sjson_file_buffer[256 * 1024]; + const size_t buffer_size = fread(sjson_file_buffer, 1, get_array_size(sjson_file_buffer), file); + fclose(file); + + std::remove(filename); + + // Read back the clip + clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1); + + REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list); + + sjson_raw_track_list file_track_list; + const bool success = reader.read_raw_track_list(file_track_list); + REQUIRE(success); + + CHECK(file_track_list.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track()); + CHECK(file_track_list.track_list.get_sample_rate() == track_list.get_sample_rate()); + CHECK(file_track_list.track_list.get_num_tracks() == track_list.get_num_tracks()); + CHECK(rtm::scalar_near_equal(file_track_list.track_list.get_duration(), track_list.get_duration(), 1.0E-8F)); + CHECK(file_track_list.track_list.get_track_type() == track_list.get_track_type()); + CHECK(file_track_list.track_list.get_track_category() == track_list.get_track_category()); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_float4f& ref_track = track_cast(track_list[track_index]); + const track_float4f& file_track = track_cast(file_track_list.track_list[track_index]); + + CHECK(file_track.get_description().output_index == ref_track.get_description().output_index); + CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F)); + CHECK(file_track.get_num_samples() == ref_track.get_num_samples()); + CHECK(file_track.get_output_index() == ref_track.get_output_index()); + CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate()); + CHECK(file_track.get_type() == ref_track.get_type()); + CHECK(file_track.get_category() == ref_track.get_category()); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::float4f& ref_sample = ref_track[sample_index]; + const rtm::float4f& file_sample = file_track[sample_index]; + CHECK(rtm::vector_all_near_equal(rtm::vector_load(&ref_sample), rtm::vector_load(&file_sample), 0.0F)); + } + } +#endif +} + +TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]") +{ + // Only test the reader/writer on non-mobile platforms +#if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON) + ansi_allocator allocator; + + const uint32_t num_tracks = 3; + const uint32_t num_samples = 4; + track_array_vector4f track_list(allocator, num_tracks); + + track_desc_scalarf desc0; + desc0.output_index = 0; + desc0.precision = 0.001F; + track_vector4f track0 = track_vector4f::make_reserve(desc0, allocator, num_samples, 32.0F); + track0[0] = rtm::vector_set(1.0F, 3123.0F, 315.13F, 123.31F); + track0[1] = rtm::vector_set(2.333F, 321.13F, 31.66F, 7154.1F); + track0[2] = rtm::vector_set(3.123F, 81.0F, 913.13F, 9817.8135F); + track0[3] = rtm::vector_set(4.5F, 91.13F, 41.135F, 755.12345F); + track_list[0] = track0.get_ref(); + track_vector4f track1 = track_vector4f::make_reserve(desc0, allocator, num_samples, 32.0F); + track1[0] = rtm::vector_set(12.0F, 91.013F, 9991.13F, 813.97F); + track1[1] = rtm::vector_set(21.1231F, 911.14F, 825.12351F, 321.517F); + track1[2] = rtm::vector_set(3.1444123F, 113.44F, 913.51F, 6136.613F); + track1[3] = rtm::vector_set(421.5156F, 913901.0F, 184.6981F, 41.1254F); + track_list[1] = track1.get_ref(); + track_vector4f track2 = track_vector4f::make_reserve(desc0, allocator, num_samples, 32.0F); + track2[0] = rtm::vector_set(11.61F, 90.13F, 918.011F, 31.13F); + track2[1] = rtm::vector_set(23313.367F, 13.3F, 913.813F, 8997.1F); + track2[2] = rtm::vector_set(313.7876F, 931.2F, 8123.123F, 813.76F); + track2[3] = rtm::vector_set(4441.514F, 913.56F, 813.61F, 873.612F); + track_list[2] = track2.get_ref(); + + const uint32_t filename_size = k_max_filename_size; + char filename[filename_size] = { 0 }; + + error_result error; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { + get_temporary_filename(filename, filename_size, "list_vector4f_"); + + // Write the clip to a temporary file + error = write_track_list(track_list, filename); + + if (error.empty()) + break; // Everything worked, stop trying + } + REQUIRE(error.empty()); + + std::FILE* file = nullptr; + for (uint32_t try_count = 0; try_count < 20; ++try_count) + { +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (file != nullptr) + break; // File is open, all good + + // Sleep a bit before tring again + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + REQUIRE(file != nullptr); + + char sjson_file_buffer[256 * 1024]; + const size_t buffer_size = fread(sjson_file_buffer, 1, get_array_size(sjson_file_buffer), file); + fclose(file); + + std::remove(filename); + + // Read back the clip + clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1); + + REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list); + + sjson_raw_track_list file_track_list; + const bool success = reader.read_raw_track_list(file_track_list); + REQUIRE(success); + + CHECK(file_track_list.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track()); + CHECK(file_track_list.track_list.get_sample_rate() == track_list.get_sample_rate()); + CHECK(file_track_list.track_list.get_num_tracks() == track_list.get_num_tracks()); + CHECK(rtm::scalar_near_equal(file_track_list.track_list.get_duration(), track_list.get_duration(), 1.0E-8F)); + CHECK(file_track_list.track_list.get_track_type() == track_list.get_track_type()); + CHECK(file_track_list.track_list.get_track_category() == track_list.get_track_category()); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_vector4f& ref_track = track_cast(track_list[track_index]); + const track_vector4f& file_track = track_cast(file_track_list.track_list[track_index]); + + CHECK(file_track.get_description().output_index == ref_track.get_description().output_index); + CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F)); + CHECK(file_track.get_num_samples() == ref_track.get_num_samples()); + CHECK(file_track.get_output_index() == ref_track.get_output_index()); + CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate()); + CHECK(file_track.get_type() == ref_track.get_type()); + CHECK(file_track.get_category() == ref_track.get_category()); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::vector4f& ref_sample = ref_track[sample_index]; + const rtm::vector4f& file_sample = file_track[sample_index]; + CHECK(rtm::vector_all_near_equal(ref_sample, file_sample, 0.0F)); + } + } +#endif +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_quat_packing.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_quat_packing.cpp new file mode 100644 index 00000000..9ceaa4b9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_quat_packing.cpp @@ -0,0 +1,90 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +using namespace acl; +using namespace rtm; + +TEST_CASE("quat packing math", "[math][quat][packing]") +{ + struct UnalignedBuffer + { + uint32_t padding0; + uint16_t padding1; + uint8_t buffer[250]; + }; + static_assert((offsetof(UnalignedBuffer, buffer) % 2) == 0, "Minimum packing alignment is 2"); + + const quatf quat0 = quat_set(0.39564531008956383F, 0.044254239301713752F, 0.22768840967675355F, 0.88863059760894492F); + + { + UnalignedBuffer tmp0; + pack_quat_128(quat0, &tmp0.buffer[0]); + quatf quat1 = unpack_quat_128(&tmp0.buffer[0]); + CHECK((float)quat_get_x(quat0) == (float)quat_get_x(quat1)); + CHECK((float)quat_get_y(quat0) == (float)quat_get_y(quat1)); + CHECK((float)quat_get_z(quat0) == (float)quat_get_z(quat1)); + CHECK((float)quat_get_w(quat0) == (float)quat_get_w(quat1)); + } + + { + UnalignedBuffer tmp0; + pack_quat_96(quat0, &tmp0.buffer[0]); + quatf quat1 = unpack_quat_96_unsafe(&tmp0.buffer[0]); + CHECK((float)quat_get_x(quat0) == (float)quat_get_x(quat1)); + CHECK((float)quat_get_y(quat0) == (float)quat_get_y(quat1)); + CHECK((float)quat_get_z(quat0) == (float)quat_get_z(quat1)); + CHECK(scalar_near_equal((float)quat_get_w(quat0), (float)quat_get_w(quat1), 1.0E-4F)); + } + + { + UnalignedBuffer tmp0; + pack_quat_48(quat0, &tmp0.buffer[0]); + quatf quat1 = unpack_quat_48(&tmp0.buffer[0]); + CHECK(scalar_near_equal((float)quat_get_x(quat0), (float)quat_get_x(quat1), 1.0E-4F)); + CHECK(scalar_near_equal((float)quat_get_y(quat0), (float)quat_get_y(quat1), 1.0E-4F)); + CHECK(scalar_near_equal((float)quat_get_z(quat0), (float)quat_get_z(quat1), 1.0E-4F)); + CHECK(scalar_near_equal((float)quat_get_w(quat0), (float)quat_get_w(quat1), 1.0E-4F)); + } + + { + UnalignedBuffer tmp0; + pack_quat_32(quat0, &tmp0.buffer[0]); + quatf quat1 = unpack_quat_32(&tmp0.buffer[0]); + CHECK(scalar_near_equal((float)quat_get_x(quat0), (float)quat_get_x(quat1), 1.0E-3F)); + CHECK(scalar_near_equal((float)quat_get_y(quat0), (float)quat_get_y(quat1), 1.0E-3F)); + CHECK(scalar_near_equal((float)quat_get_z(quat0), (float)quat_get_z(quat1), 1.0E-3F)); + CHECK(scalar_near_equal((float)quat_get_w(quat0), (float)quat_get_w(quat1), 1.0E-3F)); + } + + CHECK(get_packed_rotation_size(rotation_format8::quatf_full) == 16); + CHECK(get_packed_rotation_size(rotation_format8::quatf_drop_w_full) == 12); + + CHECK(get_range_reduction_rotation_size(rotation_format8::quatf_full) == 32); + CHECK(get_range_reduction_rotation_size(rotation_format8::quatf_drop_w_full) == 24); + CHECK(get_range_reduction_rotation_size(rotation_format8::quatf_drop_w_variable) == 24); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_scalar_packing.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_scalar_packing.cpp new file mode 100644 index 00000000..032326e9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_scalar_packing.cpp @@ -0,0 +1,157 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +using namespace acl; +using namespace rtm; + +struct UnalignedBuffer +{ + uint32_t padding0; + uint16_t padding1; + uint8_t buffer[250]; +}; +static_assert((offsetof(UnalignedBuffer, buffer) % 2) == 0, "Minimum packing alignment is 2"); + +TEST_CASE("scalar packing math", "[math][scalar][packing]") +{ + const float threshold = 1.0E-6F; + + const uint8_t max_num_bits = 23; + for (uint8_t num_bits = 1; num_bits < max_num_bits; ++num_bits) + { + const uint32_t max_value = (1 << num_bits) - 1; + + CHECK(pack_scalar_unsigned(0.0F, num_bits) == 0); + CHECK(pack_scalar_unsigned(1.0F, num_bits) == max_value); + CHECK(unpack_scalar_unsigned(0, num_bits) == 0.0F); + CHECK(rtm::scalar_near_equal(unpack_scalar_unsigned(max_value, num_bits), 1.0F, threshold)); + + CHECK(pack_scalar_signed(-1.0F, num_bits) == 0); + CHECK(pack_scalar_signed(1.0F, num_bits) == max_value); + CHECK(unpack_scalar_signed(0, num_bits) == -1.0F); + CHECK(rtm::scalar_near_equal(unpack_scalar_signed(max_value, num_bits), 1.0F, threshold)); + + uint32_t num_errors = 0; + for (uint32_t value = 0; value < max_value; ++value) + { + const float unpacked0 = unpack_scalar_unsigned(value, num_bits); + const uint32_t packed0 = pack_scalar_unsigned(unpacked0, num_bits); + if (packed0 != value || unpacked0 < 0.0F || unpacked0 > 1.0F) + num_errors++; + + const float unpacked1 = unpack_scalar_signed(value, num_bits); + const uint32_t packed1 = pack_scalar_signed(unpacked1, num_bits); + if (packed1 != value || unpacked1 < -1.0F || unpacked1 > 1.0F) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("unpack_scalarf_96_unsafe", "[math][scalar][packing]") +{ + { + UnalignedBuffer tmp0; + UnalignedBuffer tmp1; + vector4f vec0 = vector_set(6123.123812F, 19237.01293127F, 0.913912387F, 0.1816253F); + pack_vector4_128(vec0, &tmp0.buffer[0]); + + uint32_t x = unaligned_load(&tmp0.buffer[0]); + x = byte_swap(x); + unaligned_write(x, &tmp0.buffer[0]); + + uint32_t y = unaligned_load(&tmp0.buffer[4]); + y = byte_swap(y); + unaligned_write(y, &tmp0.buffer[4]); + + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + uint32_t num_errors = 0; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp1.buffer[0], offset, &tmp0.buffer[0], 0, 32); + scalarf scalar1 = unpack_scalarf_32_unsafe(&tmp1.buffer[0], offset); + if (!scalar_near_equal(vector_get_x(vec0), scalar_cast(scalar1), 1.0E-6F)) + num_errors++; + + memcpy_bits(&tmp1.buffer[0], offset, &tmp0.buffer[4], 0, 32); + scalar1 = unpack_scalarf_32_unsafe(&tmp1.buffer[0], offset); + if (!scalar_near_equal(vector_get_y(vec0), scalar_cast(scalar1), 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("unpack_scalarf_uXX_unsafe", "[math][scalar][packing]") +{ + { + UnalignedBuffer tmp0; + alignas(16) uint8_t buffer[64]; + + uint32_t num_errors = 0; + vector4f vec0 = vector_set(unpack_scalar_unsigned(0, 16), unpack_scalar_unsigned(12355, 16), unpack_scalar_unsigned(43222, 16), unpack_scalar_unsigned(54432, 16)); + pack_vector2_uXX_unsafe(vec0, 16, &buffer[0]); + scalarf scalar1 = unpack_scalarf_uXX_unsafe(16, &buffer[0], 0); + if (!scalar_near_equal(vector_get_x(vec0), scalar_cast(scalar1), 1.0E-6F)) + num_errors++; + + for (uint8_t bit_rate = 1; bit_rate < k_highest_bit_rate; ++bit_rate) + { + uint32_t num_bits = get_num_bits_at_bit_rate(bit_rate); + uint32_t max_value = (1 << num_bits) - 1; + for (uint32_t value = 0; value <= max_value; ++value) + { + const float value_unsigned = scalar_clamp(unpack_scalar_unsigned(value, num_bits), 0.0F, 1.0F); + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + pack_vector2_uXX_unsafe(vec0, num_bits, &buffer[0]); + scalar1 = unpack_scalarf_uXX_unsafe(num_bits, &buffer[0], 0); + if (!scalar_near_equal(vector_get_x(vec0), scalar_cast(scalar1), 1.0E-6F)) + num_errors++; + + { + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp0.buffer[0], offset, &buffer[0], 0, size_t(num_bits) * 4); + scalar1 = unpack_scalarf_uXX_unsafe(num_bits, &tmp0.buffer[0], offset); + if (!scalar_near_equal(vector_get_x(vec0), scalar_cast(scalar1), 1.0E-6F)) + num_errors++; + } + } + } + } + CHECK(num_errors == 0); + } +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_vector4_packing.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_vector4_packing.cpp new file mode 100644 index 00000000..38f80c2b --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/math/test_vector4_packing.cpp @@ -0,0 +1,556 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include + +#include + +using namespace acl; +using namespace rtm; + +struct UnalignedBuffer +{ + uint32_t padding0; + uint16_t padding1; + uint8_t buffer[250]; +}; +static_assert((offsetof(UnalignedBuffer, buffer) % 2) == 0, "Minimum packing alignment is 2"); + +TEST_CASE("pack_vector4_128", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp; + vector4f vec0 = vector_set(6123.123812F, 19237.01293127F, 1891.019231829F, 0.913912387F); + pack_vector4_128(vec0, &tmp.buffer[0]); + vector4f vec1 = unpack_vector4_128(&tmp.buffer[0]); + CHECK(std::memcmp(&vec0, &vec1, sizeof(vector4f)) == 0); + } + + { + UnalignedBuffer tmp0; + UnalignedBuffer tmp1; + vector4f vec0 = vector_set(6123.123812F, 19237.01293127F, 1891.019231829F, 0.913912387F); + pack_vector4_128(vec0, &tmp0.buffer[0]); + + uint32_t x = unaligned_load(&tmp0.buffer[0]); + x = byte_swap(x); + unaligned_write(x, &tmp0.buffer[0]); + + uint32_t y = unaligned_load(&tmp0.buffer[4]); + y = byte_swap(y); + unaligned_write(y, &tmp0.buffer[4]); + + uint32_t z = unaligned_load(&tmp0.buffer[8]); + z = byte_swap(z); + unaligned_write(z, &tmp0.buffer[8]); + + uint32_t w = unaligned_load(&tmp0.buffer[12]); + w = byte_swap(w); + unaligned_write(w, &tmp0.buffer[12]); + + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + uint32_t num_errors = 0; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp1.buffer[0], offset, &tmp0.buffer[0], 0, 128); + vector4f vec1 = unpack_vector4_128_unsafe(&tmp1.buffer[0], offset); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector4_64", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp; + uint32_t num_errors = 0; + for (uint32_t value = 0; value < 65536; ++value) + { + const float value_signed = unpack_scalar_signed(value, 16); + const float value_unsigned = unpack_scalar_unsigned(value, 16); + + vector4f vec0 = vector_set(value_signed); + pack_vector4_64(vec0, false, &tmp.buffer[0]); + vector4f vec1 = unpack_vector4_64(&tmp.buffer[0], false); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned); + pack_vector4_64(vec0, true, &tmp.buffer[0]); + vec1 = unpack_vector4_64(&tmp.buffer[0], true); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector4_32", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp; + uint32_t num_errors = 0; + for (uint32_t value = 0; value < 256; ++value) + { + const float value_signed = scalar_min(unpack_scalar_signed(value, 8), 1.0F); + const float value_unsigned = scalar_min(unpack_scalar_unsigned(value, 8), 1.0F); + + vector4f vec0 = vector_set(value_signed); + pack_vector4_32(vec0, false, &tmp.buffer[0]); + vector4f vec1 = unpack_vector4_32(&tmp.buffer[0], false); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned); + pack_vector4_32(vec0, true, &tmp.buffer[0]); + vec1 = unpack_vector4_32(&tmp.buffer[0], true); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector4_XX", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + alignas(16) uint8_t buffer[64]; + + uint32_t num_errors = 0; + vector4f vec0 = vector_set(unpack_scalar_unsigned(0, 16), unpack_scalar_unsigned(12355, 16), unpack_scalar_unsigned(43222, 16), unpack_scalar_unsigned(54432, 16)); + pack_vector4_uXX_unsafe(vec0, 16, &buffer[0]); + vector4f vec1 = unpack_vector4_uXX_unsafe(16, &buffer[0], 0); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + + for (uint8_t bit_rate = 1; bit_rate < k_highest_bit_rate; ++bit_rate) + { + uint32_t num_bits = get_num_bits_at_bit_rate(bit_rate); + uint32_t max_value = (1 << num_bits) - 1; + for (uint32_t value = 0; value <= max_value; ++value) + { + const float value_unsigned = scalar_clamp(unpack_scalar_unsigned(value, num_bits), 0.0F, 1.0F); + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + pack_vector4_uXX_unsafe(vec0, num_bits, &buffer[0]); + vec1 = unpack_vector4_uXX_unsafe(num_bits, &buffer[0], 0); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + + { + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp0.buffer[0], offset, &buffer[0], 0, size_t(num_bits) * 4); + vec1 = unpack_vector4_uXX_unsafe(num_bits, &tmp0.buffer[0], offset); + if (!vector_all_near_equal(vec0, vec1, 1.0E-6F)) + num_errors++; + } + } + } + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector3_96", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + UnalignedBuffer tmp1; + vector4f vec0 = vector_set(6123.123812F, 19237.01293127F, 0.913912387F); + pack_vector3_96(vec0, &tmp0.buffer[0]); + vector4f vec1 = unpack_vector3_96_unsafe(&tmp0.buffer[0]); + CHECK(vector_all_near_equal3(vec0, vec1, 1.0E-6F)); + + uint32_t x = unaligned_load(&tmp0.buffer[0]); + x = byte_swap(x); + unaligned_write(x, &tmp0.buffer[0]); + + uint32_t y = unaligned_load(&tmp0.buffer[4]); + y = byte_swap(y); + unaligned_write(y, &tmp0.buffer[4]); + + uint32_t z = unaligned_load(&tmp0.buffer[8]); + z = byte_swap(z); + unaligned_write(z, &tmp0.buffer[8]); + + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + uint32_t num_errors = 0; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp1.buffer[0], offset, &tmp0.buffer[0], 0, 96); + vec1 = unpack_vector3_96_unsafe(&tmp1.buffer[0], offset); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector3_48", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + uint32_t num_errors = 0; + for (uint32_t value = 0; value < 65536; ++value) + { + const float value_signed = unpack_scalar_signed(value, 16); + const float value_unsigned = unpack_scalar_unsigned(value, 16); + + vector4f vec0 = vector_set(value_signed, value_signed, value_signed); + pack_vector3_s48_unsafe(vec0, &tmp0.buffer[0]); + vector4f vec1 = unpack_vector3_s48_unsafe(&tmp0.buffer[0]); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + pack_vector3_u48_unsafe(vec0, &tmp0.buffer[0]); + vec1 = unpack_vector3_u48_unsafe(&tmp0.buffer[0]); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("decay_vector3_48", "[math][vector4][decay]") +{ + { + uint32_t num_errors = 0; + for (uint32_t value = 0; value < 65536; ++value) + { + const float value_signed = unpack_scalar_signed(value, 16); + const float value_unsigned = unpack_scalar_unsigned(value, 16); + + vector4f vec0 = vector_set(value_signed, value_signed, value_signed); + vector4f vec1 = decay_vector3_s48(vec0); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + vec1 = decay_vector3_u48(vec0); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector3_32", "[math][vector4][packing]") +{ + { + const uint8_t num_bits_xy = 11; + const uint8_t num_bits_z = 10; + const uint32_t max_value_xy = (1 << num_bits_xy) - 1; + + UnalignedBuffer tmp0; + uint32_t num_errors = 0; + for (uint32_t value = 0; value < max_value_xy; ++value) + { + const uint32_t value_xy = value; + const uint32_t value_z = value % (1 << num_bits_z); + const float value_signed_xy = unpack_scalar_signed(value_xy, num_bits_xy); + const float value_signed_z = unpack_scalar_signed(value_z, num_bits_z); + const float value_unsigned_xy = unpack_scalar_unsigned(value_xy, num_bits_xy); + const float value_unsigned_z = unpack_scalar_unsigned(value_z, num_bits_z); + + vector4f vec0 = vector_set(value_signed_xy, value_signed_xy, value_signed_z); + pack_vector3_32(vec0, 11, 11, 10, false, &tmp0.buffer[0]); + vector4f vec1 = unpack_vector3_32(11, 11, 10, false, &tmp0.buffer[0]); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned_xy, value_unsigned_xy, value_unsigned_z); + pack_vector3_32(vec0, 11, 11, 10, true, &tmp0.buffer[0]); + vec1 = unpack_vector3_32(11, 11, 10, true, &tmp0.buffer[0]); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("decay_vector3_32", "[math][vector4][decay]") +{ + { + const uint8_t num_bits_xy = 11; + const uint8_t num_bits_z = 10; + const uint32_t max_value_xy = (1 << num_bits_xy) - 1; + + uint32_t num_errors = 0; + for (uint32_t value = 0; value < max_value_xy; ++value) + { + const uint32_t value_xy = value; + const uint32_t value_z = value % (1 << num_bits_z); + const float value_signed_xy = unpack_scalar_signed(value_xy, num_bits_xy); + const float value_signed_z = unpack_scalar_signed(value_z, num_bits_z); + const float value_unsigned_xy = unpack_scalar_unsigned(value_xy, num_bits_xy); + const float value_unsigned_z = unpack_scalar_unsigned(value_z, num_bits_z); + + vector4f vec0 = vector_set(value_signed_xy, value_signed_xy, value_signed_z); + vector4f vec1 = decay_vector3_s32(vec0, num_bits_xy, num_bits_xy, num_bits_z); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned_xy, value_unsigned_xy, value_unsigned_z); + vec1 = decay_vector3_u32(vec0, num_bits_xy, num_bits_xy, num_bits_z); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector3_24", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + uint32_t num_errors = 0; + for (uint32_t value = 0; value < 256; ++value) + { + const float value_signed = scalar_min(unpack_scalar_signed(value, 8), 1.0F); + const float value_unsigned = scalar_min(unpack_scalar_unsigned(value, 8), 1.0F); + + vector4f vec0 = vector_set(value_signed, value_signed, value_signed); + pack_vector3_s24_unsafe(vec0, &tmp0.buffer[0]); + vector4f vec1 = unpack_vector3_s24_unsafe(&tmp0.buffer[0]); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + pack_vector3_u24_unsafe(vec0, &tmp0.buffer[0]); + vec1 = unpack_vector3_u24_unsafe(&tmp0.buffer[0]); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector3_XX", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + alignas(16) uint8_t buffer[64]; + + uint32_t num_errors = 0; + vector4f vec0 = vector_set(unpack_scalar_signed(0, 16), unpack_scalar_signed(12355, 16), unpack_scalar_signed(43222, 16)); + pack_vector3_sXX_unsafe(vec0, 16, &buffer[0]); + vector4f vec1 = unpack_vector3_sXX_unsafe(16, &buffer[0], 0); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(unpack_scalar_unsigned(0, 16), unpack_scalar_unsigned(12355, 16), unpack_scalar_unsigned(43222, 16)); + pack_vector3_uXX_unsafe(vec0, 16, &buffer[0]); + vec1 = unpack_vector3_uXX_unsafe(16, &buffer[0], 0); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + for (uint8_t bit_rate = 1; bit_rate < k_highest_bit_rate; ++bit_rate) + { + uint32_t num_bits = get_num_bits_at_bit_rate(bit_rate); + uint32_t max_value = (1 << num_bits) - 1; + for (uint32_t value = 0; value <= max_value; ++value) + { + const float value_signed = scalar_clamp(unpack_scalar_signed(value, num_bits), -1.0F, 1.0F); + const float value_unsigned = scalar_clamp(unpack_scalar_unsigned(value, num_bits), 0.0F, 1.0F); + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + pack_vector3_uXX_unsafe(vec0, num_bits, &buffer[0]); + vec1 = unpack_vector3_uXX_unsafe(num_bits, &buffer[0], 0); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + { + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp0.buffer[0], offset, &buffer[0], 0, size_t(num_bits) * 3); + vec1 = unpack_vector3_uXX_unsafe(num_bits, &tmp0.buffer[0], offset); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + } + + vec0 = vector_set(value_signed, value_signed, value_signed); + pack_vector3_sXX_unsafe(vec0, num_bits, &buffer[0]); + vec1 = unpack_vector3_sXX_unsafe(num_bits, &buffer[0], 0); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + { + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp0.buffer[0], offset, &buffer[0], 0, size_t(num_bits) * 3); + vec1 = unpack_vector3_sXX_unsafe(num_bits, &tmp0.buffer[0], offset); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + } + } + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("decay_vector3_XX", "[math][vector4][decay]") +{ + { + uint32_t num_errors = 0; + + vector4f vec0 = vector_set(unpack_scalar_signed(0, 16), unpack_scalar_signed(12355, 16), unpack_scalar_signed(43222, 16)); + vector4f vec1 = decay_vector3_sXX(vec0, 16); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(unpack_scalar_unsigned(0, 16), unpack_scalar_unsigned(12355, 16), unpack_scalar_unsigned(43222, 16)); + vec1 = decay_vector3_uXX(vec0, 16); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + for (uint8_t bit_rate = 1; bit_rate < k_highest_bit_rate; ++bit_rate) + { + uint32_t num_bits = get_num_bits_at_bit_rate(bit_rate); + uint32_t max_value = (1 << num_bits) - 1; + for (uint32_t value = 0; value <= max_value; ++value) + { + const float value_signed = scalar_clamp(unpack_scalar_signed(value, num_bits), -1.0F, 1.0F); + const float value_unsigned = scalar_clamp(unpack_scalar_unsigned(value, num_bits), 0.0F, 1.0F); + + vec0 = vector_set(value_signed, value_signed, value_signed); + vec1 = decay_vector3_sXX(vec0, num_bits); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + vec1 = decay_vector3_uXX(vec0, num_bits); + if (!vector_all_near_equal3(vec0, vec1, 1.0E-6F)) + num_errors++; + } + } + + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector2_64", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + UnalignedBuffer tmp1; + vector4f vec0 = vector_set(6123.123812F, 19237.01293127F, 0.913912387F, 0.1816253F); + pack_vector4_128(vec0, &tmp0.buffer[0]); + + uint32_t x = unaligned_load(&tmp0.buffer[0]); + x = byte_swap(x); + unaligned_write(x, &tmp0.buffer[0]); + + uint32_t y = unaligned_load(&tmp0.buffer[4]); + y = byte_swap(y); + unaligned_write(y, &tmp0.buffer[4]); + + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + uint32_t num_errors = 0; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp1.buffer[0], offset, &tmp0.buffer[0], 0, 64); + vector4f vec1 = unpack_vector2_64_unsafe(&tmp1.buffer[0], offset); + if (!vector_all_near_equal2(vec0, vec1, 1.0E-6F)) + num_errors++; + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("pack_vector2_XX", "[math][vector4][packing]") +{ + { + UnalignedBuffer tmp0; + alignas(16) uint8_t buffer[64]; + + uint32_t num_errors = 0; + vector4f vec0 = vector_set(unpack_scalar_unsigned(0, 16), unpack_scalar_unsigned(12355, 16), unpack_scalar_unsigned(43222, 16), unpack_scalar_unsigned(54432, 16)); + pack_vector2_uXX_unsafe(vec0, 16, &buffer[0]); + vector4f vec1 = unpack_vector2_uXX_unsafe(16, &buffer[0], 0); + if (!vector_all_near_equal2(vec0, vec1, 1.0E-6F)) + num_errors++; + + for (uint8_t bit_rate = 1; bit_rate < k_highest_bit_rate; ++bit_rate) + { + uint32_t num_bits = get_num_bits_at_bit_rate(bit_rate); + uint32_t max_value = (1 << num_bits) - 1; + for (uint32_t value = 0; value <= max_value; ++value) + { + const float value_unsigned = scalar_clamp(unpack_scalar_unsigned(value, num_bits), 0.0F, 1.0F); + + vec0 = vector_set(value_unsigned, value_unsigned, value_unsigned); + pack_vector2_uXX_unsafe(vec0, num_bits, &buffer[0]); + vec1 = unpack_vector2_uXX_unsafe(num_bits, &buffer[0], 0); + if (!vector_all_near_equal2(vec0, vec1, 1.0E-6F)) + num_errors++; + + { + const uint8_t offsets[] = { 0, 1, 5, 31, 32, 33, 63, 64, 65, 93 }; + for (uint8_t offset_idx = 0; offset_idx < get_array_size(offsets); ++offset_idx) + { + const uint8_t offset = offsets[offset_idx]; + + memcpy_bits(&tmp0.buffer[0], offset, &buffer[0], 0, size_t(num_bits) * 4); + vec1 = unpack_vector2_uXX_unsafe(num_bits, &tmp0.buffer[0], offset); + if (!vector_all_near_equal2(vec0, vec1, 1.0E-6F)) + num_errors++; + } + } + } + } + CHECK(num_errors == 0); + } +} + +TEST_CASE("misc vector4 packing", "[math][vector4][packing]") +{ + CHECK(get_packed_vector_size(vector_format8::vector3f_full) == 12); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/test_header_fwd.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/test_header_fwd.cpp new file mode 100644 index 00000000..896c8774 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tests/sources/test_header_fwd.cpp @@ -0,0 +1,25 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2022 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/CMakeLists.txt new file mode 100644 index 00000000..a659dcb7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_compressor_root NONE) + +if(PLATFORM_ANDROID) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_android") +elseif(PLATFORM_IOS) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_ios") +elseif(PLATFORM_EMSCRIPTEN) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_emscripten") +else() + add_subdirectory("${PROJECT_SOURCE_DIR}/main_generic") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/README.md new file mode 100644 index 00000000..f702a1dc --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/README.md @@ -0,0 +1,18 @@ +# acl_compressor in a nutshell + +This tool serves two purposes: + +* Test compression and decompression +* Extract compression statistics + +The various command line arguments to the python script and the executable allow the user to control this behavior. Note that this tool isn't meant to be used at part of a pipeline to compress animation clips to later be used at runtime by a game. + +## Regression testing + +When regression testing, the python script isn't used but the executable is. By passing the argument `-test` to it, decompression will be tested and an assert will trigger should the error exceed the provided threshold by the configuration passed by `-config=`. + +## Compression statistics + +When generating the [graphs](../../docs/graph_generation.md), a python script is used in order to run the compression over a large dataset and aggregate the results into various CSV files as well as the standard output. + +Use `python acl_compressor.py -help` in order to get a description of the supported script arguments. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/acl_compressor.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/acl_compressor.py new file mode 100644 index 00000000..279520d4 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/acl_compressor.py @@ -0,0 +1,1006 @@ +import multiprocessing +import numpy +import os +import platform +import queue +import threading +import time +import signal +import sys + +from itertools import chain + +# This script depends on a SJSON parsing package: +# https://pypi.python.org/pypi/SJSON/1.1.0 +# https://shelter13.net/projects/SJSON/ +# https://bitbucket.org/Anteru/sjson/src +import sjson + + +def parse_argv(): + options = {} + options['acl'] = "" + options['stats'] = "" + options['out'] = "" + options['csv_summary'] = False + options['csv_bit_rate'] = False + options['csv_animated_size'] = False + options['csv_error'] = False + options['refresh'] = False + options['num_threads'] = 1 + options['has_progress_bar'] = True + options['stat_detailed'] = False + options['stat_exhaustive'] = False + options['level'] = 'Medium' + options['print_help'] = False + + for i in range(1, len(sys.argv)): + value = sys.argv[i] + + # TODO: Strip trailing '/' or '\' + if value.startswith('-acl='): + options['acl'] = value[len('-acl='):].replace('"', '') + options['acl'] = os.path.expanduser(options['acl']) + + if value.startswith('-stats='): + options['stats'] = value[len('-stats='):].replace('"', '') + options['stats'] = os.path.expanduser(options['stats']) + + if value.startswith('-out='): + options['out'] = value[len('-out='):].replace('"', '') + options['out'] = os.path.expanduser(options['out']) + + if value == '-csv_summary': + options['csv_summary'] = True + + if value == '-csv_bit_rate': + options['csv_bit_rate'] = True + + if value == '-csv_animated_size': + options['csv_animated_size'] = True + + if value == '-csv_error': + options['csv_error'] = True + + if value == '-refresh': + options['refresh'] = True + + if value == '-no_progress_bar': + options['has_progress_bar'] = False + + if value == '-stat_detailed': + options['stat_detailed'] = True + + if value == '-stat_exhaustive': + options['stat_exhaustive'] = True + + if value.startswith('-parallel='): + options['num_threads'] = int(value[len('-parallel='):].replace('"', '')) + + if value.startswith('-level='): + options['level'] = value[len('-level='):].replace('"', '').capitalize() + + if value == '-help': + options['print_help'] = True + + if options['print_help']: + print_help() + sys.exit(1) + + if len(options['acl']) == 0: + print('ACL input directory not found') + print_usage() + sys.exit(1) + + if len(options['stats']) == 0: + print('Stat output directory not found') + print_usage() + sys.exit(1) + + if options['num_threads'] <= 0: + print('-parallel switch argument must be greater than 0') + print_usage() + sys.exit(1) + + if not os.path.exists(options['acl']) or not os.path.isdir(options['acl']): + print('ACL input directory not found: {}'.format(options['acl'])) + print_usage() + sys.exit(1) + + if not os.path.exists(options['stats']): + os.makedirs(options['stats']) + + if not os.path.isdir(options['stats']): + print('The output stat argument must be a directory') + print_usage() + sys.exit(1) + + return options + +def print_usage(): + print('Usage: python acl_compressor.py -acl= -stats= [-csv_summary] [-csv_bit_rate] [-csv_animated_size] [-csv_error] [-refresh] [-parallel={Num Threads}] [-help]') + +def print_help(): + print('Usage: python acl_compressor.py [arguments]') + print() + print('Arguments:') + print(' At least one argument must be provided.') + print(' -acl=: Input directory tree containing clips to compress.') + print(' -stats=: Output directory tree for the stats to output.') + print(' -out=: Output directory tree for the compressed binaries to output.') + print(' -csv_summary: Generates a basic summary CSV file with various clip information and statistics.') + print(' -csv_bit_rate: Generates a CSV with the bit rate usage frequency by the variable quantization algorithm. The executable must be compiled with detailed statistics enabled.') + print(' -csv_animated_size: Generates a CSV with statistics about the animated size of key frames. The executable must be compiled with detailed statistics enabled.') + print(' -csv_error: Generates a CSV with the error for every bone at every key frame. The executable must be compiled with exhaustive statistics enabled.') + print(' -refresh: If an output stat file already exists for a particular clip, it is recompressed anyway instead of being skipped.') + print(' -parallel=: Allows multiple clips to be compressed and processed in parallel.') + print(' -no_progress_bar: Suppresses the progress bar output') + print(' -stat_detailed: Enables detailed stat logging') + print(' -stat_exhaustive: Enables exhaustive stat logging') + print(' -help: Prints this help message.') + +def print_stat(stat): + print('Algorithm: {}, Format: [{}], Ratio: {:.2f}, Error: {:.4f}'.format(stat['algorithm_name'], stat['desc'], stat['compression_ratio'], stat['max_error'])) + print('') + +def bytes_to_mb(size_in_bytes): + return size_in_bytes / (1024.0 * 1024.0) + +def bytes_to_kb(size_in_bytes): + return size_in_bytes / 1024.0 + +def format_elapsed_time(elapsed_time): + hours, rem = divmod(elapsed_time, 3600) + minutes, seconds = divmod(rem, 60) + return '{:0>2}h {:0>2}m {:05.2f}s'.format(int(hours), int(minutes), seconds) + +def sanitize_csv_entry(entry): + return entry.replace(', ', ' ').replace(',', '_') + +def create_csv(options): + csv_data = {} + stat_dir = options['stats'] + if options['csv_summary']: + stats_summary_csv_filename = os.path.join(stat_dir, 'stats_summary.csv') + stats_summary_csv_file = open(stats_summary_csv_filename, 'w') + csv_data['stats_summary_csv_file'] = stats_summary_csv_file + + print('Generating CSV file {} ...'.format(stats_summary_csv_filename)) + print('Clip Name, Algorithm Name, Raw Size, Compressed Size, Compression Ratio, Compression Time, Clip Duration, Num Animated Tracks, Max Error, Num Transforms, Num Samples Per Track, Quantization Memory Usage', file = stats_summary_csv_file) + + if options['csv_bit_rate']: + stats_bit_rate_csv_filename = os.path.join(stat_dir, 'stats_bit_rate.csv') + stats_bit_rate_csv_file = open(stats_bit_rate_csv_filename, 'w') + csv_data['stats_bit_rate_csv_file'] = stats_bit_rate_csv_file + + print('Generating CSV file {} ...'.format(stats_bit_rate_csv_filename)) + print('Algorithm Name, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 32', file = stats_bit_rate_csv_file) + + if options['csv_animated_size']: + stats_animated_size_csv_filename = os.path.join(stat_dir, 'stats_animated_size.csv') + stats_animated_size_csv_file = open(stats_animated_size_csv_filename, 'w') + csv_data['stats_animated_size_csv_file'] = stats_animated_size_csv_file + + print('Generating CSV file {} ...'.format(stats_animated_size_csv_filename)) + print('Algorithm Name, Segment Index, Animated Size, Num Animated Tracks', file = stats_animated_size_csv_file) + + if options['csv_error']: + stats_error_csv_filename = os.path.join(stat_dir, 'stats_error.csv') + stats_error_csv_file = open(stats_error_csv_filename, 'w') + csv_data['stats_error_csv_file'] = stats_error_csv_file + + print('Generating CSV file {} ...'.format(stats_error_csv_filename)) + print('Clip Name, Key Frame, Bone Index, Error', file = stats_error_csv_file) + + return csv_data + +def close_csv(csv_data): + if len(csv_data) == 0: + return + + if 'stats_summary_csv_file' in csv_data: + csv_data['stats_summary_csv_file'].close() + + if 'stats_bit_rate_csv_file' in csv_data: + csv_data['stats_bit_rate_csv_file'].close() + + if 'stats_animated_size_csv_file' in csv_data: + csv_data['stats_animated_size_csv_file'].close() + + if 'stats_error_csv_file' in csv_data: + csv_data['stats_error_csv_file'].close() + +def append_csv(csv_data, job_data): + if 'stats_summary_csv_file' in csv_data: + data = job_data['stats_summary_data'] + for (clip_name, algo_name, raw_size, compressed_size, compression_ratio, compression_time, duration, num_animated_tracks, max_error, num_transforms, num_samples_per_track, quantization_memory_usage) in data: + print('{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}'.format(clip_name, algo_name, raw_size, compressed_size, compression_ratio, compression_time, duration, num_animated_tracks, max_error, num_transforms, num_samples_per_track, quantization_memory_usage), file = csv_data['stats_summary_csv_file']) + + if 'stats_animated_size_csv_file' in csv_data: + size_data = job_data['stats_animated_size'] + for (name, segment_index, animated_size, num_animated) in size_data: + print('{}, {}, {}, {}'.format(name, segment_index, animated_size, num_animated), file = csv_data['stats_animated_size_csv_file']) + + if 'stats_error_csv_file' in csv_data: + error_data = job_data['stats_error_data'] + for (name, segment_index, data) in error_data: + key_frame = 0 + for frame_errors in data: + bone_index = 0 + for bone_error in frame_errors: + print('{}, {}, {}, {}'.format(name, key_frame, bone_index, bone_error), file = csv_data['stats_error_csv_file']) + bone_index += 1 + + key_frame += 1 + +def write_csv(csv_data, agg_data): + if 'stats_bit_rate_csv_file' in csv_data: + for algorithm_uid, algo_data in agg_data.items(): + total_count = float(sum(algo_data['bit_rates'])) + if total_count <= 0.0: + inv_total_count = 0.0 # Clamp to zero if a bit rate isn't used + else: + inv_total_count = 1.0 / total_count + print('{}, {}'.format(algo_data['csv_name'], ', '.join([str((float(x) * inv_total_count) * 100.0) for x in algo_data['bit_rates']])), file = csv_data['stats_bit_rate_csv_file']) + +def print_progress(iteration, total, prefix='', suffix='', decimals = 1, bar_length = 40): + # Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console + # With minor tweaks + """ + Call in a loop to create terminal progress bar + @params: + iteration - Required : current iteration (Int) + total - Required : total iterations (Int) + prefix - Optional : prefix string (Str) + suffix - Optional : suffix string (Str) + decimals - Optional : positive number of decimals in percent complete (Int) + bar_length - Optional : character length of bar (Int) + """ + str_format = "{0:." + str(decimals) + "f}" + percents = str_format.format(100 * (iteration / float(total))) + filled_length = int(round(bar_length * iteration / float(total))) + bar = '█' * filled_length + '-' * (bar_length - filled_length) + + # We need to clear any previous line we might have to ensure we have no visual artifacts + # Note that if this function is called too quickly, the text might flicker + terminal_width = 80 + sys.stdout.write('{}\r'.format(' ' * terminal_width)) + sys.stdout.flush() + + sys.stdout.write('%s |%s| %s%s %s\r' % (prefix, bar, percents, '%', suffix)), + sys.stdout.flush() + + if iteration == total: + sys.stdout.write('\n') + +def run_acl_compressor(cmd_queue, result_queue): + while True: + entry = cmd_queue.get() + if entry is None: + return + + (acl_filename, cmd) = entry + + result = os.system(cmd) + if result != 0: + print('Failed to execute cmd: {}'.format(cmd)) + result_queue.put(acl_filename) + +def compress_clips(options): + acl_dir = options['acl'] + stat_dir = options['stats'] + if platform.system() == 'Windows': + stat_dir = '\\\\?\\{}'.format(stat_dir) + refresh = options['refresh'] + + if platform.system() == 'Windows': + compressor_exe_path = '../../build/bin/acl_compressor.exe' + else: + compressor_exe_path = '../../build/bin/acl_compressor' + + compressor_exe_path = os.path.abspath(compressor_exe_path) + if not os.path.exists(compressor_exe_path): + print('Compressor exe not found: {}'.format(compressor_exe_path)) + sys.exit(1) + + stat_files = [] + cmd_queue = queue.Queue() + + out_dir = None + if len(options['out']) != 0: + if not os.path.exists(options['out']): + os.makedirs(options['out']) + + if os.path.exists(options['out']) and os.path.isdir(options['out']): + out_dir = options['out'] + + for (dirpath, dirnames, filenames) in os.walk(acl_dir): + stat_dirname = dirpath.replace(acl_dir, stat_dir) + + for filename in filenames: + if not (filename.endswith('.acl.sjson') or filename.endswith('.acl')): + continue + + acl_filename = os.path.join(dirpath, filename) + if filename.endswith('.acl.sjson'): + stat_filename = os.path.join(stat_dirname, filename.replace('.acl.sjson', '_stats.sjson')) + else: + stat_filename = os.path.join(stat_dirname, filename.replace('.acl', '_stats.sjson')) + + stat_files.append(stat_filename) + + if os.path.exists(stat_filename) and os.path.isfile(stat_filename) and not refresh: + continue + + if not os.path.exists(stat_dirname): + os.makedirs(stat_dirname) + + stat_filename = stat_filename.replace('\\\\?\\', '') + + cmd = '{} -acl="{}" -stats="{}" -level={}'.format(compressor_exe_path, acl_filename, stat_filename, options['level']) + + if out_dir: + if filename.endswith('.acl.sjson'): + out_filename = os.path.join(options['out'], filename.replace('.acl.sjson', '.acl')) + else: + out_filename = os.path.join(options['out'], filename) + cmd = '{} -out="{}"'.format(cmd, out_filename) + + if options['stat_detailed']: + cmd = '{} -stat_detailed'.format(cmd) + + if options['stat_exhaustive']: + cmd = '{} -stat_exhaustive'.format(cmd) + + if platform.system() == 'Windows': + cmd = cmd.replace('/', '\\') + + cmd_queue.put((acl_filename, cmd)) + + if len(stat_files) == 0: + print("No ACL clips found to compress") + sys.exit(0) + + if not cmd_queue.empty(): + # Add a marker to terminate the threads + for i in range(options['num_threads']): + cmd_queue.put(None) + + result_queue = queue.Queue() + compression_start_time = time.perf_counter() + + threads = [ threading.Thread(target = run_acl_compressor, args = (cmd_queue, result_queue)) for _i in range(options['num_threads']) ] + for thread in threads: + thread.daemon = True + thread.start() + + if options['has_progress_bar']: + print_progress(0, len(stat_files), 'Compressing clips:', '{} / {}'.format(0, len(stat_files))) + try: + while True: + for thread in threads: + thread.join(1.0) + + num_processed = result_queue.qsize() + if options['has_progress_bar']: + print_progress(num_processed, len(stat_files), 'Compressing clips:', '{} / {}'.format(num_processed, len(stat_files))) + + all_threads_done = True + for thread in threads: + if thread.is_alive(): + all_threads_done = False + + if all_threads_done: + break + except KeyboardInterrupt: + sys.exit(1) + + compression_end_time = time.perf_counter() + print() + print('Compressed {} clips in {}'.format(len(stat_files), format_elapsed_time(compression_end_time - compression_start_time))) + + return stat_files + +def shorten_rotation_format(format): + if format == 'quatf_full': + return 'R:Quat' + elif format == 'quatf_drop_w_full': + return 'R:QuatNoW96' + elif format == 'QuatDropW_48': + return 'R:QuatNoW48' + elif format == 'QuatDropW_32': + return 'R:QuatNoW32' + elif format == 'quatf_drop_w_variable': + return 'R:QuatNoWVar' + else: + return 'R:???' + +def shorten_translation_format(format): + if format == 'vector3f_full': + return 'T:Vec3_96' + elif format == 'Vector3_48': + return 'T:Vec3_48' + elif format == 'Vector3_32': + return 'T:Vec3_32' + elif format == 'vector3f_variable': + return 'T:Vec3Var' + else: + return 'T:???' + +def shorten_scale_format(format): + if format == 'vector3f_full': + return 'S:Vec3_96' + elif format == 'Vector3_48': + return 'S:Vec3_48' + elif format == 'Vector3_32': + return 'S:Vec3_32' + elif format == 'vector3f_variable': + return 'S:Vec3Var' + else: + return 'S:???' + +def aggregate_stats(agg_run_stats, run_stats): + algorithm_uid = run_stats['algorithm_uid'] + if not algorithm_uid in agg_run_stats: + agg_data = {} + agg_data['name'] = run_stats['desc'] + agg_data['csv_name'] = run_stats['csv_desc'] + agg_data['total_raw_size'] = 0 + agg_data['total_compressed_size'] = 0 + agg_data['total_compression_time'] = 0.0 + agg_data['total_duration'] = 0.0 + agg_data['max_error'] = 0 + agg_data['num_runs'] = 0 + agg_data['bit_rates'] = [0] * 19 + agg_data['compressed_size'] = [] + + # Detailed stats + agg_data['num_segments'] = [] + agg_data['num_default_rotation_tracks'] = [] + agg_data['num_default_translation_tracks'] = [] + agg_data['num_default_scale_tracks'] = [] + agg_data['num_constant_rotation_tracks'] = [] + agg_data['num_constant_translation_tracks'] = [] + agg_data['num_constant_scale_tracks'] = [] + agg_data['num_animated_rotation_tracks'] = [] + agg_data['num_animated_translation_tracks'] = [] + agg_data['num_animated_scale_tracks'] = [] + agg_data['num_default_tracks'] = [] + agg_data['num_constant_tracks'] = [] + agg_data['num_animated_tracks'] = [] + agg_data['clip_header_size'] = [] + agg_data['clip_metadata_common_size'] = [] + agg_data['clip_metadata_rotation_constant_size'] = [] + agg_data['clip_metadata_translation_constant_size'] = [] + agg_data['clip_metadata_scale_constant_size'] = [] + agg_data['clip_metadata_rotation_animated_size'] = [] + agg_data['clip_metadata_translation_animated_size'] = [] + agg_data['clip_metadata_scale_animated_size'] = [] + agg_data['segment_metadata_common_size'] = [] + agg_data['segment_metadata_rotation_size'] = [] + agg_data['segment_metadata_translation_size'] = [] + agg_data['segment_metadata_scale_size'] = [] + agg_data['segment_animated_rotation_size'] = [] + agg_data['segment_animated_translation_size'] = [] + agg_data['segment_animated_scale_size'] = [] + agg_data['unknown_overhead_size'] = [] + + agg_run_stats[algorithm_uid] = agg_data + + agg_data = agg_run_stats[algorithm_uid] + agg_data['total_raw_size'] += run_stats['raw_size'] + agg_data['total_compressed_size'] += run_stats['compressed_size'] + agg_data['total_compression_time'] += run_stats['compression_time'] + agg_data['total_duration'] += run_stats['duration'] + agg_data['max_error'] = max(agg_data['max_error'], run_stats['max_error']) + agg_data['num_runs'] += 1 + agg_data['compressed_size'].append(run_stats['compressed_size']) + if 'segments' in run_stats and len(run_stats['segments']) > 0: + for segment in run_stats['segments']: + if 'bit_rate_counts' in segment: + for i in range(19): + agg_data['bit_rates'][i] += segment['bit_rate_counts'][i] + + # Detailed stats + if 'num_default_rotation_tracks' in run_stats: + agg_data['num_segments'].append(run_stats['segmenting']['num_segments']) + agg_data['num_default_rotation_tracks'].append(run_stats['num_default_rotation_tracks']) + agg_data['num_default_translation_tracks'].append(run_stats['num_default_translation_tracks']) + agg_data['num_default_scale_tracks'].append(run_stats['num_default_scale_tracks']) + agg_data['num_constant_rotation_tracks'].append(run_stats['num_constant_rotation_tracks']) + agg_data['num_constant_translation_tracks'].append(run_stats['num_constant_translation_tracks']) + agg_data['num_constant_scale_tracks'].append(run_stats['num_constant_scale_tracks']) + agg_data['num_animated_rotation_tracks'].append(run_stats['num_animated_rotation_tracks']) + agg_data['num_animated_translation_tracks'].append(run_stats['num_animated_translation_tracks']) + agg_data['num_animated_scale_tracks'].append(run_stats['num_animated_scale_tracks']) + agg_data['num_default_tracks'].append(run_stats['num_default_tracks']) + agg_data['num_constant_tracks'].append(run_stats['num_constant_tracks']) + agg_data['num_animated_tracks'].append(run_stats['num_animated_tracks']) + agg_data['clip_header_size'].append(run_stats['clip_header_size']) + agg_data['clip_metadata_common_size'].append(run_stats['clip_metadata_common_size']) + agg_data['clip_metadata_rotation_constant_size'].append(run_stats['clip_metadata_rotation_constant_size']) + agg_data['clip_metadata_translation_constant_size'].append(run_stats['clip_metadata_translation_constant_size']) + agg_data['clip_metadata_scale_constant_size'].append(run_stats['clip_metadata_scale_constant_size']) + agg_data['clip_metadata_rotation_animated_size'].append(run_stats['clip_metadata_rotation_animated_size']) + agg_data['clip_metadata_translation_animated_size'].append(run_stats['clip_metadata_translation_animated_size']) + agg_data['clip_metadata_scale_animated_size'].append(run_stats['clip_metadata_scale_animated_size']) + agg_data['segment_metadata_common_size'].append(run_stats['segment_metadata_common_size']) + agg_data['segment_metadata_rotation_size'].append(run_stats['segment_metadata_rotation_size']) + agg_data['segment_metadata_translation_size'].append(run_stats['segment_metadata_translation_size']) + agg_data['segment_metadata_scale_size'].append(run_stats['segment_metadata_scale_size']) + agg_data['segment_animated_rotation_size'].append(run_stats['segment_animated_rotation_size']) + agg_data['segment_animated_translation_size'].append(run_stats['segment_animated_translation_size']) + agg_data['segment_animated_scale_size'].append(run_stats['segment_animated_scale_size']) + agg_data['unknown_overhead_size'].append(run_stats['unknown_overhead_size']) + +def track_best_runs(best_runs, run_stats): + if run_stats['max_error'] < best_runs['best_error']: + best_runs['best_error'] = run_stats['max_error'] + best_runs['best_error_entry'] = run_stats + + if run_stats['compression_ratio'] > best_runs['best_ratio']: + best_runs['best_ratio'] = run_stats['compression_ratio'] + best_runs['best_ratio_entry'] = run_stats + +def track_worst_runs(worst_runs, run_stats): + if run_stats['max_error'] > worst_runs['worst_error']: + worst_runs['worst_error'] = run_stats['max_error'] + worst_runs['worst_error_entry'] = run_stats + + if run_stats['compression_ratio'] < worst_runs['worst_ratio']: + worst_runs['worst_ratio'] = run_stats['compression_ratio'] + worst_runs['worst_ratio_entry'] = run_stats + +def run_stat_parsing(options, stat_queue, result_queue): + #signal.signal(signal.SIGINT, signal.SIG_IGN) + + try: + agg_run_stats = {} + best_runs = {} + best_runs['best_error'] = 100000000.0 + best_runs['best_error_entry'] = None + best_runs['best_ratio'] = 0.0 + best_runs['best_ratio_entry'] = None + worst_runs = {} + worst_runs['worst_error'] = -100000000.0 + worst_runs['worst_error_entry'] = None + worst_runs['worst_ratio'] = 100000000.0 + worst_runs['worst_ratio_entry'] = None + num_runs = 0 + total_compression_time = 0.0 + stats_summary_data = [] + stats_error_data = [] + stats_animated_size = [] + bone_error_values = [] + compression_times = [] + + while True: + stat_filename = stat_queue.get() + if stat_filename is None: + break + + with open(stat_filename, 'r') as file: + try: + file_data = sjson.loads(file.read()) + runs = file_data['runs'] + + for run_stats in runs: + if len(run_stats) == 0: + continue + + run_stats['filename'] = stat_filename.replace('\\\\?\\', '') + run_stats['clip_name'] = os.path.splitext(os.path.basename(stat_filename))[0] + run_stats['rotation_format'] = shorten_rotation_format(run_stats['rotation_format']) + run_stats['translation_format'] = shorten_translation_format(run_stats['translation_format']) + run_stats['scale_format'] = shorten_scale_format(run_stats['scale_format']) + + if isinstance(run_stats['duration'], str): + run_stats['duration'] = 0.0 + + if 'segmenting' in run_stats: + run_stats['desc'] = '{}|{}|{}'.format(run_stats['rotation_format'], run_stats['translation_format'], run_stats['scale_format']) + run_stats['csv_desc'] = '{}|{}|{}'.format(run_stats['rotation_format'], run_stats['translation_format'], run_stats['scale_format']) + else: + run_stats['desc'] = '{}|{}|{}'.format(run_stats['rotation_format'], run_stats['translation_format'], run_stats['scale_format']) + run_stats['csv_desc'] = '{}|{}|{}'.format(run_stats['rotation_format'], run_stats['translation_format'], run_stats['scale_format']) + + aggregate_stats(agg_run_stats, run_stats) + track_best_runs(best_runs, run_stats) + track_worst_runs(worst_runs, run_stats) + + num_runs += 1 + total_compression_time += run_stats['compression_time'] + compression_times.append(run_stats['compression_time']) + + if options['csv_summary']: + #(name, raw_size, compressed_size, compression_ratio, compression_time, duration, num_animated_tracks, max_error, num_transforms, num_samples_per_track, quantization_memory_usage) + num_transforms = run_stats['num_bones'] + num_samples_per_track = run_stats['num_samples'] + num_animated_tracks = run_stats.get('num_animated_tracks', 0) + quantization_memory_usage = run_stats.get('track_bit_rate_database_size', 0) + run_stats.get('transform_cache_size', 0) + data = (run_stats['clip_name'], run_stats['csv_desc'], run_stats['raw_size'], run_stats['compressed_size'], run_stats['compression_ratio'], run_stats['compression_time'], run_stats['duration'], num_animated_tracks, run_stats['max_error'], num_transforms, num_samples_per_track, quantization_memory_usage) + stats_summary_data.append(data) + + if 'segments' in run_stats and len(run_stats['segments']) > 0: + segment_index = 0 + for segment in run_stats['segments']: + if 'animated_frame_size' in segment and options['csv_animated_size']: + stats_animated_size.append((run_stats['clip_name'], segment_index, segment['animated_frame_size'], run_stats['num_animated_tracks'])) + + if 'error_per_frame_and_bone' in segment and len(segment['error_per_frame_and_bone']) > 0: + # Convert to array, lower memory footprint and more efficient + if options['csv_error']: + #(name, segment_index, data) + data = (run_stats['clip_name'], segment_index, segment['error_per_frame_and_bone']) + stats_error_data.append(data) + + for frame_error_values in segment['error_per_frame_and_bone']: + bone_error_values.extend([float(v) for v in frame_error_values]) + + # Data isn't needed anymore, discard it + segment['error_per_frame_and_bone'] = [] + + segment_index += 1 + + result_queue.put(('progress', stat_filename)) + except sjson.ParseException: + print('Failed to parse SJSON file: {}'.format(stat_filename)) + + # Done + results = {} + results['agg_run_stats'] = agg_run_stats + results['best_runs'] = best_runs + results['worst_runs'] = worst_runs + results['num_runs'] = num_runs + results['total_compression_time'] = total_compression_time + results['stats_summary_data'] = stats_summary_data + results['stats_error_data'] = stats_error_data + results['stats_animated_size'] = stats_animated_size + results['bone_error_values'] = bone_error_values + results['compression_times'] = compression_times + + result_queue.put(('done', results)) + except KeyboardInterrupt: + print('Interrupted') + +def pretty_print(d, indent = 0): + for key, value in d.items(): + if isinstance(value, dict): + print('\t' * indent + str(key)) + pretty(value, indent + 1) + else: + print('\t' * indent + str(key) + ': ' + str(value)) + +def aggregate_job_stats(agg_job_results, job_results): + if job_results['num_runs'] == 0: + return + + if len(agg_job_results) == 0: + # Convert array to numpy array + job_results['bone_error_values'] = numpy.array(job_results['bone_error_values']) + job_results['compression_times'] = numpy.array(job_results['compression_times']) + + agg_job_results.update(job_results) + else: + agg_job_results['num_runs'] += job_results['num_runs'] + agg_job_results['total_compression_time'] += job_results['total_compression_time'] + for key in job_results['agg_run_stats'].keys(): + if not key in agg_job_results['agg_run_stats']: + agg_job_results['agg_run_stats'][key] = job_results['agg_run_stats'][key].copy() + else: + agg_job_results['agg_run_stats'][key]['total_raw_size'] += job_results['agg_run_stats'][key]['total_raw_size'] + agg_job_results['agg_run_stats'][key]['total_compressed_size'] += job_results['agg_run_stats'][key]['total_compressed_size'] + agg_job_results['agg_run_stats'][key]['total_compression_time'] += job_results['agg_run_stats'][key]['total_compression_time'] + agg_job_results['agg_run_stats'][key]['total_duration'] += job_results['agg_run_stats'][key]['total_duration'] + agg_job_results['agg_run_stats'][key]['max_error'] = max(agg_job_results['agg_run_stats'][key]['max_error'], job_results['agg_run_stats'][key]['max_error']) + agg_job_results['agg_run_stats'][key]['num_runs'] += job_results['agg_run_stats'][key]['num_runs'] + agg_job_results['agg_run_stats'][key]['compressed_size'] += job_results['agg_run_stats'][key]['compressed_size'] + for i in range(19): + agg_job_results['agg_run_stats'][key]['bit_rates'][i] += job_results['agg_run_stats'][key]['bit_rates'][i] + + # Detailed stats + if 'num_default_rotation_tracks' in job_results['agg_run_stats'][key]: + agg_job_results['agg_run_stats'][key]['num_segments'] += job_results['agg_run_stats'][key]['num_segments'] + agg_job_results['agg_run_stats'][key]['num_default_rotation_tracks'] += job_results['agg_run_stats'][key]['num_default_rotation_tracks'] + agg_job_results['agg_run_stats'][key]['num_default_translation_tracks'] += job_results['agg_run_stats'][key]['num_default_translation_tracks'] + agg_job_results['agg_run_stats'][key]['num_default_scale_tracks'] += job_results['agg_run_stats'][key]['num_default_scale_tracks'] + agg_job_results['agg_run_stats'][key]['num_constant_rotation_tracks'] += job_results['agg_run_stats'][key]['num_constant_rotation_tracks'] + agg_job_results['agg_run_stats'][key]['num_constant_translation_tracks'] += job_results['agg_run_stats'][key]['num_constant_translation_tracks'] + agg_job_results['agg_run_stats'][key]['num_constant_scale_tracks'] += job_results['agg_run_stats'][key]['num_constant_scale_tracks'] + agg_job_results['agg_run_stats'][key]['num_animated_rotation_tracks'] += job_results['agg_run_stats'][key]['num_animated_rotation_tracks'] + agg_job_results['agg_run_stats'][key]['num_animated_translation_tracks'] += job_results['agg_run_stats'][key]['num_animated_translation_tracks'] + agg_job_results['agg_run_stats'][key]['num_animated_scale_tracks'] += job_results['agg_run_stats'][key]['num_animated_scale_tracks'] + agg_job_results['agg_run_stats'][key]['num_default_tracks'] += job_results['agg_run_stats'][key]['num_default_tracks'] + agg_job_results['agg_run_stats'][key]['num_constant_tracks'] += job_results['agg_run_stats'][key]['num_constant_tracks'] + agg_job_results['agg_run_stats'][key]['num_animated_tracks'] += job_results['agg_run_stats'][key]['num_animated_tracks'] + agg_job_results['agg_run_stats'][key]['clip_header_size'] += job_results['agg_run_stats'][key]['clip_header_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_common_size'] += job_results['agg_run_stats'][key]['clip_metadata_common_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_rotation_constant_size'] += job_results['agg_run_stats'][key]['clip_metadata_rotation_constant_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_translation_constant_size'] += job_results['agg_run_stats'][key]['clip_metadata_translation_constant_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_scale_constant_size'] += job_results['agg_run_stats'][key]['clip_metadata_scale_constant_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_rotation_animated_size'] += job_results['agg_run_stats'][key]['clip_metadata_rotation_animated_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_translation_animated_size'] += job_results['agg_run_stats'][key]['clip_metadata_translation_animated_size'] + agg_job_results['agg_run_stats'][key]['clip_metadata_scale_animated_size'] += job_results['agg_run_stats'][key]['clip_metadata_scale_animated_size'] + agg_job_results['agg_run_stats'][key]['segment_metadata_common_size'] += job_results['agg_run_stats'][key]['segment_metadata_common_size'] + agg_job_results['agg_run_stats'][key]['segment_metadata_rotation_size'] += job_results['agg_run_stats'][key]['segment_metadata_rotation_size'] + agg_job_results['agg_run_stats'][key]['segment_metadata_translation_size'] += job_results['agg_run_stats'][key]['segment_metadata_translation_size'] + agg_job_results['agg_run_stats'][key]['segment_metadata_scale_size'] += job_results['agg_run_stats'][key]['segment_metadata_scale_size'] + agg_job_results['agg_run_stats'][key]['segment_animated_rotation_size'] += job_results['agg_run_stats'][key]['segment_animated_rotation_size'] + agg_job_results['agg_run_stats'][key]['segment_animated_translation_size'] += job_results['agg_run_stats'][key]['segment_animated_translation_size'] + agg_job_results['agg_run_stats'][key]['segment_animated_scale_size'] += job_results['agg_run_stats'][key]['segment_animated_scale_size'] + agg_job_results['agg_run_stats'][key]['unknown_overhead_size'] += job_results['agg_run_stats'][key]['unknown_overhead_size'] + + if job_results['best_runs']['best_error'] < agg_job_results['best_runs']['best_error']: + agg_job_results['best_runs']['best_error'] = job_results['best_runs']['best_error'] + agg_job_results['best_runs']['best_error_entry'] = job_results['best_runs']['best_error_entry'] + + if job_results['best_runs']['best_ratio'] > agg_job_results['best_runs']['best_ratio']: + agg_job_results['best_runs']['best_ratio'] = job_results['best_runs']['best_ratio'] + agg_job_results['best_runs']['best_ratio_entry'] = job_results['best_runs']['best_ratio_entry'] + + if job_results['worst_runs']['worst_error'] > agg_job_results['worst_runs']['worst_error']: + agg_job_results['worst_runs']['worst_error'] = job_results['worst_runs']['worst_error'] + agg_job_results['worst_runs']['worst_error_entry'] = job_results['worst_runs']['worst_error_entry'] + + if job_results['worst_runs']['worst_ratio'] < agg_job_results['worst_runs']['worst_ratio']: + agg_job_results['worst_runs']['worst_ratio'] = job_results['worst_runs']['worst_ratio'] + agg_job_results['worst_runs']['worst_ratio_entry'] = job_results['worst_runs']['worst_ratio_entry'] + + agg_job_results['bone_error_values'] = numpy.append(agg_job_results['bone_error_values'], job_results['bone_error_values']) + agg_job_results['compression_times'] = numpy.append(agg_job_results['compression_times'], job_results['compression_times']) + +def percentile_rank(values, value): + return (values < value).mean() * 100.0 + +def aggregate_and_print_track_results(agg_run_stats, key): + if key: + label = '{} '.format(key) + key = '{}_'.format(key) + else: + label = '' + key = '' + + total_key = 'total_{}tracks'.format(key) + default_ratios_key = 'default_{}tracks_ratios'.format(key) + constant_ratios_key = 'constant_{}tracks_ratios'.format(key) + animated_ratios_key = 'animated_{}tracks_ratios'.format(key) + num_default_key = 'num_default_{}tracks'.format(key) + num_constant_key = 'num_constant_{}tracks'.format(key) + num_animated_key = 'num_animated_{}tracks'.format(key) + + for value in agg_run_stats.values(): + value[total_key] = [x + y + z for x, y, z in zip(value[num_default_key], value[num_constant_key], value[num_animated_key])] + value[default_ratios_key] = [(x / y) * 100.0 for x, y in zip(value[num_default_key], value[total_key])] + value[constant_ratios_key] = [(x / y) * 100.0 for x, y in zip(value[num_constant_key], value[total_key])] + value[animated_ratios_key] = [(x / y) * 100.0 for x, y in zip(value[num_animated_key], value[total_key])] + + total_tracks = sum([sum(x[total_key]) for x in agg_run_stats.values()]) + total_default_tracks = sum([sum(x[num_default_key]) for x in agg_run_stats.values()]) + total_constant_tracks = sum([sum(x[num_constant_key]) for x in agg_run_stats.values()]) + total_animated_tracks = sum([sum(x[num_animated_key]) for x in agg_run_stats.values()]) + + tmp = list(chain.from_iterable([x[default_ratios_key] for x in agg_run_stats.values()])) + total_default_tracks_p50 = numpy.percentile(tmp, 50.0) + total_default_tracks_p85 = numpy.percentile(tmp, 85.0) + total_default_tracks_p99 = numpy.percentile(tmp, 99.0) + tmp = list(chain.from_iterable([x[constant_ratios_key] for x in agg_run_stats.values()])) + total_constant_tracks_p50 = numpy.percentile(tmp, 50.0) + total_constant_tracks_p85 = numpy.percentile(tmp, 85.0) + total_constant_tracks_p99 = numpy.percentile(tmp, 99.0) + tmp = list(chain.from_iterable([x[animated_ratios_key] for x in agg_run_stats.values()])) + total_animated_tracks_p50 = numpy.percentile(tmp, 50.0) + total_animated_tracks_p85 = numpy.percentile(tmp, 85.0) + total_animated_tracks_p99 = numpy.percentile(tmp, 99.0) + + print('Total {}tracks: {}'.format(label, total_tracks)) + print('Total default {}tracks: {} ({:.2f} %)'.format(label, total_default_tracks, (total_default_tracks / total_tracks) * 100.0)) + print(' 50, 85, 99th percentile: {:.2f} %, {:.2f} %, {:.2f} %'.format(total_default_tracks_p50, total_default_tracks_p85, total_default_tracks_p99)) + print('Total constant {}tracks: {} ({:.2f} %)'.format(label, total_constant_tracks, (total_constant_tracks / total_tracks) * 100.0)) + print(' 50, 85, 99th percentile: {:.2f} %, {:.2f} %, {:.2f} %'.format(total_constant_tracks_p50, total_constant_tracks_p85, total_constant_tracks_p99)) + print('Total animated {}tracks: {} ({:.2f} %)'.format(label, total_animated_tracks, (total_animated_tracks / total_tracks) * 100.0)) + print(' 50, 85, 99th percentile: {:.2f} %, {:.2f} %, {:.2f} %'.format(total_animated_tracks_p50, total_animated_tracks_p85, total_animated_tracks_p99)) + +def aggregate_and_print_clip_metadata_results(agg_run_stats): + for value in agg_run_stats.values(): + value['clip_metadata_total_constant_size'] = [x + y + z for x, y, z in zip(value['clip_metadata_rotation_constant_size'], value['clip_metadata_translation_constant_size'], value['clip_metadata_scale_constant_size'])] + value['clip_metadata_total_animated_size'] = [x + y + z for x, y, z in zip(value['clip_metadata_rotation_animated_size'], value['clip_metadata_translation_animated_size'], value['clip_metadata_scale_animated_size'])] + value['clip_metadata_total_size'] = [x + y + z + w for x, y, z, w in zip(value['clip_header_size'], value['clip_metadata_common_size'], value['clip_metadata_total_constant_size'], value['clip_metadata_total_animated_size'])] + + clip_metadata_total_size = sum([sum(x['clip_metadata_total_size']) for x in agg_run_stats.values()]) + total_compressed_size = sum([sum(x['compressed_size']) for x in agg_run_stats.values()]) + + value['clip_metadata_ratios'] = [(x / y) * 100.0 for x, y in zip(value['clip_metadata_total_size'], value['compressed_size'])] + + tmp = list(chain.from_iterable([x['clip_metadata_ratios'] for x in agg_run_stats.values()])) + clip_metadata_ratio_p50 = numpy.percentile(tmp, 50.0) + clip_metadata_ratio_p85 = numpy.percentile(tmp, 85.0) + clip_metadata_ratio_p99 = numpy.percentile(tmp, 99.0) + + print('Total clip metadata size: {:.2f} MB ({:.2f} %)'.format(bytes_to_mb(clip_metadata_total_size), (clip_metadata_total_size / total_compressed_size) * 100.0)) + print(' 50, 85, 99th percentile: {:.2f} %, {:.2f} %, {:.2f} %'.format(clip_metadata_ratio_p50, clip_metadata_ratio_p85, clip_metadata_ratio_p99)) + +def aggregate_and_print_segment_metadata_results(agg_run_stats): + for value in agg_run_stats.values(): + value['segment_metadata_total_size'] = [x + y + z + w for x, y, z, w in zip(value['segment_metadata_common_size'], value['segment_metadata_rotation_size'], value['segment_metadata_translation_size'], value['segment_metadata_scale_size'])] + + segment_metadata_total_size = sum([sum(x['segment_metadata_total_size']) for x in agg_run_stats.values()]) + total_compressed_size = sum([sum(x['compressed_size']) for x in agg_run_stats.values()]) + + value['segment_metadata_ratios'] = [(x / y) * 100.0 for x, y in zip(value['segment_metadata_total_size'], value['compressed_size'])] + + tmp = list(chain.from_iterable([x['segment_metadata_ratios'] for x in agg_run_stats.values()])) + segment_metadata_ratio_p50 = numpy.percentile(tmp, 50.0) + segment_metadata_ratio_p85 = numpy.percentile(tmp, 85.0) + segment_metadata_ratio_p99 = numpy.percentile(tmp, 99.0) + + print('Total segment metadata size: {:.2f} MB ({:.2f} %)'.format(bytes_to_mb(segment_metadata_total_size), (segment_metadata_total_size / total_compressed_size) * 100.0)) + print(' 50, 85, 99th percentile: {:.2f} %, {:.2f} %, {:.2f} %'.format(segment_metadata_ratio_p50, segment_metadata_ratio_p85, segment_metadata_ratio_p99)) + +def aggregate_and_print_segment_animated_results(agg_run_stats): + for value in agg_run_stats.values(): + value['segment_animated_total_size'] = [x + y + z for x, y, z in zip(value['segment_animated_rotation_size'], value['segment_animated_translation_size'], value['segment_animated_scale_size'])] + + segment_animated_total_size = sum([sum(x['segment_animated_total_size']) for x in agg_run_stats.values()]) + total_compressed_size = sum([sum(x['compressed_size']) for x in agg_run_stats.values()]) + + value['segment_animated_ratios'] = [(x / y) * 100.0 for x, y in zip(value['segment_animated_total_size'], value['compressed_size'])] + + tmp = list(chain.from_iterable([x['segment_animated_ratios'] for x in agg_run_stats.values()])) + segment_animated_ratio_p50 = numpy.percentile(tmp, 50.0) + segment_animated_ratio_p85 = numpy.percentile(tmp, 85.0) + segment_animated_ratio_p99 = numpy.percentile(tmp, 99.0) + + print('Total segment animated size: {:.2f} MB ({:.2f} %)'.format(bytes_to_mb(segment_animated_total_size), (segment_animated_total_size / total_compressed_size) * 100.0)) + print(' 50, 85, 99th percentile: {:.2f} %, {:.2f} %, {:.2f} %'.format(segment_animated_ratio_p50, segment_animated_ratio_p85, segment_animated_ratio_p99)) + +def aggregate_and_print_num_segment_results(agg_run_stats): + for value in agg_run_stats.values(): + value['total_num_segments'] = [x + y + z for x, y, z in zip(value['segment_animated_rotation_size'], value['segment_animated_translation_size'], value['segment_animated_scale_size'])] + + total_num_segments = sum([sum(x['num_segments']) for x in agg_run_stats.values()]) + + value['segment_animated_ratios'] = [(x / y) * 100.0 for x, y in zip(value['segment_animated_total_size'], value['compressed_size'])] + + tmp = list(chain.from_iterable([x['num_segments'] for x in agg_run_stats.values()])) + num_segments_p50 = numpy.percentile(tmp, 50.0) + num_segments_p85 = numpy.percentile(tmp, 85.0) + num_segments_p99 = numpy.percentile(tmp, 99.0) + + print('Total num segments: {}'.format(total_num_segments)) + print(' 50, 85, 99th percentile: {:.2f}, {:.2f}, {:.2f}'.format(num_segments_p50, num_segments_p85, num_segments_p99)) + +if __name__ == "__main__": + if sys.version_info < (3, 4): + print('Python 3.4 or higher needed to run this script') + sys.exit(1) + + options = parse_argv() + + stat_files = compress_clips(options) + + csv_data = create_csv(options) + + aggregating_start_time = time.perf_counter() + + stat_queue = multiprocessing.Queue() + for stat_filename in stat_files: + stat_queue.put(stat_filename) + + # Add a marker to terminate the jobs + for i in range(options['num_threads']): + stat_queue.put(None) + + result_queue = multiprocessing.Queue() + + jobs = [ multiprocessing.Process(target = run_stat_parsing, args = (options, stat_queue, result_queue)) for _i in range(options['num_threads']) ] + for job in jobs: + job.start() + + agg_job_results = {} + num_stat_file_processed = 0 + if options['has_progress_bar']: + print_progress(num_stat_file_processed, len(stat_files), 'Aggregating results:', '{} / {}'.format(num_stat_file_processed, len(stat_files))) + try: + while True: + try: + (msg, data) = result_queue.get(True, 1.0) + if msg == 'progress': + num_stat_file_processed += 1 + if options['has_progress_bar']: + print_progress(num_stat_file_processed, len(stat_files), 'Aggregating results:', '{} / {}'.format(num_stat_file_processed, len(stat_files))) + elif msg == 'done': + aggregate_job_stats(agg_job_results, data) + append_csv(csv_data, data) + except queue.Empty: + all_jobs_done = True + for job in jobs: + if job.is_alive(): + all_jobs_done = False + + if all_jobs_done: + break + except KeyboardInterrupt: + sys.exit(1) + + agg_run_stats = agg_job_results['agg_run_stats'] + best_runs = agg_job_results['best_runs'] + worst_runs = agg_job_results['worst_runs'] + num_runs = agg_job_results['num_runs'] + + write_csv(csv_data, agg_run_stats) + + aggregating_end_time = time.perf_counter() + print() + print('Found {} runs in {}'.format(num_runs, format_elapsed_time(aggregating_end_time - aggregating_start_time))) + print() + + close_csv(csv_data) + + print('Stats per run type:') + run_types_by_size = sorted(agg_run_stats.values(), key = lambda entry: entry['total_compressed_size']) + for run_stats in run_types_by_size: + ratio = float(run_stats['total_raw_size']) / float(run_stats['total_compressed_size']) + print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [{:.4f}] Run type: {}'.format(bytes_to_mb(run_stats['total_compressed_size']), format_elapsed_time(run_stats['total_compression_time']), ratio, run_stats['max_error'], run_stats['name'])) + print() + print('Total:') + total_raw_size = sum([x['total_raw_size'] for x in agg_run_stats.values()]) + total_compressed_size = sum([x['total_compressed_size'] for x in agg_run_stats.values()]) + total_compression_time = sum([x['total_compression_time'] for x in agg_run_stats.values()]) + total_max_error = max([x['max_error'] for x in agg_run_stats.values()]) + total_ratio = float(total_raw_size) / float(total_compressed_size) + tmp = list(chain.from_iterable([x['compressed_size'] for x in agg_run_stats.values()])) + compressed_size_p50 = bytes_to_kb(numpy.percentile(tmp, 50.0)) + compressed_size_p85 = bytes_to_kb(numpy.percentile(tmp, 85.0)) + compressed_size_p99 = bytes_to_kb(numpy.percentile(tmp, 99.0)) + print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [{:.4f}]'.format(bytes_to_mb(total_compressed_size), format_elapsed_time(total_compression_time), total_ratio, total_max_error)) + print(' 50, 85, 99th percentile: {:.2f} KB, {:.2f} KB, {:.2f} KB'.format(compressed_size_p50, compressed_size_p85, compressed_size_p99)) + print() + + total_duration = sum([x['total_duration'] for x in agg_run_stats.values()]) + + print('Sum of clip durations: {}'.format(format_elapsed_time(total_duration))) + print('Total compression time: {} ({:.3f} seconds)'.format(format_elapsed_time(total_compression_time), total_compression_time)) + print('Total raw size: {:.2f} MB'.format(bytes_to_mb(total_raw_size))) + print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(total_raw_size) / total_compression_time)) + print('Compression time 50, 85, 99th percentile: {:.3f}, {:.3f}, {:.3f} seconds'.format(numpy.percentile(agg_job_results['compression_times'], 50.0), numpy.percentile(agg_job_results['compression_times'], 85.0), numpy.percentile(agg_job_results['compression_times'], 99.0))) + if len(agg_job_results['bone_error_values']) > 0: + print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(agg_job_results['bone_error_values'], 99.0))) + print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(agg_job_results['bone_error_values'], 0.01))) + print() + + print('Most accurate: {}'.format(best_runs['best_error_entry']['filename'])) + print_stat(best_runs['best_error_entry']) + + print('Best ratio: {}'.format(best_runs['best_ratio_entry']['filename'])) + print_stat(best_runs['best_ratio_entry']) + + print('Least accurate: {}'.format(worst_runs['worst_error_entry']['filename'])) + print_stat(worst_runs['worst_error_entry']) + + print('Worst ratio: {}'.format(worst_runs['worst_ratio_entry']['filename'])) + print_stat(worst_runs['worst_ratio_entry']) + + if options['stat_detailed']: + print('----- Detailed stats -----') + print() + + aggregate_and_print_track_results(agg_run_stats, None) + print() + aggregate_and_print_track_results(agg_run_stats, 'rotation') + print() + aggregate_and_print_track_results(agg_run_stats, 'translation') + print() + aggregate_and_print_track_results(agg_run_stats, 'scale') + print() + + aggregate_and_print_clip_metadata_results(agg_run_stats) + print() + + aggregate_and_print_segment_metadata_results(agg_run_stats) + print() + + aggregate_and_print_segment_animated_results(agg_run_stats) + print() + + aggregate_and_print_num_segment_results(agg_run_stats) + print() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/includes/acl_compressor.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/includes/acl_compressor.h new file mode 100644 index 00000000..50c7da70 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/includes/acl_compressor.h @@ -0,0 +1,30 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +bool is_acl_sjson_file(const char* filename); +bool is_acl_bin_file(const char* filename); + +int main_impl(int argc, char* argv[]); diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/CMakeLists.txt new file mode 100644 index 00000000..6c7356d9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_compressor_gradle_shim NONE) + +# Set our project root since our gradle files used to build live in the binary output directory +# but the actual source files live in the source/current directory. +file(RELATIVE_PATH ACL_PROJECT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/app ${PROJECT_SOURCE_DIR}/app) + +# Setup our state +set(ACL_ANDROID_PROJECT_NAME "ACL Compressor") +set(ACL_ANDROID_PROJECT_PACKAGE_NAME "com.acl.compressor") + +# Configure gradle for our build configuration +configure_file(${ACL_ANDROID_MISC_DIR}/app/build.gradle.in app/build.gradle @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/app/src/main/AndroidManifest.xml.in app/src/main/AndroidManifest.xml @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/app/src/main/res/values/strings.xml.in app/src/main/res/values/strings.xml @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/settings.gradle.in settings.gradle @ONLY) + +# Copy gradle related files +file(COPY + ${ACL_ANDROID_MISC_DIR}/build.gradle + ${ACL_ANDROID_MISC_DIR}/gradle.properties + ${ACL_ANDROID_MISC_DIR}/gradle + ${ACL_ANDROID_MISC_DIR}/gradlew + ${ACL_ANDROID_MISC_DIR}/gradlew.bat + DESTINATION .) + +file(COPY + ${ACL_ANDROID_MISC_DIR}/app/proguard-rules.pro + DESTINATION app) + +add_custom_target(${PROJECT_NAME} ALL + COMMAND gradlew.bat + # Decide whether we should build Debug or Release + $<$:assembleDebug> + $<$:assembleRelease>) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..15fa60f8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_compressor CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Project root is \tools\compressor\main_android +set(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") + +include_directories("${PROJECT_ROOT_DIR}/../../../includes") +include_directories("${PROJECT_ROOT_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_ROOT_DIR}/../includes") + +if(USE_SJSON) + include_directories("${PROJECT_ROOT_DIR}/../../../external/sjson-cpp/includes") +endif() + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_ROOT_DIR}/../sources/*.h + ${PROJECT_ROOT_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_library(${PROJECT_NAME} SHARED ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Enable exceptions +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) + +# Enable debug symbols +target_compile_options(${PROJECT_NAME} PRIVATE -g) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +# Enable SJSON when needed +if(USE_SJSON) + add_definitions(-DACL_USE_SJSON) +endif() + +target_include_directories(${PROJECT_NAME} PUBLIC jni) + +target_link_libraries(${PROJECT_NAME} m log) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/cpp/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/cpp/main.cpp new file mode 100644 index 00000000..0e3b9a9c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/cpp/main.cpp @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +#include + +extern "C" jint Java_com_acl_compressor_MainActivity_nativeMain(JNIEnv* env, jobject caller) +{ + char* argv[1] = { nullptr }; + return main_impl(0, argv); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/java/com/acl/compressor/MainActivity.java b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/java/com/acl/compressor/MainActivity.java new file mode 100644 index 00000000..8d4f2fd0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_android/app/src/main/java/com/acl/compressor/MainActivity.java @@ -0,0 +1,29 @@ +package com.acl.compressor; + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class MainActivity extends Activity { + static { + System.loadLibrary("acl_compressor"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView resultTextView = new TextView(this); + + int result = nativeMain(); + + if (result == 0) + resultTextView.setText("Success!"); + else + resultTextView.setText("Failed!"); + + setContentView(resultTextView); + } + + public native int nativeMain(); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_emscripten/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_emscripten/CMakeLists.txt new file mode 100644 index 00000000..bceecbc3 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_emscripten/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_compressor CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../includes") + +if(USE_SJSON) + include_directories("${PROJECT_SOURCE_DIR}/../../../external/sjson-cpp/includes") +endif() + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../includes/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp + ${PROJECT_SOURCE_DIR}/../*.py + ${PROJECT_SOURCE_DIR}/../*.md) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_executable(${PROJECT_NAME} ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Abort on failure, easier to debug issues this way +add_definitions(-DACL_ON_ASSERT_ABORT) +add_definitions(-DRTM_ON_ASSERT_ABORT) +add_definitions(-DSJSON_CPP_ON_ASSERT_ABORT) + +# Enable SJSON when needed +if(USE_SJSON) + add_definitions(-DACL_USE_SJSON) +endif() + +# Remove '-g' from compilation flags since it sometimes crashes the compiler +string(REPLACE "-g" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") +string(REPLACE "-g" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + +target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) # Enable all warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow) # Enable shadowing warnings +target_compile_options(${PROJECT_NAME} PRIVATE -Werror) # Treat warnings as errors + +# Exceptions are not enabled by default, enable them +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) +target_link_libraries(${PROJECT_NAME} "-s DISABLE_EXCEPTION_CATCHING=0") + +target_link_libraries(${PROJECT_NAME} "-s NODERAWFS=1") # Enable the raw node file system +target_link_libraries(${PROJECT_NAME} -lnodefs.js) # Link the node file system + +target_link_libraries(${PROJECT_NAME} "-s ENVIRONMENT=node") # Force the environment to node + +target_link_libraries(${PROJECT_NAME} "-s ALLOW_MEMORY_GROWTH=1") # Allow dynamic memory allocation + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.js + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wasm + DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_emscripten/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_emscripten/main.cpp new file mode 100644 index 00000000..4839d45a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_emscripten/main.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +int main(int argc, char* argv[]) +{ + return main_impl(argc, argv); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_generic/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_generic/CMakeLists.txt new file mode 100644 index 00000000..ece9888a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_generic/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_compressor CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../includes") + +if(USE_SJSON) + include_directories("${PROJECT_SOURCE_DIR}/../../../external/sjson-cpp/includes") +endif() + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../includes/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp + ${PROJECT_SOURCE_DIR}/../*.py + ${PROJECT_SOURCE_DIR}/../*.md) + +create_source_groups("${ALL_COMMON_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +setup_default_compiler_flags(${PROJECT_NAME}) + +if(MSVC) + if(CPU_INSTRUCTION_SET MATCHES "arm64") + # Exceptions are not enabled by default for ARM targets, enable them + target_compile_options(${PROJECT_NAME} PRIVATE /EHsc) + endif() +endif() + +# Abort on failure, easier to debug issues this way +add_definitions(-DACL_ON_ASSERT_ABORT) +add_definitions(-DRTM_ON_ASSERT_ABORT) +add_definitions(-DSJSON_CPP_ON_ASSERT_ABORT) + +# Enable SJSON when needed +if(USE_SJSON) + add_definitions(-DACL_USE_SJSON) +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_generic/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_generic/main.cpp new file mode 100644 index 00000000..ccd8e8c8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_generic/main.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +int main(int argc, char* argv[]) +{ + return main_impl(argc, argv); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_ios/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_ios/CMakeLists.txt new file mode 100644 index 00000000..53feeb5c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_ios/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_compressor) + +# iOS cmake toolchain does not support CMAKE_CXX_STANDARD +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Force enable debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + +# Enable optimizations in Release +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +set(MACOSX_BUNDLE_EXECUTABLE_NAME ${PROJECT_NAME}) +set(MACOSX_BUNDLE_INFO_STRING "com.acl.acl-compressor") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.acl.acl-compressor") +set(MACOSX_BUNDLE_BUNDLE_NAME "acl-compressor") + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../includes") + +if(USE_SJSON) + include_directories("${PROJECT_SOURCE_DIR}/../../../external/sjson-cpp/includes") +endif() + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../includes/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_COMMON_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +# Enable SJSON when needed +if(USE_SJSON) + add_definitions(-DACL_USE_SJSON) +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_ios/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_ios/main.cpp new file mode 100644 index 00000000..ccd8e8c8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/main_ios/main.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +int main(int argc, char* argv[]) +{ + return main_impl(argc, argv); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/acl_compressor.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/acl_compressor.cpp new file mode 100644 index 00000000..47aff977 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/acl_compressor.cpp @@ -0,0 +1,1253 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +#define DEBUG_MEGA_LARGE_CLIP 0 + +// Enable 64 bit file IO +#ifndef _WIN32 + #define _FILE_OFFSET_BITS 64 +#endif + +// Used to debug and validate that we compile without sjson-cpp +// Defaults to being enabled +#if defined(ACL_USE_SJSON) + #include + #include +#else + namespace sjson { class ArrayWriter; } +#endif + +#if defined(RTM_COMPILER_MSVC) && RTM_COMPILER_MSVC > RTM_COMPILER_MSVC_2015 +// VS2015 internal state appears to get corrupted when some forward declarations are made +#include "acl/fwd.h" // Make sure forward declaration matches +#endif + +#include "acl/core/ansi_allocator.h" +#include "acl/core/floating_point_exceptions.h" +#include "acl/core/string.h" +#include "acl/core/impl/debug_track_writer.h" +#include "acl/compression/compress.h" +#include "acl/compression/convert.h" +#include "acl/compression/transform_pose_utils.h" // Just to test compilation +#include "acl/decompression/decompress.h" +#include "acl/io/clip_reader.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + // The below excludes some other unused services from the windows headers -- see windows.h for details. + #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ + #define NOVIRTUALKEYCODES // VK_* + #define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* + #define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* + #define NOSYSMETRICS // SM_* + #define NOMENUS // MF_* + #define NOICONS // IDI_* + #define NOKEYSTATES // MK_* + #define NOSYSCOMMANDS // SC_* + #define NORASTEROPS // Binary and Tertiary raster ops + #define NOSHOWWINDOW // SW_* + #define OEMRESOURCE // OEM Resource values + #define NOATOM // Atom Manager routines + #define NOCLIPBOARD // Clipboard routines + #define NOCOLOR // Screen colors + #define NOCTLMGR // Control and Dialog routines + #define NODRAWTEXT // DrawText() and DT_* + #define NOGDI // All GDI #defines and routines + #define NOKERNEL // All KERNEL #defines and routines + #define NOUSER // All USER #defines and routines + #define NONLS // All NLS #defines and routines + #define NOMB // MB_* and MessageBox() + #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines + #define NOMETAFILE // typedef METAFILEPICT + #define NOMINMAX // Macros min(a,b) and max(a,b) + #define NOMSG // typedef MSG and associated routines + #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* + #define NOSCROLL // SB_* and scrolling routines + #define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. + #define NOSOUND // Sound driver routines + #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines + #define NOWH // SetWindowsHook and WH_* + #define NOWINOFFSETS // GWL_*, GCL_*, associated routines + #define NOCOMM // COMM driver routines + #define NOKANJI // Kanji support stuff. + #define NOHELP // Help engine interface. + #define NOPROFILER // Profiler interface. + #define NODEFERWINDOWPOS // DeferWindowPos routines + #define NOMCX // Modem Configuration Extensions + #define NOCRYPT + #define NOTAPE + #define NOIMAGE + #define NOPROXYSTUB + #define NORPC + + #include + #include + +#endif // _WIN32 + +using namespace acl; + +struct Options +{ +#if defined(__ANDROID__) + const char* input_buffer = nullptr; + size_t input_buffer_size = 0; + bool input_buffer_binary = false; + const char* config_buffer = nullptr; + size_t config_buffer_size = 0; +#else + const char* input_filename = nullptr; + const char* config_filename = nullptr; +#endif + + bool do_output_stats = false; + const char* output_stats_filename = nullptr; + std::FILE* output_stats_file = nullptr; + + const char* output_bin_filename = nullptr; + const char* output_db_filename = nullptr; + + compression_level8 compression_level = compression_level8::lowest; + bool compression_level_specified = false; + + bool regression_testing = false; + bool exhaustive_compression = false; + + bool use_matrix_error_metric = false; + + bool is_bind_pose_relative = false; + bool is_bind_pose_additive0 = false; + bool is_bind_pose_additive1 = false; + + bool split_into_database = false; + + bool stat_detailed_output = false; + bool stat_exhaustive_output = false; + + ////////////////////////////////////////////////////////////////////////// + + Options() noexcept = default; + + ~Options() + { + if (output_stats_file != nullptr && output_stats_file != stdout) + std::fclose(output_stats_file); + } + + Options(const Options&) = delete; + Options& operator=(const Options&) = delete; + + void open_output_stats_file() + { + std::FILE* file = nullptr; + if (output_stats_filename != nullptr) + { +#ifdef _WIN32 + char path[64 * 1024] = { 0 }; + snprintf(path, get_array_size(path), "\\\\?\\%s", output_stats_filename); + fopen_s(&file, path, "w"); +#else + file = fopen(output_stats_filename, "w"); +#endif + ACL_ASSERT(file != nullptr, "Failed to open output stats file: ", output_stats_filename); + } + + output_stats_file = file != nullptr ? file : stdout; + } +}; + +static constexpr const char* k_acl_input_file_option = "-acl="; +static constexpr const char* k_config_input_file_option = "-config="; +static constexpr const char* k_stats_output_option = "-stats"; +static constexpr const char* k_bin_output_option = "-out="; +static constexpr const char* k_compression_level_option = "-level="; +static constexpr const char* k_regression_test_option = "-test"; +static constexpr const char* k_exhaustive_compression_option = "-exhaustive"; +static constexpr const char* k_bind_pose_relative_option = "-bind_rel"; +static constexpr const char* k_bind_pose_additive0_option = "-bind_add0"; +static constexpr const char* k_bind_pose_additive1_option = "-bind_add1"; +static constexpr const char* k_matrix_error_metric_option = "-error_mtx"; +static constexpr const char* k_split_into_database = "-db"; +static constexpr const char* k_stat_detailed_output_option = "-stat_detailed"; +static constexpr const char* k_stat_exhaustive_output_option = "-stat_exhaustive"; + +bool is_acl_sjson_file(const char* filename) +{ + const size_t filename_len = filename != nullptr ? std::strlen(filename) : 0; + return filename_len >= 10 && strncmp(filename + filename_len - 10, ".acl.sjson", 10) == 0; +} + +bool is_acl_bin_file(const char* filename) +{ + const size_t filename_len = filename != nullptr ? std::strlen(filename) : 0; + return filename_len >= 4 && strncmp(filename + filename_len - 4, ".acl", 4) == 0; +} + +static bool parse_options(int argc, char** argv, Options& options) +{ + for (int arg_index = 1; arg_index < argc; ++arg_index) + { + const char* argument = argv[arg_index]; + + size_t option_length = std::strlen(k_acl_input_file_option); + if (std::strncmp(argument, k_acl_input_file_option, option_length) == 0) + { +#if defined(__ANDROID__) + unsigned int buffer_size; + int is_acl_bin_buffer; + sscanf(argument + option_length, "@%u,%p,%d", &buffer_size, &options.input_buffer, &is_acl_bin_buffer); + options.input_buffer_size = buffer_size; + options.input_buffer_binary = is_acl_bin_buffer != 0; +#else + options.input_filename = argument + option_length; + if (!is_acl_sjson_file(options.input_filename) && !is_acl_bin_file(options.input_filename)) + { + printf("Input file must be an ACL SJSON file of the form: [*.acl.sjson] or a binary ACL file of the form: [*.acl]\n"); + return false; + } +#endif + continue; + } + + option_length = std::strlen(k_config_input_file_option); + if (std::strncmp(argument, k_config_input_file_option, option_length) == 0) + { +#if defined(__ANDROID__) + unsigned int buffer_size; + sscanf(argument + option_length, "@%u,%p", &buffer_size, &options.config_buffer); + options.config_buffer_size = buffer_size; +#else + options.config_filename = argument + option_length; + const size_t filename_len = std::strlen(options.config_filename); + if (filename_len < 13 || strncmp(options.config_filename + filename_len - 13, ".config.sjson", 13) != 0) + { + printf("Configuration file must be a config SJSON file of the form: [*.config.sjson]\n"); + return false; + } +#endif + continue; + } + + option_length = std::strlen(k_stats_output_option); + if (std::strncmp(argument, k_stats_output_option, option_length) == 0) + { + options.do_output_stats = true; + if (argument[option_length] == '=') + { + options.output_stats_filename = argument + option_length + 1; + const size_t filename_len = std::strlen(options.output_stats_filename); + if (filename_len < 6 || strncmp(options.output_stats_filename + filename_len - 6, ".sjson", 6) != 0) + { + printf("Stats output file must be an SJSON file of the form: [*.sjson]\n"); + return false; + } + } + else + options.output_stats_filename = nullptr; + + options.open_output_stats_file(); + continue; + } + + option_length = std::strlen(k_bin_output_option); + if (std::strncmp(argument, k_bin_output_option, option_length) == 0) + { + options.output_bin_filename = argument + option_length; + if (!is_acl_bin_file(options.output_bin_filename)) + { + printf("Binary output file must be an ACL binary file of the form: [*.acl]\n"); + return false; + } + continue; + } + + option_length = std::strlen(k_compression_level_option); + if (std::strncmp(argument, k_compression_level_option, option_length) == 0) + { + const char* level_name = argument + option_length; + if (!get_compression_level(level_name, options.compression_level)) + { + printf("Invalid compression level name specified: %s\n", level_name); + return false; + } + options.compression_level_specified = true; + continue; + } + + option_length = std::strlen(k_regression_test_option); + if (std::strncmp(argument, k_regression_test_option, option_length) == 0) + { + options.regression_testing = true; + continue; + } + + option_length = std::strlen(k_exhaustive_compression_option); + if (std::strncmp(argument, k_exhaustive_compression_option, option_length) == 0) + { + options.exhaustive_compression = true; + continue; + } + + option_length = std::strlen(k_matrix_error_metric_option); + if (std::strncmp(argument, k_matrix_error_metric_option, option_length) == 0) + { + options.use_matrix_error_metric = true; + continue; + } + + option_length = std::strlen(k_bind_pose_relative_option); + if (std::strncmp(argument, k_bind_pose_relative_option, option_length) == 0) + { + options.is_bind_pose_relative = true; + continue; + } + + option_length = std::strlen(k_bind_pose_additive0_option); + if (std::strncmp(argument, k_bind_pose_additive0_option, option_length) == 0) + { + options.is_bind_pose_additive0 = true; + continue; + } + + option_length = std::strlen(k_bind_pose_additive1_option); + if (std::strncmp(argument, k_bind_pose_additive1_option, option_length) == 0) + { + options.is_bind_pose_additive1 = true; + continue; + } + + option_length = std::strlen(k_split_into_database); + if (std::strncmp(argument, k_split_into_database, option_length) == 0) + { + options.split_into_database = true; + if (argument[option_length] == '=') + { + options.output_db_filename = argument + option_length + 1; + const size_t filename_len = std::strlen(options.output_db_filename); + if (filename_len < 6 || strncmp(options.output_db_filename + filename_len - 6, ".acldb", 6) != 0) + { + printf("Database output filename must be of the form: [*.acldb]\n"); + return false; + } + } + else + options.output_db_filename = nullptr; + continue; + } + + option_length = std::strlen(k_stat_detailed_output_option); + if (std::strncmp(argument, k_stat_detailed_output_option, option_length) == 0) + { + options.stat_detailed_output = true; + continue; + } + + option_length = std::strlen(k_stat_exhaustive_output_option); + if (std::strncmp(argument, k_stat_exhaustive_output_option, option_length) == 0) + { + options.stat_exhaustive_output = true; + continue; + } + + printf("Unrecognized option %s\n", argument); + return false; + } + +#if defined(__ANDROID__) + if (options.input_buffer == nullptr || options.input_buffer_size == 0) +#else + if (options.input_filename == nullptr || std::strlen(options.input_filename) == 0) +#endif + { + printf("An input file is required.\n"); + return false; + } + + return true; +} + +#if defined(ACL_USE_SJSON) +#if defined(ACL_HAS_ASSERT_CHECKS) +// We extern our regression test functions for simplicity +extern void validate_accuracy(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, const itransform_error_metric& error_metric, const compressed_tracks& compressed_tracks_, double regression_error_threshold); +extern void validate_accuracy(iallocator& allocator, const track_array& raw_tracks, const compressed_tracks& tracks, double regression_error_threshold); +extern void validate_metadata(const track_array& raw_tracks, const compressed_tracks& tracks); +extern void validate_convert(iallocator& allocator, const track_array& raw_tracks); +extern void validate_db(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, + const compression_database_settings& settings, const itransform_error_metric& error_metric, + const compressed_tracks& compressed_tracks0, const compressed_tracks& compressed_tracks1); +#endif + +static void try_algorithm(const Options& options, iallocator& allocator, const track_array_qvvf& transform_tracks, + const track_array_qvvf& additive_base, additive_clip_format8 additive_format, + compression_settings settings, const compression_database_settings& database_settings, + stat_logging logging, sjson::ArrayWriter* runs_writer, double regression_error_threshold) +{ + (void)runs_writer; + (void)regression_error_threshold; + + auto try_algorithm_impl = [&](sjson::ObjectWriter* stats_writer) + { + // When regression testing or writing to a binary output, we include all the metadata + if (options.regression_testing || options.output_bin_filename != nullptr) + { + settings.metadata.include_track_list_name = true; + settings.metadata.include_track_names = true; + settings.metadata.include_parent_track_indices = true; + settings.metadata.include_track_descriptions = true; + } + + settings.enable_database_support = options.split_into_database; + + output_stats stats; + stats.logging = logging; + stats.writer = stats_writer; + + compressed_tracks* compressed_tracks_ = nullptr; + + error_result result = compress_track_list(allocator, transform_tracks, settings, additive_base, additive_format, compressed_tracks_, stats); + + ACL_ASSERT(result.empty(), result.c_str()); + ACL_ASSERT(compressed_tracks_->is_valid(true).empty(), "Compressed tracks are invalid"); + +#if defined(SJSON_CPP_WRITER) + if (logging != stat_logging::none) + { + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + decompression_context context; + + const bool initialized = context.initialize(*compressed_tracks_); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); (void)initialized; + + const track_error error = calculate_compression_error(allocator, transform_tracks, context, *settings.error_metric); + + stats_writer->insert("max_error", error.error); + stats_writer->insert("worst_track", error.index); + stats_writer->insert("worst_time", error.sample_time); + } +#endif + +#if defined(ACL_HAS_ASSERT_CHECKS) + if (options.regression_testing) + { + validate_accuracy(allocator, transform_tracks, additive_base, *settings.error_metric, *compressed_tracks_, regression_error_threshold); + validate_metadata(transform_tracks, *compressed_tracks_); + validate_convert(allocator, transform_tracks); + + if (options.split_into_database) + { + // Drop all the metadata and make a second copy for testing + // This will ensure we have two clips with different hashes + settings.metadata.include_track_list_name = false; + settings.metadata.include_track_names = false; + settings.metadata.include_parent_track_indices = false; + settings.metadata.include_track_descriptions = false; + + // No logging for second copy + output_stats stats1; + + compressed_tracks* compressed_tracks1 = nullptr; + + result = compress_track_list(allocator, transform_tracks, settings, additive_base, additive_format, compressed_tracks1, stats1); + + ACL_ASSERT(result.empty(), result.c_str()); + ACL_ASSERT(compressed_tracks1->is_valid(true).empty(), "Compressed tracks are invalid"); + ACL_ASSERT(compressed_tracks_->get_hash() != compressed_tracks1->get_hash(), "Hashes should not match"); + + validate_db(allocator, transform_tracks, additive_base, database_settings, *settings.error_metric, *compressed_tracks_, *compressed_tracks1); + + allocator.deallocate(compressed_tracks1, compressed_tracks1->get_size()); + } + } +#endif + + if (options.output_bin_filename != nullptr) + { +#ifdef _WIN32 + char output_bin_filename[64 * 1024] = { 0 }; + snprintf(output_bin_filename, get_array_size(output_bin_filename), "\\\\?\\%s", options.output_bin_filename); +#else + const char* output_bin_filename = options.output_bin_filename; +#endif + + std::ofstream output_file_stream(output_bin_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + if (output_file_stream.is_open()) + output_file_stream.write(reinterpret_cast(compressed_tracks_), compressed_tracks_->get_size()); + } + + // TODO +#if 0 + if (options.split_into_database && options.output_db_filename != nullptr) + { + std::ofstream output_file_stream(options.output_db_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + if (output_file_stream.is_open()) + output_file_stream.write(reinterpret_cast(db), db->get_size()); + } +#endif + + allocator.deallocate(compressed_tracks_, compressed_tracks_->get_size()); + }; + +#if defined(SJSON_CPP_WRITER) + if (runs_writer != nullptr) + runs_writer->push([&](sjson::ObjectWriter& writer) { try_algorithm_impl(&writer); }); + else +#endif + try_algorithm_impl(nullptr); +} + +static void try_algorithm(const Options& options, iallocator& allocator, const track_array& track_list, stat_logging logging, sjson::ArrayWriter* runs_writer, double regression_error_threshold) +{ + (void)runs_writer; + (void)regression_error_threshold; + + auto try_algorithm_impl = [&](sjson::ObjectWriter* stats_writer) + { + if (track_list.is_empty()) + return; + + compression_settings settings; + + // When regression testing or writing to a binary output, we include all the metadata + if (options.regression_testing || options.output_bin_filename != nullptr) + { + settings.metadata.include_track_list_name = true; + settings.metadata.include_track_names = true; + settings.metadata.include_track_descriptions = true; + } + + output_stats stats; + stats.logging = logging; + stats.writer = stats_writer; + + compressed_tracks* compressed_tracks_ = nullptr; + const error_result result = compress_track_list(allocator, track_list, settings, compressed_tracks_, stats); + + ACL_ASSERT(result.empty(), result.c_str()); (void)result; + ACL_ASSERT(compressed_tracks_->is_valid(true).empty(), "Compressed tracks are invalid"); + +#if defined(SJSON_CPP_WRITER) + if (logging != stat_logging::none && stats_writer != nullptr) + { + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + acl::decompression_context context; + context.initialize(*compressed_tracks_); + + const track_error error = calculate_compression_error(allocator, track_list, context); + + stats_writer->insert("max_error", error.error); + stats_writer->insert("worst_track", error.index); + stats_writer->insert("worst_time", error.sample_time); + } +#endif + +#if defined(ACL_HAS_ASSERT_CHECKS) + if (options.regression_testing) + { + validate_accuracy(allocator, track_list, *compressed_tracks_, regression_error_threshold); + validate_metadata(track_list, *compressed_tracks_); + } +#endif + + if (options.output_bin_filename != nullptr) + { +#ifdef _WIN32 + char output_bin_filename[64 * 1024] = { 0 }; + snprintf(output_bin_filename, get_array_size(output_bin_filename), "\\\\?\\%s", options.output_bin_filename); +#else + const char* output_bin_filename = options.output_bin_filename; +#endif + + std::ofstream output_file_stream(output_bin_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + if (output_file_stream.is_open()) + output_file_stream.write(reinterpret_cast(compressed_tracks_), compressed_tracks_->get_size()); + } + + allocator.deallocate(compressed_tracks_, compressed_tracks_->get_size()); + }; + +#if defined(SJSON_CPP_WRITER) + if (runs_writer != nullptr) + runs_writer->push([&](sjson::ObjectWriter& writer) { try_algorithm_impl(&writer); }); + else +#endif + try_algorithm_impl(nullptr); +} + +static bool read_file(iallocator& allocator, const char* input_filename, char*& out_buffer, size_t& out_file_size) +{ + // Use the raw C API with a large buffer to ensure this is as fast as possible + std::FILE* file = nullptr; + +#ifdef _WIN32 + char path[64 * 1024] = { 0 }; + snprintf(path, get_array_size(path), "\\\\?\\%s", input_filename); + fopen_s(&file, path, "rb"); +#else + file = fopen(input_filename, "rb"); +#endif + + if (file == nullptr) + { + printf("Failed to open input file\n"); + return false; + } + + // Make sure to enable buffering with a large buffer + const int setvbuf_result = setvbuf(file, nullptr, _IOFBF, 1 * 1024 * 1024); + if (setvbuf_result != 0) + { + printf("Failed to set input file buffering settings\n"); + fclose(file); + return false; + } + + const int fseek_result = fseek(file, 0, SEEK_END); + if (fseek_result != 0) + { + printf("Failed to seek in input file\n"); + fclose(file); + return false; + } + +#ifdef _WIN32 + out_file_size = static_cast(_ftelli64(file)); +#else + out_file_size = static_cast(ftello(file)); +#endif + + if (out_file_size == static_cast(-1L)) + { + printf("Failed to read input file size\n"); + fclose(file); + return false; + } + + rewind(file); + + out_buffer = allocate_type_array_aligned(allocator, out_file_size, 64); + const size_t result = fread(out_buffer, 1, out_file_size, file); + fclose(file); + + if (result != out_file_size) + { + printf("Failed to read input file\n"); + deallocate_type_array(allocator, out_buffer, out_file_size); + return false; + } + + return true; +} + +static bool read_acl_bin_file(iallocator& allocator, const Options& options, acl::compressed_tracks*& out_tracks) +{ + char* tracks_data = nullptr; + size_t file_size = 0; + +#if defined(__ANDROID__) + // Duplicate the data on android so we can free it normally later + tracks_data = allocate_type_array_aligned(allocator, options.input_buffer_size, 64); + file_size = options.input_buffer_size; + std::memcpy(tracks_data, options.input_buffer, options.input_buffer_size); +#else + if (!read_file(allocator, options.input_filename, tracks_data, file_size)) + return false; +#endif + + out_tracks = reinterpret_cast(tracks_data); + if (file_size != out_tracks->get_size() || out_tracks->is_valid(true).any()) + { + printf("Invalid binary ACL file provided\n"); + deallocate_type_array(allocator, tracks_data, file_size); + return false; + } + + return true; +} + +static bool read_acl_sjson_file(iallocator& allocator, const Options& options, + sjson_file_type& out_file_type, + sjson_raw_clip& out_raw_clip, + sjson_raw_track_list& out_raw_track_list) +{ + char* sjson_file_buffer = nullptr; + size_t file_size = 0; + +#if defined(__ANDROID__) + clip_reader reader(allocator, options.input_buffer, options.input_buffer_size - 1); +#else + if (!read_file(allocator, options.input_filename, sjson_file_buffer, file_size)) + return false; + + clip_reader reader(allocator, sjson_file_buffer, file_size - 1); +#endif + + const sjson_file_type ftype = reader.get_file_type(); + out_file_type = ftype; + + bool success = false; + switch (ftype) + { + case sjson_file_type::unknown: + default: + printf("\nUnknown file type\n"); + break; + case sjson_file_type::raw_clip: + success = reader.read_raw_clip(out_raw_clip); + break; + case sjson_file_type::raw_track_list: + success = reader.read_raw_track_list(out_raw_track_list); + break; + } + + if (!success) + { + const clip_reader_error err = reader.get_error(); + if (err.error != clip_reader_error::None) + printf("\nError on line %d column %d: %s\n", err.line, err.column, err.get_description()); + } + + deallocate_type_array(allocator, sjson_file_buffer, file_size); + return success; +} + +static bool read_config(iallocator& allocator, Options& options, compression_settings& out_settings, compression_database_settings& out_database_settings, double& out_regression_error_threshold) +{ +#if defined(__ANDROID__) + sjson::Parser parser(options.config_buffer, options.config_buffer_size - 1); +#else + std::ifstream t(options.config_filename); + std::stringstream buffer; + buffer << t.rdbuf(); + std::string str = buffer.str(); + + sjson::Parser parser(str.c_str(), str.length()); +#endif + + double version = 0.0; + if (!parser.read("version", version)) + { + uint32_t line; + uint32_t column; + parser.get_position(line, column); + + printf("Error on line %d column %d: Missing config version\n", line, column); + return false; + } + + if (version != 2.0) + { + printf("Unsupported version: %f\n", version); + return false; + } + + sjson::StringView algorithm_name; + if (!parser.read("algorithm_name", algorithm_name)) + { + uint32_t line; + uint32_t column; + parser.get_position(line, column); + + printf("Error on line %d column %d: Missing algorithm name\n", line, column); + return false; + } + + algorithm_type8 algorithm_type; + if (!get_algorithm_type(algorithm_name.c_str(), algorithm_type)) + { + printf("Invalid algorithm name: %s\n", string(allocator, algorithm_name.c_str(), algorithm_name.size()).c_str()); + return false; + } + + compression_settings default_settings; + + sjson::StringView compression_level; + parser.try_read("level", compression_level, get_compression_level_name(default_settings.level)); + if (!get_compression_level(compression_level.c_str(), out_settings.level)) + { + printf("Invalid compression level: %s\n", string(allocator, compression_level.c_str(), compression_level.size()).c_str()); + return false; + } + + sjson::StringView rotation_format; + parser.try_read("rotation_format", rotation_format, get_rotation_format_name(default_settings.rotation_format)); + if (!get_rotation_format(rotation_format.c_str(), out_settings.rotation_format)) + { + printf("Invalid rotation format: %s\n", string(allocator, rotation_format.c_str(), rotation_format.size()).c_str()); + return false; + } + + sjson::StringView translation_format; + parser.try_read("translation_format", translation_format, get_vector_format_name(default_settings.translation_format)); + if (!get_vector_format(translation_format.c_str(), out_settings.translation_format)) + { + printf("Invalid translation format: %s\n", string(allocator, translation_format.c_str(), translation_format.size()).c_str()); + return false; + } + + sjson::StringView scale_format; + parser.try_read("scale_format", scale_format, get_vector_format_name(default_settings.scale_format)); + if (!get_vector_format(scale_format.c_str(), out_settings.scale_format)) + { + printf("Invalid scale format: %s\n", string(allocator, scale_format.c_str(), scale_format.size()).c_str()); + return false; + } + + double dummy; + parser.try_read("constant_rotation_threshold_angle", dummy, 0.0F); + + parser.try_read("constant_translation_threshold", dummy, 0.0F); + parser.try_read("constant_scale_threshold", dummy, 0.0F); + parser.try_read("error_threshold", dummy, 0.0F); + + parser.try_read("regression_error_threshold", out_regression_error_threshold, 0.0); + + bool is_bind_pose_relative; + if (parser.try_read("is_bind_pose_relative", is_bind_pose_relative, false)) + options.is_bind_pose_relative = is_bind_pose_relative; + + bool use_matrix_error_metric; + if (parser.try_read("use_matrix_error_metric", use_matrix_error_metric, false)) + options.use_matrix_error_metric = use_matrix_error_metric; + + bool split_into_database; + if (parser.try_read("split_into_database", split_into_database, false)) + options.split_into_database = split_into_database; + + compression_database_settings default_database_settings; + + uint32_t database_max_chunk_size; + if (parser.try_read("database_max_chunk_size", database_max_chunk_size, default_database_settings.max_chunk_size)) + out_database_settings.max_chunk_size = database_max_chunk_size; + + float medium_importance_tier; + if (parser.try_read("medium_importance_tier", medium_importance_tier, default_database_settings.medium_importance_tier_proportion)) + out_database_settings.medium_importance_tier_proportion = medium_importance_tier; + + float low_importance_tier; + if (parser.try_read("low_importance_tier", low_importance_tier, default_database_settings.low_importance_tier_proportion)) + out_database_settings.low_importance_tier_proportion = low_importance_tier; + + if (!parser.is_valid() || !parser.remainder_is_comments_and_whitespace()) + { + uint32_t line; + uint32_t column; + parser.get_position(line, column); + + printf("Error on line %d column %d: Expected end of file\n", line, column); + return false; + } + + return true; +} + +static itransform_error_metric* create_additive_error_metric(iallocator& allocator, additive_clip_format8 format) +{ + switch (format) + { + case additive_clip_format8::relative: + return allocate_type>(allocator); + case additive_clip_format8::additive0: + return allocate_type>(allocator); + case additive_clip_format8::additive1: + return allocate_type>(allocator); + default: + return nullptr; + } +} + +static void create_additive_base_clip(const Options& options, track_array_qvvf& clip, const track_qvvf& bind_pose, track_array_qvvf& out_base_clip, additive_clip_format8& out_additive_format) +{ + // Convert the animation clip to be relative to the bind pose + const uint32_t num_bones = clip.get_num_tracks(); + const uint32_t num_samples = clip.get_num_samples_per_track(); + iallocator& allocator = *clip.get_allocator(); + const track_desc_transformf bind_desc; + + out_base_clip = track_array_qvvf(*clip.get_allocator(), num_bones); + + additive_clip_format8 additive_format = additive_clip_format8::none; + if (options.is_bind_pose_relative) + additive_format = additive_clip_format8::relative; + else if (options.is_bind_pose_additive0) + additive_format = additive_clip_format8::additive0; + else if (options.is_bind_pose_additive1) + additive_format = additive_clip_format8::additive1; + out_additive_format = additive_format; + + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + // Get the bind transform and make sure it has no scale + rtm::qvvf bind_transform = bind_pose[bone_index]; + bind_transform.scale = rtm::vector_set(1.0F); + + track_qvvf& track = clip[bone_index]; + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const rtm::qvvf bone_transform = track[sample_index]; + + rtm::qvvf bind_local_transform = bone_transform; + if (options.is_bind_pose_relative) + bind_local_transform = convert_to_relative(bind_transform, bone_transform); + else if (options.is_bind_pose_additive0) + bind_local_transform = convert_to_additive0(bind_transform, bone_transform); + else if (options.is_bind_pose_additive1) + bind_local_transform = convert_to_additive1(bind_transform, bone_transform); + + track[sample_index] = bind_local_transform; + } + + out_base_clip[bone_index] = track_qvvf::make_copy(bind_desc, allocator, &bind_transform, 1, 30.0F); + } +} + +static compression_settings make_settings(rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format) +{ + compression_settings settings; + settings.rotation_format = rotation_format; + settings.translation_format = translation_format; + settings.scale_format = scale_format; + return settings; +} +#endif // defined(ACL_USE_SJSON) + +static int safe_main_impl(int argc, char* argv[]) +{ + Options options; + + if (!parse_options(argc, argv, options)) + return -1; + +#if defined(ACL_USE_SJSON) + ansi_allocator allocator; + track_array_qvvf transform_tracks; + track_array_qvvf base_clip; + additive_clip_format8 additive_format = additive_clip_format8::none; + track_qvvf bind_pose; + track_array scalar_tracks; + +#if defined(__ANDROID__) + const bool is_input_acl_bin_file = options.input_buffer_binary; +#else + const bool is_input_acl_bin_file = is_acl_bin_file(options.input_filename); +#endif + + bool use_external_config = false; + compression_settings settings; + compression_database_settings database_settings; + + sjson_file_type sjson_type = sjson_file_type::unknown; + sjson_raw_clip sjson_clip; + sjson_raw_track_list sjson_track_list; + + if (is_input_acl_bin_file) + { + acl::compressed_tracks* bin_tracks = nullptr; + if (!read_acl_bin_file(allocator, options, bin_tracks)) + return -1; + + if (bin_tracks->get_track_type() == track_type8::qvvf) + { + const acl::error_result result = acl::convert_track_list(allocator, *bin_tracks, transform_tracks); + if (result.any()) + { + printf("Failed to convert input binary track list\n"); + deallocate_type_array(allocator, bin_tracks, bin_tracks->get_size()); + return -1; + } + + sjson_type = sjson_file_type::raw_clip; + } + else + { + const acl::error_result result = acl::convert_track_list(allocator, *bin_tracks, scalar_tracks); + if (result.any()) + { + printf("Failed to convert input binary track list\n"); + deallocate_type_array(allocator, bin_tracks, bin_tracks->get_size()); + return -1; + } + + sjson_type = sjson_file_type::raw_track_list; + } + + deallocate_type_array(allocator, bin_tracks, bin_tracks->get_size()); + } + else + { + if (!read_acl_sjson_file(allocator, options, sjson_type, sjson_clip, sjson_track_list)) + return -1; + + transform_tracks = std::move(sjson_clip.track_list); + base_clip = std::move(sjson_clip.additive_base_track_list); + additive_format = sjson_clip.additive_format; + bind_pose = std::move(sjson_clip.bind_pose); + use_external_config = sjson_clip.has_settings; + settings = sjson_clip.settings; + scalar_tracks = std::move(sjson_track_list.track_list); + } + +#if DEBUG_MEGA_LARGE_CLIP + track_array_qvvf new_transforms(allocator, transform_tracks.get_num_tracks()); + float new_sample_rate = 19200.0F; + uint32_t new_num_samples = calculate_num_samples(transform_tracks.get_duration(), new_sample_rate); + float new_duration = calculate_duration(new_num_samples, new_sample_rate); + acl_impl::debug_track_writer dummy_writer(allocator, track_type8::qvvf, transform_tracks.get_num_tracks()); + + for (uint32_t track_index = 0; track_index < transform_tracks.get_num_tracks(); ++track_index) + { + track_qvvf& track = transform_tracks[track_index]; + new_transforms[track_index] = track_qvvf::make_reserve(track.get_description(), allocator, new_num_samples, new_sample_rate); + } + + for (uint32_t sample_index = 0; sample_index < new_num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / new_sample_rate, new_duration); + + transform_tracks.sample_tracks(sample_time, sample_rounding_policy::none, dummy_writer); + + for (uint32_t track_index = 0; track_index < new_transforms.get_num_tracks(); ++track_index) + { + track_qvvf& track = new_transforms[track_index]; + track[sample_index] = dummy_writer.tracks_typed.qvvf[track_index]; + } + } + + transform_tracks = std::move(new_transforms); +#endif + + double regression_error_threshold = 0.1; + +#if defined(__ANDROID__) + if (options.config_buffer != nullptr && options.config_buffer_size != 0) +#else + if (options.config_filename != nullptr && std::strlen(options.config_filename) != 0) +#endif + { + // Override whatever the ACL SJSON file might have contained + settings = compression_settings(); + database_settings = compression_database_settings(); + + if (!read_config(allocator, options, settings, database_settings, regression_error_threshold)) + return -1; + + use_external_config = true; + } + + if (sjson_type == sjson_file_type::raw_clip) + { + // Grab whatever clip we might have read from the sjson file and cast the const away so we can manage the memory + if (base_clip.is_empty() && !bind_pose.is_empty()) + { + if (options.is_bind_pose_relative || options.is_bind_pose_additive0 || options.is_bind_pose_additive1) + create_additive_base_clip(options, transform_tracks, bind_pose, base_clip, additive_format); + } + + // First try to create an additive error metric + settings.error_metric = create_additive_error_metric(allocator, additive_format); + + if (settings.error_metric == nullptr) + { + if (options.use_matrix_error_metric) + settings.error_metric = allocate_type(allocator); + else + settings.error_metric = allocate_type(allocator); + } + } + + // Compress & Decompress + auto exec_algos = [&](sjson::ArrayWriter* runs_writer) + { + stat_logging logging = options.do_output_stats ? stat_logging::summary : stat_logging::none; + + if (options.stat_detailed_output) + logging |= stat_logging::detailed; + + if (options.stat_exhaustive_output) + logging |= stat_logging::exhaustive; + + if (sjson_type == sjson_file_type::raw_clip) + { + if (use_external_config) + { + if (options.compression_level_specified) + settings.level = options.compression_level; + + try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, settings, database_settings, logging, runs_writer, regression_error_threshold); + } + else if (options.exhaustive_compression) + { + { + compression_settings uniform_tests[] = + { + make_settings(rotation_format8::quatf_full, vector_format8::vector3f_full, vector_format8::vector3f_full), + make_settings(rotation_format8::quatf_drop_w_full, vector_format8::vector3f_full, vector_format8::vector3f_full), + + make_settings(rotation_format8::quatf_drop_w_variable, vector_format8::vector3f_variable, vector_format8::vector3f_full), + make_settings(rotation_format8::quatf_drop_w_variable, vector_format8::vector3f_variable, vector_format8::vector3f_variable), + }; + + for (compression_settings test_settings : uniform_tests) + { + test_settings.error_metric = settings.error_metric; + + try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, test_settings, database_settings, logging, runs_writer, regression_error_threshold); + } + } + + { + compression_settings uniform_tests[] = + { + make_settings(rotation_format8::quatf_full, vector_format8::vector3f_full, vector_format8::vector3f_full), + make_settings(rotation_format8::quatf_drop_w_full, vector_format8::vector3f_full, vector_format8::vector3f_full), + + make_settings(rotation_format8::quatf_drop_w_variable, vector_format8::vector3f_variable, vector_format8::vector3f_full), + make_settings(rotation_format8::quatf_drop_w_variable, vector_format8::vector3f_variable, vector_format8::vector3f_variable), + }; + + for (compression_settings test_settings : uniform_tests) + { + test_settings.error_metric = settings.error_metric; + + if (options.compression_level_specified) + test_settings.level = options.compression_level; + + try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, test_settings, database_settings, logging, runs_writer, regression_error_threshold); + } + } + } + else + { + compression_settings default_settings = get_default_compression_settings(); + default_settings.error_metric = settings.error_metric; + + if (options.compression_level_specified) + default_settings.level = options.compression_level; + + compression_database_settings default_database_settings; + + try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, default_settings, default_database_settings, logging, runs_writer, regression_error_threshold); + } + } + else if (sjson_type == sjson_file_type::raw_track_list) + { + try_algorithm(options, allocator, scalar_tracks, logging, runs_writer, regression_error_threshold); + } + }; + +#if defined(SJSON_CPP_WRITER) + if (options.do_output_stats) + { + sjson::FileStreamWriter stream_writer(options.output_stats_file); + sjson::Writer writer(stream_writer); + + writer["runs"] = [&](sjson::ArrayWriter& runs_writer) { exec_algos(&runs_writer); }; + } + else +#endif + exec_algos(nullptr); + + deallocate_type(allocator, settings.error_metric); +#endif // defined(ACL_USE_SJSON) + + return 0; +} + +#ifdef _WIN32 +static LONG WINAPI unhandled_exception_filter(EXCEPTION_POINTERS *info) +{ + (void)info; + + if (IsDebuggerPresent()) + return EXCEPTION_CONTINUE_SEARCH; + + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + +int main_impl(int argc, char* argv[]) +{ +#ifdef _WIN32 + // Disables Windows OS generated error dialogs and reporting + SetErrorMode(SEM_FAILCRITICALERRORS); + SetUnhandledExceptionFilter(&unhandled_exception_filter); + _set_abort_behavior(0, _CALL_REPORTFAULT); +#endif + + // Enable floating point exceptions when possible to detect errors when regression testing + scope_enable_fp_exceptions fp_on; + + int result = -1; +#if defined(ACL_ON_ASSERT_THROW) || defined(SJSON_CPP_ON_ASSERT_THROW) || defined(RTM_ON_ASSERT_THROW) + try +#endif + { + result = safe_main_impl(argc, argv); + } +#if defined(ACL_ON_ASSERT_THROW) + catch (const runtime_assert& exception) + { + printf("ACL assert occurred: %s\n", exception.what()); + result = -1; + } +#endif +#if defined(SJSON_CPP_ON_ASSERT_THROW) + catch (const sjson::runtime_assert& exception) + { + printf("sjson-cpp assert occurred: %s\n", exception.what()); + result = -1; + } +#endif +#if defined(RTM_ON_ASSERT_THROW) + catch (const rtm::runtime_assert& exception) + { + printf("RTM assert occurred: %s\n", exception.what()); + result = -1; + } +#endif + +#ifdef _WIN32 + if (IsDebuggerPresent()) + { + printf("Press any key to continue...\n"); + while (_kbhit() == 0); + } +#endif + + return result; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/validate_database.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/validate_database.cpp new file mode 100644 index 00000000..58c08d58 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/validate_database.cpp @@ -0,0 +1,645 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +#include "acl/core/compressed_database.h" +#include "acl/core/compressed_tracks.h" +#include "acl/core/floating_point_exceptions.h" +#include "acl/core/iallocator.h" +#include "acl/compression/compress.h" +#include "acl/compression/track_array.h" +#include "acl/compression/track_error.h" +#include "acl/compression/transform_error_metrics.h" +#include "acl/decompression/decompress.h" +#include "acl/decompression/database/database.h" +#include "acl/decompression/database/null_database_streamer.h" // Just to test compilation +#include "acl/decompression/database/impl/debug_database_streamer.h" + +using namespace acl; + +#if defined(ACL_USE_SJSON) && defined(ACL_HAS_ASSERT_CHECKS) +struct debug_transform_decompression_settings_with_db : public acl::debug_transform_decompression_settings +{ + using database_settings_type = acl::debug_database_settings; +}; + +static void stream_in_database_tier(database_context& context, const debug_database_streamer& streamer, const compressed_database& db, quality_tier tier) +{ + const uint32_t num_chunks = db.get_num_chunks(tier); + + bool is_streamed_in = context.is_streamed_in(tier); + ACL_ASSERT((num_chunks == 0 && is_streamed_in) || !is_streamed_in, "Tier should not be streamed in"); + ACL_ASSERT(streamer.get_bulk_data(tier) == nullptr, "Bulk data should not be allocated"); + + acl::database_stream_request_result stream_in_result = context.stream_in(tier, 2); + const uint8_t* streamer_bulk_data = streamer.get_bulk_data(tier); + + ACL_ASSERT((num_chunks == 0 && stream_in_result == database_stream_request_result::done) || stream_in_result == acl::database_stream_request_result::dispatched, "Failed to stream in tier"); + ACL_ASSERT(num_chunks == 0 || streamer.get_bulk_data(tier) != nullptr, "Bulk data should be allocated"); + + is_streamed_in = context.is_streamed_in(tier); + ACL_ASSERT((num_chunks <= 2 && is_streamed_in) || !is_streamed_in, "Failed to stream in tier (first 2 chunks)"); + + stream_in_result = context.stream_in(tier); + + ACL_ASSERT((num_chunks <= 2 && stream_in_result == database_stream_request_result::done) || stream_in_result == acl::database_stream_request_result::dispatched, "Failed to stream in tier"); + ACL_ASSERT(num_chunks == 0 || streamer.get_bulk_data(tier) != nullptr, "Bulk data should be allocated"); + ACL_ASSERT(streamer.get_bulk_data(tier) == streamer_bulk_data, "Bulk data should not have been reallocated"); + + is_streamed_in = context.is_streamed_in(tier); + ACL_ASSERT(is_streamed_in, "Failed to stream in tier"); +} + +static void stream_out_database_tier(database_context& context, const debug_database_streamer& streamer, const compressed_database& db, quality_tier tier) +{ + const uint8_t* streamer_bulk_data = streamer.get_bulk_data(tier); + const uint32_t num_chunks = db.get_num_chunks(tier); + + const bool is_streamed_in = context.is_streamed_in(tier); + ACL_ASSERT(is_streamed_in, "Tier should be streamed in"); + ACL_ASSERT(num_chunks == 0 || streamer.get_bulk_data(tier) != nullptr, "Bulk data should be allocated"); + + acl::database_stream_request_result stream_out_result = context.stream_out(tier, 2); + + ACL_ASSERT((num_chunks == 0 && stream_out_result == database_stream_request_result::done) || stream_out_result == acl::database_stream_request_result::dispatched, "Failed to stream out tier 1"); + if (num_chunks <= 2) + { + ACL_ASSERT(streamer.get_bulk_data(tier) == nullptr, "Bulk data should not be allocated"); + } + else + { + ACL_ASSERT(streamer.get_bulk_data(tier) != nullptr, "Bulk data should be allocated"); + ACL_ASSERT(streamer.get_bulk_data(tier) == streamer_bulk_data, "Bulk data should not have been reallocated"); + } + + bool is_streamed_out = !context.is_streamed_in(tier); + ACL_ASSERT(num_chunks == 0 || is_streamed_out, "Failed to stream out tier 1 (first 2 chunks)"); + + stream_out_result = context.stream_out(tier); + + ACL_ASSERT((num_chunks <= 2 && stream_out_result == database_stream_request_result::done) || stream_out_result == acl::database_stream_request_result::dispatched, "Failed to stream out tier 1"); + ACL_ASSERT(streamer.get_bulk_data(tier) == nullptr, "Bulk data should not be allocated"); + + is_streamed_out = !context.is_streamed_in(tier); + ACL_ASSERT(num_chunks == 0 || is_streamed_out, "Failed to stream out tier 1"); +} + +static void validate_db_streaming(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, const itransform_error_metric& error_metric, + const track_error& high_quality_tier_error_ref, + const compressed_tracks& tracks0, const compressed_tracks& tracks1, + const compressed_database& db, const uint8_t* db_bulk_data_medium, const uint8_t* db_bulk_data_low) +{ + // Our desired error threshold + // When intrinsics aren't used with x86, the floating point arithmetic falls back to + // using x87 instructions. When this happens, depending on how code is generated some + // small inaccuracies can pop up because rounding happens when we store to memory. + // The full pose decompression stores samples into the stack while working with them + // while the single track decompression does not which causes the issue. + // With SSE2 and NEON, there are no such rounding issues. +#if !defined(RTM_SSE2_INTRINSICS) && defined(RTM_ARCH_X86) + const float threshold = 1.0E-3F; +#else + const float threshold = 1.0E-4F; +#endif + + decompression_context context0; + decompression_context context1; + database_context db_context; + debug_database_streamer db_medium_streamer(allocator, db_bulk_data_medium, db.get_bulk_data_size(quality_tier::medium_importance)); + debug_database_streamer db_low_streamer(allocator, db_bulk_data_low, db.get_bulk_data_size(quality_tier::lowest_importance)); + ACL_ASSERT(db_medium_streamer.get_bulk_data(acl::quality_tier::medium_importance) == nullptr, "Bulk data should not be allocated"); + ACL_ASSERT(db_low_streamer.get_bulk_data(acl::quality_tier::lowest_importance) == nullptr, "Bulk data should not be allocated"); + + bool initialized = db_context.initialize(allocator, db, db_medium_streamer, db_low_streamer); + initialized = initialized && context0.initialize(tracks0, db_context); + initialized = initialized && context1.initialize(tracks1, db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + ACL_ASSERT(!db_context.is_streamed_in(quality_tier::medium_importance) || db.get_num_chunks(quality_tier::medium_importance) == 0, "Tier shouldn't be streamed in yet"); + ACL_ASSERT(!db_context.is_streamed_in(quality_tier::lowest_importance) || db.get_num_chunks(quality_tier::lowest_importance) == 0, "Tier shouldn't be streamed in yet"); + + // Nothing is streamed in yet, we have low quality + const track_error low_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error0.error >= high_quality_tier_error_ref.error, "Low quality tier split error should be higher or equal to high quality tier inline"); + const track_error low_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error1.error >= high_quality_tier_error_ref.error, "Low quality tier split error should be higher or equal to high quality tier inline"); + + // Stream in our medium importance tier + stream_in_database_tier(db_context, db_medium_streamer, db, quality_tier::medium_importance); + + const track_error medium_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error0.error >= high_quality_tier_error_ref.error, "Medium quality tier split error should be higher or equal to high quality tier inline"); + const track_error medium_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error1.error >= high_quality_tier_error_ref.error, "Medium quality tier split error should be higher or equal to high quality tier inline"); + + ACL_ASSERT(low_quality_tier_error0.error >= medium_quality_tier_error0.error, "Low quality tier split error should be higher or equal to medium quality tier split error"); + ACL_ASSERT(low_quality_tier_error1.error >= medium_quality_tier_error1.error, "Low quality tier split error should be higher or equal to medium quality tier split error"); + + // Stream in our low importance tier, restoring the full high quality + stream_in_database_tier(db_context, db_low_streamer, db, quality_tier::lowest_importance); + + { + const track_error high_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(high_quality_tier_error0.error, high_quality_tier_error_ref.error, threshold), "High quality tier split error should be equal to high quality tier inline"); + const track_error high_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(high_quality_tier_error1.error, high_quality_tier_error_ref.error, threshold), "High quality tier split error should be equal to high quality tier inline"); + } + + // Stream out our medium importance tier, we'll have mixed quality + stream_out_database_tier(db_context, db_medium_streamer, db, quality_tier::medium_importance); + + { + // Not guaranteed to always be the case due to linear interpolation + //const track_error mixed_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + //const track_error mixed_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + + //ACL_ASSERT(mixed_quality_tier_error0.error >= high_quality_tier_error_ref.error, "Mixed quality split error should be higher or equal to high quality tier inline"); + //ACL_ASSERT(mixed_quality_tier_error1.error >= high_quality_tier_error_ref.error, "Mixed quality split error should be higher or equal to high quality tier inline"); + + //ACL_ASSERT(low_quality_tier_error0.error >= mixed_quality_tier_error0.error, "Low quality tier split error should be higher or equal to mixed quality split error"); + //ACL_ASSERT(low_quality_tier_error1.error >= mixed_quality_tier_error1.error, "Low quality tier split error should be higher or equal to mixed quality split error"); + } + + // Stream in our medium importance tier, restoring the full high quality + stream_in_database_tier(db_context, db_medium_streamer, db, quality_tier::medium_importance); + + { + const track_error high_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(high_quality_tier_error0.error, high_quality_tier_error_ref.error, threshold), "High quality tier split error should be equal to high quality tier inline"); + const track_error high_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(high_quality_tier_error1.error, high_quality_tier_error_ref.error, threshold), "High quality tier split error should be equal to high quality tier inline"); + } + + // Stream out our low importance tier, restoring medium quality + stream_out_database_tier(db_context, db_low_streamer, db, quality_tier::lowest_importance); + + { + const track_error medium_quality_tier_error0_ = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error0_.error == medium_quality_tier_error0.error, "Medium quality should be restored"); + const track_error medium_quality_tier_error1_ = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error1_.error == medium_quality_tier_error1.error, "Medium quality should be restored"); + } + + // Stream out our medium importance tier, restoring low quality + stream_out_database_tier(db_context, db_medium_streamer, db, quality_tier::medium_importance); + + { + const track_error low_quality_tier_error0_ = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error0_.error == low_quality_tier_error0.error, "Low quality should be restored"); + const track_error low_quality_tier_error1_ = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error1_.error == low_quality_tier_error1.error, "Low quality should be restored"); + } +} + +static void validate_db_stripping(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, const itransform_error_metric& error_metric, + const compressed_tracks& tracks0, const compressed_tracks& tracks1, + const compressed_database& db, const uint8_t* db_bulk_data_medium, const uint8_t* db_bulk_data_low) +{ + compressed_database* db_no_medium = nullptr; + compressed_database* db_no_low = nullptr; + compressed_database* db_neither0 = nullptr; + compressed_database* db_neither1 = nullptr; + + track_error low_quality_tier_error_ref0; + track_error low_quality_tier_error_ref1; + track_error medium_quality_tier_error_ref0; + track_error medium_quality_tier_error_ref1; + track_error mixed_quality_tier_error_ref0; + track_error mixed_quality_tier_error_ref1; + + // Grab our reference values before we strip anything + { + decompression_context context0; + decompression_context context1; + database_context db_context; + debug_database_streamer db_medium_streamer(allocator, db_bulk_data_medium, db.get_bulk_data_size(quality_tier::medium_importance)); + debug_database_streamer db_low_streamer(allocator, db_bulk_data_low, db.get_bulk_data_size(quality_tier::lowest_importance)); + + bool initialized = db_context.initialize(allocator, db, db_medium_streamer, db_low_streamer); + initialized = initialized && context0.initialize(tracks0, db_context); + initialized = initialized && context1.initialize(tracks1, db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + // Nothing is streamed in yet, we have low quality + low_quality_tier_error_ref0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + low_quality_tier_error_ref1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + + // Stream in our medium importance tier + stream_in_database_tier(db_context, db_medium_streamer, db, quality_tier::medium_importance); + + medium_quality_tier_error_ref0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + medium_quality_tier_error_ref1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + + // Stream in our low importance tier, restoring the full high quality + stream_in_database_tier(db_context, db_low_streamer, db, quality_tier::lowest_importance); + + // Stream out our medium importance tier, we'll have mixed quality + stream_out_database_tier(db_context, db_medium_streamer, db, quality_tier::medium_importance); + + mixed_quality_tier_error_ref0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + mixed_quality_tier_error_ref1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + } + + // Strip medium tier + if (db.has_bulk_data(quality_tier::medium_importance)) + { + const error_result result = strip_database_quality_tier(allocator, db, quality_tier::medium_importance, db_no_medium); + ACL_ASSERT(result.empty(), result.c_str()); + + decompression_context context0; + decompression_context context1; + database_context db_context; + debug_database_streamer db_medium_streamer(allocator, db_bulk_data_medium, db_no_medium->get_bulk_data_size(quality_tier::medium_importance)); + debug_database_streamer db_low_streamer(allocator, db_bulk_data_low, db_no_medium->get_bulk_data_size(quality_tier::lowest_importance)); + + bool initialized = db_context.initialize(allocator, *db_no_medium, db_medium_streamer, db_low_streamer); + initialized = initialized && context0.initialize(tracks0, db_context); + initialized = initialized && context1.initialize(tracks1, db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + // Nothing is streamed in yet, we have low quality + { + const track_error low_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Low quality tier stripped error should be equal to low quality tier whole"); + const track_error low_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Low quality tier stripped error should be equal to low quality tier whole"); + } + + // Stream in our medium importance tier + stream_in_database_tier(db_context, db_medium_streamer, *db_no_medium, quality_tier::medium_importance); + + { + const track_error medium_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Medium quality tier stripped error should be equal to low quality tier whole"); + const track_error medium_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Medium quality tier stripped error should be equal to low quality tier whole"); + } + + // Stream in our low importance tier, restoring the full high quality + stream_in_database_tier(db_context, db_low_streamer, *db_no_medium, quality_tier::lowest_importance); + + { + const track_error high_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(high_quality_tier_error0.error == mixed_quality_tier_error_ref0.error, "High quality tier stripped error should be equal to mixed quality tier whole"); + const track_error high_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(high_quality_tier_error1.error == mixed_quality_tier_error_ref1.error, "High quality tier stripped error should be equal to mixed quality tier whole"); + } + + // Stream out our medium importance tier, we'll have mixed quality + stream_out_database_tier(db_context, db_medium_streamer, *db_no_medium, quality_tier::medium_importance); + + { + const track_error mixed_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(mixed_quality_tier_error0.error == mixed_quality_tier_error_ref0.error, "Mixed quality tier stripped error should be equal to mixed quality tier whole"); + const track_error mixed_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(mixed_quality_tier_error1.error == mixed_quality_tier_error_ref1.error, "Mixed quality tier stripped error should be equal to mixed quality tier whole"); + } + } + + // Strip lowest tier + if (db.has_bulk_data(quality_tier::lowest_importance)) + { + const error_result result = strip_database_quality_tier(allocator, db, quality_tier::lowest_importance, db_no_low); + ACL_ASSERT(result.empty(), result.c_str()); + + decompression_context context0; + decompression_context context1; + database_context db_context; + debug_database_streamer db_medium_streamer(allocator, db_bulk_data_medium, db_no_low->get_bulk_data_size(quality_tier::medium_importance)); + debug_database_streamer db_low_streamer(allocator, db_bulk_data_low, db_no_low->get_bulk_data_size(quality_tier::lowest_importance)); + + bool initialized = db_context.initialize(allocator, *db_no_low, db_medium_streamer, db_low_streamer); + initialized = initialized && context0.initialize(tracks0, db_context); + initialized = initialized && context1.initialize(tracks1, db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + // Nothing is streamed in yet, we have low quality + { + const track_error low_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Low quality tier stripped error should be equal to low quality tier whole"); + const track_error low_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Low quality tier stripped error should be equal to low quality tier whole"); + } + + // Stream in our medium importance tier + stream_in_database_tier(db_context, db_medium_streamer, *db_no_low, quality_tier::medium_importance); + + { + const track_error medium_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error0.error == medium_quality_tier_error_ref0.error, "Medium quality tier stripped error should be equal to medium quality tier whole"); + const track_error medium_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error1.error == medium_quality_tier_error_ref1.error, "Medium quality tier stripped error should be equal to medium quality tier whole"); + } + + // Stream in our low importance tier, restoring the full high quality + stream_in_database_tier(db_context, db_low_streamer, *db_no_low, quality_tier::lowest_importance); + + { + const track_error high_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(high_quality_tier_error0.error == medium_quality_tier_error_ref0.error, "High quality tier stripped error should be equal to medium quality tier whole"); + const track_error high_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(high_quality_tier_error1.error == medium_quality_tier_error_ref1.error, "High quality tier stripped error should be equal to medium quality tier whole"); + } + + // Stream out our medium importance tier, we'll have mixed quality + stream_out_database_tier(db_context, db_medium_streamer, *db_no_low, quality_tier::medium_importance); + + { + const track_error mixed_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(mixed_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Mixed quality tier stripped error should be equal to low quality tier whole"); + const track_error mixed_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(mixed_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Mixed quality tier stripped error should be equal to low quality tier whole"); + } + } + + // Strip medium and lowest tiers + if (db.has_bulk_data(quality_tier::medium_importance) && db.has_bulk_data(quality_tier::lowest_importance)) + { + ACL_ASSERT(db_no_medium != nullptr, "Expected a valid database"); + ACL_ASSERT(db_no_low != nullptr, "Expected a valid database"); + + error_result result = strip_database_quality_tier(allocator, *db_no_medium, quality_tier::lowest_importance, db_neither0); + ACL_ASSERT(result.empty(), result.c_str()); + result = strip_database_quality_tier(allocator, *db_no_low, quality_tier::medium_importance, db_neither1); + ACL_ASSERT(result.empty(), result.c_str()); + ACL_ASSERT(db_neither0->get_hash() == db_neither1->get_hash(), "Stripping order should not matter"); + + decompression_context context0; + decompression_context context1; + database_context db_context; + debug_database_streamer db_medium_streamer(allocator, db_bulk_data_medium, db_neither0->get_bulk_data_size(quality_tier::medium_importance)); + debug_database_streamer db_low_streamer(allocator, db_bulk_data_low, db_neither0->get_bulk_data_size(quality_tier::lowest_importance)); + + bool initialized = db_context.initialize(allocator, *db_neither0, db_medium_streamer, db_low_streamer); + initialized = initialized && context0.initialize(tracks0, db_context); + initialized = initialized && context1.initialize(tracks1, db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + // Nothing is streamed in yet, we have low quality + { + const track_error low_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Low quality tier stripped error should be equal to low quality tier whole"); + const track_error low_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(low_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Low quality tier stripped error should be equal to low quality tier whole"); + } + + // Stream in our medium importance tier + stream_in_database_tier(db_context, db_medium_streamer, *db_neither0, quality_tier::medium_importance); + + { + const track_error medium_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Medium quality tier stripped error should be equal to low quality tier whole"); + const track_error medium_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(medium_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Medium quality tier stripped error should be equal to low quality tier whole"); + } + + // Stream in our low importance tier, restoring the full high quality + stream_in_database_tier(db_context, db_low_streamer, *db_neither0, quality_tier::lowest_importance); + + { + const track_error high_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(high_quality_tier_error0.error == low_quality_tier_error_ref0.error, "High quality tier stripped error should be equal to low quality tier whole"); + const track_error high_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(high_quality_tier_error1.error == low_quality_tier_error_ref1.error, "High quality tier stripped error should be equal to low quality tier whole"); + } + + // Stream out our medium importance tier, we'll have mixed quality + stream_out_database_tier(db_context, db_medium_streamer, *db_neither0, quality_tier::medium_importance); + + { + const track_error mixed_quality_tier_error0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(mixed_quality_tier_error0.error == low_quality_tier_error_ref0.error, "Mixed quality tier stripped error should be equal to low quality tier whole"); + const track_error mixed_quality_tier_error1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(mixed_quality_tier_error1.error == low_quality_tier_error_ref1.error, "Mixed quality tier stripped error should be equal to low quality tier whole"); + } + } + + if (db_no_medium != nullptr) + allocator.deallocate(db_no_medium, db_no_medium->get_size()); + if (db_no_low != nullptr) + allocator.deallocate(db_no_low, db_no_low->get_size()); + if (db_neither0 != nullptr) + allocator.deallocate(db_neither0, db_neither0->get_size()); + if (db_neither1 != nullptr) + allocator.deallocate(db_neither1, db_neither1->get_size()); +} + +static bool has_zero_scale(const track_array_qvvf& raw_tracks) +{ + const float threshold = 1.0E-6F; + const rtm::vector4f zero = rtm::vector_zero(); + const uint32_t num_samples_per_track = raw_tracks.get_num_samples_per_track(); + + for (const track_qvvf& raw_track : raw_tracks) + { + for (uint32_t sample_index = 0; sample_index < num_samples_per_track; ++sample_index) + { + const rtm::qvvf& sample = raw_track[sample_index]; + if (rtm::vector_any_near_equal3(sample.scale, zero, threshold)) + return true; + } + } + + return false; +} + +void validate_db(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, + const compression_database_settings& settings, const itransform_error_metric& error_metric, + const compressed_tracks& compressed_tracks0, const compressed_tracks& compressed_tracks1) +{ + using namespace acl_impl; + + // We do not support building empty databases + if (compressed_tracks0.get_num_tracks() == 0 || compressed_tracks0.get_num_samples_per_track() == 0) + return; + + // When we have a database, we are forced to linearly interpolate our keys which means + // we can end up with slightly different values than we otherwise would. + // When this happens, zero scale ends up being non-zero and the results can end up vastly different. + // Don't validate our database if we have zero scale. + if (has_zero_scale(raw_tracks)) + return; + + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + // Our desired error threshold + // When intrinsics aren't used with x86, the floating point arithmetic falls back to + // using x87 instructions. When this happens, depending on how code is generated some + // small inaccuracies can pop up because rounding happens when we store to memory. + // The full pose decompression stores samples into the stack while working with them + // while the single track decompression does not which causes the issue. + // With SSE2 and NEON, there are no such rounding issues. +#if !defined(RTM_SSE2_INTRINSICS) && defined(RTM_ARCH_X86) + const float threshold = 1.0E-3F; +#else + const float threshold = 1.0E-4F; +#endif + + // Build our databases + const compressed_tracks* input_tracks[2] = { &compressed_tracks0, &compressed_tracks1 }; + + compressed_tracks* db_tracks0[1] = { nullptr }; + compressed_tracks* db_tracks1[1] = { nullptr }; + compressed_tracks* db_tracks01[2] = { nullptr, nullptr }; + compressed_database* db0 = nullptr; + compressed_database* db1 = nullptr; + compressed_database* db01 = nullptr; + + { + error_result db_result = build_database(allocator, settings, &input_tracks[0], 1, db_tracks0, db0); + ACL_ASSERT(db_result.empty(), db_result.c_str()); + + db_result = build_database(allocator, settings, &input_tracks[1], 1, db_tracks1, db1); + ACL_ASSERT(db_result.empty(), db_result.c_str()); + + db_result = build_database(allocator, settings, &input_tracks[0], 2, db_tracks01, db01); + ACL_ASSERT(db_result.empty(), db_result.c_str()); + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C6011: Dereferencing NULL pointer '...'. + // Crashing is fine since this is used for regression testing + #pragma warning(disable : 6011) +#endif + + ACL_ASSERT(db0->contains(*db_tracks0[0]), "Database should contain our clip"); + ACL_ASSERT(db1->contains(*db_tracks1[0]), "Database should contain our clip"); + ACL_ASSERT(db01->contains(*db_tracks01[0]), "Database should contain our clip"); + ACL_ASSERT(db01->contains(*db_tracks01[1]), "Database should contain our clip"); + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + } + + // Reference error without the database with everything highest quality + track_error high_quality_tier_error_ref; + { + acl::decompression_context context; + + const bool initialized = context.initialize(compressed_tracks0); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + high_quality_tier_error_ref = calculate_compression_error(allocator, raw_tracks, context, error_metric, additive_base_tracks); + } + + // Make sure the databases agree with our reference + { + acl::decompression_context context; + acl::database_context db_context; + + bool initialized = db_context.initialize(allocator, *db0); + initialized = initialized && context.initialize(*db_tracks0[0], db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + const track_error error_tier0 = calculate_compression_error(allocator, raw_tracks, context, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(error_tier0.error, high_quality_tier_error_ref.error, threshold), "Database 0 should have the same error"); + } + + { + acl::decompression_context context; + acl::database_context db_context; + + bool initialized = db_context.initialize(allocator, *db1); + initialized = initialized && context.initialize(*db_tracks1[0], db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + const track_error error_tier1 = calculate_compression_error(allocator, raw_tracks, context, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(error_tier1.error, high_quality_tier_error_ref.error, threshold), "Database 1 should have the same error"); + } + + { + acl::decompression_context context0; + acl::decompression_context context1; + acl::database_context db_context; + + bool initialized = db_context.initialize(allocator, *db01); + initialized = initialized && context0.initialize(*db_tracks01[0], db_context); + initialized = initialized && context1.initialize(*db_tracks01[1], db_context); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); + + const track_error error_tier0 = calculate_compression_error(allocator, raw_tracks, context0, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(error_tier0.error, high_quality_tier_error_ref.error, threshold), "Database 01 should have the same error"); + + const track_error error_tier1 = calculate_compression_error(allocator, raw_tracks, context1, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_near_equal(error_tier1.error, high_quality_tier_error_ref.error, threshold), "Database 01 should have the same error"); + } + + // Measure the tier error when stripping + validate_db_stripping(allocator, raw_tracks, additive_base_tracks, error_metric, *db_tracks01[0], *db_tracks01[1], *db01, db01->get_bulk_data(quality_tier::medium_importance), db01->get_bulk_data(quality_tier::lowest_importance)); + + // Split the database bulk data out + compressed_database* split_db = nullptr; + uint8_t* split_db_bulk_data_medium = nullptr; + uint8_t* split_db_bulk_data_low = nullptr; + const error_result split_result = split_database_bulk_data(allocator, *db01, split_db, split_db_bulk_data_medium, split_db_bulk_data_low); + ACL_ASSERT(split_result.empty(), "Failed to split database"); + ACL_ASSERT(split_db->is_valid(true).empty(), "Failed to split database"); + + ACL_ASSERT(split_db->contains(*db_tracks01[0]), "Database should contain our clip"); + ACL_ASSERT(split_db->contains(*db_tracks01[1]), "Database should contain our clip"); + + // Measure the tier error through simulated streaming + validate_db_streaming(allocator, raw_tracks, additive_base_tracks, error_metric, high_quality_tier_error_ref, *db_tracks01[0], *db_tracks01[1], *split_db, split_db_bulk_data_medium, split_db_bulk_data_low); + + // Measure the tier error when stripping + validate_db_stripping(allocator, raw_tracks, additive_base_tracks, error_metric, *db_tracks01[0], *db_tracks01[1], *split_db, split_db_bulk_data_medium, split_db_bulk_data_low); + + // Duplicate our clips so we can modify them + compressed_tracks* compressed_tracks_copy0 = safe_ptr_cast(allocate_type_array_aligned(allocator, db_tracks0[0]->get_size(), alignof(compressed_tracks))); + compressed_tracks* compressed_tracks_copy1 = safe_ptr_cast(allocate_type_array_aligned(allocator, db_tracks1[0]->get_size(), alignof(compressed_tracks))); + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(push) + // warning C6011: Dereferencing NULL pointer '...'. + // Crashing is fine since this is used for regression testing + #pragma warning(disable : 6011) + // warning C6387: '...' could be '0': this does not adhere to the specification for the function 'memcpy'. See line 523 for an earlier location where this can occur + // Pointers will never be null and if they are, we'll crash which is fine in a regression test + #pragma warning(disable : 6387) +#endif + + std::memcpy(reinterpret_cast(compressed_tracks_copy0), db_tracks0[0], db_tracks0[0]->get_size()); + std::memcpy(reinterpret_cast(compressed_tracks_copy1), db_tracks1[0], db_tracks1[0]->get_size()); + +#if defined(RTM_COMPILER_MSVC) + #pragma warning(pop) +#endif + + // Free our memory + allocator.deallocate(split_db_bulk_data_medium, split_db->get_bulk_data_size(quality_tier::medium_importance)); + allocator.deallocate(split_db_bulk_data_low, split_db->get_bulk_data_size(quality_tier::lowest_importance)); + allocator.deallocate(split_db, split_db->get_size()); + allocator.deallocate(compressed_tracks_copy0, compressed_tracks_copy0->get_size()); + allocator.deallocate(compressed_tracks_copy1, compressed_tracks_copy1->get_size()); + allocator.deallocate(db_tracks0[0], db_tracks0[0]->get_size()); + allocator.deallocate(db_tracks1[0], db_tracks1[0]->get_size()); + allocator.deallocate(db_tracks01[0], db_tracks01[0]->get_size()); + allocator.deallocate(db_tracks01[1], db_tracks01[1]->get_size()); + allocator.deallocate(db0, db0->get_size()); + allocator.deallocate(db1, db1->get_size()); + allocator.deallocate(db01, db01->get_size()); +} +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/validate_tracks.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/validate_tracks.cpp new file mode 100644 index 00000000..afc7ab9f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_compressor/sources/validate_tracks.cpp @@ -0,0 +1,523 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2021 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "acl_compressor.h" + +#include "acl/core/compressed_tracks.h" +#include "acl/core/floating_point_exceptions.h" +#include "acl/core/iallocator.h" +#include "acl/compression/compress.h" +#include "acl/compression/convert.h" +#include "acl/compression/track_array.h" +#include "acl/compression/track_error.h" +#include "acl/compression/transform_error_metrics.h" +#include "acl/decompression/decompress.h" + +using namespace acl; + +#if defined(ACL_USE_SJSON) && defined(ACL_HAS_ASSERT_CHECKS) +void validate_accuracy(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, const itransform_error_metric& error_metric, const compressed_tracks& compressed_tracks_, double regression_error_threshold) +{ + using namespace acl_impl; + + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + // When intrinsics aren't used with x86, the floating point arithmetic falls back to + // using x87 instructions. When this happens, depending on how code is generated some + // small inaccuracies can pop up because rounding happens when we store to memory. + // The full pose decompression stores samples into the stack while working with them + // while the single track decompression does not which causes the issue. + // With SSE2 and NEON, there are no such rounding issues. +#if !defined(RTM_SSE2_INTRINSICS) && defined(RTM_ARCH_X86) + const float quat_error_threshold = 0.001F; + const float vec3_error_threshold = 0.0001F; +#else + const float quat_error_threshold = 0.0001F; + const float vec3_error_threshold = 0.0F; +#endif + + acl::decompression_context context; + + const bool initialized = context.initialize(compressed_tracks_); + ACL_ASSERT(initialized, "Failed to initialize decompression context"); (void)initialized; + + const track_error error = calculate_compression_error(allocator, raw_tracks, context, error_metric, additive_base_tracks); + ACL_ASSERT(rtm::scalar_is_finite(error.error), "Returned error is not a finite value"); (void)error; + ACL_ASSERT(error.error < regression_error_threshold, "Error too high for bone %u: %f at time %f", error.index, error.error, error.sample_time); + + const uint32_t num_bones = raw_tracks.get_num_tracks(); + const float clip_duration = raw_tracks.get_duration(); + const float sample_rate = raw_tracks.get_sample_rate(); + const uint32_t num_samples = raw_tracks.get_num_samples_per_track(); + + debug_track_writer track_writer(allocator, track_type8::qvvf, num_bones); + + { + // Try to decompress something at 0.0, if we have no tracks or samples, it should be handled + context.seek(0.0F, sample_rounding_policy::nearest); + context.decompress_tracks(track_writer); + } + + // Regression test + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, clip_duration); + + // We use the nearest sample to accurately measure the loss that happened, if any + context.seek(sample_time, sample_rounding_policy::nearest); + context.decompress_tracks(track_writer); + + // Validate decompress_track against decompress_tracks + for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) + { + const rtm::qvvf transform0 = track_writer.read_qvv(bone_index); + + context.decompress_track(bone_index, track_writer); + const rtm::qvvf transform1 = track_writer.read_qvv(bone_index); + + // Rotations can differ a bit due to how we normalize during interpolation + ACL_ASSERT(rtm::vector_all_near_equal(rtm::quat_to_vector(transform0.rotation), rtm::quat_to_vector(transform1.rotation), quat_error_threshold), "Failed to sample bone index: %u", bone_index); + ACL_ASSERT(rtm::vector_all_near_equal3(transform0.translation, transform1.translation, vec3_error_threshold), "Failed to sample bone index: %u", bone_index); + ACL_ASSERT(rtm::vector_all_near_equal3(transform0.scale, transform1.scale, vec3_error_threshold), "Failed to sample bone index: %u", bone_index); + } + } +} + +void validate_accuracy(iallocator& allocator, const track_array& raw_tracks, const compressed_tracks& tracks, double regression_error_threshold) +{ + using namespace acl_impl; + + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + const float regression_error_thresholdf = static_cast(regression_error_threshold); + const rtm::vector4f regression_error_thresholdv = rtm::vector_set(regression_error_thresholdf); + (void)regression_error_thresholdf; + (void)regression_error_thresholdv; + + const float duration = tracks.get_duration(); + const float sample_rate = tracks.get_sample_rate(); + const uint32_t num_tracks = tracks.get_num_tracks(); + const uint32_t num_samples = tracks.get_num_samples_per_track(); + const track_type8 track_type = raw_tracks.get_track_type(); + + ACL_ASSERT(rtm::scalar_near_equal(duration, raw_tracks.get_duration(), 1.0E-7F), "Duration mismatch"); + ACL_ASSERT(sample_rate == raw_tracks.get_sample_rate(), "Sample rate mismatch"); + ACL_ASSERT(num_tracks <= raw_tracks.get_num_tracks(), "Num tracks mismatch"); + ACL_ASSERT(num_samples == raw_tracks.get_num_samples_per_track(), "Num samples mismatch"); + + decompression_context context; + context.initialize(tracks); + + debug_track_writer raw_tracks_writer(allocator, track_type, num_tracks); + debug_track_writer raw_track_writer(allocator, track_type, num_tracks); + debug_track_writer lossy_tracks_writer(allocator, track_type, num_tracks); + debug_track_writer lossy_track_writer(allocator, track_type, num_tracks); + + const rtm::vector4f zero = rtm::vector_zero(); + + { + // Try to decompress something at 0.0, if we have no tracks or samples, it should be handled + context.seek(0.0F, sample_rounding_policy::nearest); + context.decompress_tracks(lossy_tracks_writer); + } + + // Regression test + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration); + + // We use the nearest sample to accurately measure the loss that happened, if any + raw_tracks.sample_tracks(sample_time, sample_rounding_policy::nearest, raw_tracks_writer); + + context.seek(sample_time, sample_rounding_policy::nearest); + context.decompress_tracks(lossy_tracks_writer); + + // Validate decompress_tracks + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& track_ = raw_tracks[track_index]; + const uint32_t output_index = track_.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Track is being stripped, ignore it + + rtm::vector4f error = zero; + + switch (track_type) + { + case track_type8::float1f: + { + const float raw_value = raw_tracks_writer.read_float1(track_index); + const float lossy_value = lossy_tracks_writer.read_float1(output_index); + error = rtm::vector_set(rtm::scalar_abs(raw_value - lossy_value)); + break; + } + case track_type8::float2f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_float2(track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_float2(output_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + error = rtm::vector_mix(error, zero); + break; + } + case track_type8::float3f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_float3(track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_float3(output_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + error = rtm::vector_mix(error, zero); + break; + } + case track_type8::float4f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_float4(track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_float4(output_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + break; + } + case track_type8::vector4f: + { + const rtm::vector4f raw_value = raw_tracks_writer.read_vector4(track_index); + const rtm::vector4f lossy_value = lossy_tracks_writer.read_vector4(output_index); + error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value)); + break; + } + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + + (void)error; + ACL_ASSERT(rtm::vector_is_finite(error), "Returned error is not a finite value"); + ACL_ASSERT(rtm::vector_all_less_than(error, regression_error_thresholdv), "Error too high for track %u at time %f", track_index, sample_time); + } + + // Validate decompress_track + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& track_ = raw_tracks[track_index]; + const uint32_t output_index = track_.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Track is being stripped, ignore it + + // We use the nearest sample to accurately measure the loss that happened, if any + raw_tracks.sample_track(track_index, sample_time, sample_rounding_policy::nearest, raw_track_writer); + context.decompress_track(output_index, lossy_track_writer); + + switch (track_type) + { + case track_type8::float1f: + { + const float raw_value_ = raw_tracks_writer.read_float1(track_index); + const float lossy_value_ = lossy_tracks_writer.read_float1(output_index); + const float raw_value = raw_track_writer.read_float1(track_index); + const float lossy_value = lossy_track_writer.read_float1(output_index); + ACL_ASSERT(rtm::scalar_near_equal(raw_value, lossy_value, regression_error_thresholdf), "Error too high for track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::scalar_near_equal(raw_value_, raw_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::scalar_near_equal(lossy_value_, lossy_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + break; + } + case track_type8::float2f: + { + const rtm::vector4f raw_value_ = raw_tracks_writer.read_float2(track_index); + const rtm::vector4f lossy_value_ = lossy_tracks_writer.read_float2(output_index); + const rtm::vector4f raw_value = raw_track_writer.read_float2(track_index); + const rtm::vector4f lossy_value = lossy_track_writer.read_float2(output_index); + ACL_ASSERT(rtm::vector_all_near_equal2(raw_value, lossy_value, regression_error_thresholdf), "Error too high for track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal2(raw_value_, raw_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal2(lossy_value_, lossy_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + break; + } + case track_type8::float3f: + { + const rtm::vector4f raw_value_ = raw_tracks_writer.read_float3(track_index); + const rtm::vector4f lossy_value_ = lossy_tracks_writer.read_float3(output_index); + const rtm::vector4f raw_value = raw_track_writer.read_float3(track_index); + const rtm::vector4f lossy_value = lossy_track_writer.read_float3(output_index); + ACL_ASSERT(rtm::vector_all_near_equal3(raw_value, lossy_value, regression_error_thresholdf), "Error too high for track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal3(raw_value_, raw_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal3(lossy_value_, lossy_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + break; + } + case track_type8::float4f: + { + const rtm::vector4f raw_value_ = raw_tracks_writer.read_float4(track_index); + const rtm::vector4f lossy_value_ = lossy_tracks_writer.read_float4(output_index); + const rtm::vector4f raw_value = raw_track_writer.read_float4(track_index); + const rtm::vector4f lossy_value = lossy_track_writer.read_float4(output_index); + ACL_ASSERT(rtm::vector_all_near_equal(raw_value, lossy_value, regression_error_thresholdf), "Error too high for track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal(raw_value_, raw_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal(lossy_value_, lossy_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + break; + } + case track_type8::vector4f: + { + const rtm::vector4f raw_value_ = raw_tracks_writer.read_vector4(track_index); + const rtm::vector4f lossy_value_ = lossy_tracks_writer.read_vector4(output_index); + const rtm::vector4f raw_value = raw_track_writer.read_vector4(track_index); + const rtm::vector4f lossy_value = lossy_track_writer.read_vector4(output_index); + ACL_ASSERT(rtm::vector_all_near_equal(raw_value, lossy_value, regression_error_thresholdf), "Error too high for track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal(raw_value_, raw_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + ACL_ASSERT(rtm::vector_all_near_equal(lossy_value_, lossy_value, 0.00001F), "Failed to sample track %u at time %f", track_index, sample_time); + break; + } + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + } + } +} + +void validate_metadata(const track_array& raw_tracks, const compressed_tracks& tracks) +{ + const uint32_t num_tracks = raw_tracks.get_num_tracks(); + + // Validate track list name + const string& raw_list_name = raw_tracks.get_name(); + const char* compressed_list_name = tracks.get_name(); + ACL_ASSERT(raw_list_name == compressed_list_name, "Unexpected track list name"); + + // Validate track names + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& raw_track = raw_tracks[track_index]; + const uint32_t output_index = raw_track.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Stripped + + const string& raw_name = raw_track.get_name(); + const char* compressed_name = tracks.get_track_name(output_index); + ACL_ASSERT(raw_name == compressed_name, "Unexpected track name"); + } + + if (raw_tracks.get_track_type() == track_type8::qvvf) + { + // Specific to transform tracks + const track_array_qvvf& transform_tracks = track_array_cast(raw_tracks); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track_qvvf& raw_track = transform_tracks[track_index]; + const uint32_t output_index = raw_track.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Stripped + + const track_desc_transformf& raw_desc = raw_track.get_description(); + const uint32_t parent_track_index = raw_desc.parent_index; + const uint32_t parent_track_output_index = parent_track_index != k_invalid_track_index ? transform_tracks[parent_track_index].get_output_index() : k_invalid_track_index; + + const uint32_t compressed_parent_track_index = tracks.get_parent_track_index(output_index); + ACL_ASSERT(parent_track_output_index == compressed_parent_track_index, "Unexpected parent track index"); + + track_desc_transformf compressed_desc; + const bool compressed_track_desc_found = tracks.get_track_description(output_index, compressed_desc); + ACL_ASSERT(compressed_track_desc_found, "Expected track description"); + ACL_ASSERT(output_index == compressed_desc.output_index, "Unexpected output index"); + ACL_ASSERT(parent_track_output_index == compressed_desc.parent_index, "Unexpected parent track index"); + ACL_ASSERT(raw_desc.precision == compressed_desc.precision, "Unexpected precision"); + ACL_ASSERT(raw_desc.shell_distance == compressed_desc.shell_distance, "Unexpected shell_distance"); + ACL_ASSERT(raw_desc.constant_rotation_threshold_angle == compressed_desc.constant_rotation_threshold_angle, "Unexpected constant_rotation_threshold_angle"); + ACL_ASSERT(raw_desc.constant_translation_threshold == compressed_desc.constant_translation_threshold, "Unexpected constant_translation_threshold"); + ACL_ASSERT(raw_desc.constant_scale_threshold == compressed_desc.constant_scale_threshold, "Unexpected constant_scale_threshold"); + } + } + else + { + // Specific to scalar tracks + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& raw_track = raw_tracks[track_index]; + const uint32_t output_index = raw_track.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Stripped + + const track_desc_scalarf& raw_desc = raw_track.get_description(); + + track_desc_scalarf compressed_desc; + const bool compressed_track_desc_found = tracks.get_track_description(output_index, compressed_desc); + ACL_ASSERT(compressed_track_desc_found, "Expected track description"); + ACL_ASSERT(output_index == compressed_desc.output_index, "Unexpected output index"); + ACL_ASSERT(raw_desc.precision == compressed_desc.precision, "Unexpected precision"); + } + } +} + +static void compare_raw_with_compressed(iallocator& allocator, const track_array& raw_tracks, const compressed_tracks& compressed_tracks_) +{ + const uint32_t num_tracks = raw_tracks.get_num_tracks(); + + uint32_t num_output_tracks = 0; + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& raw_track = raw_tracks[track_index]; + const uint32_t output_index = raw_track.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Stripped + + num_output_tracks++; + } + + ACL_ASSERT(num_output_tracks == compressed_tracks_.get_num_tracks(), "Unexpected num tracks"); + ACL_ASSERT(raw_tracks.get_num_samples_per_track() == compressed_tracks_.get_num_samples_per_track(), "Unexpected num samples"); + ACL_ASSERT(raw_tracks.get_sample_rate() == compressed_tracks_.get_sample_rate(), "Unexpected sample rate"); + ACL_ASSERT(raw_tracks.get_track_type() == compressed_tracks_.get_track_type(), "Unexpected track type"); + ACL_ASSERT(raw_tracks.get_name() == compressed_tracks_.get_name(), "Unexpected track list name"); + + const track_category8 track_category = raw_tracks.get_track_category(); + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& raw_track = raw_tracks[track_index]; + const uint32_t output_index = raw_track.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Stripped + + if (track_category == track_category8::scalarf) + { + const track_desc_scalarf& raw_desc = raw_track.get_description(); + track_desc_scalarf desc; + compressed_tracks_.get_track_description(output_index, desc); + + ACL_ASSERT(raw_desc.precision == desc.precision, "Unexpected precision"); + } + else + { + const track_desc_transformf& raw_desc = raw_track.get_description(); + track_desc_transformf desc; + compressed_tracks_.get_track_description(output_index, desc); + + ACL_ASSERT(raw_desc.parent_index == desc.parent_index, "Unexpected parent index"); + ACL_ASSERT(raw_desc.precision == desc.precision, "Unexpected precision"); + ACL_ASSERT(raw_desc.shell_distance == desc.shell_distance, "Unexpected shell_distance"); + ACL_ASSERT(raw_desc.constant_rotation_threshold_angle == desc.constant_rotation_threshold_angle, "Unexpected constant_rotation_threshold_angle"); + ACL_ASSERT(raw_desc.constant_translation_threshold == desc.constant_translation_threshold, "Unexpected constant_translation_threshold"); + ACL_ASSERT(raw_desc.constant_scale_threshold == desc.constant_scale_threshold, "Unexpected constant_scale_threshold"); + } + } + + // Disable floating point exceptions since decompression assumes it + scope_disable_fp_exceptions fp_off; + + acl::decompression_context context; + context.initialize(compressed_tracks_); + + const track_type8 track_type = raw_tracks.get_track_type(); + acl_impl::debug_track_writer writer(allocator, track_type, num_tracks); + + const uint32_t num_samples = raw_tracks.get_num_samples_per_track(); + const float sample_rate = raw_tracks.get_sample_rate(); + const float duration = raw_tracks.get_duration(); + + for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index) + { + const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration); + + // Round to nearest to land directly on a sample + context.seek(sample_time, sample_rounding_policy::nearest); + + context.decompress_tracks(writer); + + for (uint32_t track_index = 0; track_index < num_tracks; ++track_index) + { + const track& raw_track = raw_tracks[track_index]; + const uint32_t output_index = raw_track.get_output_index(); + if (output_index == k_invalid_track_index) + continue; // Track is stripped + + switch (track_type) + { + case track_type8::float1f: + { + const float raw_sample = *reinterpret_cast(raw_track[sample_index]); + const float compressed_sample = writer.read_float1(track_index); + ACL_ASSERT(raw_sample == compressed_sample, "Unexpected sample"); + break; + } + case track_type8::float2f: + { + const rtm::vector4f raw_sample = rtm::vector_load2(reinterpret_cast(raw_track[sample_index])); + const rtm::vector4f compressed_sample = writer.read_float2(track_index); + ACL_ASSERT(rtm::vector_all_near_equal2(raw_sample, compressed_sample, 0.0F), "Unexpected sample"); + break; + } + case track_type8::float3f: + { + const rtm::vector4f raw_sample = rtm::vector_load3(reinterpret_cast(raw_track[sample_index])); + const rtm::vector4f compressed_sample = writer.read_float3(track_index); + ACL_ASSERT(rtm::vector_all_near_equal3(raw_sample, compressed_sample, 0.0F), "Unexpected sample"); + break; + } + case track_type8::float4f: + { + const rtm::vector4f raw_sample = rtm::vector_load(reinterpret_cast(raw_track[sample_index])); + const rtm::vector4f compressed_sample = writer.read_float4(track_index); + ACL_ASSERT(rtm::vector_all_near_equal(raw_sample, compressed_sample, 0.0F), "Unexpected sample"); + break; + } + case track_type8::vector4f: + { + const rtm::vector4f raw_sample = *reinterpret_cast(raw_track[sample_index]); + const rtm::vector4f compressed_sample = writer.read_vector4(track_index); + ACL_ASSERT(rtm::vector_all_near_equal(raw_sample, compressed_sample, 0.0F), "Unexpected sample"); + break; + } + case track_type8::qvvf: + { + const rtm::qvvf raw_sample = *reinterpret_cast(raw_track[sample_index]); + const rtm::qvvf compressed_sample = writer.read_qvv(track_index); + + // Rotations can differ a bit due to how we normalize during interpolation + ACL_ASSERT(rtm::quat_near_equal(raw_sample.rotation, compressed_sample.rotation, 0.0001F), "Unexpected sample"); + ACL_ASSERT(rtm::vector_all_near_equal3(raw_sample.translation, compressed_sample.translation, 0.0F), "Unexpected sample"); + ACL_ASSERT(rtm::vector_all_near_equal3(raw_sample.scale, compressed_sample.scale, 0.0F), "Unexpected sample"); + break; + } + default: + ACL_ASSERT(false, "Unsupported track type"); + break; + } + } + } +} + +void validate_convert(iallocator& allocator, const track_array& raw_tracks) +{ + compressed_tracks* compressed_tracks_ = nullptr; + error_result result = convert_track_list(allocator, raw_tracks, compressed_tracks_); + ACL_ASSERT(result.empty() && compressed_tracks_ != nullptr, "Convert failed"); + + compare_raw_with_compressed(allocator, raw_tracks, *compressed_tracks_); + + track_array new_raw_tracks; + result = convert_track_list(allocator, *compressed_tracks_, new_raw_tracks); + + const bool is_input_empty = compressed_tracks_->get_num_tracks() == 0; + ACL_ASSERT(result.empty() && new_raw_tracks.is_empty() == is_input_empty, "Convert failed"); + + compare_raw_with_compressed(allocator, new_raw_tracks, *compressed_tracks_); + + allocator.deallocate(compressed_tracks_, compressed_tracks_->get_size()); +} +#endif diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/CMakeLists.txt new file mode 100644 index 00000000..5339134d --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_decompressor_root NONE) + +if(PLATFORM_ANDROID) + if(USE_SJSON) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_android") + endif() +elseif(PLATFORM_IOS) + if(USE_SJSON) + add_subdirectory("${PROJECT_SOURCE_DIR}/main_ios") + endif() +else() + add_subdirectory("${PROJECT_SOURCE_DIR}/main_generic") +endif() diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/README.md new file mode 100644 index 00000000..cd8b9283 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/README.md @@ -0,0 +1,14 @@ +# acl_decompressor in a nutshell + +This tool serves two purposes: + +* Approximate a real game engine integration in order to reliably measure decompression performance +* Extract decompression statistics + +The various command line arguments to the python script and the executable allow the user to control this behavior. + +## Decompression statistics + +When generating the [graphs](../../docs/graph_generation.md), a python script is used in order to run the decompression over a small dataset and aggregate the results into CSV files as well as the standard output. + +Use `python acl_decompressor.py -help` in order to get a description of the supported script arguments. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/compare.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/compare.py new file mode 100644 index 00000000..22d61f08 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/compare.py @@ -0,0 +1,13 @@ +import os +import subprocess +import sys +import time + +if __name__ == "__main__": + current_dir = os.getcwd() + google_benchmark_dir = os.path.join(current_dir, '..', '..', 'external', 'benchmark', 'tools') + google_benchmark_compare_script = os.path.join(google_benchmark_dir, 'compare.py') + + compare_cmd = ['python', google_benchmark_compare_script, 'benchmarks'] + sys.argv[1:] + + subprocess.Popen(compare_cmd, stdout=sys.stdout, stderr=sys.stderr) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/CMakeLists.txt new file mode 100644 index 00000000..aef34e35 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_decompressor_gradle_shim NONE) + +# Set our project root since our gradle files used to build live in the binary output directory +# but the actual source files live in the source/current directory. +file(RELATIVE_PATH ACL_PROJECT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/app ${PROJECT_SOURCE_DIR}/app) + +# Setup our state +set(ACL_ANDROID_PROJECT_NAME "ACL Decompressor") +set(ACL_ANDROID_PROJECT_PACKAGE_NAME "com.acl.decompressor") + +# Configure gradle for our build configuration +configure_file(${ACL_ANDROID_MISC_DIR}/app/build.gradle.in app/build.gradle @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/app/src/main/AndroidManifest.xml.in app/src/main/AndroidManifest.xml @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/app/src/main/res/values/strings.xml.in app/src/main/res/values/strings.xml @ONLY) +configure_file(${ACL_ANDROID_MISC_DIR}/settings.gradle.in settings.gradle @ONLY) + +# Copy gradle related files +file(COPY + ${ACL_ANDROID_MISC_DIR}/build.gradle + ${ACL_ANDROID_MISC_DIR}/gradle.properties + ${ACL_ANDROID_MISC_DIR}/gradle + ${ACL_ANDROID_MISC_DIR}/gradlew + ${ACL_ANDROID_MISC_DIR}/gradlew.bat + DESTINATION .) + +file(COPY + ${ACL_ANDROID_MISC_DIR}/app/proguard-rules.pro + DESTINATION app) + +# Copy our decompression clip data +if(EXISTS ${DECOMP_DATA_DIR}) + file(GLOB_RECURSE ALL_DECOMP_DATA_FILES LIST_DIRECTORIES false ${DECOMP_DATA_DIR}/*.*) + file(COPY ${ALL_DECOMP_DATA_FILES} DESTINATION app/src/main/assets) +else() + message(WARNING "Decompression data directory not found") +endif() + +add_custom_target(${PROJECT_NAME} ALL + COMMAND gradlew.bat + # Decide whether we should build Debug or Release + $<$:assembleDebug> + $<$:assembleRelease>) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/cpp/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..8f0c3d43 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.2) +project(acl_decompressor CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Project root is \tools\decompressor\main_android +set(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") + +# Google Benchmark +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "No need to run benchmark's tests" FORCE) +add_subdirectory("${PROJECT_ROOT_DIR}/../../../external/benchmark" google_benchmark) + +include_directories("${PROJECT_ROOT_DIR}/../../../includes") +include_directories("${PROJECT_ROOT_DIR}/../../../external/benchmark/include") +include_directories("${PROJECT_ROOT_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_ROOT_DIR}/../../../external/sjson-cpp/includes") +include_directories("${PROJECT_ROOT_DIR}/../sources") + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_ROOT_DIR}/../sources/*.h + ${PROJECT_ROOT_DIR}/../sources/*.cpp) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +add_library(${PROJECT_NAME} SHARED ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +# Enable exceptions +target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions) + +# Enable debug symbols +target_compile_options(${PROJECT_NAME} PRIVATE -g) + +# Disable allocation tracking +add_definitions(-DACL_NO_ALLOCATOR_TRACKING) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +# Enable SJSON +add_definitions(-DACL_USE_SJSON) + +target_include_directories(${PROJECT_NAME} PUBLIC jni) + +# Link system libraries +target_link_libraries(${PROJECT_NAME} m log android) + +# Link Google Benchmark +target_link_libraries(${PROJECT_NAME} benchmark) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/cpp/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/cpp/main.cpp new file mode 100644 index 00000000..2fd94005 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/cpp/main.cpp @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static int load_file(AAssetManager* asset_manager, const char* filename, void*& out_buffer, size_t& out_buffer_size) +{ + AAsset* asset = AAssetManager_open(asset_manager, filename, AASSET_MODE_UNKNOWN); + if (asset == nullptr) + { + __android_log_print(ANDROID_LOG_ERROR, "acl", "%s not found", filename); + return -1; + } + + off_t size = AAsset_getLength(asset); + out_buffer = (char*)malloc(sizeof(char) * size); + out_buffer_size = sizeof(char) * size; + AAsset_read(asset, out_buffer, size); + + AAsset_close(asset); + + return 0; +} + +static int read_metadata(AAssetManager* asset_manager, std::string& out_clip_dir, std::vector& out_clips) +{ + void* buffer; + size_t buffer_size; + int result = load_file(asset_manager, "metadata.sjson", buffer, buffer_size); + if (result != 0) + return result; + + if (!parse_metadata((const char*)buffer, buffer_size, out_clip_dir, out_clips)) + { + __android_log_print(ANDROID_LOG_ERROR, "acl", "Failed to parse metadata"); + result = -1; + } + + free(buffer); + return result; +} + +// Inspired from https://stackoverflow.com/questions/8870174/is-stdcout-usable-in-android-ndk +class androidbuf final : public std::streambuf +{ +public: + enum { bufsize = 4096 }; + androidbuf() + { + this->setp(buffer, buffer + bufsize - 1); + } + +private: + int overflow(int c) + { + if (c == traits_type::eof()) + { + *this->pptr() = traits_type::to_char_type(c); + this->sbumpc(); + } + return this->sync() ? traits_type::eof() : traits_type::not_eof(c); + } + + int sync() + { + int rc = 0; + if (this->pbase() != this->pptr()) + { + char writebuf[bufsize + 1]; + memcpy(writebuf, this->pbase(), this->pptr() - this->pbase()); + writebuf[this->pptr() - this->pbase()] = '\0'; + + rc = __android_log_write(ANDROID_LOG_INFO, "acl", writebuf) > 0; + this->setp(buffer, buffer + bufsize - 1); + } + return rc; + } + + char buffer[bufsize]; +}; + +extern "C" jint Java_com_acl_decompressor_MainActivity_nativeMain(JNIEnv* env, jobject caller, jobject java_asset_manager, jstring java_output_directory) +{ + std::cout.rdbuf(new androidbuf()); + + AAssetManager* asset_manager = AAssetManager_fromJava(env, java_asset_manager); + + const char* output_directory = env->GetStringUTFChars(java_output_directory, nullptr); + __android_log_print(ANDROID_LOG_INFO, "acl", "Benchmark results will be written to: %s", output_directory); + + const std::string output_filename = std::string(output_directory) + "/benchmark_results.json"; + + std::string clip_dir; + std::vector clips; + int result = read_metadata(asset_manager, clip_dir, clips); + if (result != 0) + { + env->ReleaseStringUTFChars(java_output_directory, output_directory); + return result; + } + + std::vector compressed_clips; + for (const std::string& clip : clips) + { + void* clip_buffer = nullptr; + size_t clip_buffer_size = 0; + if (load_file(asset_manager, clip.c_str(), clip_buffer, clip_buffer_size) != 0) + { + printf("Failed to read clip %s!\n", clip.c_str()); + continue; + } + + acl::compressed_tracks* raw_tracks = reinterpret_cast(clip_buffer); + + prepare_clip(clip, *raw_tracks, compressed_clips); + + // We are done with this now + free(raw_tracks); + } + + const int num_failed_decompression = clips.size() - compressed_clips.size(); + + char argv_0_executable_name[64]; + snprintf(argv_0_executable_name, sizeof(argv_0_executable_name), "Android APK"); + + char argv_1_benchmark_out[2048]; + snprintf(argv_1_benchmark_out, sizeof(argv_1_benchmark_out), "--benchmark_out=%s", output_filename.c_str()); + + char argv_2_benchmark_out_format[64]; + snprintf(argv_2_benchmark_out_format, sizeof(argv_2_benchmark_out_format), "--benchmark_out_format=json"); + + int argc = 3; + char* argv[3] = { argv_0_executable_name, argv_1_benchmark_out, argv_2_benchmark_out_format }; + + benchmark::Initialize(&argc, argv); + + // Run benchmarks + benchmark::RunSpecifiedBenchmarks(); + + // Clean up + clear_benchmark_state(); + + for (acl::compressed_tracks* compressed_tracks : compressed_clips) + s_allocator.deallocate(compressed_tracks, compressed_tracks->get_size()); + + if (num_failed_decompression != 0) + __android_log_print(ANDROID_LOG_ERROR, "acl", "Number of decompression failures: %d", num_failed_decompression); + + __android_log_print(ANDROID_LOG_INFO, "acl", "Done!"); + + env->ReleaseStringUTFChars(java_output_directory, output_directory); + + return num_failed_decompression; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/java/com/acl/decompressor/MainActivity.java b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/java/com/acl/decompressor/MainActivity.java new file mode 100644 index 00000000..c637e6c0 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_android/app/src/main/java/com/acl/decompressor/MainActivity.java @@ -0,0 +1,31 @@ +package com.acl.decompressor; + +import android.app.Activity; +import android.content.res.AssetManager; +import android.widget.TextView; +import android.os.Bundle; + +public class MainActivity extends Activity { + static { + System.loadLibrary("acl_decompressor"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView resultTextView = new TextView(this); + String outputDirectory = getExternalFilesDir(null).getAbsolutePath(); + + int result = nativeMain(getAssets(), outputDirectory); + + if (result == 0) + resultTextView.setText("Success!"); + else + resultTextView.setText("Failed!"); + + setContentView(resultTextView); + } + + public native int nativeMain(AssetManager assetManager, String outputDirectory); +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_generic/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_generic/CMakeLists.txt new file mode 100644 index 00000000..60c13a98 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_generic/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_decompressor CXX) + +set(CMAKE_CXX_STANDARD 11) + +# Google Benchmark +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "No need to run benchmark's tests" FORCE) +add_subdirectory("${PROJECT_SOURCE_DIR}/../../../external/benchmark" google_benchmark) + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/benchmark/include") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/sjson-cpp/includes") +include_directories("${PROJECT_SOURCE_DIR}/../sources") + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_COMMON_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp + ${PROJECT_SOURCE_DIR}/../*.py + ${PROJECT_SOURCE_DIR}/../*.md) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES}) + +setup_default_compiler_flags(${PROJECT_NAME}) + +# Link Google Benchmark +target_link_libraries(${PROJECT_NAME} PRIVATE benchmark) + +if(MSVC) + if(CPU_INSTRUCTION_SET MATCHES "arm64") + # Exceptions are not enabled by default for ARM targets, enable them + target_compile_options(${PROJECT_NAME} PRIVATE /EHsc) + endif() +endif() + +# Disable allocation tracking +add_definitions(-DACL_NO_ALLOCATOR_TRACKING) + +# Enable SJSON +add_definitions(-DACL_USE_SJSON) + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_generic/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_generic/main.cpp new file mode 100644 index 00000000..6d56c18f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_generic/main.cpp @@ -0,0 +1,234 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include +#include + +#ifdef _WIN32 +// The below excludes some other unused services from the windows headers -- see windows.h for details. +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI #defines and routines +#define NOKERNEL // All KERNEL #defines and routines +#define NOUSER // All USER #defines and routines +#define NONLS // All NLS #defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions +#define NOCRYPT +#define NOTAPE +#define NOIMAGE +#define NOPROXYSTUB +#define NORPC + +#include +#include + +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); +#endif + +static bool is_sjson_file(const char* filename) +{ + const size_t filename_len = std::strlen(filename); + return filename_len >= 6 && strncmp(filename + filename_len - 6, ".sjson", 6) == 0; +} + +static bool parse_options(int argc, char* argv[], const char*& out_metadata_filename) +{ + out_metadata_filename = nullptr; + + for (int arg_index = 1; arg_index < argc; ++arg_index) + { + const char* argument = argv[arg_index]; + + static constexpr const char* k_metadata_input_file_option = "-metadata="; + size_t option_length = std::strlen(k_metadata_input_file_option); + if (std::strncmp(argument, k_metadata_input_file_option, option_length) == 0) + { + out_metadata_filename = argument + option_length; + if (!is_sjson_file(out_metadata_filename)) + { + printf("Input file must be an SJSON file of the form: [*.sjson]\n"); + return false; + } + + continue; + } + } + + return out_metadata_filename != nullptr; +} + +static bool read_metadata_file(const char* metadata_filename, const char*& out_metadata_buffer, size_t& out_metadata_buffer_size) +{ + out_metadata_buffer = nullptr; + out_metadata_buffer_size = 0; + + std::FILE* file = nullptr; + +#ifdef _WIN32 + char path[64 * 1024] = { 0 }; + snprintf(path, acl::get_array_size(path), "\\\\?\\%s", metadata_filename); + fopen_s(&file, path, "rb"); +#else + file = fopen(metadata_filename, "rb"); +#endif + + if (file == nullptr) + return false; + + const int fseek_result = fseek(file, 0, SEEK_END); + if (fseek_result != 0) + { + fclose(file); + return false; + } + +#ifdef _WIN32 + const size_t file_size = static_cast(_ftelli64(file)); +#else + const size_t file_size = static_cast(ftello(file)); +#endif + + if (file_size == static_cast(-1L)) + { + fclose(file); + return false; + } + + rewind(file); + + char* metadata_buffer = new char[file_size]; + const size_t result = fread(metadata_buffer, 1, file_size, file); + fclose(file); + + if (result != file_size) + { + delete[] metadata_buffer; + return false; + } + + out_metadata_buffer = metadata_buffer; + out_metadata_buffer_size = file_size; + + return true; +} + +int main(int argc, char* argv[]) +{ +#if defined(_WIN32) + // To improve the consistency of the performance results, pin our process to a specific processor core + // Set the process affinity to physical core 6, on Ryzen 2950X it is the fastest core of the Die 1 + const DWORD_PTR physical_core_index = 5; + const DWORD_PTR logical_core_index = physical_core_index * 2; + SetProcessAffinityMask(GetCurrentProcess(), 1 << logical_core_index); +#endif + + const char* metadata_filename = nullptr; + if (!parse_options(argc, argv, metadata_filename)) + return -1; + + const char* metadata_buffer = nullptr; + size_t metadata_buffer_size = 0; + if (!read_metadata_file(metadata_filename, metadata_buffer, metadata_buffer_size)) + return -2; + + std::string clip_dir; + std::vector clips; + if (!parse_metadata(metadata_buffer, metadata_buffer_size, clip_dir, clips)) + return -3; + + delete[] metadata_buffer; + metadata_buffer = nullptr; + + std::vector compressed_clips; + for (const std::string& clip : clips) + { + acl::compressed_tracks* raw_tracks = nullptr; + if (!read_clip(clip_dir, clip, s_allocator, raw_tracks)) + { + printf("Failed to read clip %s!\n", clip.c_str()); + continue; + } + + prepare_clip(clip, *raw_tracks, compressed_clips); + + s_allocator.deallocate(raw_tracks, raw_tracks->get_size()); + } + + benchmark::Initialize(&argc, argv); + + // Run benchmarks + benchmark::RunSpecifiedBenchmarks(); + + // Clean up + clear_benchmark_state(); + + for (acl::compressed_tracks* compressed_tracks : compressed_clips) + s_allocator.deallocate(compressed_tracks, compressed_tracks->get_size()); + +#ifdef _WIN32 + if (IsDebuggerPresent()) + { + printf("Press any key to continue...\n"); + while (_kbhit() == 0); + } +#endif + + return 0; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/CMakeLists.txt b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/CMakeLists.txt new file mode 100644 index 00000000..be35e28f --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/CMakeLists.txt @@ -0,0 +1,71 @@ +cmake_minimum_required (VERSION 3.2) +project(acl_decompressor) + +# Google Benchmark +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "No need to run benchmark's tests" FORCE) +add_subdirectory("${PROJECT_SOURCE_DIR}/../../../external/benchmark" google_benchmark) + +# iOS cmake toolchain does not support CMAKE_CXX_STANDARD +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Force enable debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + +# Enable optimizations in Release +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +set(MACOSX_BUNDLE_EXECUTABLE_NAME ${PROJECT_NAME}) +set(MACOSX_BUNDLE_INFO_STRING "com.acl.acl-decompressor") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.acl.acl-decompressor") +set(MACOSX_BUNDLE_BUNDLE_NAME "acl-decompressor") + +include_directories("${PROJECT_SOURCE_DIR}/../../../includes") +include_directories("${PROJECT_SOURCE_DIR}/../sources") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/rtm/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/sjson-cpp/includes") +include_directories("${PROJECT_SOURCE_DIR}/../../../external/benchmark/include") + +# Setup resources +if(EXISTS ${DECOMP_DATA_DIR}) + set(DECOMP_DATA_DIR_DST "${PROJECT_BINARY_DIR}/assets") + file(GLOB_RECURSE ALL_DECOMP_DATA_FILES LIST_DIRECTORIES false ${DECOMP_DATA_DIR}/*.*) + file(COPY ${ALL_DECOMP_DATA_FILES} DESTINATION ${DECOMP_DATA_DIR_DST}) + + file(GLOB_RECURSE ALL_DECOMP_DATA_RESOURCE_FILES LIST_DIRECTORIES false ${DECOMP_DATA_DIR_DST}/*.*) + set_source_files_properties(${ALL_DECOMP_DATA_RESOURCE_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) +else() + message(WARNING "Decompression data directory not found") +endif() + +# Grab all of our common source files +file(GLOB_RECURSE ALL_COMMON_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/../sources/*.h + ${PROJECT_SOURCE_DIR}/../sources/*.cpp) + +create_source_groups("${ALL_COMMON_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}/..) + +# Grab all of our main source files +file(GLOB_RECURSE ALL_MAIN_SOURCE_FILES LIST_DIRECTORIES false + ${PROJECT_SOURCE_DIR}/*.cpp) + +create_source_groups("${ALL_MAIN_SOURCE_FILES}" ${PROJECT_SOURCE_DIR}) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ALL_COMMON_SOURCE_FILES} ${ALL_MAIN_SOURCE_FILES} ${ALL_DECOMP_DATA_RESOURCE_FILES}) + +# Link Google Benchmark +target_link_libraries(${PROJECT_NAME} PRIVATE benchmark) + +# Disable allocation tracking +add_definitions(-DACL_NO_ALLOCATOR_TRACKING) + +# Disable SIMD if not needed +if(NOT USE_SIMD_INSTRUCTIONS) + add_definitions(-DRTM_NO_INTRINSICS) +endif() + +# Enable SJSON +add_definitions(-DACL_USE_SJSON) + +target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation") + +set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/Info.plist.in) diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/Info.plist.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/Info.plist.in new file mode 100644 index 00000000..1a02e886 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/Info.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + UIFileSharingEnabled + + + \ No newline at end of file diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/main.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/main.cpp new file mode 100644 index 00000000..b6537695 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/main_ios/main.cpp @@ -0,0 +1,149 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +static int get_bundle_resource_path(const char* resource_filename, char* out_path, size_t path_max_size) +{ + CFStringRef resource_filename_str = CFStringCreateWithCString(nullptr, resource_filename, kCFStringEncodingUTF8); + CFURLRef resource_url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), resource_filename_str, nullptr, nullptr); + CFRelease(resource_filename_str); + if (!resource_url) + return -1040; + Boolean result = CFURLGetFileSystemRepresentation(resource_url, true, (unsigned char*)out_path, path_max_size); + CFRelease(resource_url); + if (!result) + return -1050; + return 0; +} + +static int read_metadata(std::string& out_clip_dir, std::vector& out_clips) +{ + char metadata_path[1024]; + int result = get_bundle_resource_path("metadata.sjson", metadata_path, sizeof(metadata_path)); + if (result != 0) + return result; + + std::ifstream t(metadata_path); + std::stringstream buffer; + buffer << t.rdbuf(); + std::string str = buffer.str(); + + if (!parse_metadata(str.c_str(), str.size(), out_clip_dir, out_clips)) + { + printf("Failed to parse metadata\n"); + return -1; + } + + return 0; +} + +static int read_clip(const std::string& clip_filename, std::vector& out_buffer) +{ + char clip_path[1024]; + int result = get_bundle_resource_path(clip_filename.c_str(), clip_path, 1024); + if (result != 0) + return result; + + std::ifstream t(clip_path, std::ios::binary); + out_buffer = std::vector(std::istreambuf_iterator(t), {}); + + return out_buffer.empty() ? -1 : 0; +} + +int main(int argc, char* argv[]) +{ + std::string clip_dir; + std::vector clips; + int result = read_metadata(clip_dir, clips); + if (result != 0) + return result; + + char output_directory[1024]; + std::strcpy(output_directory, getenv("HOME")); + std::strcat(output_directory, "/Documents"); + + const std::string output_filename = std::string(output_directory) + "/benchmark_results.json"; + + std::vector compressed_clips; + for (const std::string& clip : clips) + { + std::vector buffer; + if (read_clip(clip, buffer) != 0) + { + printf("Failed to read clip %s!\n", clip.c_str()); + continue; + } + + const acl::compressed_tracks* raw_tracks = reinterpret_cast(buffer.data()); + + prepare_clip(clip, *raw_tracks, compressed_clips); + } + + const int num_failed_decompression = int(clips.size() - compressed_clips.size()); + + char argv_0_executable_name[64]; + snprintf(argv_0_executable_name, sizeof(argv_0_executable_name), "iOS Bundle"); + + char argv_1_benchmark_out[2048]; + snprintf(argv_1_benchmark_out, sizeof(argv_1_benchmark_out), "--benchmark_out=%s", output_filename.c_str()); + + char argv_2_benchmark_out_format[64]; + snprintf(argv_2_benchmark_out_format, sizeof(argv_2_benchmark_out_format), "--benchmark_out_format=json"); + + int bench_argc = 3; + char* bench_argv[3] = { argv_0_executable_name, argv_1_benchmark_out, argv_2_benchmark_out_format }; + + benchmark::Initialize(&bench_argc, bench_argv); + + // Run benchmarks + benchmark::RunSpecifiedBenchmarks(); + + // Clean up + clear_benchmark_state(); + + for (acl::compressed_tracks* compressed_tracks : compressed_clips) + s_allocator.deallocate(compressed_tracks, compressed_tracks->get_size()); + + if (num_failed_decompression != 0) + printf("Number of decompression failures: %d\n", num_failed_decompression); + + printf("Done!\n"); + + return num_failed_decompression; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/sources/benchmark.cpp b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/sources/benchmark.cpp new file mode 100644 index 00000000..7ea74e6c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/sources/benchmark.cpp @@ -0,0 +1,454 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include "benchmark.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// +// Constants + +static constexpr uint32_t k_cpu_cache_size = 8 * 1024 * 1024; // Assume a 8 MB cache which is common for L3 modules (iPad, Zen2) + +// In practice, CPUs do not always evict the least recently used cache line. +// To ensure every cache line is evicted, we allocate our buffer 4x larger than the CPU cache. +// We use a custom memset function to make sure that streaming writes aren't used which would +// bypass the CPU cache and not evict anything. +static constexpr uint32_t k_flush_buffer_size = k_cpu_cache_size * 4; + +// The VMEM Level 1 translation has 512 entries each spanning 1 GB. We'll assume that in the real world +// there is a reasonable chance that memory touched will live within the same 1 GB region and thus be +// in some level of the CPU cache. + +// The VMEM Level 2 translation has 512 entries each spanning 2 MB. +// This means the cache line we load to find a page offset contains a span of 16 MB within it (a cache +// line contains 8 entries). +// To ensure we don't touch cache lines that belong to our input buffer as we flush the CPU cache, +// we add sufficient padding at both ends of the flush buffer. Since we'll access it linearly, +// the hardware prefetcher might pull in cache lines ahead. We assume it won't pull more than 4 cache +// lines ahead. This means we need this much padding on each end: 4 * 16 MB = 64 MB +static constexpr uint32_t k_vmem_padding = 16 * 1024 * 1024; +static constexpr uint32_t k_padded_flush_buffer_size = k_vmem_padding + k_flush_buffer_size + k_vmem_padding; + +// We allocate 100 copies of the compressed clip and align them to reduce the flush cost +// by flushing only when we loop around. We pad each copy to 16 MB to ensure no VMEM entry sharing in L2. +// A compressed clip that takes less than 160 MB would end up using 16 MB * 100 = 1.56 GB +static constexpr uint32_t k_num_copies = 220; + +// Align our clip copy buffer to a 2 MB boundary to further reduce VMEM noise +static constexpr uint32_t k_clip_buffer_alignment = 2 * 1024 * 1024; + +////////////////////////////////////////////////////////////////////////// + +enum class PlaybackDirection +{ + Forward, + Backward, + Random, +}; + +enum class DecompressionFunction +{ + DecompressPose, + DecompressBone, + Memcpy, +}; + +struct benchmark_transform_decompression_settings final : public acl::default_transform_decompression_settings +{ + // Only support our latest version + static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::latest; } + + // No need for safety checks + static constexpr bool skip_initialize_safety_checks() { return true; } +}; + +struct benchmark_state +{ + acl::compressed_tracks* compressed_tracks = nullptr; // Original clip + + acl::compressed_tracks** decompression_instances = nullptr; + acl::decompression_context* decompression_contexts = nullptr; + uint8_t* clip_copy_buffer = nullptr; + uint8_t* flush_buffer = nullptr; + + uint32_t pose_size = 0; + uint32_t clip_copy_buffer_size = 0; +}; + +acl::ansi_allocator s_allocator; +static benchmark_state s_benchmark_state; + +void clear_benchmark_state() +{ + acl::deallocate_type_array(s_allocator, s_benchmark_state.decompression_contexts, k_num_copies); + acl::deallocate_type_array(s_allocator, s_benchmark_state.decompression_instances, k_num_copies); + acl::deallocate_type_array(s_allocator, s_benchmark_state.clip_copy_buffer, s_benchmark_state.clip_copy_buffer_size); + acl::deallocate_type_array(s_allocator, s_benchmark_state.flush_buffer, k_padded_flush_buffer_size); + + s_benchmark_state = benchmark_state(); +} + +static void allocate_static_buffers() +{ + if (s_benchmark_state.flush_buffer != nullptr) + return; // Already allocated + + s_benchmark_state.decompression_instances = acl::allocate_type_array(s_allocator, k_num_copies); + s_benchmark_state.decompression_contexts = acl::allocate_type_array>(s_allocator, k_num_copies); + s_benchmark_state.flush_buffer = acl::allocate_type_array(s_allocator, k_padded_flush_buffer_size); +} + +static void setup_benchmark_state(acl::compressed_tracks& compressed_tracks) +{ + allocate_static_buffers(); + + const uint32_t num_tracks = compressed_tracks.get_num_tracks(); + const uint32_t compressed_size = compressed_tracks.get_size(); + + const uint32_t num_bytes_per_track = (4 + 3 + 3) * sizeof(float); // Rotation, Translation, Scale + const uint32_t pose_size = num_tracks * num_bytes_per_track; + + // Each clip is rounded up to a multiple of our VMEM padding + const uint32_t padded_clip_size = acl::align_to(compressed_size, k_vmem_padding); + const uint32_t clip_buffer_size = padded_clip_size * k_num_copies; + + acl::compressed_tracks** decompression_instances = s_benchmark_state.decompression_instances; + acl::decompression_context* decompression_contexts = s_benchmark_state.decompression_contexts; + uint8_t* clip_copy_buffer = s_benchmark_state.clip_copy_buffer; + + if (clip_buffer_size > s_benchmark_state.clip_copy_buffer_size) + { + // Allocate our new clip copy buffer + clip_copy_buffer = acl::allocate_type_array_aligned(s_allocator, clip_buffer_size, k_clip_buffer_alignment); + + s_benchmark_state.clip_copy_buffer = clip_copy_buffer; + s_benchmark_state.clip_copy_buffer_size = clip_buffer_size; + } + + printf("Pose size: %u bytes, clip size: %.2f MB\n", pose_size, double(compressed_size) / (1024.0 * 1024.0)); + + // Create our copies + for (uint32_t copy_index = 0; copy_index < k_num_copies; ++copy_index) + { + uint8_t* buffer = clip_copy_buffer + (copy_index * padded_clip_size); + std::memcpy(buffer, &compressed_tracks, compressed_size); + + decompression_instances[copy_index] = reinterpret_cast(buffer); + } + + // Create our decompression contexts + for (uint32_t instance_index = 0; instance_index < k_num_copies; ++instance_index) + decompression_contexts[instance_index].initialize(*decompression_instances[instance_index]); + + s_benchmark_state.compressed_tracks = &compressed_tracks; + s_benchmark_state.pose_size = pose_size; +} + +static void memset_impl(uint8_t* buffer, size_t buffer_size, uint8_t value) +{ + for (uint8_t* ptr = buffer; ptr < buffer + buffer_size; ++ptr) + *ptr = value; +} + +static void benchmark_decompression(benchmark::State& state) +{ + acl::compressed_tracks& compressed_tracks = *reinterpret_cast(state.range(0)); + const PlaybackDirection playback_direction = static_cast(state.range(1)); + const DecompressionFunction decompression_function = static_cast(state.range(2)); + + if (s_benchmark_state.compressed_tracks != &compressed_tracks) + setup_benchmark_state(compressed_tracks); // We have a new clip, setup everything + + const float duration = compressed_tracks.get_duration(); + + constexpr uint32_t k_num_decompression_samples = 100; + float sample_times[k_num_decompression_samples]; + for (uint32_t sample_index = 0; sample_index < k_num_decompression_samples; ++sample_index) + { + const float normalized_sample_time = float(sample_index) / float(k_num_decompression_samples - 1); + sample_times[sample_index] = rtm::scalar_clamp(normalized_sample_time, 0.0F, 1.0F) * duration; + } + + switch (playback_direction) + { + case PlaybackDirection::Forward: + default: + break; + case PlaybackDirection::Backward: + std::reverse(&sample_times[0], &sample_times[k_num_decompression_samples]); + break; + case PlaybackDirection::Random: + std::shuffle(&sample_times[0], &sample_times[k_num_decompression_samples], std::default_random_engine(0)); + break; + } + + acl::compressed_tracks** decompression_instances = s_benchmark_state.decompression_instances; + acl::decompression_context* decompression_contexts = s_benchmark_state.decompression_contexts; + uint8_t* flush_buffer = s_benchmark_state.flush_buffer; + const uint32_t pose_size = s_benchmark_state.pose_size; + + const uint32_t num_tracks = compressed_tracks.get_num_tracks(); + acl::acl_impl::debug_track_writer pose_writer(s_allocator, acl::track_type8::qvvf, num_tracks); + + // Flush the CPU cache + memset_impl(flush_buffer + k_vmem_padding, k_flush_buffer_size, 1); + + uint32_t current_context_index = 0; + uint32_t current_sample_index = 0; + uint8_t flush_value = 2; + for (auto _ : state) + { + (void)_; + + const auto start = std::chrono::high_resolution_clock::now(); + + const float sample_time = sample_times[current_sample_index]; + + acl::decompression_context& context = decompression_contexts[current_context_index]; + context.seek(sample_time, acl::sample_rounding_policy::none); + + switch (decompression_function) + { + case DecompressionFunction::DecompressPose: + context.decompress_tracks(pose_writer); + break; + case DecompressionFunction::DecompressBone: + for (uint32_t bone_index = 0; bone_index < num_tracks; ++bone_index) + context.decompress_track(bone_index, pose_writer); + break; + case DecompressionFunction::Memcpy: + std::memcpy(pose_writer.tracks_typed.qvvf, decompression_instances[current_context_index], pose_size); + break; + } + + const auto end = std::chrono::high_resolution_clock::now(); + const auto elapsed_seconds = std::chrono::duration_cast>(end - start); + state.SetIterationTime(elapsed_seconds.count()); + + // Move on to the next context and sample + // We only move on to the next sample once every context has been touched + current_context_index++; + if (current_context_index >= k_num_copies) + { + current_context_index = 0; + current_sample_index++; + + if (current_sample_index >= k_num_decompression_samples) + current_sample_index = 0; + + // Flush the CPU cache + memset_impl(flush_buffer + k_vmem_padding, k_flush_buffer_size, flush_value++); + } + } + + state.counters["Speed"] = benchmark::Counter(s_benchmark_state.pose_size, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::OneK::kIs1024); +} + +bool parse_metadata(const char* buffer, size_t buffer_size, std::string& out_clip_dir, std::vector& out_clips) +{ + sjson::Parser parser(buffer, buffer_size); + + sjson::StringView clip_dir; + parser.try_read("clip_dir", clip_dir, ""); + out_clip_dir = std::string(clip_dir.c_str(), clip_dir.size()); + + if (!parser.array_begins("clips")) + return false; + + while (!parser.try_array_ends()) + { + sjson::StringView clip_filename; + if (parser.read(&clip_filename, 1)) + out_clips.push_back(std::string(clip_filename.c_str(), clip_filename.size())); + } + + if (!parser.remainder_is_comments_and_whitespace()) + return false; + + return true; +} + +bool read_clip(const std::string& clip_dir, const std::string& clip, acl::iallocator& allocator, acl::compressed_tracks*& out_compressed_tracks) +{ + out_compressed_tracks = nullptr; + + std::string clip_filename; + clip_filename = clip_dir; + +#ifdef _WIN32 + clip_filename += '\\'; +#else + clip_filename += '/'; +#endif + + clip_filename += clip; + + std::FILE* file = nullptr; + +#ifdef _WIN32 + char path[64 * 1024] = { 0 }; + snprintf(path, acl::get_array_size(path), "\\\\?\\%s", clip_filename.c_str()); + fopen_s(&file, path, "rb"); +#else + file = fopen(clip_filename.c_str(), "rb"); +#endif + + if (file == nullptr) + return false; + + // Make sure to enable buffering with a large buffer + const int setvbuf_result = setvbuf(file, NULL, _IOFBF, 1 * 1024 * 1024); + if (setvbuf_result != 0) + return false; + + const int fseek_result = fseek(file, 0, SEEK_END); + if (fseek_result != 0) + { + fclose(file); + return false; + } + +#ifdef _WIN32 + const size_t file_size = static_cast(_ftelli64(file)); +#else + const size_t file_size = static_cast(ftello(file)); +#endif + + if (file_size == static_cast(-1L)) + { + fclose(file); + return false; + } + + rewind(file); + + char* buffer = (char*)allocator.allocate(file_size); + const size_t result = fread(buffer, 1, file_size, file); + fclose(file); + + if (result != file_size) + { + delete[] buffer; + return false; + } + + out_compressed_tracks = reinterpret_cast(buffer); + return true; +} + +bool prepare_clip(const std::string& clip_name, const acl::compressed_tracks& raw_tracks, std::vector& out_compressed_clips) +{ + printf("Preparing clip %s ...\n", clip_name.c_str()); + + acl::error_result result = raw_tracks.is_valid(false); + if (result.any()) + { + printf(" Failed to validate clip!\n"); + return false; + } + + // Compress our clip + acl::track_array track_list; + result = acl::convert_track_list(s_allocator, raw_tracks, track_list); + if (result.any()) + { + printf(" Failed to convert clip!\n"); + return false; + } + + if (track_list.get_track_type() != acl::track_type8::qvvf) + { + printf(" Invalid clip track type!\n"); + return false; + } + + acl::compression_settings settings = acl::get_default_compression_settings(); + + acl::qvvf_transform_error_metric error_metric; + settings.error_metric = &error_metric; + + acl::output_stats stats; + + acl::compressed_tracks* compressed_tracks = nullptr; + result = acl::compress_track_list(s_allocator, track_list, settings, compressed_tracks, stats); + if (result.any()) + { + printf(" Failed to compress clip!\n"); + return false; + } + + if (compressed_tracks->is_valid(false).any()) + { + printf(" Invalid compressed clip!\n"); + return false; + } + + // Dynamically register our benchmark + benchmark::internal::Benchmark* bench = benchmark::internal::RegisterBenchmarkInternal(new benchmark::internal::FunctionBenchmark(clip_name.c_str(), benchmark_decompression)); + + bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Forward, (int64_t)DecompressionFunction::DecompressPose }); + bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Forward, (int64_t)DecompressionFunction::DecompressBone }); + + // These are for debugging purposes and aren't measured as often + // By design, ACL's performance should be consistent regardless of the playback direction + //bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Forward, (int64_t)DecompressionFunction::Memcpy }); + //bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Backward, (int64_t)DecompressionFunction::DecompressPose }); + //bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Backward, (int64_t)DecompressionFunction::DecompressBone }); + //bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Random, (int64_t)DecompressionFunction::DecompressPose }); + //bench->Args({ reinterpret_cast(compressed_tracks), (int64_t)PlaybackDirection::Random, (int64_t)DecompressionFunction::DecompressBone }); + + // Name our arguments + bench->ArgNames({ "", "Dir", "Func" }); + + // Sometimes the numbers are slightly different from run to run, we'll run a few times + bench->Repetitions(3); + + // Our benchmark has a very low standard deviation, there is no need to run 100k+ times + bench->Iterations(10000); + + // Use manual timing since we clear the CPU cache explicitly + bench->UseManualTime(); + + // Add min/max tracking + bench->ComputeStatistics("min", [](const std::vector& v) { return *std::min_element(std::begin(v), std::end(v)); }); + bench->ComputeStatistics("max", [](const std::vector& v) { return *std::max_element(std::begin(v), std::end(v)); }); + + out_compressed_clips.push_back(compressed_tracks); + return true; +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/sources/benchmark.h b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/sources/benchmark.h new file mode 100644 index 00000000..a62cbde9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/acl_decompressor/sources/benchmark.h @@ -0,0 +1,41 @@ +//////////////////////////////////////////////////////////////////////////////// +// The MIT License (MIT) +// +// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include +#include + +extern acl::ansi_allocator s_allocator; + +void clear_benchmark_state(); + +bool parse_metadata(const char* buffer, size_t buffer_size, std::string& out_clip_dir, std::vector& out_clips); + +bool read_clip(const std::string& clip_dir, const std::string& clip, acl::iallocator& allocator, acl::compressed_tracks*& out_compressed_tracks); + +bool prepare_clip(const std::string& clip_name, const acl::compressed_tracks& raw_tracks, std::vector& out_compressed_clips); diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/README.md new file mode 100644 index 00000000..bb279bb5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/README.md @@ -0,0 +1,3 @@ +# Android Misc + +This directory contains various Android related files that are shared between the various projects. diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/build.gradle.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/build.gradle.in new file mode 100644 index 00000000..0f728299 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/build.gradle.in @@ -0,0 +1,56 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "@ACL_ANDROID_PROJECT_PACKAGE_NAME@" + minSdkVersion 24 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + arguments '-DUSE_SIMD_INSTRUCTIONS=@USE_SIMD_INSTRUCTIONS@', '-DUSE_SJSON=@USE_SJSON@' + } + } + ndk { + abiFilters '@CPU_INSTRUCTION_SET@' + } + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + debuggable = true + signingConfig signingConfigs.debug + externalNativeBuild { + cmake { + // Because we want to debug 'release' builds, the 'debuggable' property above + // forces the build configuration to 'Debug' which disables optimizations. + // Force a release configutation anyway. + arguments '-DCMAKE_BUILD_TYPE=Release' + } + } + } + } + externalNativeBuild { + cmake { + path '@ACL_PROJECT_ROOT@/src/main/cpp/CMakeLists.txt' + } + } + sourceSets { + main { + manifest.srcFile './src/main/AndroidManifest.xml' + java.srcDirs = ['@ACL_PROJECT_ROOT@/src/main/java'] + res.srcDirs = ['./src/main/res'] + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/proguard-rules.pro b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/src/main/AndroidManifest.xml.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/src/main/AndroidManifest.xml.in new file mode 100644 index 00000000..8f5da30e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/src/main/AndroidManifest.xml.in @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/src/main/res/values/strings.xml.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/src/main/res/values/strings.xml.in new file mode 100644 index 00000000..4f20aa30 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/app/src/main/res/values/strings.xml.in @@ -0,0 +1,4 @@ + + + @ACL_ANDROID_PROJECT_NAME@ + diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/build.gradle b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/build.gradle new file mode 100644 index 00000000..1606295a --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/build.gradle @@ -0,0 +1,25 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle.properties new file mode 100644 index 00000000..fe2852f9 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + +# Run tasks in parallel +org.gradle.parallel=true diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle/wrapper/gradle-wrapper.jar b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle/wrapper/gradle-wrapper.properties b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..656c0945 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Mar 27 21:02:15 EDT 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradlew b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradlew.bat b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/settings.gradle.in b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/settings.gradle.in new file mode 100644 index 00000000..d1d76d0c --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/android_misc/settings.gradle.in @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='@ACL_ANDROID_PROJECT_NAME@' diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/appveyor_ci.bat b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/appveyor_ci.bat new file mode 100644 index 00000000..a879f7a8 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/appveyor_ci.bat @@ -0,0 +1,61 @@ +@echo off + +REM Unpack arguments +SET WORKER_IMAGE=%1 +SET PLATFORM=%2 +SET CONFIG=%3 +SET TOOLCHAIN=%4 +SET PYTHON_PATH=%5 + +echo Worker image: %WORKER_IMAGE% +echo Platform: %PLATFORM% +echo Config: %CONFIG% +echo Toolchain: %TOOLCHAIN% +echo Python path: %PYTHON_PATH% + +REM Convert the build image and toolchain into our compiler string +IF /i %TOOLCHAIN%==msvc GOTO :msvc +IF /i %TOOLCHAIN%==clang GOTO :clang + +echo Unknown toolchain: %TOOLCHAIN% +exit /B 1 + +:msvc +IF /i %WORKER_IMAGE%=="Visual Studio 2015" SET COMPILER=vs2015 +IF /i %WORKER_IMAGE%=="Visual Studio 2017" SET COMPILER=vs2017 +IF /i %WORKER_IMAGE%=="Visual Studio 2019" SET COMPILER=vs2019 +IF /i %WORKER_IMAGE%=="Previous Visual Studio 2019" SET COMPILER=vs2019 +GOTO :next + +:clang +IF /i %WORKER_IMAGE%=="Visual Studio 2019" SET COMPILER=vs2019-clang +IF /i %WORKER_IMAGE%=="Previous Visual Studio 2019" SET COMPILER=vs2019-clang + +REM HACK!!! Disable clang build for now with appveyor since vcpkg breaks the compiler detection of cmake +REM Fake build success +echo VS2019 clang build is disabled for now due to issues with vcpkg breaking compiler detection with cmake +exit /B 0 + +GOTO :next + +:next +REM Set our switch if we need to run unit tests +SET UNIT_TEST_FLAG=-unit_test +IF /i %PLATFORM%==arm64 SET UNIT_TEST_FLAG= + +REM If PYTHON_PATH isn't set, assume it is in PATH +IF NOT DEFINED PYTHON_PATH SET PYTHON_PATH=python.exe + +REM Build and run unit tests +%PYTHON_PATH% make.py -build %UNIT_TEST_FLAG% -compiler %COMPILER% -config %CONFIG% -cpu %PLATFORM% -clean +IF NOT %ERRORLEVEL% EQU 0 GOTO :build_failure + +%PYTHON_PATH% make.py -build %UNIT_TEST_FLAG% -compiler %COMPILER% -config %CONFIG% -cpu %PLATFORM% -nosimd +IF NOT %ERRORLEVEL% EQU 0 GOTO :build_failure + +REM Done! +exit /B 0 + +:build_failure +echo Build failed! +exit /B 1 diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/calc_local_bit_rates.py b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/calc_local_bit_rates.py new file mode 100644 index 00000000..2c063ed7 --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/calc_local_bit_rates.py @@ -0,0 +1,45 @@ +# coding: utf-8 + +import sys + +k_bit_rate_num_bits = [ 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 32 ] + +k_highest_bit_rate = len(k_bit_rate_num_bits) - 1 +k_lowest_bit_rate = 1 +k_num_bit_rates = len(k_bit_rate_num_bits) +k_invalid_bit_rate = 255 + +# This code assumes that rotations, translations, and scales are packed on 3 components (e.g. quat drop w) + +if __name__ == "__main__": + if sys.version_info < (3, 4): + print('Python 3.4 or higher needed to run this script') + sys.exit(1) + + permutation_tries = [] + permutation_tries_no_scale = [] + + for rotation_bit_rate in range(k_num_bit_rates): + for translation_bit_rate in range(k_num_bit_rates): + transform_size = k_bit_rate_num_bits[rotation_bit_rate] * 3 + k_bit_rate_num_bits[translation_bit_rate] * 3 + permutation_tries_no_scale.append((transform_size, rotation_bit_rate, translation_bit_rate)) + + for scale_bit_rate in range(k_num_bit_rates): + transform_size = k_bit_rate_num_bits[rotation_bit_rate] * 3 + k_bit_rate_num_bits[translation_bit_rate] * 3 + k_bit_rate_num_bits[scale_bit_rate] * 3 + permutation_tries.append((transform_size, rotation_bit_rate, translation_bit_rate, scale_bit_rate)) + + # Sort by transform size, then by each bit rate + permutation_tries.sort() + permutation_tries_no_scale.sort() + + print('constexpr uint8_t k_local_bit_rate_permutations_no_scale[{}][2] ='.format(len(permutation_tries_no_scale))) + print('{') + for transform_size, rotation_bit_rate, translation_bit_rate in permutation_tries_no_scale: + print('\t{{ {}, {} }},\t\t// {} bits per transform'.format(rotation_bit_rate, translation_bit_rate, transform_size)) + print('};') + print() + print('constexpr uint8_t k_local_bit_rate_permutations[{}][3] ='.format(len(permutation_tries))) + print('{') + for transform_size, rotation_bit_rate, translation_bit_rate, scale_bit_rate in permutation_tries: + print('\t{{ {}, {}, {} }},\t\t// {} bits per transform'.format(rotation_bit_rate, translation_bit_rate, scale_bit_rate, transform_size)) + print('};') diff --git a/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/fbx2acl/README.md b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/fbx2acl/README.md new file mode 100644 index 00000000..baa3b84e --- /dev/null +++ b/Plugins/ACLPlugin/Source/ThirdParty/acl/tools/fbx2acl/README.md @@ -0,0 +1,9 @@ +# fbx2acl + +This python script uses the FBK SDK in order to convert animation clips within an FBX file into the [ACL file format](../../docs/the_acl_file_format.md) suitable for the [acl_compressor](../acl_compressor) tool. + +Usage: `python fbx2acl -fbx= [-stack=] [-start=