From fd4b9a5a9f18cdfa5c52aea56dd819431fb4be36 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 16 Dec 2022 16:29:43 -0600 Subject: [PATCH 001/294] Return focus to parent on mouse leave. --- Source/Editor/Windows/GameWindow.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 23558c396..89598265c 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -345,6 +345,13 @@ namespace FlaxEditor.Windows Cursor = CursorType.Default; } + /// + public override void OnMouseLeave() + { + Parent?.Focus(); + base.OnMouseLeave(); + } + /// public override void OnShowContextMenu(ContextMenu menu) { From 9a71e0274f5fef4e9282dea1a4e14f6db9372b6e Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 20 May 2023 00:03:40 -0400 Subject: [PATCH 002/294] Add required options for material instance importing. --- .../Editor/Content/Import/ModelImportEntry.cs | 21 +++++++++++++++++++ .../Editor/Managed/ManagedEditor.Internal.cpp | 6 ++++++ .../Tools/ModelTool/ModelTool.Options.cpp | 4 ++++ Source/Engine/Tools/ModelTool/ModelTool.h | 3 +++ 4 files changed, 34 insertions(+) diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index b17c8314d..8bdd7264a 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -316,6 +317,20 @@ namespace FlaxEditor.Content.Import [EditorOrder(400), DefaultValue(true)] public bool ImportMaterials { get; set; } = true; + /// + /// If checked, materials will be imported as instances of a base material. + /// + [EditorDisplay("Materials"), VisibleIf(nameof(ImportMaterials))] + [EditorOrder(405), DefaultValue(false)] + public bool ImportMaterialsAsInstances = false; + + /// + /// The material to import the model's materials as an instance of. + /// + [EditorDisplay("Materials"), VisibleIf(nameof(ImportMaterialsAsInstances))] + [EditorOrder(406)] + public Material InstanceToImportAs = null; // TODO: only show if BOTH ImportMaterials and ImportMaterialsAsInstances are true. + /// /// If checked, the importer will import texture files used by the model and any embedded texture resources. /// @@ -406,6 +421,8 @@ namespace FlaxEditor.Content.Import // Misc public byte ImportMaterials; + public byte ImportMaterialsAsInstances; + public Guid InstanceToImportAs; public byte ImportTextures; public byte RestoreMaterialsOnReimport; @@ -454,6 +471,8 @@ namespace FlaxEditor.Content.Import LODCount = LODCount, TriangleReduction = TriangleReduction, ImportMaterials = (byte)(ImportMaterials ? 1 : 0), + ImportMaterialsAsInstances = (byte)(ImportMaterialsAsInstances ? 1 : 0), + InstanceToImportAs = InstanceToImportAs.ID, ImportTextures = (byte)(ImportTextures ? 1 : 0), RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0), GenerateSDF = (byte)(GenerateSDF ? 1 : 0), @@ -496,6 +515,8 @@ namespace FlaxEditor.Content.Import LODCount = options.LODCount; TriangleReduction = options.TriangleReduction; ImportMaterials = options.ImportMaterials != 0; + ImportMaterialsAsInstances = options.ImportMaterialsAsInstances != 0; + InstanceToImportAs = FlaxEngine.Content.Load(options.InstanceToImportAs); ImportTextures = options.ImportTextures != 0; RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0; GenerateSDF = options.GenerateSDF != 0; diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index dcaa25e6c..17708dab5 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -195,6 +195,8 @@ struct InternalModelOptions // Misc byte ImportMaterials; + byte ImportMaterialsAsInstances; + Guid InstanceToImportAs; byte ImportTextures; byte RestoreMaterialsOnReimport; @@ -240,6 +242,8 @@ struct InternalModelOptions to->LODCount = from->LODCount; to->TriangleReduction = from->TriangleReduction; to->ImportMaterials = from->ImportMaterials; + to->ImportMaterialsAsInstances = from->ImportMaterialsAsInstances; + to->InstanceToImportAs = from->InstanceToImportAs; to->ImportTextures = from->ImportTextures; to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport; to->GenerateSDF = from->GenerateSDF; @@ -282,6 +286,8 @@ struct InternalModelOptions to->LODCount = from->LODCount; to->TriangleReduction = from->TriangleReduction; to->ImportMaterials = from->ImportMaterials; + to->ImportMaterialsAsInstances = from->ImportMaterialsAsInstances; + to->InstanceToImportAs = from->InstanceToImportAs; to->ImportTextures = from->ImportTextures; to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport; to->GenerateSDF = from->GenerateSDF; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp index b7fb04855..7d1315a1f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp @@ -61,6 +61,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(LODCount); SERIALIZE(TriangleReduction); SERIALIZE(ImportMaterials); + SERIALIZE(ImportMaterialsAsInstances); + SERIALIZE(InstanceToImportAs); SERIALIZE(ImportTextures); SERIALIZE(RestoreMaterialsOnReimport); SERIALIZE(GenerateSDF); @@ -102,6 +104,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(LODCount); DESERIALIZE(TriangleReduction); DESERIALIZE(ImportMaterials); + DESERIALIZE(ImportMaterialsAsInstances); + DESERIALIZE(InstanceToImportAs); DESERIALIZE(ImportTextures); DESERIALIZE(RestoreMaterialsOnReimport); DESERIALIZE(GenerateSDF); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index b29bed71f..cf6b15c3f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -11,6 +11,7 @@ #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Animations/AnimationData.h" +#include class JsonWriter; @@ -247,6 +248,8 @@ public: // Materials bool ImportMaterials = true; + bool ImportMaterialsAsInstances = false; + Guid InstanceToImportAs; bool ImportTextures = true; bool RestoreMaterialsOnReimport = true; From 89ce2b087ec1b00852f56665a1720b45fa7fd0b6 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 20 May 2023 00:13:45 -0400 Subject: [PATCH 003/294] Add parameter for AddMaterial() as an experiment, messing around with ideas. --- Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 1eb9e1f27..a4eebe830 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -207,7 +207,7 @@ struct OpenFbxImporterData return false; } - int32 AddMaterial(ImportedModelData& result, const ofbx::Material* mat) + int32 AddMaterial(ImportedModelData& result, const ofbx::Material* mat, const Material* instanceOf) { int32 index = Materials.Find(mat); if (index == -1) From f03bb60d5bad400e0e67836d8d3e273c12d8de7f Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sun, 21 May 2023 00:20:54 -0400 Subject: [PATCH 004/294] Figured out where I actually need to add code, and added code there. --- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 3 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 70 ++++++++++++++----- Source/ThirdParty/OpenFBX/ofbx.h | 1 + 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index a4eebe830..37b3f45a8 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -207,7 +207,7 @@ struct OpenFbxImporterData return false; } - int32 AddMaterial(ImportedModelData& result, const ofbx::Material* mat, const Material* instanceOf) + int32 AddMaterial(ImportedModelData& result, const ofbx::Material* mat) { int32 index = Materials.Find(mat); if (index == -1) @@ -542,6 +542,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb else aMaterial = aMesh->getMaterial(0); } + mesh.MaterialSlotIndex = data.AddMaterial(result, aMaterial); // Vertex positions diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 103d2187a..e15891a62 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -30,6 +30,7 @@ #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/CreateMaterial.h" +#include "Engine/ContentImporters/CreateMaterialInstance.h" #include "Engine/ContentImporters/CreateCollisionData.h" #include "Editor/Utilities/EditorUtilities.h" #include @@ -862,24 +863,57 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op importedFileNames.Add(filename); #if COMPILE_WITH_ASSETS_IMPORTER auto assetPath = autoImportOutput / filename + ASSET_FILES_EXTENSION_WITH_DOT; - CreateMaterial::Options materialOptions; - materialOptions.Diffuse.Color = material.Diffuse.Color; - if (material.Diffuse.TextureIndex != -1) - materialOptions.Diffuse.Texture = data.Textures[material.Diffuse.TextureIndex].AssetID; - materialOptions.Diffuse.HasAlphaMask = material.Diffuse.HasAlphaMask; - materialOptions.Emissive.Color = material.Emissive.Color; - if (material.Emissive.TextureIndex != -1) - materialOptions.Emissive.Texture = data.Textures[material.Emissive.TextureIndex].AssetID; - materialOptions.Opacity.Value = material.Opacity.Value; - if (material.Opacity.TextureIndex != -1) - materialOptions.Opacity.Texture = data.Textures[material.Opacity.TextureIndex].AssetID; - if (material.Normals.TextureIndex != -1) - materialOptions.Normals.Texture = data.Textures[material.Normals.TextureIndex].AssetID; - if (material.TwoSided || material.Diffuse.HasAlphaMask) - materialOptions.Info.CullMode = CullMode::TwoSided; - if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) - materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; - AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); + if (options.ImportMaterialsAsInstances) { + LOG(Warning, "Did work poggers"); + if (AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID)) + { + LOG(Error, "Failed to create material instance."); + return true; + } + else + { + MaterialInstance* materialInstance = (MaterialInstance*) LoadAsset(material.AssetID, ScriptingTypeHandle()); + if (materialInstance == nullptr) + { + LOG(Error, "Failed to find created material instance."); + return true; + } + + MaterialBase *materialInstanceOf = (MaterialBase*) LoadAsset(options.InstanceToImportAs, ScriptingTypeHandle()); + if (materialInstanceOf == nullptr) + { + LOG(Error, "Failed to find the material to create an instance of."); + return true; + } + + materialInstance->SetBaseMaterial(materialInstanceOf); + if (materialInstance->Save()) + { + LOG(Error, "Failed to save the material instance."); + return true; + } + } + } + else { + CreateMaterial::Options materialOptions; + materialOptions.Diffuse.Color = material.Diffuse.Color; + if (material.Diffuse.TextureIndex != -1) + materialOptions.Diffuse.Texture = data.Textures[material.Diffuse.TextureIndex].AssetID; + materialOptions.Diffuse.HasAlphaMask = material.Diffuse.HasAlphaMask; + materialOptions.Emissive.Color = material.Emissive.Color; + if (material.Emissive.TextureIndex != -1) + materialOptions.Emissive.Texture = data.Textures[material.Emissive.TextureIndex].AssetID; + materialOptions.Opacity.Value = material.Opacity.Value; + if (material.Opacity.TextureIndex != -1) + materialOptions.Opacity.Texture = data.Textures[material.Opacity.TextureIndex].AssetID; + if (material.Normals.TextureIndex != -1) + materialOptions.Normals.Texture = data.Textures[material.Normals.TextureIndex].AssetID; + if (material.TwoSided || material.Diffuse.HasAlphaMask) + materialOptions.Info.CullMode = CullMode::TwoSided; + if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) + materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; + AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); + } #endif } diff --git a/Source/ThirdParty/OpenFBX/ofbx.h b/Source/ThirdParty/OpenFBX/ofbx.h index 7066659e3..87dd5999f 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.h +++ b/Source/ThirdParty/OpenFBX/ofbx.h @@ -1,4 +1,5 @@ #pragma once +#include namespace ofbx From 37fe1154a3a00e6259b14303b415a534e79e662c Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Mon, 22 May 2023 10:12:00 -0400 Subject: [PATCH 005/294] Importing as instances works now --- Source/Engine/Content/Asset.h | 1 + Source/Engine/Content/Content.cpp | 5 +++ Source/Engine/Tools/ModelTool/ModelTool.cpp | 40 ++++++++------------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index 2d2ba4b3f..0ecc6db3f 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -246,3 +246,4 @@ public: // Don't include Content.h but just Load method extern FLAXENGINE_API Asset* LoadAsset(const Guid& id, const ScriptingTypeHandle& type); +extern FLAXENGINE_API Asset* LoadAsset(const StringView& path, const ScriptingTypeHandle& type); diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index e58aa458d..f12619c52 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -449,6 +449,11 @@ FLAXENGINE_API Asset* LoadAsset(const Guid& id, const ScriptingTypeHandle& type) return Content::LoadAsync(id, type); } +FLAXENGINE_API Asset* LoadAsset(const StringView& path, const ScriptingTypeHandle& type) +{ + return Content::LoadAsync(path, type); +} + Asset* Content::LoadAsync(const StringView& path, MClass* type) { CHECK_RETURN(type, nullptr); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index e15891a62..c3015ea30 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -864,40 +864,28 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op #if COMPILE_WITH_ASSETS_IMPORTER auto assetPath = autoImportOutput / filename + ASSET_FILES_EXTENSION_WITH_DOT; if (options.ImportMaterialsAsInstances) { - LOG(Warning, "Did work poggers"); - if (AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID)) - { - LOG(Error, "Failed to create material instance."); + LOG(Warning, "Adding material instance for {0}", assetPath); + + AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath); + MaterialInstance* materialInstance = (MaterialInstance*) LoadAsset(assetPath, MaterialInstance::TypeInitializer); + if (materialInstance->WaitForLoaded()) { + LOG(Error, "Failed to load material instance after creation. ({0})", assetPath); return true; } - else - { - MaterialInstance* materialInstance = (MaterialInstance*) LoadAsset(material.AssetID, ScriptingTypeHandle()); - if (materialInstance == nullptr) - { - LOG(Error, "Failed to find created material instance."); - return true; - } - MaterialBase *materialInstanceOf = (MaterialBase*) LoadAsset(options.InstanceToImportAs, ScriptingTypeHandle()); - if (materialInstanceOf == nullptr) - { - LOG(Error, "Failed to find the material to create an instance of."); - return true; - } - - materialInstance->SetBaseMaterial(materialInstanceOf); - if (materialInstance->Save()) - { - LOG(Error, "Failed to save the material instance."); - return true; - } + MaterialBase* materialInstanceOf = (MaterialBase*) LoadAsset(options.InstanceToImportAs, MaterialBase::TypeInitializer); + if (materialInstanceOf->WaitForLoaded()) { + LOG(Error, "Failed to load material to create an instance of. ({0})", options.InstanceToImportAs); + return true; } + + materialInstance->SetBaseMaterial(materialInstanceOf); + materialInstance->Save(); } else { CreateMaterial::Options materialOptions; materialOptions.Diffuse.Color = material.Diffuse.Color; - if (material.Diffuse.TextureIndex != -1) + if (material.Diffuse.TextureIndex != -1) materialOptions.Diffuse.Texture = data.Textures[material.Diffuse.TextureIndex].AssetID; materialOptions.Diffuse.HasAlphaMask = material.Diffuse.HasAlphaMask; materialOptions.Emissive.Color = material.Emissive.Color; From 4acfffeaf48dcf41e0bef7cf1ab8df4b59b72a61 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Mon, 22 May 2023 13:51:13 -0400 Subject: [PATCH 006/294] Make it so the material instances get linked to the material slots. --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index c3015ea30..d1736e246 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -866,7 +866,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op if (options.ImportMaterialsAsInstances) { LOG(Warning, "Adding material instance for {0}", assetPath); - AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath); + AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID); MaterialInstance* materialInstance = (MaterialInstance*) LoadAsset(assetPath, MaterialInstance::TypeInitializer); if (materialInstance->WaitForLoaded()) { LOG(Error, "Failed to load material instance after creation. ({0})", assetPath); From 284c3d832a9212953777d80ff3c268fcaea43401 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Mon, 22 May 2023 14:26:20 -0400 Subject: [PATCH 007/294] fix a couple of bugs with importing without material instances --- .../Editor/Content/Import/ModelImportEntry.cs | 20 +++++++++++++++++-- Source/Engine/Content/Content.cpp | 2 ++ Source/Engine/Tools/ModelTool/ModelTool.cpp | 14 +++++++------ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 8bdd7264a..88ace184a 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -437,6 +437,12 @@ namespace FlaxEditor.Content.Import internal void ToInternal(out InternalOptions options) { + Guid instanceToImportAsGuid = Guid.Empty; + if (InstanceToImportAs != null) + { + instanceToImportAsGuid = InstanceToImportAs.ID; + } + options = new InternalOptions { Type = Type, @@ -472,7 +478,7 @@ namespace FlaxEditor.Content.Import TriangleReduction = TriangleReduction, ImportMaterials = (byte)(ImportMaterials ? 1 : 0), ImportMaterialsAsInstances = (byte)(ImportMaterialsAsInstances ? 1 : 0), - InstanceToImportAs = InstanceToImportAs.ID, + InstanceToImportAs = instanceToImportAsGuid, ImportTextures = (byte)(ImportTextures ? 1 : 0), RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0), GenerateSDF = (byte)(GenerateSDF ? 1 : 0), @@ -484,6 +490,16 @@ namespace FlaxEditor.Content.Import internal void FromInternal(ref InternalOptions options) { + Material instanceToImportAsMat = null; + if (options.InstanceToImportAs != Guid.Empty) + { + AssetInfo assetInfo; + if (FlaxEngine.Content.GetAssetInfo(options.InstanceToImportAs, out assetInfo)) + { + instanceToImportAsMat = FlaxEngine.Content.Load(options.InstanceToImportAs); + } + } + Type = options.Type; CalculateNormals = options.CalculateNormals != 0; SmoothingNormalsAngle = options.SmoothingNormalsAngle; @@ -516,7 +532,7 @@ namespace FlaxEditor.Content.Import TriangleReduction = options.TriangleReduction; ImportMaterials = options.ImportMaterials != 0; ImportMaterialsAsInstances = options.ImportMaterialsAsInstances != 0; - InstanceToImportAs = FlaxEngine.Content.Load(options.InstanceToImportAs); + InstanceToImportAs = instanceToImportAsMat; ImportTextures = options.ImportTextures != 0; RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0; GenerateSDF = options.GenerateSDF != 0; diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index f12619c52..d4015da7b 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -30,6 +30,7 @@ #if ENABLE_ASSETS_DISCOVERY #include "Engine/Core/Collections/HashSet.h" #endif +#include TimeSpan Content::AssetsUpdateInterval = TimeSpan::FromMilliseconds(500); TimeSpan Content::AssetsUnloadInterval = TimeSpan::FromSeconds(10); @@ -1003,6 +1004,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& if (!GetAssetInfo(id, assetInfo)) { LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString()); + LOG(Warning, "{0}", DebugLog::GetStackTrace()); return nullptr; } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index d1736e246..d5e5ebb10 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -863,18 +863,19 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op importedFileNames.Add(filename); #if COMPILE_WITH_ASSETS_IMPORTER auto assetPath = autoImportOutput / filename + ASSET_FILES_EXTENSION_WITH_DOT; - if (options.ImportMaterialsAsInstances) { - LOG(Warning, "Adding material instance for {0}", assetPath); - + if (options.ImportMaterialsAsInstances) + { AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID); MaterialInstance* materialInstance = (MaterialInstance*) LoadAsset(assetPath, MaterialInstance::TypeInitializer); - if (materialInstance->WaitForLoaded()) { + if (materialInstance->WaitForLoaded()) + { LOG(Error, "Failed to load material instance after creation. ({0})", assetPath); return true; } MaterialBase* materialInstanceOf = (MaterialBase*) LoadAsset(options.InstanceToImportAs, MaterialBase::TypeInitializer); - if (materialInstanceOf->WaitForLoaded()) { + if (materialInstanceOf->WaitForLoaded()) + { LOG(Error, "Failed to load material to create an instance of. ({0})", options.InstanceToImportAs); return true; } @@ -882,7 +883,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op materialInstance->SetBaseMaterial(materialInstanceOf); materialInstance->Save(); } - else { + else + { CreateMaterial::Options materialOptions; materialOptions.Diffuse.Color = material.Diffuse.Color; if (material.Diffuse.TextureIndex != -1) From e3f004b831f5906b4867bd7dfea8a12b9e5c890c Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Mon, 22 May 2023 14:26:55 -0400 Subject: [PATCH 008/294] remove extraneous stack trace print that i used for debugging --- Source/Engine/Content/Content.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index d4015da7b..026ae821c 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -1004,7 +1004,6 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& if (!GetAssetInfo(id, assetInfo)) { LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString()); - LOG(Warning, "{0}", DebugLog::GetStackTrace()); return nullptr; } From 1c9d8aa470b8056f552a8670ec54bb9596bb14d6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Jun 2023 11:21:35 +0200 Subject: [PATCH 009/294] Add `GPUContext::SetStencilRef` to stencil reference value --- Source/Engine/Graphics/GPUContext.h | 6 ++++++ .../GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp | 9 ++++++++- .../GraphicsDevice/DirectX/DX11/GPUContextDX11.h | 2 ++ .../GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp | 10 ++++++++++ .../GraphicsDevice/DirectX/DX12/GPUContextDX12.h | 2 ++ Source/Engine/GraphicsDevice/Null/GPUContextNull.h | 4 ++++ .../Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp | 11 +++++++++++ .../Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h | 2 ++ .../GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp | 2 +- 9 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 99a226193..3ff7ec256 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -321,6 +321,12 @@ public: /// Blend factors, one for each RGBA component. API_FUNCTION() virtual void SetBlendFactor(const Float4& value) = 0; + /// + /// Sets the reference value for depth stencil tests. + /// + /// Reference value to perform against when doing a depth-stencil test. + API_FUNCTION() virtual void SetStencilRef(uint32 value) = 0; + public: /// /// Unbinds all shader resource slots and flushes the change with the driver (used to prevent driver detection of resource hazards, eg. when down-scaling the texture). diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 10d85c179..6b6ebd6bc 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -103,6 +103,7 @@ void GPUContextDX11::FrameBegin() CurrentPS = nullptr; CurrentCS = nullptr; CurrentPrimitiveTopology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; + CurrentStencilRef = 0; CurrentBlendFactor = Float4::One; // Bind static samplers @@ -275,6 +276,12 @@ void GPUContextDX11::SetBlendFactor(const Float4& value) _context->OMSetBlendState(CurrentBlendState, CurrentBlendFactor.Raw, D3D11_DEFAULT_SAMPLE_MASK); } +void GPUContextDX11::SetStencilRef(uint32 value) +{ + if (CurrentStencilRef != value) + _context->OMSetDepthStencilState(CurrentDepthStencilState, CurrentStencilRef); +} + void GPUContextDX11::ResetSR() { _srDirtyFlag = false; @@ -559,7 +566,7 @@ void GPUContextDX11::SetState(GPUPipelineState* state) if (CurrentDepthStencilState != depthStencilState) { CurrentDepthStencilState = depthStencilState; - _context->OMSetDepthStencilState(depthStencilState, 0); + _context->OMSetDepthStencilState(depthStencilState, CurrentStencilRef); } if (CurrentRasterizerState != rasterizerState) { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h index b623cba3b..4e47c2f11 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h @@ -61,6 +61,7 @@ private: GPUShaderProgramPSDX11* CurrentPS; GPUShaderProgramCSDX11* CurrentCS; D3D11_PRIMITIVE_TOPOLOGY CurrentPrimitiveTopology; + uint32 CurrentStencilRef; Float4 CurrentBlendFactor; public: @@ -117,6 +118,7 @@ public: void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override; void SetRenderTarget(GPUTextureView* depthBuffer, const Span& rts) override; void SetBlendFactor(const Float4& value) override; + void SetStencilRef(uint32 value) override; void ResetSR() override; void ResetUA() override; void ResetCB() override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 54f1a4290..107895cf5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -222,6 +222,7 @@ void GPUContextDX12::Reset() _rtDepth = nullptr; _srMaskDirtyGraphics = 0; _srMaskDirtyCompute = 0; + _stencilRef = 0; _psDirtyFlag = false; _isCompute = false; _currentCompute = nullptr; @@ -850,6 +851,15 @@ void GPUContextDX12::SetBlendFactor(const Float4& value) _commandList->OMSetBlendFactor(value.Raw); } +void GPUContextDX12::SetStencilRef(uint32 value) +{ + if (_stencilRef != value) + { + _stencilRef = value; + _commandList->OMSetStencilRef(value); + } +} + void GPUContextDX12::ResetSR() { for (int32 slot = 0; slot < GPU_MAX_SR_BINDED; slot++) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h index c870cf71e..45dfa8162 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h @@ -47,6 +47,7 @@ private: uint32 _srMaskDirtyGraphics; uint32 _srMaskDirtyCompute; + uint32 _stencilRef; int32 _isCompute : 1; int32 _rtDirtyFlag : 1; @@ -167,6 +168,7 @@ public: void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override; void SetRenderTarget(GPUTextureView* depthBuffer, const Span& rts) override; void SetBlendFactor(const Float4& value) override; + void SetStencilRef(uint32 value) override; void ResetSR() override; void ResetUA() override; void ResetCB() override; diff --git a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h index 7568bb60c..e22c4b147 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h @@ -88,6 +88,10 @@ public: { } + void SetStencilRef(uint32 value) override + { + } + void ResetSR() override { } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index d52b446e3..64d8bfb67 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -727,6 +727,7 @@ void GPUContextVulkan::FrameBegin() _cbDirtyFlag = 0; _rtCount = 0; _vbCount = 0; + _stencilRef = 0; _renderPass = nullptr; _currentState = nullptr; _rtDepth = nullptr; @@ -972,6 +973,16 @@ void GPUContextVulkan::SetBlendFactor(const Float4& value) vkCmdSetBlendConstants(cmdBuffer->GetHandle(), value.Raw); } +void GPUContextVulkan::SetStencilRef(uint32 value) +{ + if (_stencilRef != value) + { + _stencilRef = value; + const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); + vkCmdSetStencilReference(cmdBuffer->GetHandle(), VK_STENCIL_FRONT_AND_BACK, _stencilRef); + } +} + void GPUContextVulkan::ResetSR() { Platform::MemoryClear(_srHandles, sizeof(_srHandles)); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h index 63224b2d0..08b5c1d6a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h @@ -97,6 +97,7 @@ private: int32 _rtCount; int32 _vbCount; + uint32 _stencilRef; RenderPassVulkan* _renderPass; GPUPipelineStateVulkan* _currentState; @@ -185,6 +186,7 @@ public: void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override; void SetRenderTarget(GPUTextureView* depthBuffer, const Span& rts) override; void SetBlendFactor(const Float4& value) override; + void SetStencilRef(uint32 value) override; void ResetSR() override; void ResetUA() override; void ResetCB() override; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 935f37873..d2ccf7da1 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -274,7 +274,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _descDynamic.pDynamicStates = _dynamicStates; _dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT; _dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; - //_dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; + _dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; static_assert(ARRAY_COUNT(_dynamicStates) <= 3, "Invalid dynamic states array."); _desc.pDynamicState = &_descDynamic; From 7fc3b264ac571548125b5c10ffed4c39042145a4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Jun 2023 11:21:53 +0200 Subject: [PATCH 010/294] Improve DX12 PSO debug name building --- .../DirectX/DX12/GPUPipelineStateDX12.cpp | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index 3bfe09401..6a87f100d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -65,42 +65,36 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i if (FAILED(result)) return nullptr; #if GPU_ENABLE_RESOURCE_NAMING && BUILD_DEBUG - char name[200]; - int32 nameLen = 0; + Array> name; if (DebugDesc.VS) { - Platform::MemoryCopy(name + nameLen, *DebugDesc.VS->GetName(), DebugDesc.VS->GetName().Length()); - nameLen += DebugDesc.VS->GetName().Length(); - name[nameLen++] = '+'; + name.Add(*DebugDesc.VS->GetName(), DebugDesc.VS->GetName().Length()); + name.Add('+'); } if (DebugDesc.HS) { - Platform::MemoryCopy(name + nameLen, *DebugDesc.HS->GetName(), DebugDesc.HS->GetName().Length()); - nameLen += DebugDesc.HS->GetName().Length(); - name[nameLen++] = '+'; + name.Add(*DebugDesc.HS->GetName(), DebugDesc.HS->GetName().Length()); + name.Add('+'); } if (DebugDesc.DS) { - Platform::MemoryCopy(name + nameLen, *DebugDesc.DS->GetName(), DebugDesc.DS->GetName().Length()); - nameLen += DebugDesc.DS->GetName().Length(); - name[nameLen++] = '+'; + name.Add(*DebugDesc.DS->GetName(), DebugDesc.DS->GetName().Length()); + name.Add('+'); } if (DebugDesc.GS) { - Platform::MemoryCopy(name + nameLen, *DebugDesc.GS->GetName(), DebugDesc.GS->GetName().Length()); - nameLen += DebugDesc.GS->GetName().Length(); - name[nameLen++] = '+'; + name.Add(*DebugDesc.GS->GetName(), DebugDesc.GS->GetName().Length()); + name.Add('+'); } if (DebugDesc.PS) { - Platform::MemoryCopy(name + nameLen, *DebugDesc.PS->GetName(), DebugDesc.PS->GetName().Length()); - nameLen += DebugDesc.PS->GetName().Length(); - name[nameLen++] = '+'; + name.Add(*DebugDesc.PS->GetName(), DebugDesc.PS->GetName().Length()); + name.Add('+'); } - if (nameLen && name[nameLen - 1] == '+') - nameLen--; - name[nameLen] = '\0'; - SetDebugObjectName(state, name); + if (name.Count() != 0 && name[name.Count() - 1] == '+') + name.RemoveLast(); + name.Add('\0'); + SetDebugObjectName(state, name.Get(), name.Count() - 1); #endif // Cache it From a6353c0bb96080637873398082b22563873784d1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Jun 2023 11:53:40 +0200 Subject: [PATCH 011/294] Rename `GPUPipelineState::Description::PrimitiveTopologyType` to `PrimitiveTopology` --- Source/Engine/Debug/DebugDraw.cpp | 8 ++++---- Source/Engine/Graphics/GPUPipelineState.h | 2 +- .../GraphicsDevice/DirectX/DX11/GPUPipelineStateDX11.cpp | 2 +- .../Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp | 7 ++++++- .../Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h | 1 + .../GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp | 6 +++--- .../GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h | 2 +- .../GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp | 2 +- Source/Engine/Renderer/DepthOfFieldPass.cpp | 2 +- 9 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 891792d79..7330f4339 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -638,10 +638,10 @@ void DebugDrawService::Update() // Default desc.PS = shader->GetPS("PS", 0); - desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; + desc.PrimitiveTopology = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDefault.Create(desc); desc.PS = shader->GetPS("PS", 1); - desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; + desc.PrimitiveTopology = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDefault.Create(desc); desc.Wireframe = true; failed |= DebugDrawPsWireTrianglesDefault.Create(desc); @@ -649,10 +649,10 @@ void DebugDrawService::Update() // Depth Test desc.Wireframe = false; desc.PS = shader->GetPS("PS", 2); - desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; + desc.PrimitiveTopology = PrimitiveTopologyType::Line; failed |= DebugDrawPsLinesDepthTest.Create(desc); desc.PS = shader->GetPS("PS", 3); - desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; + desc.PrimitiveTopology = PrimitiveTopologyType::Triangle; failed |= DebugDrawPsTrianglesDepthTest.Create(desc); desc.Wireframe = true; failed |= DebugDrawPsWireTrianglesDepthTest.Create(desc); diff --git a/Source/Engine/Graphics/GPUPipelineState.h b/Source/Engine/Graphics/GPUPipelineState.h index 725b1bb74..b9b6395e4 100644 --- a/Source/Engine/Graphics/GPUPipelineState.h +++ b/Source/Engine/Graphics/GPUPipelineState.h @@ -72,7 +72,7 @@ public: /// /// Input primitives topology /// - API_FIELD() PrimitiveTopologyType PrimitiveTopologyType; + API_FIELD() PrimitiveTopologyType PrimitiveTopology; /// /// True if use wireframe rendering, otherwise false diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUPipelineStateDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUPipelineStateDX11.cpp index e3458fcae..18a774793 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUPipelineStateDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUPipelineStateDX11.cpp @@ -43,7 +43,7 @@ bool GPUPipelineStateDX11::Init(const Description& desc) D3D11_PRIMITIVE_TOPOLOGY_LINELIST, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, }; - PrimitiveTopology = D3D11_primTypes[static_cast(desc.PrimitiveTopologyType)]; + PrimitiveTopology = D3D11_primTypes[static_cast(desc.PrimitiveTopology)]; if (HS) PrimitiveTopology = (D3D11_PRIMITIVE_TOPOLOGY)((int32)D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (HS->GetControlPointsCount() - 1)); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 107895cf5..343caabac 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -223,6 +223,7 @@ void GPUContextDX12::Reset() _srMaskDirtyGraphics = 0; _srMaskDirtyCompute = 0; _stencilRef = 0; + _primitiveTopology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; _psDirtyFlag = false; _isCompute = false; _currentCompute = nullptr; @@ -545,7 +546,11 @@ void GPUContextDX12::flushPS() // Change state ASSERT(_currentState->IsValid()); _commandList->SetPipelineState(_currentState->GetState(_rtDepth, _rtCount, _rtHandles)); - _commandList->IASetPrimitiveTopology(_currentState->PrimitiveTopologyType); + if (_primitiveTopology != _currentState->PrimitiveTopology) + { + _primitiveTopology = _currentState->PrimitiveTopology; + _commandList->IASetPrimitiveTopology(_primitiveTopology); + } RENDER_STAT_PS_STATE_CHANGE(); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h index 45dfa8162..a8f2fdb22 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h @@ -48,6 +48,7 @@ private: uint32 _srMaskDirtyGraphics; uint32 _srMaskDirtyCompute; uint32 _stencilRef; + D3D_PRIMITIVE_TOPOLOGY _primitiveTopology; int32 _isCompute : 1; int32 _rtDirtyFlag : 1; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index 6a87f100d..b23d4d2fe 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -157,12 +157,12 @@ bool GPUPipelineStateDX12::Init(const Description& desc) D3D_PRIMITIVE_TOPOLOGY_LINELIST, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, }; - psDesc.PrimitiveTopologyType = primTypes1[(int32)desc.PrimitiveTopologyType]; - PrimitiveTopologyType = primTypes2[(int32)desc.PrimitiveTopologyType]; + psDesc.PrimitiveTopologyType = primTypes1[(int32)desc.PrimitiveTopology]; + PrimitiveTopology = primTypes2[(int32)desc.PrimitiveTopology]; if (desc.HS) { psDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; - PrimitiveTopologyType = (D3D_PRIMITIVE_TOPOLOGY)((int32)D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (desc.HS->GetControlPointsCount() - 1)); + PrimitiveTopology = (D3D_PRIMITIVE_TOPOLOGY)((int32)D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (desc.HS->GetControlPointsCount() - 1)); } // Depth State diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h index fc02699eb..2764ce235 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h @@ -55,7 +55,7 @@ public: public: - D3D_PRIMITIVE_TOPOLOGY PrimitiveTopologyType = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; + D3D_PRIMITIVE_TOPOLOGY PrimitiveTopology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; DxShaderHeader Header; /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index d2ccf7da1..5946aee20 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -239,7 +239,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) // Input Assembly RenderToolsVulkan::ZeroStruct(_descInputAssembly, VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO);; - switch (desc.PrimitiveTopologyType) + switch (desc.PrimitiveTopology) { case PrimitiveTopologyType::Point: _descInputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index e5d772aba..4c43ccdbf 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -157,7 +157,7 @@ bool DepthOfFieldPass::setupResources() psDesc.GS = shader->GetGS("GS_Bokeh"); psDesc.PS = shader->GetPS("PS_Bokeh"); psDesc.BlendMode = BlendingMode::Additive; - psDesc.PrimitiveTopologyType = PrimitiveTopologyType::Point; + psDesc.PrimitiveTopology = PrimitiveTopologyType::Point; if (_psBokeh->Init(psDesc)) return true; } From f952a392dedc7c5f466b5873f9c673bc3f765397 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Jun 2023 13:46:37 +0200 Subject: [PATCH 012/294] Add **stencil buffer** support to `GPUPipelineState` --- Source/Engine/Graphics/GPUDevice.cpp | 141 ++++++++---------- Source/Engine/Graphics/GPUPipelineState.h | 60 ++++++++ .../DirectX/DX12/GPUPipelineStateDX12.cpp | 39 ++++- .../Vulkan/DescriptorSetVulkan.h | 49 ------ .../Vulkan/GPUPipelineStateVulkan.cpp | 33 ++++ 5 files changed, 188 insertions(+), 134 deletions(-) diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index acc1f4525..019cfad5a 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -90,96 +90,79 @@ GPUResourceType GPUPipelineState::GetResourceType() const return GPUResourceType::PipelineState; } +// @formatter:off GPUPipelineState::Description GPUPipelineState::Description::Default = { - // Enable/disable depth write - true, - // Enable/disable depth test - true, - // DepthClipEnable - true, - // DepthFunc - ComparisonFunc::Less, - // Vertex shader - nullptr, - // Hull shader - nullptr, - // Domain shader - nullptr, - // Geometry shader - nullptr, - // Pixel shader - nullptr, - // Primitives topology - PrimitiveTopologyType::Triangle, - // True if use wireframe rendering - false, - // Primitives culling mode - CullMode::Normal, - // Colors blending mode - BlendingMode::Opaque, + true, // DepthEnable + true, // DepthWriteEnable + true, // DepthClipEnable + ComparisonFunc::Less, // DepthFunc + false, // StencilEnable + 0xff, // StencilReadMask + 0xff, // StencilWriteMask + ComparisonFunc::Always, // StencilFunc + StencilOperation::Keep, // StencilFailOp + StencilOperation::Keep, // StencilDepthFailOp + StencilOperation::Keep, // StencilPassOp + nullptr, // VS + nullptr, // HS + nullptr, // DS + nullptr, // GS + nullptr, // PS + PrimitiveTopologyType::Triangle, // PrimitiveTopology + false, // Wireframe + CullMode::Normal, // CullMode + BlendingMode::Opaque, // BlendMode }; GPUPipelineState::Description GPUPipelineState::Description::DefaultNoDepth = { - // Enable/disable depth write - false, - // Enable/disable depth test - false, - // DepthClipEnable - false, - // DepthFunc - ComparisonFunc::Less, - // Vertex shader - nullptr, - // Hull shader - nullptr, - // Domain shader - nullptr, - // Geometry shader - nullptr, - // Pixel shader - nullptr, - // Primitives topology - PrimitiveTopologyType::Triangle, - // True if use wireframe rendering - false, - // Primitives culling mode - CullMode::Normal, - // Colors blending mode - BlendingMode::Opaque, + false, // DepthEnable + false, // DepthWriteEnable + false, // DepthClipEnable + ComparisonFunc::Less, // DepthFunc + false, // StencilEnable + 0xff, // StencilReadMask + 0xff, // StencilWriteMask + ComparisonFunc::Always, // StencilFunc + StencilOperation::Keep, // StencilFailOp + StencilOperation::Keep, // StencilDepthFailOp + StencilOperation::Keep, // StencilPassOp + nullptr, // VS + nullptr, // HS + nullptr, // DS + nullptr, // GS + nullptr, // PS + PrimitiveTopologyType::Triangle, // PrimitiveTopology + false, // Wireframe + CullMode::Normal, // CullMode + BlendingMode::Opaque, // BlendMode }; GPUPipelineState::Description GPUPipelineState::Description::DefaultFullscreenTriangle = { - // Enable/disable depth write - false, - // Enable/disable depth test - false, - // DepthClipEnable - false, - // DepthFunc - ComparisonFunc::Less, - // Vertex shader - nullptr, - // Set to default quad VS via GPUDevice - // Hull shader - nullptr, - // Domain shader - nullptr, - // Geometry shader - nullptr, - // Pixel shader - nullptr, - // Primitives topology - PrimitiveTopologyType::Triangle, - // True if use wireframe rendering - false, - // Primitives culling mode - CullMode::TwoSided, - // Colors blending mode - BlendingMode::Opaque, + false, // DepthEnable + false, // DepthWriteEnable + false, // DepthClipEnable + ComparisonFunc::Less, // DepthFunc + false, // StencilEnable + 0xff, // StencilReadMask + 0xff, // StencilWriteMask + ComparisonFunc::Always, // StencilFunc + StencilOperation::Keep, // StencilFailOp + StencilOperation::Keep, // StencilDepthFailOp + StencilOperation::Keep, // StencilPassOp + nullptr, // VS (Set to default quad VS via GPUDevice) + nullptr, // HS + nullptr, // DS + nullptr, // GS + nullptr, // PS + PrimitiveTopologyType::Triangle, // PrimitiveTopology + false, // Wireframe + CullMode::TwoSided, // CullMode + BlendingMode::Opaque, // BlendMode }; +// @formatter:on GPUResource::GPUResource() : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) diff --git a/Source/Engine/Graphics/GPUPipelineState.h b/Source/Engine/Graphics/GPUPipelineState.h index b9b6395e4..880817d8c 100644 --- a/Source/Engine/Graphics/GPUPipelineState.h +++ b/Source/Engine/Graphics/GPUPipelineState.h @@ -7,6 +7,31 @@ #include "Enums.h" #include "GPUResource.h" +/// +/// Stencil operation modes. +/// +API_ENUM() enum class StencilOperation : byte +{ + // Keep the existing stencil data. + Keep, + // Set the stencil data to 0. + Zero, + // Set the stencil data to the reference value (set via GPUContext::SetStencilRef). + Replace, + // Increment the stencil value by 1, and clamp the result. + IncrementSaturated, + // Decrement the stencil value by 1, and clamp the result. + DecrementSaturated, + // Invert the stencil data. + Invert, + // Increment the stencil value by 1, and wrap the result if necessary. + Increment, + // Decrement the stencil value by 1, and wrap the result if necessary. + Decrement, + + API_ENUM(Attributes="HideInEditor") MAX +}; + /// /// Describes full graphics pipeline state within single object. /// @@ -44,6 +69,41 @@ public: /// API_FIELD() ComparisonFunc DepthFunc; + /// + /// Enable/disable stencil buffer usage + /// + API_FIELD() bool StencilEnable; + + /// + /// The read mask applied to the reference value and each stencil buffer entry to determine the significant bits for the stencil test. + /// + API_FIELD() uint8 StencilReadMask; + + /// + /// The write mask applied to values written into the stencil buffer. + /// + API_FIELD() uint8 StencilWriteMask; + + /// + /// The comparison function for the stencil test. + /// + API_FIELD() ComparisonFunc StencilFunc; + + /// + /// The stencil operation to perform when stencil testing fails. + /// + API_FIELD() StencilOperation StencilFailOp; + + /// + /// The stencil operation to perform when stencil testing passes and depth testing fails. + /// + API_FIELD() StencilOperation StencilDepthFailOp; + + /// + /// The stencil operation to perform when stencil testing and depth testing both pass. + /// + API_FIELD() StencilOperation StencilPassOp; + /// /// Vertex shader program /// diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index b23d4d2fe..79f1895dc 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -9,6 +9,31 @@ #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" #include "Engine/Graphics/PixelFormatExtensions.h" +static D3D12_STENCIL_OP ToStencilOp(StencilOperation value) +{ + switch (value) + { + case StencilOperation::Keep: + return D3D12_STENCIL_OP_KEEP; + case StencilOperation::Zero: + return D3D12_STENCIL_OP_ZERO; + case StencilOperation::Replace: + return D3D12_STENCIL_OP_REPLACE; + case StencilOperation::IncrementSaturated: + return D3D12_STENCIL_OP_INCR_SAT; + case StencilOperation::DecrementSaturated: + return D3D12_STENCIL_OP_DECR_SAT; + case StencilOperation::Invert: + return D3D12_STENCIL_OP_INVERT; + case StencilOperation::Increment: + return D3D12_STENCIL_OP_INCR; + case StencilOperation::Decrement: + return D3D12_STENCIL_OP_DECR; + default: + return D3D12_STENCIL_OP_KEEP; + }; +} + GPUPipelineStateDX12::GPUPipelineStateDX12(GPUDeviceDX12* device) : GPUResourceDX12(device, StringView::Empty) , _states(16) @@ -169,12 +194,14 @@ bool GPUPipelineStateDX12::Init(const Description& desc) psDesc.DepthStencilState.DepthEnable = !!desc.DepthEnable; psDesc.DepthStencilState.DepthWriteMask = desc.DepthWriteEnable ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; psDesc.DepthStencilState.DepthFunc = static_cast(desc.DepthFunc); - psDesc.DepthStencilState.StencilEnable = FALSE; - psDesc.DepthStencilState.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; - psDesc.DepthStencilState.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; - const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = { D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS }; - psDesc.DepthStencilState.FrontFace = defaultStencilOp; - psDesc.DepthStencilState.BackFace = defaultStencilOp; + psDesc.DepthStencilState.StencilEnable = !!desc.StencilEnable; + psDesc.DepthStencilState.StencilReadMask = desc.StencilReadMask; + psDesc.DepthStencilState.StencilWriteMask = desc.StencilWriteMask; + psDesc.DepthStencilState.FrontFace.StencilFailOp = ToStencilOp(desc.StencilFailOp); + psDesc.DepthStencilState.FrontFace.StencilDepthFailOp = ToStencilOp(desc.StencilDepthFailOp); + psDesc.DepthStencilState.FrontFace.StencilPassOp = ToStencilOp(desc.StencilPassOp); + psDesc.DepthStencilState.FrontFace.StencilFunc = static_cast(desc.StencilFunc); + psDesc.DepthStencilState.BackFace = psDesc.DepthStencilState.FrontFace; // Rasterizer State psDesc.RasterizerState.FillMode = desc.Wireframe ? D3D12_FILL_MODE_WIREFRAME : D3D12_FILL_MODE_SOLID; diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h index a3aff7b60..d8d80b51e 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h @@ -26,71 +26,22 @@ namespace DescriptorSet { // Vertex shader stage Vertex = 0, - // Pixel shader stage Pixel = 1, - // Geometry shader stage Geometry = 2, - // Hull shader stage Hull = 3, - // Domain shader stage Domain = 4, - // Graphics pipeline stages count GraphicsStagesCount = 5, - // Compute pipeline slot Compute = 0, - // The maximum amount of slots for all stages Max = 5, }; - inline Stage GetSetForFrequency(ShaderStage stage) - { - switch (stage) - { - case ShaderStage::Vertex: - return Vertex; - case ShaderStage::Hull: - return Hull; - case ShaderStage::Domain: - return Domain; - case ShaderStage::Pixel: - return Pixel; - case ShaderStage::Geometry: - return Geometry; - case ShaderStage::Compute: - return Compute; - default: - CRASH; - return Max; - } - } - - inline ShaderStage GetFrequencyForGfxSet(Stage stage) - { - switch (stage) - { - case Vertex: - return ShaderStage::Vertex; - case Hull: - return ShaderStage::Hull; - case Domain: - return ShaderStage::Domain; - case Pixel: - return ShaderStage::Pixel; - case Geometry: - return ShaderStage::Geometry; - default: - CRASH; - return (ShaderStage)ShaderStage_Count; - } - } - template inline bool CopyAndReturnNotEqual(T& a, T b) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 5946aee20..136a7c6e5 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -9,6 +9,31 @@ #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" +static VkStencilOp ToVulkanStencilOp(const StencilOperation value) +{ + switch (value) + { + case StencilOperation::Keep: + return VK_STENCIL_OP_KEEP; + case StencilOperation::Zero: + return VK_STENCIL_OP_ZERO; + case StencilOperation::Replace: + return VK_STENCIL_OP_REPLACE; + case StencilOperation::IncrementSaturated: + return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case StencilOperation::DecrementSaturated: + return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case StencilOperation::Invert: + return VK_STENCIL_OP_INVERT; + case StencilOperation::Increment: + return VK_STENCIL_OP_INCREMENT_AND_WRAP; + case StencilOperation::Decrement: + return VK_STENCIL_OP_DECREMENT_AND_WRAP; + default: + return VK_STENCIL_OP_KEEP; + } +} + GPUShaderProgramCSVulkan::~GPUShaderProgramCSVulkan() { if (_pipelineState) @@ -289,6 +314,14 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _descDepthStencil.depthTestEnable = desc.DepthEnable; _descDepthStencil.depthWriteEnable = desc.DepthWriteEnable; _descDepthStencil.depthCompareOp = RenderToolsVulkan::ToVulkanCompareOp(desc.DepthFunc); + _descDepthStencil.stencilTestEnable = desc.StencilEnable; + _descDepthStencil.front.compareMask = desc.StencilReadMask; + _descDepthStencil.front.writeMask = desc.StencilWriteMask; + _descDepthStencil.front.compareOp = RenderToolsVulkan::ToVulkanCompareOp(desc.StencilFunc); + _descDepthStencil.front.failOp = ToVulkanStencilOp(desc.StencilFailOp); + _descDepthStencil.front.depthFailOp = ToVulkanStencilOp(desc.StencilDepthFailOp); + _descDepthStencil.front.passOp = ToVulkanStencilOp(desc.StencilPassOp); + _descDepthStencil.front = _descDepthStencil.back; _desc.pDepthStencilState = &_descDepthStencil; // Rasterization From 9b0fdb2cbd800c7895a37b9c116d50daafe8f117 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Jun 2023 13:59:04 +0200 Subject: [PATCH 013/294] Disable assertions in `Release` build mode --- Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp | 2 +- Source/Engine/Core/Config.h | 2 +- Source/Engine/Networking/NetworkReplicator.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index 84642ac1f..e25b83447 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -90,7 +90,7 @@ namespace ALC } ASSERT(Contexts.HasItems()); #else - ASSERT(Contexts.Count() == 1) + ASSERT(Contexts.Count() == 1); #endif return Contexts[0]; } diff --git a/Source/Engine/Core/Config.h b/Source/Engine/Core/Config.h index f3a2ba4be..889ab60bd 100644 --- a/Source/Engine/Core/Config.h +++ b/Source/Engine/Core/Config.h @@ -36,7 +36,7 @@ #define CRASH_LOG_ENABLE (!BUILD_RELEASE) // Enable/disable assertion -#define ENABLE_ASSERTION 1 +#define ENABLE_ASSERTION (!BUILD_RELEASE) // Enable/disable assertion for Engine low layers #define ENABLE_ASSERTION_LOW_LAYERS ENABLE_ASSERTION && (BUILD_DEBUG || FLAX_TESTS) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c41e104ef..11bd5f916 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1468,7 +1468,7 @@ void NetworkInternal::NetworkReplicatorUpdate() // Send object to clients const uint32 size = stream->GetPosition(); - ASSERT(size <= MAX_uint16) + ASSERT(size <= MAX_uint16); NetworkMessageObjectReplicate msgData; msgData.OwnerFrame = NetworkManager::Frame; msgData.ObjectId = item.ObjectId; @@ -1497,7 +1497,7 @@ void NetworkInternal::NetworkReplicatorUpdate() } else dataStart += size; - ASSERT(partsCount <= MAX_uint8) + ASSERT(partsCount <= MAX_uint8); msgData.PartsCount = partsCount; NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); From 4741f194f6107fc57def1d23630aeb0163d25bd9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 21 Jun 2023 23:07:52 +0200 Subject: [PATCH 014/294] Update CMake project to the latest VS 2022 --- Source/Tools/Flax.Build/Deps/Dependency.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Tools/Flax.Build/Deps/Dependency.cs b/Source/Tools/Flax.Build/Deps/Dependency.cs index 7f84c9c09..29b9fd58a 100644 --- a/Source/Tools/Flax.Build/Deps/Dependency.cs +++ b/Source/Tools/Flax.Build/Deps/Dependency.cs @@ -259,20 +259,20 @@ namespace Flax.Deps switch (architecture) { case TargetArchitecture.x86: - arch = string.Empty; + arch = "Win32"; break; case TargetArchitecture.x64: - arch = " Win64"; + arch = "x64"; break; case TargetArchitecture.ARM: - arch = " ARM"; + arch = "ARM"; break; case TargetArchitecture.ARM64: - arch = " ARM64"; + arch = "ARM64"; break; default: throw new InvalidArchitectureException(architecture); } - cmdLine = string.Format("CMakeLists.txt -G \"Visual Studio 14 2015{0}\"", arch); + cmdLine = string.Format("CMakeLists.txt -G \"Visual Studio 17 2022\" -A {0}", arch); break; } case TargetPlatform.Linux: From 656866c1d4d502295d96db496031cede5f436484 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 29 Jun 2023 16:41:15 +0200 Subject: [PATCH 015/294] Various improvements in Editor code --- Source/Editor/GUI/Popups/ScriptSearchPopup.cs | 1 - Source/Editor/Modules/ContentEditingModule.cs | 14 ++++++++++++++ Source/Editor/Windows/Assets/VisualScriptWindow.cs | 6 +----- .../Editor/Windows/VisualScriptDebuggerWindow.cs | 7 +------ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs index b771adff6..f56b07c2d 100644 --- a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs +++ b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs index 3db27af40..f1bb81ce1 100644 --- a/Source/Editor/Modules/ContentEditingModule.cs +++ b/Source/Editor/Modules/ContentEditingModule.cs @@ -19,6 +19,20 @@ namespace FlaxEditor.Modules { } + /// + /// Opens the specified asset in dedicated editor window. + /// + /// The asset. + /// True if disable automatic window showing. Used during workspace layout loading to deserialize it faster. + /// Opened window or null if cannot open item. + public EditorWindow Open(Asset asset, bool disableAutoShow = false) + { + if (asset == null) + throw new ArgumentNullException(); + var item = Editor.ContentDatabase.FindAsset(asset.ID); + return item != null ? Open(item) : null; + } + /// /// Opens the specified item in dedicated editor window. /// diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index ed20b591e..89a74c9e0 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -993,11 +993,7 @@ namespace FlaxEditor.Windows.Assets } } if (vsWindow == null) - { - var item = Editor.Instance.ContentDatabase.FindAsset(frame.Script.ID); - if (item != null) - vsWindow = Editor.Instance.ContentEditing.Open(item) as VisualScriptWindow; - } + vsWindow = Editor.Instance.ContentEditing.Open(frame.Script) as VisualScriptWindow; var node = vsWindow?.Surface.FindNode(frame.NodeId); _debugStepOutNodesIds.Add(new KeyValuePair(frame.Script, frame.NodeId)); if (node != null) diff --git a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs index 330a3963b..c84a9ba33 100644 --- a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs +++ b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs @@ -7,7 +7,6 @@ using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tabs; using FlaxEditor.GUI.Tree; -using FlaxEditor.Scripting; using FlaxEditor.Surface; using FlaxEditor.Windows.Assets; using FlaxEngine; @@ -63,11 +62,7 @@ namespace FlaxEditor.Windows } } if (vsWindow == null) - { - var item = Editor.Instance.ContentDatabase.FindAsset(nodeInfo.Script.ID); - if (item != null) - vsWindow = Editor.Instance.ContentEditing.Open(item) as VisualScriptWindow; - } + vsWindow = Editor.Instance.ContentEditing.Open(nodeInfo.Script) as VisualScriptWindow; return vsWindow?.Surface.FindNode(nodeInfo.NodeId); } return null; From e3cbe1458d5d8cae29540c2ab1809d62e727de35 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 29 Jun 2023 16:50:00 +0200 Subject: [PATCH 016/294] Add `MeshReference` to `ModelInstanceActor` for easy mesh referencing and its data access interface --- .../Dedicated/MeshReferenceEditor.cs | 336 ++++++++++++++++++ Source/Engine/Level/Actors/AnimatedModel.cpp | 12 + Source/Engine/Level/Actors/AnimatedModel.h | 1 + .../Engine/Level/Actors/ModelInstanceActor.h | 34 +- Source/Engine/Level/Actors/StaticModel.cpp | 12 + Source/Engine/Level/Actors/StaticModel.h | 1 + 6 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs new file mode 100644 index 000000000..71c5f6daf --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs @@ -0,0 +1,336 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using System.Linq; +using FlaxEditor.CustomEditors.Editors; +using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.GUI; +using FlaxEditor.Scripting; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(ModelInstanceActor.MeshReference)), DefaultEditor] + public class MeshReferenceEditor : CustomEditor + { + private class MeshRefPickerControl : Control + { + private ModelInstanceActor.MeshReference _value = new ModelInstanceActor.MeshReference { LODIndex = -1, MeshIndex = -1 }; + private string _valueName; + private Float2 _mousePos; + + public string[][] MeshNames; + public event Action ValueChanged; + + public ModelInstanceActor.MeshReference Value + { + get => _value; + set + { + if (_value.LODIndex == value.LODIndex && _value.MeshIndex == value.MeshIndex) + return; + _value = value; + if (value.LODIndex == -1 || value.MeshIndex == -1) + _valueName = null; + else if (MeshNames.Length == 1) + _valueName = MeshNames[value.LODIndex][value.MeshIndex]; + else + _valueName = $"LOD{value.LODIndex} - {MeshNames[value.LODIndex][value.MeshIndex]}"; + ValueChanged?.Invoke(); + } + } + + public MeshRefPickerControl() + : base(0, 0, 50, 16) + { + } + + private void ShowDropDownMenu() + { + // Show context menu with tree structure of LODs and meshes + Focus(); + var cm = new ItemsListContextMenu(200); + var meshNames = MeshNames; + var actor = _value.Actor; + for (int lodIndex = 0; lodIndex < meshNames.Length; lodIndex++) + { + var item = new ItemsListContextMenu.Item + { + Name = "LOD" + lodIndex, + Tag = new ModelInstanceActor.MeshReference { Actor = actor, LODIndex = lodIndex, MeshIndex = 0 }, + TintColor = new Color(0.8f, 0.8f, 1.0f, 0.8f), + }; + cm.AddItem(item); + + for (int meshIndex = 0; meshIndex < meshNames[lodIndex].Length; meshIndex++) + { + item = new ItemsListContextMenu.Item + { + Name = " " + meshNames[lodIndex][meshIndex], + Tag = new ModelInstanceActor.MeshReference { Actor = actor, LODIndex = lodIndex, MeshIndex = meshIndex }, + }; + if (_value.LODIndex == lodIndex && _value.MeshIndex == meshIndex) + item.BackgroundColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; + cm.AddItem(item); + } + } + cm.ItemClicked += item => Value = (ModelInstanceActor.MeshReference)item.Tag; + cm.Show(Parent, BottomLeft); + } + + /// + public override void Draw() + { + base.Draw(); + + // Cache data + var style = FlaxEngine.GUI.Style.Current; + bool isSelected = _valueName != null; + bool isEnabled = EnabledInHierarchy; + var frameRect = new Rectangle(0, 0, Width, 16); + if (isSelected) + frameRect.Width -= 16; + frameRect.Width -= 16; + var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14); + var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14); + var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14); + + // Draw frame + Render2D.DrawRectangle(frameRect, isEnabled && (IsMouseOver || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal); + + // Check if has item selected + if (isSelected) + { + // Draw name + Render2D.PushClip(nameRect); + Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.PopClip(); + + // Draw deselect button + Render2D.DrawSprite(style.Cross, button1Rect, isEnabled && button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); + } + else + { + // Draw info + Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); + } + + // Draw picker button + var pickerRect = isSelected ? button2Rect : button1Rect; + Render2D.DrawSprite(style.ArrowDown, pickerRect, isEnabled && pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); + } + + /// + public override void OnMouseEnter(Float2 location) + { + _mousePos = location; + + base.OnMouseEnter(location); + } + + /// + public override void OnMouseLeave() + { + _mousePos = Float2.Minimum; + + base.OnMouseLeave(); + } + + /// + public override void OnMouseMove(Float2 location) + { + _mousePos = location; + + base.OnMouseMove(location); + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + // Cache data + bool isSelected = _valueName != null; + var frameRect = new Rectangle(0, 0, Width, 16); + if (isSelected) + frameRect.Width -= 16; + frameRect.Width -= 16; + var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14); + var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14); + var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14); + + // Deselect + if (isSelected && button1Rect.Contains(ref location)) + Value = new ModelInstanceActor.MeshReference { Actor = null, LODIndex = -1, MeshIndex = -1 }; + + // Picker dropdown menu + if ((isSelected ? button2Rect : button1Rect).Contains(ref location)) + ShowDropDownMenu(); + + return base.OnMouseUp(location, button); + } + + /// + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + Focus(); + + // Open model editor window + if (_value.Actor is StaticModel staticModel) + Editor.Instance.ContentEditing.Open(staticModel.Model); + else if (_value.Actor is AnimatedModel animatedModel) + Editor.Instance.ContentEditing.Open(animatedModel.SkinnedModel); + + return base.OnMouseDoubleClick(location, button); + } + + /// + public override void OnSubmit() + { + base.OnSubmit(); + + ShowDropDownMenu(); + } + + /// + public override void OnDestroy() + { + MeshNames = null; + _valueName = null; + + base.OnDestroy(); + } + } + + private ModelInstanceActor _actor; + private CustomElement _actorPicker; + private CustomElement _meshPicker; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + // Get the context actor to pick the mesh from it + if (GetActor(out var actor)) + { + // TODO: support editing multiple values + layout.Label("Different values"); + return; + } + _actor = actor; + + var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth); + if (showActorPicker) + { + // Actor reference picker + _actorPicker = layout.Custom(); + _actorPicker.CustomControl.Type = new ScriptType(typeof(ModelInstanceActor)); + _actorPicker.CustomControl.ValueChanged += () => SetValue(new ModelInstanceActor.MeshReference { Actor = (ModelInstanceActor)_actorPicker.CustomControl.Value }); + } + + if (actor != null) + { + // Get mesh names hierarchy + string[][] meshNames; + if (actor is StaticModel staticModel) + { + var model = staticModel.Model; + if (model == null || model.WaitForLoaded()) + return; + var materials = model.MaterialSlots; + var lods = model.LODs; + meshNames = new string[lods.Length][]; + for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++) + { + var lodMeshes = lods[lodIndex].Meshes; + meshNames[lodIndex] = new string[lodMeshes.Length]; + for (int meshIndex = 0; meshIndex < lodMeshes.Length; meshIndex++) + { + var mesh = lodMeshes[meshIndex]; + var materialName = materials[mesh.MaterialSlotIndex].Name; + if (string.IsNullOrEmpty(materialName) && materials[mesh.MaterialSlotIndex].Material) + materialName = Path.GetFileNameWithoutExtension(materials[mesh.MaterialSlotIndex].Material.Path); + if (string.IsNullOrEmpty(materialName)) + meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex}"; + else + meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex} ({materialName})"; + } + } + } + else if (actor is AnimatedModel animatedModel) + { + var skinnedModel = animatedModel.SkinnedModel; + if (skinnedModel == null || skinnedModel.WaitForLoaded()) + return; + var materials = skinnedModel.MaterialSlots; + var lods = skinnedModel.LODs; + meshNames = new string[lods.Length][]; + for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++) + { + var lodMeshes = lods[lodIndex].Meshes; + meshNames[lodIndex] = new string[lodMeshes.Length]; + for (int meshIndex = 0; meshIndex < lodMeshes.Length; meshIndex++) + { + var mesh = lodMeshes[meshIndex]; + var materialName = materials[mesh.MaterialSlotIndex].Name; + if (string.IsNullOrEmpty(materialName) && materials[mesh.MaterialSlotIndex].Material) + materialName = Path.GetFileNameWithoutExtension(materials[mesh.MaterialSlotIndex].Material.Path); + if (string.IsNullOrEmpty(materialName)) + meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex}"; + else + meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex} ({materialName})"; + } + } + } + else + return; // Not supported model type + + // Mesh reference picker + _meshPicker = layout.Custom(); + _meshPicker.CustomControl.MeshNames = meshNames; + _meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0]; + _meshPicker.CustomControl.ValueChanged += () => SetValue(_meshPicker.CustomControl.Value); + } + } + + /// + public override void Refresh() + { + base.Refresh(); + + if (_actorPicker != null) + { + GetActor(out var actor); + _actorPicker.CustomControl.Value = actor; + if (actor != _actor) + { + RebuildLayout(); + return; + } + } + if (_meshPicker != null) + { + _meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0]; + } + } + + private bool GetActor(out ModelInstanceActor actor) + { + actor = null; + foreach (ModelInstanceActor.MeshReference value in Values) + { + if (actor == null) + actor = value.Actor; + else if (actor != value.Actor) + return true; + } + return false; + } + } +} diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 882026db7..5f2d65917 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -945,6 +945,18 @@ bool AnimatedModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& nor return result; } +bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const +{ + count = 0; + if (mesh.LODIndex < 0 || mesh.MeshIndex < 0) + return true; + const auto model = SkinnedModel.Get(); + if (!model || model->WaitForLoaded()) + return true; + auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)]; + return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); +} + void AnimatedModel::OnDeleteObject() { // Ensure this object is no longer referenced for anim update diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index ccd0ee3b5..904216356 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -374,6 +374,7 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; + bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; void OnDeleteObject() override; protected: diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 825b64c23..850bc8715 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -12,6 +12,23 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor { DECLARE_SCENE_OBJECT_ABSTRACT(ModelInstanceActor); + + /// + /// Utility container to reference a single mesh within . + /// + API_STRUCT(NoDefault) struct MeshReference : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(MeshReference); + API_AUTO_SERIALIZATION(); + + // Owning actor. + API_FIELD() ScriptingObjectReference Actor; + // Index of the LOD (Level Of Detail). + API_FIELD() int32 LODIndex = 0; + // Index of the mesh (within the LOD). + API_FIELD() int32 MeshIndex = 0; + }; + protected: int32 _sceneRenderingKey = -1; // Uses SceneRendering::DrawCategory::SceneDrawAsync @@ -24,8 +41,8 @@ public: /// /// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.). /// - API_PROPERTY(Attributes="Serialize, EditorOrder(1000), EditorDisplay(\"Entries\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems = false, NotNullItems = true, ReadOnly = true, Spacing = 10)") - FORCE_INLINE Array GetEntries() const + API_PROPERTY(Attributes="Serialize, EditorOrder(1000), EditorDisplay(\"Entries\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems=false, NotNullItems=true, ReadOnly=true, Spacing=10)") + FORCE_INLINE const Array& GetEntries() const { return Entries; } @@ -80,6 +97,19 @@ public: { return false; } + + /// + /// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel). + /// + /// Mesh reference. + /// Buffer type + /// The result data + /// The amount of items inside the result buffer. + /// True if failed, otherwise false + virtual bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const + { + return true; + } protected: virtual void WaitForModelLoad(); diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 3eb3b6012..4e67d90d2 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -588,6 +588,18 @@ bool StaticModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& norma return result; } +bool StaticModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const +{ + count = 0; + if (mesh.LODIndex < 0 || mesh.MeshIndex < 0) + return true; + const auto model = Model.Get(); + if (!model || model->WaitForLoaded()) + return true; + auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)]; + return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); +} + void StaticModel::OnTransformChanged() { // Base diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index af3aa53c0..ffb34e0a1 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -168,6 +168,7 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; + bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; protected: // [ModelInstanceActor] From 0d7e7edf80d432d163b00b1b56b4bb5f93972088 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 29 Jun 2023 16:50:53 +0200 Subject: [PATCH 017/294] Optimize `BoundingBox::Transform` --- Source/Engine/Core/Math/BoundingBox.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp index 8aeb2f222..9ce8f3e86 100644 --- a/Source/Engine/Core/Math/BoundingBox.cpp +++ b/Source/Engine/Core/Math/BoundingBox.cpp @@ -138,15 +138,15 @@ void BoundingBox::Transform(const BoundingBox& box, const ::Transform& transform { // Reference: http://dev.theomader.com/transform-bounding-boxes/ - const auto right = transform.GetRight(); + const auto right = Float3::Transform(Float3::Right, transform.Orientation); const auto xa = right * box.Minimum.X; const auto xb = right * box.Maximum.X; - const auto up = transform.GetUp(); + const auto up = Float3::Transform(Float3::Up, transform.Orientation); const auto ya = up * box.Minimum.Y; const auto yb = up * box.Maximum.Y; - const auto backward = transform.GetBackward(); + const auto backward = Float3::Transform(Float3::Backward, transform.Orientation); const auto za = backward * box.Minimum.Z; const auto zb = backward * box.Maximum.Z; From 04963fef18d65087f02e0da49e295433d4687705 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jul 2023 15:46:29 +0200 Subject: [PATCH 018/294] Various code fixes and tweaks --- Source/Engine/Content/AssetReference.h | 12 ++++++------ Source/Engine/Content/Assets/SkinnedModel.h | 3 ++- Source/Engine/Content/SoftAssetReference.h | 2 +- Source/Engine/Content/WeakAssetReference.h | 4 ++-- Source/Engine/Graphics/Models/ModelData.cpp | 2 ++ Source/Engine/Graphics/Models/ModelData.h | 6 +++--- Source/Engine/Graphics/Models/SkeletonUpdater.h | 3 +-- Source/Engine/Graphics/Models/SkinnedMeshDrawData.h | 2 +- Source/Engine/Scripting/ScriptingObjectReference.h | 6 +++--- Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp | 1 + Source/ThirdParty/PhysX/foundation/PxVecQuat.h | 4 ++-- Source/ThirdParty/PhysX/foundation/PxVecTransform.h | 4 ++-- .../PhysX/foundation/unix/PxUnixIntrinsics.h | 1 - 13 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index ee710539d..6aee12246 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -105,12 +105,12 @@ public: /// The other. AssetReference(const AssetReference& other) { - OnSet(other.Get()); + OnSet(other._asset); } AssetReference(AssetReference&& other) { - OnSet(other.Get()); + OnSet(other._asset); other.OnSet(nullptr); } @@ -118,7 +118,7 @@ public: { if (&other != this) { - OnSet(other.Get()); + OnSet(other._asset); other.OnSet(nullptr); } return *this; @@ -134,19 +134,19 @@ public: public: FORCE_INLINE AssetReference& operator=(const AssetReference& other) { - OnSet(other.Get()); + OnSet(other._asset); return *this; } FORCE_INLINE AssetReference& operator=(T* other) { - OnSet(other); + OnSet((Asset*)other); return *this; } FORCE_INLINE AssetReference& operator=(const Guid& id) { - OnSet((T*)::LoadAsset(id, T::TypeInitializer)); + OnSet(::LoadAsset(id, T::TypeInitializer)); return *this; } diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index 11fc0c0aa..d8db68d43 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -3,6 +3,7 @@ #pragma once #include "ModelBase.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Graphics/Models/Config.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Graphics/Models/SkinnedModelLOD.h" @@ -298,7 +299,7 @@ public: // Skeleton asset id to use for remapping. API_FIELD() Guid SkeletonAsset; // Skeleton nodes remapping table (maps this skeleton node name to other skeleton node). - API_FIELD() Dictionary NodesMapping; + API_FIELD() Dictionary NodesMapping; }; // Gets or sets the skeleton retarget entries (accessed in Editor only). API_PROPERTY() const Array& GetSkeletonRetargets() const { return _skeletonRetargets; } diff --git a/Source/Engine/Content/SoftAssetReference.h b/Source/Engine/Content/SoftAssetReference.h index b9afd7647..fe1cde8c2 100644 --- a/Source/Engine/Content/SoftAssetReference.h +++ b/Source/Engine/Content/SoftAssetReference.h @@ -143,7 +143,7 @@ public: } FORCE_INLINE SoftAssetReference& operator=(T* other) { - OnSet(other); + OnSet((Asset*)other); return *this; } FORCE_INLINE SoftAssetReference& operator=(const Guid& id) diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index 5a46d7515..898ffd562 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -127,13 +127,13 @@ public: FORCE_INLINE WeakAssetReference& operator=(T* other) { - OnSet(other); + OnSet((Asset*)other); return *this; } FORCE_INLINE WeakAssetReference& operator=(const Guid& id) { - OnSet((T*)::LoadAsset(id, T::TypeInitializer)); + OnSet((Asset*)::LoadAsset(id, T::TypeInitializer)); return *this; } diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index afa591e01..1d67f4737 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -2,6 +2,8 @@ #include "ModelData.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/WriteStream.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 1a5cdaad9..32caa2d01 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -2,9 +2,7 @@ #pragma once -#include "Engine/Core/Math/BoundingSphere.h" -#include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Serialization/Stream.h" +#include "Engine/Core/Types/Guid.h" #include "Engine/Graphics/Enums.h" #include "Types.h" #include "Config.h" @@ -12,6 +10,8 @@ #include "BlendShape.h" #include "Engine/Animations/AnimationData.h" +class WriteStream; + /// /// Data container for the common model meshes data. Supports holding all types of data related to the models pipeline. /// diff --git a/Source/Engine/Graphics/Models/SkeletonUpdater.h b/Source/Engine/Graphics/Models/SkeletonUpdater.h index c88fac326..3e3aa4e99 100644 --- a/Source/Engine/Graphics/Models/SkeletonUpdater.h +++ b/Source/Engine/Graphics/Models/SkeletonUpdater.h @@ -20,8 +20,7 @@ public: struct Node { /// - /// The parent node index. - /// The parent bone index. The root node uses value -1. + /// The parent node index. The root node uses value -1. /// int32 ParentIndex; diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h index a5293c455..982427760 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h @@ -2,7 +2,7 @@ #pragma once -#include "Engine/Core/Common.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Graphics/GPUBuffer.h" /// diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h index ec311490b..5fcf6c854 100644 --- a/Source/Engine/Scripting/ScriptingObjectReference.h +++ b/Source/Engine/Scripting/ScriptingObjectReference.h @@ -151,7 +151,7 @@ public: /// /// The other property. ScriptingObjectReference(const ScriptingObjectReference& other) - : ScriptingObjectReferenceBase(other.Get()) + : ScriptingObjectReferenceBase(other._object) { } @@ -188,12 +188,12 @@ public: } ScriptingObjectReference& operator=(const ScriptingObjectReference& other) { - OnSet(other.Get()); + OnSet(other._object); return *this; } FORCE_INLINE ScriptingObjectReference& operator=(const Guid& id) { - OnSet(static_cast(FindObject(id, T::GetStaticClass()))); + OnSet(static_cast(FindObject(id, T::GetStaticClass()))); return *this; } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index daecf8c45..64b7377bc 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/DeleteMe.h" #include "Engine/Core/Math/Matrix.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Tools/TextureTool/TextureTool.h" diff --git a/Source/ThirdParty/PhysX/foundation/PxVecQuat.h b/Source/ThirdParty/PhysX/foundation/PxVecQuat.h index 1c6d04a31..acb9a1371 100644 --- a/Source/ThirdParty/PhysX/foundation/PxVecQuat.h +++ b/Source/ThirdParty/PhysX/foundation/PxVecQuat.h @@ -298,7 +298,7 @@ PX_FORCE_INLINE bool isFiniteQuatV(const QuatV q) return isFiniteVec4V(q); } -#if PX_CLANG +#if PX_CLANG && __clang_major__ >= 12 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wbitwise-instead-of-logical" // bitwise intentionally chosen for performance #endif @@ -319,7 +319,7 @@ PX_FORCE_INLINE bool isSaneQuatV(const QuatV q) return isFiniteVec4V(q) & (BAllEqTTTT(con) == 1); } -#if PX_CLANG +#if PX_CLANG && __clang_major__ >= 12 #pragma clang diagnostic pop #endif diff --git a/Source/ThirdParty/PhysX/foundation/PxVecTransform.h b/Source/ThirdParty/PhysX/foundation/PxVecTransform.h index fcfd412f3..087303212 100644 --- a/Source/ThirdParty/PhysX/foundation/PxVecTransform.h +++ b/Source/ThirdParty/PhysX/foundation/PxVecTransform.h @@ -122,7 +122,7 @@ class PxTransformV return PxTransformV(V3Add(QuatRotate(q, src.p), p), QuatMul(q, src.q)); } -#if PX_CLANG +#if PX_CLANG && __clang_major__ >= 12 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wbitwise-instead-of-logical" // bitwise intentionally chosen for performance #endif @@ -156,7 +156,7 @@ class PxTransformV return isFiniteVec3V(p) & isFiniteQuatV(q); } -#if PX_CLANG +#if PX_CLANG && __clang_major__ >= 12 #pragma clang diagnostic pop #endif diff --git a/Source/ThirdParty/PhysX/foundation/unix/PxUnixIntrinsics.h b/Source/ThirdParty/PhysX/foundation/unix/PxUnixIntrinsics.h index 02104221a..cb529be5c 100644 --- a/Source/ThirdParty/PhysX/foundation/unix/PxUnixIntrinsics.h +++ b/Source/ThirdParty/PhysX/foundation/unix/PxUnixIntrinsics.h @@ -58,7 +58,6 @@ Return the index of the highest set bit. Undefined for zero arg. */ PX_INLINE uint32_t PxHighestSetBitUnsafe(uint32_t v) { - return uint32_t(31 - __builtin_clz(v)); } From 60181a29a3d13528430409b6a103b8e22320245c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jul 2023 15:46:55 +0200 Subject: [PATCH 019/294] Add copy/move ctor/operator to `Delegate` --- Source/Engine/Core/Delegate.h | 60 ++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index 4fcf60390..df1b4bb8d 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -12,14 +12,12 @@ class Function { friend Delegate; public: - /// /// Signature of the function to call. /// typedef ReturnType (*Signature)(Params ...); private: - typedef ReturnType (*StubSignature)(void*, Params ...); template @@ -68,7 +66,6 @@ private: } } public: - /// /// Initializes a new instance of the class. /// @@ -123,7 +120,6 @@ public: } public: - /// /// Binds a static function. /// @@ -194,7 +190,6 @@ public: } public: - /// /// Returns true if any function has been binded. /// @@ -257,7 +252,6 @@ template class Delegate { public: - /// /// Signature of the function to call. /// @@ -269,19 +263,39 @@ public: using FunctionType = Function; protected: - // Single allocation for list of binded functions. Thread-safe access via atomic operations. Removing binded function simply clears the entry to handle function unregister during invocation. intptr volatile _ptr = 0; intptr volatile _size = 0; typedef void (*StubSignature)(void*, Params ...); public: - NON_COPYABLE(Delegate); - Delegate() { } + Delegate(const Delegate& other) + { + const intptr newSize = other._size; + auto newBindings = (FunctionType*)Allocator::Allocate(newSize * sizeof(FunctionType)); + Platform::MemoryCopy((void*)newBindings, (const void*)other._ptr, newSize * sizeof(FunctionType)); + for (intptr i = 0; i < newSize; i++) + { + FunctionType& f = newBindings[i]; + if (f._function && f._lambda) + f.LambdaCtor(); + } + _ptr = (intptr)newBindings; + _size = newSize; + } + + Delegate(Delegate&& other) noexcept + { + _ptr = other._ptr; + _size = other._size; + other._ptr = 0; + other._size = 0; + } + ~Delegate() { auto ptr = (FunctionType*)_ptr; @@ -297,8 +311,32 @@ public: } } -public: + Delegate& operator=(const Delegate& other) + { + if (this != &other) + { + UnbindAll(); + const intptr size = Platform::AtomicRead((intptr volatile*)&other._size); + FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&other._ptr); + for (intptr i = 0; i < size; i++) + Bind(bindings[i]); + } + return *this; + } + Delegate& operator=(Delegate&& other) noexcept + { + if (this != &other) + { + _ptr = other._ptr; + _size = other._size; + other._ptr = 0; + other._size = 0; + } + return *this; + } + +public: /// /// Binds a static function. /// @@ -484,7 +522,7 @@ public: /// Unbinds the specified function. /// /// The function to unbind. - void Unbind(FunctionType& f) + void Unbind(const FunctionType& f) { // Find slot with that function const intptr size = Platform::AtomicRead(&_size); From 99ee0b1bfe08472e360260d41488c64d1b07f8f8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Jul 2023 09:49:23 +0200 Subject: [PATCH 020/294] Add `MeshDeformation` utility for generic meshes vertices morphing (eg. via Blend Shapes or Cloth) --- Flax.sln.DotSettings | 2 + .../Utilities/ViewportIconsRenderer.cpp | 2 + Source/Engine/Content/Assets/SkinnedModel.cpp | 2 + Source/Engine/Foliage/Foliage.cpp | 1 + Source/Engine/Graphics/DynamicBuffer.cpp | 6 +- Source/Engine/Graphics/Models/BlendShape.cpp | 180 ------------ Source/Engine/Graphics/Models/BlendShape.h | 63 ----- Source/Engine/Graphics/Models/Mesh.cpp | 11 + Source/Engine/Graphics/Models/MeshBase.h | 14 +- .../Graphics/Models/MeshDeformation.cpp | 166 +++++++++++ .../Engine/Graphics/Models/MeshDeformation.h | 58 ++++ Source/Engine/Graphics/Models/ModelLOD.cpp | 20 +- Source/Engine/Graphics/Models/ModelLOD.h | 3 +- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 37 +-- .../Graphics/Models/SkinnedModelLOD.cpp | 28 +- .../Engine/Graphics/Models/SkinnedModelLOD.h | 23 +- Source/Engine/Graphics/Models/Types.h | 4 + Source/Engine/Level/Actors/AnimatedModel.cpp | 257 +++++++++++++++--- Source/Engine/Level/Actors/AnimatedModel.h | 22 +- Source/Engine/Level/Actors/Camera.cpp | 1 + .../Level/Actors/ModelInstanceActor.cpp | 8 + .../Engine/Level/Actors/ModelInstanceActor.h | 19 +- Source/Engine/Level/Actors/SplineModel.cpp | 13 +- Source/Engine/Level/Actors/SplineModel.h | 2 +- Source/Engine/Level/Actors/StaticModel.cpp | 21 +- Source/Engine/Level/Actors/StaticModel.h | 5 +- 26 files changed, 600 insertions(+), 368 deletions(-) delete mode 100644 Source/Engine/Graphics/Models/BlendShape.cpp create mode 100644 Source/Engine/Graphics/Models/MeshDeformation.cpp create mode 100644 Source/Engine/Graphics/Models/MeshDeformation.h diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index fe1d1463e..9d82c0e65 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -255,6 +255,8 @@ True True True + True + True True True True diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index a5ed0356f..d16a31c63 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -127,6 +127,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene Matrix m1, m2, world; GeometryDrawStateData drawState; draw.DrawState = &drawState; + draw.Deformation = nullptr; draw.World = &world; AssetReference texture; for (Actor* icon : icons) @@ -205,6 +206,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor // Draw icon GeometryDrawStateData drawState; draw.DrawState = &drawState; + draw.Deformation = nullptr; // Support custom icons through types, but not onces that were added through actors, // since they cant register while in prefab view anyway diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index a2e687131..5871087d9 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -852,6 +852,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) { auto& lod = LODs[lodIndex]; lod._model = this; + lod._lodIndex = lodIndex; lod.ScreenSize = 1.0f; const int32 meshesCount = meshesCountPerLod[lodIndex]; if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES) @@ -1112,6 +1113,7 @@ Asset::LoadResult SkinnedModel::load() { auto& lod = LODs[lodIndex]; lod._model = this; + lod._lodIndex = lodIndex; // Screen Size stream->ReadFloat(&lod.ScreenSize); diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 23a31ad89..82765f151 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -1180,6 +1180,7 @@ void Foliage::Draw(RenderContext& renderContext) draw.Buffer = &type.Entries; draw.World = &world; draw.DrawState = &instance.DrawState; + draw.Deformation = nullptr; draw.Bounds = instance.Bounds; draw.PerInstanceRandom = instance.Random; draw.DrawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index 66b259027..23251418a 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -4,10 +4,10 @@ #include "GPUContext.h" #include "PixelFormatExtensions.h" #include "GPUDevice.h" +#include "RenderTask.h" #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" #include "Engine/Core/Math/Math.h" -#include "Engine/Threading/Threading.h" DynamicBuffer::DynamicBuffer(uint32 initialCapacity, uint32 stride, const String& name) : _buffer(nullptr) @@ -46,9 +46,11 @@ void DynamicBuffer::Flush() } // Upload data to the buffer - if (IsInMainThread() && GPUDevice::Instance->IsRendering()) + if (GPUDevice::Instance->IsRendering()) { + RenderContext::GPULocker.Lock(); GPUDevice::Instance->GetMainContext()->UpdateBuffer(_buffer, Data.Get(), size); + RenderContext::GPULocker.Unlock(); } else { diff --git a/Source/Engine/Graphics/Models/BlendShape.cpp b/Source/Engine/Graphics/Models/BlendShape.cpp deleted file mode 100644 index 15b63d1f4..000000000 --- a/Source/Engine/Graphics/Models/BlendShape.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -#include "BlendShape.h" -#include "Engine/Content/Assets/SkinnedModel.h" -#include "Engine/Graphics/GPUDevice.h" -#include "Engine/Profiler/ProfilerCPU.h" - -BlendShapesInstance::MeshInstance::MeshInstance() - : IsUsed(false) - , IsDirty(false) - , DirtyMinVertexIndex(0) - , DirtyMaxVertexIndex(MAX_uint32 - 1) - , VertexBuffer(0, sizeof(VB0SkinnedElementType), TEXT("Skinned Mesh Blend Shape")) -{ -} - -BlendShapesInstance::MeshInstance::~MeshInstance() -{ -} - -BlendShapesInstance::~BlendShapesInstance() -{ - Meshes.ClearDelete(); -} - -void BlendShapesInstance::Update(SkinnedModel* skinnedModel) -{ - if (!WeightsDirty) - return; - WeightsDirty = false; - ASSERT(skinnedModel); - if (skinnedModel->IsVirtual()) - { - // Blend shapes are not supported for virtual skinned models - return; - } - PROFILE_CPU_NAMED("Update Blend Shapes"); - - // Collect used meshes - for (auto& e : Meshes) - { - MeshInstance* instance = e.Value; - instance->IsUsed = false; - instance->IsDirty = false; - } - for (auto& e : Weights) - { - for (auto& lod : skinnedModel->LODs) - { - for (auto& mesh : lod.Meshes) - { - for (auto& blendShape : mesh.BlendShapes) - { - if (blendShape.Name != e.First) - continue; - - // Setup mesh instance and mark it as used for blending - MeshInstance* instance; - if (!Meshes.TryGet(&mesh, instance)) - { - instance = New(); - Meshes.Add(&mesh, instance); - } - instance->IsUsed = true; - instance->IsDirty = true; - - // One blend shape of that name per mesh - break; - } - } - } - } - - // Update all used meshes - for (auto& e : Meshes) - { - MeshInstance& instance = *e.Value; - if (!instance.IsUsed) - continue; - const SkinnedMesh* mesh = e.Key; - - // Get skinned mesh vertex buffer data (original, cached on CPU) - BytesContainer vertexBuffer; - int32 vertexCount; - if (mesh->DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffer, vertexCount)) - { - // Don't use this mesh if failed to get it's vertices data - instance.IsUsed = false; - continue; - } - - // Estimate the range of the vertices to modify by the currently active blend shapes - uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0; - bool useNormals = false; - Array, InlinedAllocation<32>> blendShapes; - for (const BlendShape& blendShape : mesh->BlendShapes) - { - for (auto& q : Weights) - { - if (q.First == blendShape.Name) - { - const float weight = q.Second * blendShape.Weight; - blendShapes.Add(Pair(blendShape, weight)); - minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex); - maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex); - useNormals |= blendShape.UseNormals; - break; - } - } - } - - // Initialize the dynamic vertex buffer data (use the dirty range from the previous update to be cleared with initial data) - instance.VertexBuffer.Data.Resize(vertexBuffer.Length()); - const uint32 dirtyVertexDataStart = instance.DirtyMinVertexIndex * sizeof(VB0SkinnedElementType); - const uint32 dirtyVertexDataLength = Math::Min(instance.DirtyMaxVertexIndex - instance.DirtyMinVertexIndex + 1, vertexCount) * sizeof(VB0SkinnedElementType); - Platform::MemoryCopy(instance.VertexBuffer.Data.Get() + dirtyVertexDataStart, vertexBuffer.Get() + dirtyVertexDataStart, dirtyVertexDataLength); - - // Blend all blend shapes - auto data = (VB0SkinnedElementType*)instance.VertexBuffer.Data.Get(); - for (const auto& q : blendShapes) - { - // TODO: use SIMD - if (useNormals) - { - for (int32 i = 0; i < q.First.Vertices.Count(); i++) - { - const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < (uint32)vertexCount); - VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); - vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; - Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta; - vertex.Normal = normal * 0.5f + 0.5f; - } - } - else - { - for (int32 i = 0; i < q.First.Vertices.Count(); i++) - { - const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < (uint32)vertexCount); - VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); - vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; - } - } - } - - if (useNormals) - { - // Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range) - // TODO: use SIMD - for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++) - { - VB0SkinnedElementType& vertex = *(data + vertexIndex); - - Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f; - normal.Normalize(); - vertex.Normal = normal * 0.5f + 0.5f; - - Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f; - tangent = tangent - ((tangent | normal) * normal); - tangent.Normalize(); - const auto tangentSign = vertex.Tangent.W; - vertex.Tangent = tangent * 0.5f + 0.5f; - vertex.Tangent.W = tangentSign; - } - } - - // Mark as dirty to be flushed before next rendering - instance.IsDirty = true; - instance.DirtyMinVertexIndex = minVertexIndex; - instance.DirtyMaxVertexIndex = maxVertexIndex; - } -} - -void BlendShapesInstance::Clear() -{ - Meshes.ClearDelete(); - Weights.Clear(); - WeightsDirty = false; -} diff --git a/Source/Engine/Graphics/Models/BlendShape.h b/Source/Engine/Graphics/Models/BlendShape.h index 10dc3b0a7..e2bfd82bc 100644 --- a/Source/Engine/Graphics/Models/BlendShape.h +++ b/Source/Engine/Graphics/Models/BlendShape.h @@ -3,15 +3,8 @@ #pragma once #include "Engine/Core/Types/String.h" -#include "Engine/Core/Types/Pair.h" #include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Math/Vector3.h" -#include "Engine/Graphics/DynamicBuffer.h" - -class SkinnedMesh; -class SkinnedModel; -class GPUBuffer; /// /// The blend shape vertex data optimized for runtime meshes morphing. @@ -76,59 +69,3 @@ public: /// Array Vertices; }; - -/// -/// The blend shapes runtime instance data. Handles blend shapes updating, blending and preparing for skinned mesh rendering. -/// -class BlendShapesInstance -{ -public: - /// - /// The runtime data for blend shapes used for on a mesh. - /// - class MeshInstance - { - public: - bool IsUsed; - bool IsDirty; - uint32 DirtyMinVertexIndex; - uint32 DirtyMaxVertexIndex; - DynamicVertexBuffer VertexBuffer; - - MeshInstance(); - ~MeshInstance(); - }; - -public: - /// - /// The blend shapes weights (pair of blend shape name and the weight). - /// - Array> Weights; - - /// - /// Flag that marks if blend shapes weights has been modified. - /// - bool WeightsDirty = false; - - /// - /// The blend shapes meshes data. - /// - Dictionary Meshes; - -public: - /// - /// Finalizes an instance of the class. - /// - ~BlendShapesInstance(); - - /// - /// Updates the instanced meshes. Performs the blend shapes blending (only if weights were modified). - /// - /// The skinned model used for blend shapes. - void Update(SkinnedModel* skinnedModel); - - /// - /// Clears the runtime data. - /// - void Clear(); -}; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e5ba5488d..a2d962b64 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Mesh.h" +#include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/Model.h" @@ -480,6 +481,11 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + { + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); + info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]); + } if (info.VertexColors && info.VertexColors[_lodIndex]) { // TODO: cache vertexOffset within the model LOD per-mesh @@ -540,6 +546,11 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + { + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); + info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]); + } if (info.VertexColors && info.VertexColors[_lodIndex]) { // TODO: cache vertexOffset within the model LOD per-mesh diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 0d07ec4ec..441267436 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -159,7 +159,7 @@ public: struct DrawInfo { /// - /// The instance buffer to use during model rendering + /// The instance buffer to use during model rendering. /// ModelInstanceEntries* Buffer; @@ -169,10 +169,15 @@ public: Matrix* World; /// - /// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating. + /// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating. /// GeometryDrawStateData* DrawState; + /// + /// The instance deformation utility. + /// + MeshDeformation* Deformation; + union { struct @@ -181,11 +186,6 @@ public: /// The skinning. /// SkinnedMeshDrawData* Skinning; - - /// - /// The blend shapes. - /// - BlendShapesInstance* BlendShapes; }; struct diff --git a/Source/Engine/Graphics/Models/MeshDeformation.cpp b/Source/Engine/Graphics/Models/MeshDeformation.cpp new file mode 100644 index 000000000..5a654459b --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshDeformation.cpp @@ -0,0 +1,166 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "MeshDeformation.h" +#include "Engine/Graphics/Models/MeshBase.h" +#include "Engine/Profiler/ProfilerCPU.h" + +struct Key +{ + union + { + struct + { + uint8 Lod; + uint8 BufferType; + uint16 MeshIndex; + }; + + uint32 Value; + }; +}; + +FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferType type) +{ + Key key; + key.Value = 0; + key.Lod = (uint8)lodIndex; + key.BufferType = (uint8)type; + key.MeshIndex = (uint16)meshIndex; + return key.Value; +} + +void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const +{ + const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0); + for (MeshDeformationData* deformation : _deformations) + { + if (deformation->Key == key) + { + bounds = deformation->Bounds; + break; + } + } +} + +void MeshDeformation::Clear() +{ + for (MeshDeformationData* e : _deformations) + Delete(e); + _deformations.Clear(); +} + +void MeshDeformation::Dirty() +{ + for (MeshDeformationData* deformation : _deformations) + deformation->Dirty = true; +} + +void MeshDeformation::Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + for (MeshDeformationData* deformation : _deformations) + { + if (deformation->Key == key) + { + deformation->Dirty = true; + break; + } + } +} + +void MeshDeformation::Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type, const BoundingBox& bounds) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + for (MeshDeformationData* deformation : _deformations) + { + if (deformation->Key == key) + { + deformation->Dirty = true; + deformation->Bounds = bounds; + break; + } + } +} + +void MeshDeformation::AddDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + _deformers[key].Bind(deformer); + Dirty(lodIndex, meshIndex, type); +} + +void MeshDeformation::RemoveDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + _deformers[key].Unbind(deformer); + Dirty(lodIndex, meshIndex, type); +} + +void MeshDeformation::RunDeformers(const MeshBase* mesh, MeshBufferType type, GPUBuffer*& vertexBuffer) +{ + const auto key = GetKey(mesh->GetLODIndex(), mesh->GetIndex(), type); + if (const auto* e = _deformers.TryGet(key)) + { + PROFILE_CPU(); + const int32 vertexStride = vertexBuffer->GetStride(); + + // Get mesh deformation container + MeshDeformationData* deformation = nullptr; + for (int32 i = 0; i < _deformations.Count(); i++) + { + if (_deformations[i]->Key == key) + { + deformation = _deformations[i]; + break; + } + } + if (!e->IsBinded()) + { + // Auto-recycle unused deformations + if (deformation) + { + _deformations.Remove(deformation); + Delete(deformation); + } + _deformers.Remove(key); + return; + } + if (!deformation) + { + deformation = New(key, vertexStride); + deformation->VertexBuffer.Data.Resize(vertexBuffer->GetSize()); + deformation->Bounds = mesh->GetBox(); + _deformations.Add(deformation); + } + + if (deformation->Dirty) + { + // Get original mesh vertex buffer data (cached on CPU) + BytesContainer vertexData; + int32 vertexCount; + if (mesh->DownloadDataCPU(type, vertexData, vertexCount)) + return; + ASSERT(vertexData.Length() / vertexCount == vertexStride); + + // Init dirty range with valid data (use the dirty range from the previous update to be cleared with initial data) + deformation->VertexBuffer.Data.Resize(vertexData.Length()); + const uint32 dirtyDataStart = Math::Min(deformation->DirtyMinIndex, vertexCount - 1) * vertexStride; + const uint32 dirtyDataLength = Math::Min(deformation->DirtyMaxIndex - deformation->DirtyMinIndex + 1, vertexCount) * vertexStride; + Platform::MemoryCopy(deformation->VertexBuffer.Data.Get() + dirtyDataStart, vertexData.Get() + dirtyDataStart, dirtyDataLength); + + // Reset dirty state + deformation->DirtyMinIndex = MAX_uint32 - 1; + deformation->DirtyMaxIndex = 0; + deformation->Dirty = false; + + // Run deformers + (*e)(mesh, *deformation); + + // Upload modified vertex data to the GPU + deformation->VertexBuffer.Flush(); + } + + // Override vertex buffer for draw call + vertexBuffer = deformation->VertexBuffer.GetBuffer(); + } +} diff --git a/Source/Engine/Graphics/Models/MeshDeformation.h b/Source/Engine/Graphics/Models/MeshDeformation.h new file mode 100644 index 000000000..c33025926 --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshDeformation.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Graphics/Models/Types.h" +#include "Engine/Graphics/DynamicBuffer.h" + +/// +/// The mesh deformation data container. +/// +struct MeshDeformationData +{ + uint64 Key; + uint32 DirtyMinIndex = 0; + uint32 DirtyMaxIndex = MAX_uint32 - 1; + bool Dirty = true; + BoundingBox Bounds; + DynamicVertexBuffer VertexBuffer; + + MeshDeformationData(uint64 key, uint32 stride) + : Key(key) + , VertexBuffer(0, stride, TEXT("MeshDeformation")) + { + } + + NON_COPYABLE(MeshDeformationData); + + ~MeshDeformationData() + { + } +}; + +/// +/// The mesh deformation utility for editing or morphing models dynamically at runtime (eg. via Blend Shapes or Cloth). +/// +class FLAXENGINE_API MeshDeformation +{ +private: + Dictionary> _deformers; + Array _deformations; + +public: + ~MeshDeformation() + { + Clear(); + } + + void GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const; + void Clear(); + void Dirty(); + void Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type); + void Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type, const BoundingBox& bounds); + void AddDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer); + void RemoveDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer); + void RunDeformers(const MeshBase* mesh, MeshBufferType type, GPUBuffer*& vertexBuffer); +}; diff --git a/Source/Engine/Graphics/Models/ModelLOD.cpp b/Source/Engine/Graphics/Models/ModelLOD.cpp index 31216dea8..ccd99902e 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.cpp +++ b/Source/Engine/Graphics/Models/ModelLOD.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "ModelLOD.h" +#include "MeshDeformation.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/GPUDevice.h" @@ -108,9 +109,9 @@ BoundingBox ModelLOD::GetBox(const Matrix& world) const { Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) { - const auto& mesh = Meshes[j]; + const auto& mesh = Meshes[meshIndex]; mesh.GetBox().GetCorners(corners); for (int32 i = 0; i < 8; i++) { @@ -122,14 +123,17 @@ BoundingBox ModelLOD::GetBox(const Matrix& world) const return BoundingBox(min, max); } -BoundingBox ModelLOD::GetBox(const Transform& transform) const +BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const { Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) { - const auto& mesh = Meshes[j]; - mesh.GetBox().GetCorners(corners); + const auto& mesh = Meshes[meshIndex]; + BoundingBox box = mesh.GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); for (int32 i = 0; i < 8; i++) { transform.LocalToWorld(corners[i], tmp); @@ -144,9 +148,9 @@ BoundingBox ModelLOD::GetBox() const { Vector3 min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) { - Meshes[j].GetBox().GetCorners(corners); + Meshes[meshIndex].GetBox().GetCorners(corners); for (int32 i = 0; i < 8; i++) { min = Vector3::Min(min, corners[i]); diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h index c6cd33976..3a99cbb44 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.h +++ b/Source/Engine/Graphics/Models/ModelLOD.h @@ -109,8 +109,9 @@ public: /// Get model bounding box in transformed world. /// /// The instance transformation. + /// The meshes deformation container (optional). /// Bounding box - BoundingBox GetBox(const Transform& transform) const; + BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; /// /// Gets the bounding box combined for all meshes in this model LOD. diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b6c966508..8bc938684 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "SkinnedMesh.h" +#include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/SkinnedModel.h" @@ -172,26 +173,14 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; - BlendShapesInstance::MeshInstance* blendShapeMeshInstance; - if (info.BlendShapes && info.BlendShapes->Meshes.TryGet(this, blendShapeMeshInstance) && blendShapeMeshInstance->IsUsed) - { - // Use modified vertex buffer from the blend shapes - if (blendShapeMeshInstance->IsDirty) - { - blendShapeMeshInstance->VertexBuffer.Flush(); - blendShapeMeshInstance->IsDirty = false; - } - drawCall.Geometry.VertexBuffers[0] = blendShapeMeshInstance->VertexBuffer.GetBuffer(); - } - else - { - drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; - } + drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; drawCall.Geometry.VertexBuffers[1] = nullptr; drawCall.Geometry.VertexBuffers[2] = nullptr; drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); drawCall.Draw.StartIndex = 0; drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; @@ -232,26 +221,14 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; - BlendShapesInstance::MeshInstance* blendShapeMeshInstance; - if (info.BlendShapes && info.BlendShapes->Meshes.TryGet(this, blendShapeMeshInstance) && blendShapeMeshInstance->IsUsed) - { - // Use modified vertex buffer from the blend shapes - if (blendShapeMeshInstance->IsDirty) - { - blendShapeMeshInstance->VertexBuffer.Flush(); - blendShapeMeshInstance->IsDirty = false; - } - drawCall.Geometry.VertexBuffers[0] = blendShapeMeshInstance->VertexBuffer.GetBuffer(); - } - else - { - drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; - } + drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; drawCall.Geometry.VertexBuffers[1] = nullptr; drawCall.Geometry.VertexBuffers[2] = nullptr; drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); drawCall.Draw.StartIndex = 0; drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp index d0c87608c..0e2b60aea 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp @@ -1,17 +1,12 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "SkinnedModelLOD.h" +#include "MeshDeformation.h" #include "Engine/Core/Log.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Serialization/MemoryReadStream.h" -bool SkinnedModelLOD::HasAnyMeshInitialized() const -{ - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); -} - bool SkinnedModelLOD::Load(MemoryReadStream& stream) { // Load LOD for each mesh @@ -147,6 +142,27 @@ BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const return BoundingBox(min, max); } +BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + { + const auto& mesh = Meshes[meshIndex]; + BoundingBox box = mesh.GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + transform.LocalToWorld(corners[i], tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const { // Find minimum and maximum points of the mesh diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h index 41bb754c0..c5a312f4a 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h @@ -16,6 +16,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject friend SkinnedModel; private: SkinnedModel* _model = nullptr; + int32 _lodIndex = 0; public: /// @@ -28,10 +29,22 @@ public: /// API_FIELD(ReadOnly) Array Meshes; + /// + /// Gets the model LOD index. + /// + API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const + { + return _lodIndex; + } + /// /// Determines whether any mesh has been initialized. /// - bool HasAnyMeshInitialized() const; + bool HasAnyMeshInitialized() const + { + // Note: we initialize all meshes at once so the last one can be used to check it. + return Meshes.HasItems() && Meshes.Last().IsInitialized(); + } public: /// @@ -81,6 +94,14 @@ public: /// Bounding box BoundingBox GetBox(const Matrix& world) const; + /// + /// Get model bounding box in transformed world. + /// + /// The instance transformation. + /// The meshes deformation container (optional). + /// Bounding box + BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; + /// /// Get model bounding box in transformed world for given instance buffer for only one mesh /// diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index b03d4948e..446d76850 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -11,12 +11,16 @@ class Model; class SkinnedModel; +class ModelBase; class Mesh; class SkinnedMesh; +class MeshBase; class ModelData; class ModelInstanceEntries; class Model; class SkinnedModel; +class MeshDeformation; +class GPUContext; struct RenderView; /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 5f2d65917..728758736 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -12,6 +12,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Serialization/Serialization.h" @@ -34,6 +35,12 @@ AnimatedModel::AnimatedModel(const SpawnParams& params) AnimationGraph.Loaded.Bind(this); } +AnimatedModel::~AnimatedModel() +{ + if (_deformation) + Delete(_deformation); +} + void AnimatedModel::ResetAnimation() { GraphInstance.ClearState(); @@ -56,11 +63,6 @@ void AnimatedModel::UpdateAnimation() // Request an animation update Animations::AddToUpdate(this); } - else - { - // Allow to use blend shapes without animation graph assigned - _blendShapes.Update(SkinnedModel.Get()); - } } void AnimatedModel::SetupSkinningData() @@ -292,7 +294,7 @@ void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value) float AnimatedModel::GetBlendShapeWeight(const StringView& name) { - for (auto& e : _blendShapes.Weights) + for (auto& e : _blendShapeWeights) { if (e.First == name) return e.Second; @@ -302,36 +304,130 @@ float AnimatedModel::GetBlendShapeWeight(const StringView& name) void AnimatedModel::SetBlendShapeWeight(const StringView& name, float value) { + const auto* model = SkinnedModel.Get(); + CHECK(model); + model->WaitForLoaded(); value = Math::Clamp(value, -1.0f, 1.0f); - for (int32 i = 0; i < _blendShapes.Weights.Count(); i++) + const bool isZero = Math::IsZero(value); + if (!_deformation && !isZero) + _deformation = New(); + Function deformer; + deformer.Bind(this); + for (int32 i = 0; i < _blendShapeWeights.Count(); i++) { - auto& e = _blendShapes.Weights[i]; + auto& e = _blendShapeWeights[i]; if (e.First == name) { - if (Math::IsZero(value)) + if (isZero) { - _blendShapes.WeightsDirty = true; - _blendShapes.Weights.RemoveAt(i); + _blendShapeWeights.RemoveAt(i); + + // Remove deformers for meshes using this blend shape + for (const auto& lod : model->LODs) + { + for (const auto& mesh : lod.Meshes) + { + for (const auto& blendShape : mesh.BlendShapes) + { + if (blendShape.Name == name) + { + for (int32 j = 0; j < _blendShapeMeshes.Count(); j++) + { + auto& blendShapeMesh = _blendShapeMeshes[j]; + if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex()) + { + blendShapeMesh.Usages--; + if (blendShapeMesh.Usages == 0) + { + _deformation->RemoveDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _blendShapeMeshes.RemoveAt(j); + } + break; + } + } + break; + } + } + } + } } else if (Math::NotNearEqual(e.Second, value)) { - _blendShapes.WeightsDirty = true; + // Update blend shape weight e.Second = value; + + // Dirty deformers for meshes using this blend shape + for (const auto& lod : model->LODs) + { + for (const auto& mesh : lod.Meshes) + { + for (const auto& blendShape : mesh.BlendShapes) + { + if (blendShape.Name == name) + { + _deformation->Dirty(mesh.GetLODIndex(), mesh.GetIndex(), MeshBufferType::Vertex0); + break; + } + } + } + } } return; } } + if (!isZero) { - auto& e = _blendShapes.Weights.AddOne(); + // Add blend shape weight + auto& e = _blendShapeWeights.AddOne(); e.First = name; e.Second = value; - _blendShapes.WeightsDirty = true; + + // Add deformers for meshes using this blend shape + for (const auto& lod : model->LODs) + { + for (const auto& mesh : lod.Meshes) + { + for (const auto& blendShape : mesh.BlendShapes) + { + if (blendShape.Name == name) + { + int32 i = 0; + for (; i < _blendShapeMeshes.Count(); i++) + { + auto& blendShapeMesh = _blendShapeMeshes[i]; + if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex()) + { + blendShapeMesh.Usages++; + break; + } + } + if (i == _blendShapeMeshes.Count()) + { + auto& blendShapeMesh = _blendShapeMeshes.AddOne(); + blendShapeMesh.LODIndex = mesh.GetLODIndex(); + blendShapeMesh.MeshIndex = mesh.GetIndex(); + blendShapeMesh.Usages = 1; + _deformation->AddDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer); + } + break; + } + } + } + } } } void AnimatedModel::ClearBlendShapeWeights() { - _blendShapes.Clear(); + if (_deformation) + { + Function deformer; + deformer.Bind(this); + for (auto e : _blendShapeMeshes) + _deformation->RemoveDeformer(e.LODIndex, e.MeshIndex, MeshBufferType::Vertex0, deformer); + } + _blendShapeWeights.Clear(); + _blendShapeMeshes.Clear(); } void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime, int32 loopCount) @@ -469,6 +565,87 @@ void AnimatedModel::SyncParameters() } } +void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationData& deformation) +{ + PROFILE_CPU_NAMED("BlendShapes"); + auto* skinnedMesh = (const SkinnedMesh*)mesh; + + // Estimate the range of the vertices to modify by the currently active blend shapes + uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0; + bool useNormals = false; + Array, InlinedAllocation<32>> blendShapes; + for (const BlendShape& blendShape : skinnedMesh->BlendShapes) + { + for (auto& q : _blendShapeWeights) + { + if (q.First == blendShape.Name) + { + const float weight = q.Second * blendShape.Weight; + blendShapes.Add(Pair(blendShape, weight)); + minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex); + maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex); + useNormals |= blendShape.UseNormals; + break; + } + } + } + + // Blend all blend shapes + auto vertexCount = (uint32)mesh->GetVertexCount(); + auto data = (VB0SkinnedElementType*)deformation.VertexBuffer.Data.Get(); + for (const auto& q : blendShapes) + { + // TODO: use SIMD + if (useNormals) + { + for (int32 i = 0; i < q.First.Vertices.Count(); i++) + { + const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; + ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); + VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); + vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; + Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta; + vertex.Normal = normal * 0.5f + 0.5f; + } + } + else + { + for (int32 i = 0; i < q.First.Vertices.Count(); i++) + { + const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; + ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); + VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); + vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; + } + } + } + + if (useNormals) + { + // Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range) + // TODO: use SIMD + for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++) + { + VB0SkinnedElementType& vertex = *(data + vertexIndex); + + Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f; + normal.Normalize(); + vertex.Normal = normal * 0.5f + 0.5f; + + Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f; + tangent = tangent - ((tangent | normal) * normal); + tangent.Normalize(); + const auto tangentSign = vertex.Tangent.W; + vertex.Tangent = tangent * 0.5f + 0.5f; + vertex.Tangent.W = tangentSign; + } + } + + // Mark as dirty to be cleared before next rendering + deformation.DirtyMinIndex = Math::Min(minVertexIndex, deformation.DirtyMinIndex); + deformation.DirtyMaxIndex = Math::Max(maxVertexIndex, deformation.DirtyMaxIndex); +} + void AnimatedModel::BeginPlay(SceneBeginData* data) { if (SkinnedModel && SkinnedModel->IsLoaded()) @@ -513,31 +690,26 @@ void AnimatedModel::OnActiveInTreeChanged() void AnimatedModel::UpdateBounds() { + auto model = SkinnedModel.Get(); if (CustomBounds.GetSize().LengthSquared() > 0.01f) { BoundingBox::Transform(CustomBounds, _transform, _box); } - else if (SkinnedModel && SkinnedModel->IsLoaded()) + else if (model && model->IsLoaded()) { + BoundingBox box = model->LODs[0].GetBox(_transform, _deformation); if (GraphInstance.NodesPose.Count() != 0) { // Per-bone bounds estimated from positions - auto& skeleton = SkinnedModel->Skeleton; + auto& skeleton = model->Skeleton; const int32 bonesCount = skeleton.Bones.Count(); -#define GET_NODE_POS(i) _transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones[i].NodeIndex].GetTranslation()) - BoundingBox box(GET_NODE_POS(0)); - for (int32 boneIndex = 1; boneIndex < bonesCount; boneIndex++) - box.Merge(GET_NODE_POS(boneIndex)); - _box = box; -#undef GET_NODE_POS - } - else - { - _box = SkinnedModel->GetBox(_transform.GetWorld()); + for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) + box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation())); } + _box = box; // Apply margin based on model dimensions - const Vector3 modelBoxSize = SkinnedModel->GetBox().GetSize(); + const Vector3 modelBoxSize = model->GetBox().GetSize(); const Vector3 center = _box.GetCenter(); const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f; _box = BoundingBox(center - sizeHalf, center + sizeHalf); @@ -595,7 +767,6 @@ void AnimatedModel::OnAnimationUpdated_Async() } UpdateBounds(); - _blendShapes.Update(SkinnedModel.Get()); } void AnimatedModel::OnAnimationUpdated_Sync() @@ -616,23 +787,20 @@ void AnimatedModel::OnAnimationUpdated() void AnimatedModel::OnSkinnedModelChanged() { Entries.Release(); - if (SkinnedModel && !SkinnedModel->IsLoaded()) { UpdateBounds(); GraphInstance.Invalidate(); } + if (_deformation) + _deformation->Clear(); GraphInstance.NodesSkeleton = SkinnedModel; } void AnimatedModel::OnSkinnedModelLoaded() { Entries.SetupIfInvalid(SkinnedModel); - GraphInstance.Invalidate(); - if (_blendShapes.Weights.HasItems()) - _blendShapes.WeightsDirty = true; - PreInitSkinningData(); } @@ -699,7 +867,7 @@ void AnimatedModel::Draw(RenderContext& renderContext) if (!SkinnedModel || !SkinnedModel->IsLoaded()) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) - return; // TODO: Animated Model rendering to Global SDF + return; if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) return; // No supported Matrix world; @@ -721,9 +889,9 @@ void AnimatedModel::Draw(RenderContext& renderContext) SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; draw.Skinning = &_skinningData; - draw.BlendShapes = &_blendShapes; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; PRAGMA_DISABLE_DEPRECATION_WARNINGS draw.DrawModes = DrawModes & renderContext.View.GetShadowsDrawPassMask(ShadowsMode); PRAGMA_ENABLE_DEPRECATION_WARNINGS @@ -764,9 +932,9 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch) SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; draw.Skinning = &_skinningData; - draw.BlendShapes = &_blendShapes; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; draw.DrawModes = DrawModes; draw.Bounds = _sphere; draw.Bounds.Center -= renderContext.View.Origin; @@ -957,6 +1125,13 @@ bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); } +MeshDeformation* AnimatedModel::GetMeshDeformation() const +{ + if (!_deformation) + _deformation = New(); + return _deformation; +} + void AnimatedModel::OnDeleteObject() { // Ensure this object is no longer referenced for anim update @@ -965,14 +1140,6 @@ void AnimatedModel::OnDeleteObject() ModelInstanceActor::OnDeleteObject(); } -void AnimatedModel::OnTransformChanged() -{ - // Base - ModelInstanceActor::OnTransformChanged(); - - UpdateBounds(); -} - void AnimatedModel::WaitForModelLoad() { if (SkinnedModel) diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 904216356..d9623f77f 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -17,7 +17,7 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor { DECLARE_SCENE_OBJECT(AnimatedModel); friend class AnimationsSystem; -public: + /// /// Describes the animation graph updates frequency for the animated model. /// @@ -55,16 +55,27 @@ public: }; private: + struct BlendShapeMesh + { + uint16 LODIndex; + uint16 MeshIndex; + uint32 Usages; + }; + GeometryDrawStateData _drawState; SkinnedMeshDrawData _skinningData; AnimationUpdateMode _actualMode; uint32 _counter; Real _lastMinDstSqr; uint64 _lastUpdateFrame; - BlendShapesInstance _blendShapes; + mutable MeshDeformation* _deformation = nullptr; ScriptingObjectReference _masterPose; + Array> _blendShapeWeights; + Array _blendShapeMeshes; public: + ~AnimatedModel(); + /// /// The skinned model asset used for rendering. /// @@ -291,7 +302,7 @@ public: API_FUNCTION() void SetBlendShapeWeight(const StringView& name, float value); /// - /// Clears the weights of the blend shapes (disabled any used blend shapes). + /// Clears the weights of the blend shapes (disables any used blend shapes). /// API_FUNCTION() void ClearBlendShapeWeights(); @@ -346,9 +357,9 @@ public: private: void ApplyRootMotion(const Transform& rootMotionDelta); void SyncParameters(); + void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); void Update(); - void UpdateBounds(); void UpdateSockets(); void OnAnimationUpdated_Async(); void OnAnimationUpdated_Sync(); @@ -375,6 +386,8 @@ public: bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; + void UpdateBounds() override; + MeshDeformation* GetMeshDeformation() const override; void OnDeleteObject() override; protected: @@ -384,6 +397,5 @@ protected: void OnEnable() override; void OnDisable() override; void OnActiveInTreeChanged() override; - void OnTransformChanged() override; void WaitForModelLoad() override; }; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index f2625ca3b..72c7f4c54 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -332,6 +332,7 @@ void Camera::Draw(RenderContext& renderContext) draw.Buffer = &_previewModelBuffer; draw.World = &world; draw.DrawState = &drawState; + draw.Deformation = nullptr; draw.Lightmap = nullptr; draw.LightmapUVs = nullptr; draw.Flags = StaticFlags::Transform; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 0c1879d83..9209f6af0 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -59,6 +59,14 @@ void ModelInstanceActor::OnLayerChanged() GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } +void ModelInstanceActor::OnTransformChanged() +{ + // Base + Actor::OnTransformChanged(); + + UpdateBounds(); +} + void ModelInstanceActor::OnEnable() { GetSceneRendering()->AddActor(this, _sceneRenderingKey); diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 850bc8715..e72d790d8 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -97,7 +97,7 @@ public: { return false; } - + /// /// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel). /// @@ -105,18 +105,33 @@ public: /// Buffer type /// The result data /// The amount of items inside the result buffer. - /// True if failed, otherwise false + /// True if failed, otherwise false. virtual bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const { return true; } + /// + /// Gets the mesh deformation utility for this model instance (optional). + /// + /// Model deformation utility or null if not supported. + virtual MeshDeformation* GetMeshDeformation() const + { + return nullptr; + } + + /// + /// Updates the model bounds (eg. when mesh has applied significant deformation). + /// + virtual void UpdateBounds() = 0; + protected: virtual void WaitForModelLoad(); public: // [Actor] void OnLayerChanged() override; + void OnTransformChanged() override; protected: // [Actor] diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 08dc5ddea..99c691ce9 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -341,6 +341,11 @@ void SplineModel::OnParentChanged() OnSplineUpdated(); } +void SplineModel::UpdateBounds() +{ + OnSplineUpdated(); +} + bool SplineModel::HasContentLoaded() const { return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); @@ -489,14 +494,6 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DrawModes |= DrawPass::GlobalSurfaceAtlas; } -void SplineModel::OnTransformChanged() -{ - // Base - ModelInstanceActor::OnTransformChanged(); - - OnSplineUpdated(); -} - void SplineModel::OnActiveInTreeChanged() { // Base diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index 50864d7b7..312da07d6 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -115,9 +115,9 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void OnParentChanged() override; + void UpdateBounds() override; protected: // [ModelInstanceActor] - void OnTransformChanged() override; void OnActiveInTreeChanged() override; }; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 4e67d90d2..17d5ca805 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -7,6 +7,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" @@ -36,6 +37,8 @@ StaticModel::~StaticModel() { for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) SAFE_DELETE_GPU_RESOURCE(_vertexColorsBuffer[lodIndex]); + if (_deformation) + Delete(_deformation); } float StaticModel::GetScaleInLightmap() const @@ -230,6 +233,8 @@ void StaticModel::OnModelChanged() Entries.Release(); if (Model && !Model->IsLoaded()) UpdateBounds(); + if (_deformation) + _deformation->Clear(); else if (!Model && _sceneRenderingKey != -1) GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } @@ -265,11 +270,12 @@ void StaticModel::OnModelResidencyChanged() void StaticModel::UpdateBounds() { - if (Model && Model->IsLoaded()) + auto model = Model.Get(); + if (model && model->IsLoaded()) { Transform transform = _transform; transform.Scale *= _boundsScale; - _box = Model->GetBox(transform); + _box = model->LODs[0].GetBox(transform, _deformation); } else { @@ -339,6 +345,7 @@ void StaticModel::Draw(RenderContext& renderContext) draw.Buffer = &Entries; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; @@ -372,6 +379,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch) draw.Buffer = &Entries; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; @@ -600,12 +608,11 @@ bool StaticModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, By return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); } -void StaticModel::OnTransformChanged() +MeshDeformation* StaticModel::GetMeshDeformation() const { - // Base - ModelInstanceActor::OnTransformChanged(); - - UpdateBounds(); + if (!_deformation) + _deformation = New(); + return _deformation; } void StaticModel::OnEnable() diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index ffb34e0a1..8ce945316 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -25,6 +25,7 @@ private: Array _vertexColorsData[MODEL_MAX_LODS]; GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS]; Model* _residencyChangedModel = nullptr; + mutable MeshDeformation* _deformation = nullptr; public: /// @@ -155,7 +156,6 @@ private: void OnModelChanged(); void OnModelLoaded(); void OnModelResidencyChanged(); - void UpdateBounds(); void FlushVertexColors(); public: @@ -169,10 +169,11 @@ public: bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; + MeshDeformation* GetMeshDeformation() const override; + void UpdateBounds() override; protected: // [ModelInstanceActor] - void OnTransformChanged() override; void OnEnable() override; void OnDisable() override; void WaitForModelLoad() override; From 8818b3b07c941cba69eb1f3427da01ceb4c74fac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Jul 2023 09:51:42 +0200 Subject: [PATCH 021/294] Add `NvCloth` dependency --- .../Binaries/ThirdParty/ARM64/libNvCloth.a | 3 + .../Binaries/ThirdParty/x64/NvCloth_x64.lib | 3 + .../Binaries/ThirdParty/x64/NvCloth_x64.pdb | 3 + Source/ThirdParty/NvCloth/Allocator.h | 168 ++++ Source/ThirdParty/NvCloth/Callbacks.h | 192 +++++ Source/ThirdParty/NvCloth/Cloth.h | 477 +++++++++++ .../NvCloth/DxContextManagerCallback.h | 84 ++ Source/ThirdParty/NvCloth/Fabric.h | 130 +++ Source/ThirdParty/NvCloth/Factory.h | 194 +++++ Source/ThirdParty/NvCloth/License.txt | 69 ++ Source/ThirdParty/NvCloth/NvCloth.Build.cs | 48 ++ .../NvCloth/NvClothExt/ClothFabricCooker.h | 225 +++++ .../NvCloth/NvClothExt/ClothMeshDesc.h | 211 +++++ .../NvCloth/NvClothExt/ClothMeshQuadifier.h | 78 ++ .../NvCloth/NvClothExt/ClothTetherCooker.h | 94 ++ Source/ThirdParty/NvCloth/PhaseConfig.h | 62 ++ Source/ThirdParty/NvCloth/Range.h | 155 ++++ Source/ThirdParty/NvCloth/Solver.h | 111 +++ Source/ThirdParty/NvCloth/ps/Ps.h | 75 ++ .../ThirdParty/NvCloth/ps/PsAlignedMalloc.h | 93 ++ Source/ThirdParty/NvCloth/ps/PsAllocator.h | 351 ++++++++ Source/ThirdParty/NvCloth/ps/PsArray.h | 726 ++++++++++++++++ Source/ThirdParty/NvCloth/ps/PsAtomic.h | 69 ++ .../ThirdParty/NvCloth/ps/PsBasicTemplates.h | 151 ++++ Source/ThirdParty/NvCloth/ps/PsBitUtils.h | 114 +++ Source/ThirdParty/NvCloth/ps/PsHash.h | 167 ++++ .../ThirdParty/NvCloth/ps/PsHashInternals.h | 800 ++++++++++++++++++ Source/ThirdParty/NvCloth/ps/PsHashMap.h | 123 +++ Source/ThirdParty/NvCloth/ps/PsIntrinsics.h | 47 + Source/ThirdParty/NvCloth/ps/PsMathUtils.h | 699 +++++++++++++++ .../ThirdParty/NvCloth/ps/PsUserAllocated.h | 97 +++ .../NvCloth/ps/unix/PsUnixIntrinsics.h | 138 +++ .../NvCloth/ps/windows/PsWindowsIntrinsics.h | 173 ++++ Source/Tools/Flax.Build/Deps/Dependency.cs | 10 +- 34 files changed, 6135 insertions(+), 5 deletions(-) create mode 100644 Source/Platforms/Android/Binaries/ThirdParty/ARM64/libNvCloth.a create mode 100644 Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.lib create mode 100644 Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.pdb create mode 100644 Source/ThirdParty/NvCloth/Allocator.h create mode 100644 Source/ThirdParty/NvCloth/Callbacks.h create mode 100644 Source/ThirdParty/NvCloth/Cloth.h create mode 100644 Source/ThirdParty/NvCloth/DxContextManagerCallback.h create mode 100644 Source/ThirdParty/NvCloth/Fabric.h create mode 100644 Source/ThirdParty/NvCloth/Factory.h create mode 100644 Source/ThirdParty/NvCloth/License.txt create mode 100644 Source/ThirdParty/NvCloth/NvCloth.Build.cs create mode 100644 Source/ThirdParty/NvCloth/NvClothExt/ClothFabricCooker.h create mode 100644 Source/ThirdParty/NvCloth/NvClothExt/ClothMeshDesc.h create mode 100644 Source/ThirdParty/NvCloth/NvClothExt/ClothMeshQuadifier.h create mode 100644 Source/ThirdParty/NvCloth/NvClothExt/ClothTetherCooker.h create mode 100644 Source/ThirdParty/NvCloth/PhaseConfig.h create mode 100644 Source/ThirdParty/NvCloth/Range.h create mode 100644 Source/ThirdParty/NvCloth/Solver.h create mode 100644 Source/ThirdParty/NvCloth/ps/Ps.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsAlignedMalloc.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsAllocator.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsArray.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsAtomic.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsBasicTemplates.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsBitUtils.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsHash.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsHashInternals.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsHashMap.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsIntrinsics.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsMathUtils.h create mode 100644 Source/ThirdParty/NvCloth/ps/PsUserAllocated.h create mode 100644 Source/ThirdParty/NvCloth/ps/unix/PsUnixIntrinsics.h create mode 100644 Source/ThirdParty/NvCloth/ps/windows/PsWindowsIntrinsics.h diff --git a/Source/Platforms/Android/Binaries/ThirdParty/ARM64/libNvCloth.a b/Source/Platforms/Android/Binaries/ThirdParty/ARM64/libNvCloth.a new file mode 100644 index 000000000..7b77e9bc2 --- /dev/null +++ b/Source/Platforms/Android/Binaries/ThirdParty/ARM64/libNvCloth.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc4f2a3a7fc559e0891d47190110d8e0de5d5d96655374fee3f909bc71764a1d +size 3656516 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.lib new file mode 100644 index 000000000..d5ddd1770 --- /dev/null +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.lib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e92cff7b740448dcb882892a8ff1e7c360cccad07c8333af9c7232e9266482ba +size 2425458 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.pdb b/Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.pdb new file mode 100644 index 000000000..e3ec6d317 --- /dev/null +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/NvCloth_x64.pdb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f322c45592d9b58fe8ec9d75c2c9fbb523e752be768fcbc9f867ab9eb12f527f +size 520192 diff --git a/Source/ThirdParty/NvCloth/Allocator.h b/Source/ThirdParty/NvCloth/Allocator.h new file mode 100644 index 000000000..15c1cc2df --- /dev/null +++ b/Source/ThirdParty/NvCloth/Allocator.h @@ -0,0 +1,168 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + + +/** \file Allocator.h + This file together with Callbacks.h define most memory management interfaces for internal use. +*/ + +/** \cond HIDDEN_SYMBOLS */ +#pragma once + +#include "NvCloth/ps/PsArray.h" +#include "NvCloth/ps/PsHashMap.h" +#include "NvCloth/Callbacks.h" +#include "NvCloth/ps/PsAlignedMalloc.h" + +namespace nv +{ +namespace cloth +{ + +void* allocate(size_t); +void deallocate(void*); + + +/* templated typedefs for convenience */ + +template +struct Vector +{ + typedef ps::Array Type; +}; + +template +struct AlignedVector +{ + typedef ps::Array > Type; +}; + +template > +struct HashMap +{ + typedef ps::HashMap Type; +}; + +struct NvClothOverload{}; +#define NV_CLOTH_NEW(T) new (__FILE__, __LINE__, nv::cloth::NvClothOverload()) T +#define NV_CLOTH_ALLOC(n, name) GetNvClothAllocator()->allocate(n, name, __FILE__, __LINE__) +#define NV_CLOTH_FREE(x) GetNvClothAllocator()->deallocate(x) +#define NV_CLOTH_DELETE(x) delete x + +} // namespace cloth +} // namespace nv + +//new/delete operators need to be declared in global scope +template +PX_INLINE void* operator new(size_t size, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line, nv::cloth::NvClothOverload overload) +{ + PX_UNUSED(overload); + return GetNvClothAllocator()->allocate(size, "", fileName, line); +} + +template +PX_INLINE void* operator new [](size_t size, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line, nv::cloth::NvClothOverload overload) +{ + PX_UNUSED(overload); + return GetNvClothAllocator()->allocate(size, "", fileName, line); +} + +// If construction after placement new throws, this placement delete is being called. +template +PX_INLINE void operator delete(void* ptr, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line, nv::cloth::NvClothOverload overload) +{ + PX_UNUSED(fileName); + PX_UNUSED(line); + PX_UNUSED(overload); + + return GetNvClothAllocator()->deallocate(ptr); +} + +// If construction after placement new throws, this placement delete is being called. +template +PX_INLINE void operator delete [](void* ptr, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line, nv::cloth::NvClothOverload overload) +{ + PX_UNUSED(fileName); + PX_UNUSED(line); + PX_UNUSED(overload); + + return GetNvClothAllocator()->deallocate(ptr); +} + +namespace nv +{ +namespace cloth +{ + +class UserAllocated +{ + public: + PX_INLINE void* operator new(size_t size, const char* fileName, int line, NvClothOverload overload) + { + PX_UNUSED(overload); + return GetNvClothAllocator()->allocate(size, "", fileName, line); + } + PX_INLINE void* operator new [](size_t size, const char* fileName, int line, NvClothOverload overload) + { + PX_UNUSED(overload); + return GetNvClothAllocator()->allocate(size, "", fileName, line); + } + + // placement delete + PX_INLINE void operator delete(void* ptr, const char* fileName, int line, NvClothOverload overload) + { + PX_UNUSED(fileName); + PX_UNUSED(line); + PX_UNUSED(overload); + GetNvClothAllocator()->deallocate(ptr); + } + PX_INLINE void operator delete [](void* ptr, const char* fileName, int line, NvClothOverload overload) + { + PX_UNUSED(fileName); + PX_UNUSED(line); + PX_UNUSED(overload); + GetNvClothAllocator()->deallocate(ptr); + } + PX_INLINE void operator delete(void* ptr) + { + return GetNvClothAllocator()->deallocate(ptr); + } + PX_INLINE void operator delete [](void* ptr) + { + return GetNvClothAllocator()->deallocate(ptr); + } +}; + +} // namespace cloth +} // namespace nv +/** \endcond */ diff --git a/Source/ThirdParty/NvCloth/Callbacks.h b/Source/ThirdParty/NvCloth/Callbacks.h new file mode 100644 index 000000000..5a44fa581 --- /dev/null +++ b/Source/ThirdParty/NvCloth/Callbacks.h @@ -0,0 +1,192 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +/** \file Callbacks.h + \brief All functions to initialize and use user provided callbacks are declared in this header. + Initialize the callbacks with InitializeNvCloth(...) before using any other NvCloth API. + The other functions defined in this header are used to access the functionality provided by the callbacks, and are mostly for internal use. + */ + +#pragma once +#include +#include +#include +#ifndef NV_CLOTH_IMPORT +#define NV_CLOTH_IMPORT PX_DLL_IMPORT +#endif + +#define NV_CLOTH_DLL_ID 0x2 + +#define NV_CLOTH_LINKAGE PX_C_EXPORT NV_CLOTH_IMPORT +#define NV_CLOTH_CALL_CONV PX_CALL_CONV +#define NV_CLOTH_API(ret_type) NV_CLOTH_LINKAGE ret_type NV_CLOTH_CALL_CONV + + + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ + class PxAllocatorCallback; + class PxErrorCallback; + class PxProfilerCallback; + class PxAssertHandler; + +/** \brief Initialize the library by passing in callback functions. + This needs to be called before using any other part of the library. + @param allocatorCallback Callback interface for memory allocations. Needs to return 16 byte aligned memory. + @param errorCallback Callback interface for debug/warning/error messages. + @param assertHandler Callback interface for asserts. + @param profilerCallback Optional callback interface for performance information. + @param autoDllIDCheck Leave as default parameter. This is used to check header and dll version compatibility. + */ +NV_CLOTH_API(void) + InitializeNvCloth(physx::PxAllocatorCallback* allocatorCallback, physx::PxErrorCallback* errorCallback, + nv::cloth::PxAssertHandler* assertHandler, physx::PxProfilerCallback* profilerCallback, + int autoDllIDCheck = NV_CLOTH_DLL_ID); +} +} + +//Allocator +NV_CLOTH_API(physx::PxAllocatorCallback*) GetNvClothAllocator(); //Only use internally + +namespace nv +{ +namespace cloth +{ + +/* Base class to handle assert failures */ +class PxAssertHandler +{ +public: + virtual ~PxAssertHandler() + { + } + virtual void operator()(const char* exp, const char* file, int line, bool& ignore) = 0; +}; + + +//Logging +void LogErrorFn (const char* fileName, int lineNumber, const char* msg, ...); +void LogInvalidParameterFn (const char* fileName, int lineNumber, const char* msg, ...); +void LogWarningFn(const char* fileName, int lineNumber, const char* msg, ...); +void LogInfoFn (const char* fileName, int lineNumber, const char* msg, ...); +///arguments: NV_CLOTH_LOG_ERROR("format %s %s\n","additional","arguments"); +#define NV_CLOTH_LOG_ERROR(...) nv::cloth::LogErrorFn(__FILE__,__LINE__,__VA_ARGS__) +#define NV_CLOTH_LOG_INVALID_PARAMETER(...) nv::cloth::LogInvalidParameterFn(__FILE__,__LINE__,__VA_ARGS__) +#define NV_CLOTH_LOG_WARNING(...) nv::cloth::LogWarningFn(__FILE__,__LINE__,__VA_ARGS__) +#define NV_CLOTH_LOG_INFO(...) nv::cloth::LogInfoFn(__FILE__,__LINE__,__VA_ARGS__) + +//ASSERT +NV_CLOTH_API(nv::cloth::PxAssertHandler*) GetNvClothAssertHandler(); //This function needs to be exposed to properly inline asserts outside this dll +#if !PX_ENABLE_ASSERTS +#if PX_VC +#define NV_CLOTH_ASSERT(exp) __noop +#define NV_CLOTH_ASSERT_WITH_MESSAGE(message, exp) __noop +#else +#define NV_CLOTH_ASSERT(exp) ((void)0) +#define NV_CLOTH_ASSERT_WITH_MESSAGE(message, exp) ((void)0) +#endif +#else +#if PX_VC +#define PX_CODE_ANALYSIS_ASSUME(exp) \ + __analysis_assume(!!(exp)) // This macro will be used to get rid of analysis warning messages if a NV_CLOTH_ASSERT is used + // to "guard" illegal mem access, for example. +#else +#define PX_CODE_ANALYSIS_ASSUME(exp) +#endif +#define NV_CLOTH_ASSERT(exp) \ + { \ + static bool _ignore = false; \ + ((void)((!!(exp)) || (!_ignore && ((*nv::cloth::GetNvClothAssertHandler())(#exp, __FILE__, __LINE__, _ignore), false)))); \ + PX_CODE_ANALYSIS_ASSUME(exp); \ + } +#define NV_CLOTH_ASSERT_WITH_MESSAGE(message, exp) \ + { \ + static bool _ignore = false; \ + ((void)((!!(exp)) || (!_ignore && ((*nv::cloth::GetNvClothAssertHandler())(message, __FILE__, __LINE__, _ignore), false)))); \ + PX_CODE_ANALYSIS_ASSUME(exp); \ + } +#endif + +//Profiler +physx::PxProfilerCallback* GetNvClothProfiler(); //Only use internally +#if PX_DEBUG || PX_CHECKED || PX_PROFILE +class NvClothProfileScoped +{ + public: + PX_FORCE_INLINE NvClothProfileScoped(const char* eventName, bool detached, uint64_t contextId, + const char* fileName, int lineno, physx::PxProfilerCallback* callback) + : mCallback(callback) + { + PX_UNUSED(fileName); + PX_UNUSED(lineno); + mProfilerData = NULL; //nullptr doesn't work here on mac + if (mCallback) + { + mEventName = eventName; + mDetached = detached; + mContextId = contextId; + mProfilerData = mCallback->zoneStart(mEventName, mDetached, mContextId); + } + } + ~NvClothProfileScoped(void) + { + if (mCallback) + { + mCallback->zoneEnd(mProfilerData, mEventName, mDetached, mContextId); + } + } + physx::PxProfilerCallback* mCallback; + const char* mEventName; + bool mDetached; + uint64_t mContextId; + void* mProfilerData; +}; + +#define NV_CLOTH_PROFILE_ZONE(x, y) \ + nv::cloth::NvClothProfileScoped PX_CONCAT(_scoped, __LINE__)(x, false, y, __FILE__, __LINE__, nv::cloth::GetNvClothProfiler()) +#define NV_CLOTH_PROFILE_START_CROSSTHREAD(x, y) \ + (GetNvClothProfiler()!=nullptr? \ + GetNvClothProfiler()->zoneStart(x, true, y):nullptr) +#define NV_CLOTH_PROFILE_STOP_CROSSTHREAD(profilerData, x, y) \ + if (GetNvClothProfiler()) \ + GetNvClothProfiler()->zoneEnd(profilerData, x, true, y) +#else + +#define NV_CLOTH_PROFILE_ZONE(x, y) +#define NV_CLOTH_PROFILE_START_CROSSTHREAD(x, y) nullptr +#define NV_CLOTH_PROFILE_STOP_CROSSTHREAD(profilerData, x, y) + +#endif + +} +} diff --git a/Source/ThirdParty/NvCloth/Cloth.h b/Source/ThirdParty/NvCloth/Cloth.h new file mode 100644 index 000000000..af913dda8 --- /dev/null +++ b/Source/ThirdParty/NvCloth/Cloth.h @@ -0,0 +1,477 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +#include "NvCloth/Range.h" +#include "NvCloth/PhaseConfig.h" +#include +#include "NvCloth/Allocator.h" + +struct ID3D11Buffer; + +namespace nv +{ +namespace cloth +{ + +class Factory; +class Fabric; +class Cloth; + +#ifdef _MSC_VER +#pragma warning(disable : 4371) // layout of class may have changed from a previous version of the compiler due to + // better packing of member +#endif + +template +struct MappedRange : public Range +{ + MappedRange(T* first, T* last, const Cloth& cloth, void (Cloth::*lock)() const, void (Cloth::*unlock)() const) + : Range(first, last), mCloth(cloth), mLock(lock), mUnlock(unlock) + { + } + + MappedRange(const MappedRange& other) + : Range(other), mCloth(other.mCloth), mLock(other.mLock), mUnlock(other.mUnlock) + { + (mCloth.*mLock)(); + } + + ~MappedRange() + { + (mCloth.*mUnlock)(); + } + + private: + MappedRange& operator = (const MappedRange&); + + const Cloth& mCloth; + void (Cloth::*mLock)() const; + void (Cloth::*mUnlock)() const; +}; + +struct GpuParticles +{ + physx::PxVec4* mCurrent; + physx::PxVec4* mPrevious; + ID3D11Buffer* mBuffer; +}; + +// abstract cloth instance +class Cloth : public UserAllocated +{ + protected: + Cloth() {} + Cloth(const Cloth&); + Cloth& operator = (const Cloth&); + + public: + virtual ~Cloth() {} + + /** \brief Creates a duplicate of this Cloth instance. + Same as: + \code + getFactory().clone(*this); + \endcode + */ + virtual Cloth* clone(Factory& factory) const = 0; + + /** \brief Returns the fabric used to create this Cloth.*/ + virtual Fabric& getFabric() const = 0; + /** \brief Returns the Factory used to create this Cloth.*/ + virtual Factory& getFactory() const = 0; + + /* particle properties */ + /// Returns the number of particles simulated by this fabric. + virtual uint32_t getNumParticles() const = 0; + /** \brief Used internally to synchronize CPU and GPU particle memory.*/ + virtual void lockParticles() const = 0; //Might be better if it was called map/unmapParticles + /** \brief Used internally to synchronize CPU and GPU particle memory.*/ + virtual void unlockParticles() const = 0; + + /** \brief Returns the simulation particles of the current frame. + Each PxVec4 element contains the particle position in the XYZ components and the inverse mass in the W component. + The returned memory may be overwritten (to change attachment point locations for animation for example). + Setting the inverse mass to 0 locks the particle in place. + */ + virtual MappedRange getCurrentParticles() = 0; + + /** \brief Returns the simulation particles of the current frame, read only. + Similar to the non-const version of this function. + This version is preferred as it doesn't wake up the cloth to account for the possibility that particles were changed. + */ + virtual MappedRange getCurrentParticles() const = 0; + + /** \brief Returns the simulation particles of the previous frame. + Similar to getCurrentParticles(). + */ + virtual MappedRange getPreviousParticles() = 0; + + /** \brief Returns the simulation particles of the previous frame. + Similar to getCurrentParticles() const. + */ + virtual MappedRange getPreviousParticles() const = 0; + + /** \brief Returns platform dependent pointers to the current GPU particle memory.*/ + virtual GpuParticles getGpuParticles() = 0; + + + /** \brief Set the translation of the local space simulation after next call to simulate(). + This applies a force to make the cloth behave as if it was moved through space. + This does not move the particles as they are in local space. + Use the graphics transformation matrices to render the cloth in the proper location. + The applied force is proportional to the value set with Cloth::setLinearInertia(). + */ + virtual void setTranslation(const physx::PxVec3& trans) = 0; + + /** \brief Set the rotation of the local space simulation after next call to simulate(). + Similar to Cloth::setTranslation(). + The applied force is proportional to the value set with Cloth::setAngularInertia() and Cloth::setCentrifugalInertia(). + */ + virtual void setRotation(const physx::PxQuat& rot) = 0; + + /** \brief Returns the current translation value that was set using setTranslation().*/ + virtual const physx::PxVec3& getTranslation() const = 0; + /** \brief Returns the current rotation value that was set using setRotation().*/ + virtual const physx::PxQuat& getRotation() const = 0; + + /** \brief Set inertia derived from setTranslation() and setRotation() to zero (once).*/ + virtual void clearInertia() = 0; + + /** \brief Adjust the position of the cloth without affecting the dynamics (to call after a world origin shift, for example). */ + virtual void teleport(const physx::PxVec3& delta) = 0; + + /** \brief Adjust the position and rotation of the cloth without affecting the dynamics (to call after a world origin shift, for example). + The velocity will be set to zero this frame, unless setTranslation/setRotation is called with a different value after this function is called. + The correct order to use this is: + \code + cloth->teleportToLocation(pos, rot); + pos += velocity * dt; + rot += 0.5 * angularVelocity * rot * dt; + cloth->setTranslation(pos); + cloth->setRotation(rot); + \endcode + */ + virtual void teleportToLocation(const physx::PxVec3& translation, const physx::PxQuat& rotation) = 0; + + /** \brief Don't recalculate the velocity based on the values provided by setTranslation and setRotation for one frame (so it acts as if the velocity was the same as last frame). + This is useful when the cloth is moving while teleported, but the integration of the cloth position for that frame is already included in the teleport. + Example: + \code + pos += velocity * dt; + rot += 0.5 * angularVelocity * rot * dt; + cloth->teleportToLocation(pos, rot); + cloth->ignoreVelocityDiscontinuity(); + \endcode + */ + virtual void ignoreVelocityDiscontinuity() = 0; + + + /* solver parameters */ + + /** \brief Returns the delta time used for previous iteration.*/ + virtual float getPreviousIterationDt() const = 0; + + /** \brief Sets gravity in global coordinates.*/ + virtual void setGravity(const physx::PxVec3&) = 0; + /// Returns gravity set with setGravity(). + virtual physx::PxVec3 getGravity() const = 0; + + /** \brief Sets damping of local particle velocity (1/stiffnessFrequency). + 0 (default): velocity is unaffected, 1: velocity is zeroed + */ + virtual void setDamping(const physx::PxVec3&) = 0; + /// Returns value set with setDamping(). + virtual physx::PxVec3 getDamping() const = 0; + + // portion of local frame velocity applied to particles + // 0 (default): particles are unaffected + // same as damping: damp global particle velocity + virtual void setLinearDrag(const physx::PxVec3&) = 0; + virtual physx::PxVec3 getLinearDrag() const = 0; + virtual void setAngularDrag(const physx::PxVec3&) = 0; + virtual physx::PxVec3 getAngularDrag() const = 0; + + /** \brief Set the portion of local frame linear acceleration applied to particles. + 0: particles are unaffected, 1 (default): physically correct. + */ + virtual void setLinearInertia(const physx::PxVec3&) = 0; + /// Returns value set with getLinearInertia(). + virtual physx::PxVec3 getLinearInertia() const = 0; + /** \brief Similar to setLinearInertia(), but for angular inertia.*/ + virtual void setAngularInertia(const physx::PxVec3&) = 0; + /// Returns value set with setAngularInertia(). + virtual physx::PxVec3 getAngularInertia() const = 0; + /** \brief Similar to setLinearInertia(), but for centrifugal inertia.*/ + virtual void setCentrifugalInertia(const physx::PxVec3&) = 0; + ///Returns value set with setCentrifugalInertia(). + virtual physx::PxVec3 getCentrifugalInertia() const = 0; + + /** \brief Set target solver iterations per second. + At least 1 iteration per frame will be solved regardless of the value set. + */ + virtual void setSolverFrequency(float) = 0; + /// Returns gravity set with getSolverFrequency().*/ + virtual float getSolverFrequency() const = 0; + + // damp, drag, stiffness exponent per second + virtual void setStiffnessFrequency(float) = 0; + virtual float getStiffnessFrequency() const = 0; + + // filter width for averaging dt^2 factor of gravity and + // external acceleration, in numbers of iterations (default=30). + virtual void setAcceleationFilterWidth(uint32_t) = 0; + virtual uint32_t getAccelerationFilterWidth() const = 0; + + // setup edge constraint solver iteration + virtual void setPhaseConfig(Range configs) = 0; + + /* collision parameters */ + + /** \brief Set spheres for collision detection. + Elements of spheres contain PxVec4(x,y,z,r) where [x,y,z] is the center and r the radius of the sphere. + The values currently in range[first, last[ will be replaced with the content of spheres. + \code + cloth->setSpheres(Range(), 0, cloth->getNumSpheres()); //Removes all spheres + \endcode + */ + virtual void setSpheres(Range spheres, uint32_t first, uint32_t last) = 0; + virtual void setSpheres(Range startSpheres, Range targetSpheres) = 0; + /// Returns the number of spheres currently set. + virtual uint32_t getNumSpheres() const = 0; + + + /** \brief Set indices for capsule collision detection. + The indices define the spheres that form the end points between the capsule. + Every two elements in capsules define one capsule. + The values currently in range[first, last[ will be replaced with the content of capsules. + Note that first and last are indices to whole capsules consisting of 2 indices each. So if + you want to update the first two capsules (without changing the total number of capsules) + you would use the following code: + \code + uint32_t capsules[4] = { 0,1, 1,2 }; //Define indices for 2 capsules + //updates the indices of the first 2 capsules in cloth + cloth->setCapsules(Range(capsules, capsules + 4), 0, 2); + \endcode + */ + virtual void setCapsules(Range capsules, uint32_t first, uint32_t last) = 0; + /// Returns the number of capsules (which is half the number of capsule indices). + virtual uint32_t getNumCapsules() const = 0; + + /** \brief Sets plane values to be used with convex collision detection. + The planes are specified in the form ax + by + cz + d = 0, where elements in planes contain PxVec4(x,y,z,d). + [x,y,z] is required to be normalized. + The values currently in range [first, last[ will be replaced with the content of planes. + Use setConvexes to enable planes for collision detection. + */ + virtual void setPlanes(Range planes, uint32_t first, uint32_t last) = 0; + virtual void setPlanes(Range startPlanes, Range targetPlanes) = 0; + /// Returns the number of planes currently set. + virtual uint32_t getNumPlanes() const = 0; + + /** \brief Enable planes for collision. + convexMasks must contain masks of the form (1< convexMasks, uint32_t first, uint32_t last) = 0; + /// Returns the number of convexMasks currently set. + virtual uint32_t getNumConvexes() const = 0; + + /** \brief Set triangles for collision. + Each triangle in the list is defined by of 3 vertices. + The values currently in range [first, last[ will be replaced with the content of triangles. + */ + virtual void setTriangles(Range triangles, uint32_t first, uint32_t last) = 0; + virtual void setTriangles(Range startTriangles, Range targetTriangles, uint32_t first) = 0; + /// Returns the number of triangles currently set. + virtual uint32_t getNumTriangles() const = 0; + + /// Returns true if we use ccd + virtual bool isContinuousCollisionEnabled() const = 0; + /// Set if we use ccd or not (disabled by default) + virtual void enableContinuousCollision(bool) = 0; + + // controls how quickly mass is increased during collisions + virtual float getCollisionMassScale() const = 0; + virtual void setCollisionMassScale(float) = 0; + + /** \brief Set the cloth collision shape friction coefficient.*/ + virtual void setFriction(float) = 0; + ///Returns value set with setFriction(). + virtual float getFriction() const = 0; + + // set virtual particles for collision handling. + // each indices element consists of 3 particle + // indices and an index into the lerp weights array. + virtual void setVirtualParticles(Range indices, Range weights) = 0; + virtual uint32_t getNumVirtualParticles() const = 0; + virtual uint32_t getNumVirtualParticleWeights() const = 0; + + /* tether constraint parameters */ + + /** \brief Set Tether constraint scale. + 1.0 is the original scale of the Fabric. + 0.0 disables tether constraints in the Solver. + */ + virtual void setTetherConstraintScale(float scale) = 0; + ///Returns value set with setTetherConstraintScale(). + virtual float getTetherConstraintScale() const = 0; + /** \brief Set Tether constraint stiffness.. + 1.0 is the default. + <1.0 makes the constraints behave springy. + */ + virtual void setTetherConstraintStiffness(float stiffness) = 0; + ///Returns value set with setTetherConstraintStiffness(). + virtual float getTetherConstraintStiffness() const = 0; + + /* motion constraint parameters */ + + /** \brief Returns reference to motion constraints (position, radius) + The entire range must be written after calling this function. + */ + virtual Range getMotionConstraints() = 0; + /** \brief Removes all motion constraints. + */ + virtual void clearMotionConstraints() = 0; + virtual uint32_t getNumMotionConstraints() const = 0; + virtual void setMotionConstraintScaleBias(float scale, float bias) = 0; + virtual float getMotionConstraintScale() const = 0; + virtual float getMotionConstraintBias() const = 0; + virtual void setMotionConstraintStiffness(float stiffness) = 0; + virtual float getMotionConstraintStiffness() const = 0; + + /* separation constraint parameters */ + + // return reference to separation constraints (position, radius) + // The entire range must be written after calling this function. + virtual Range getSeparationConstraints() = 0; + virtual void clearSeparationConstraints() = 0; + virtual uint32_t getNumSeparationConstraints() const = 0; + + /* clear interpolation */ + + // assign current to previous positions for + // collision spheres, motion, and separation constraints + virtual void clearInterpolation() = 0; + + /* particle acceleration parameters */ + + // return reference to particle accelerations (in local coordinates) + // The entire range must be written after calling this function. + virtual Range getParticleAccelerations() = 0; + virtual void clearParticleAccelerations() = 0; + virtual uint32_t getNumParticleAccelerations() const = 0; + + /* wind */ + + /** /brief Set wind in global coordinates. Acts on the fabric's triangles. */ + virtual void setWindVelocity(physx::PxVec3) = 0; + ///Returns value set with setWindVelocity(). + virtual physx::PxVec3 getWindVelocity() const = 0; + /** /brief Sets the air drag coefficient. */ + virtual void setDragCoefficient(float) = 0; + ///Returns value set with setDragCoefficient(). + virtual float getDragCoefficient() const = 0; + /** /brief Sets the air lift coefficient. */ + virtual void setLiftCoefficient(float) = 0; + ///Returns value set with setLiftCoefficient(). + virtual float getLiftCoefficient() const = 0; + /** /brief Sets the fluid density used for air drag/lift calculations. */ + virtual void setFluidDensity(float) = 0; + ///Returns value set with setFluidDensity(). + virtual float getFluidDensity() const = 0; + + /* self collision */ + + /** /brief Set the distance particles need to be separated from each other withing the cloth. */ + virtual void setSelfCollisionDistance(float distance) = 0; + ///Returns value set with setSelfCollisionDistance(). + virtual float getSelfCollisionDistance() const = 0; + /** /brief Set the constraint stiffness for the self collision constraints. */ + virtual void setSelfCollisionStiffness(float stiffness) = 0; + ///Returns value set with setSelfCollisionStiffness(). + virtual float getSelfCollisionStiffness() const = 0; + + /** \brief Set self collision indices. + Each index in the range indicates that the particle at that index should be used for self collision. + If set to an empty range (default) all particles will be used. + */ + virtual void setSelfCollisionIndices(Range) = 0; + ///Returns the number of self collision indices set. + virtual uint32_t getNumSelfCollisionIndices() const = 0; + + /* rest positions */ + + // set rest particle positions used during self-collision + virtual void setRestPositions(Range) = 0; + virtual uint32_t getNumRestPositions() const = 0; + + /* bounding box */ + + /** \brief Returns current particle position bounds center in local space */ + virtual const physx::PxVec3& getBoundingBoxCenter() const = 0; + /** \brief Returns current particle position bounds size in local space */ + virtual const physx::PxVec3& getBoundingBoxScale() const = 0; + + /* sleeping (disabled by default) */ + + // max particle velocity (per axis) to pass sleep test + virtual void setSleepThreshold(float) = 0; + virtual float getSleepThreshold() const = 0; + // test sleep condition every nth millisecond + virtual void setSleepTestInterval(uint32_t) = 0; + virtual uint32_t getSleepTestInterval() const = 0; + // put cloth to sleep when n consecutive sleep tests pass + virtual void setSleepAfterCount(uint32_t) = 0; + virtual uint32_t getSleepAfterCount() const = 0; + virtual uint32_t getSleepPassCount() const = 0; + virtual bool isAsleep() const = 0; + virtual void putToSleep() = 0; + virtual void wakeUp() = 0; + + /** \brief Set user data. Not used internally. */ + virtual void setUserData(void*) = 0; + // Returns value set by setUserData(). + virtual void* getUserData() const = 0; +}; + +// wrappers to prevent non-const overload from marking particles dirty +inline MappedRange readCurrentParticles(const Cloth& cloth) +{ + return cloth.getCurrentParticles(); +} +inline MappedRange readPreviousParticles(const Cloth& cloth) +{ + return cloth.getPreviousParticles(); +} + +} // namespace cloth +} // namespace nv diff --git a/Source/ThirdParty/NvCloth/DxContextManagerCallback.h b/Source/ThirdParty/NvCloth/DxContextManagerCallback.h new file mode 100644 index 000000000..dee89c9fd --- /dev/null +++ b/Source/ThirdParty/NvCloth/DxContextManagerCallback.h @@ -0,0 +1,84 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +struct ID3D11Device; +struct ID3D11DeviceContext; + +namespace nv +{ +namespace cloth +{ + +/** Callback interface to manage the DirectX context/device used for compute + * + */ +class DxContextManagerCallback +{ +public: + virtual ~DxContextManagerCallback() {} + /** + * \brief Acquire the D3D context for the current thread + * + * Acquisitions are allowed to be recursive within a single thread. + * You can acquire the context multiple times so long as you release + * it the same count. + */ + virtual void acquireContext() = 0; + + /** + * \brief Release the D3D context from the current thread + */ + virtual void releaseContext() = 0; + + /** + * \brief Return the D3D device to use for compute work + */ + virtual ID3D11Device* getDevice() const = 0; + + /** + * \brief Return the D3D context to use for compute work + */ + virtual ID3D11DeviceContext* getContext() const = 0; + + /** + * \brief Return if exposed buffers (only cloth particles at the moment) + * are created with D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX. + * + * The user is responsible to query and acquire the mutex of all + * corresponding buffers. + * todo: We should acquire the mutex locally if we continue to + * allow resource sharing across devices. + */ + virtual bool synchronizeResources() const = 0; +}; + +} +} \ No newline at end of file diff --git a/Source/ThirdParty/NvCloth/Fabric.h b/Source/ThirdParty/NvCloth/Fabric.h new file mode 100644 index 000000000..693a76ce7 --- /dev/null +++ b/Source/ThirdParty/NvCloth/Fabric.h @@ -0,0 +1,130 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +#include "NvCloth/Callbacks.h" +#include "NvCloth/Allocator.h" +#include "NvCloth/ps/PsAtomic.h" + +namespace nv +{ +namespace cloth +{ + +class Factory; + +// abstract cloth constraints and triangle indices +class Fabric : public UserAllocated +{ +protected: + Fabric(const Fabric&); + Fabric& operator = (const Fabric&); + +protected: + Fabric() : mRefCount(1) + { + } + + virtual ~Fabric() + { + NV_CLOTH_ASSERT(0 == mRefCount); + } + +public: + /** \brief Returns the Factory used to create this Fabric.*/ + virtual Factory& getFactory() const = 0; + + /** \brief Returns the number of constraint solve phases stored. + Phases are groups of constraints that make up the general structure of the fabric. + Cloth instances can have different configuration settings per phase (see Cloth::setPhaseConfig()). + Phases are usually split by type (horizontal, vertical, bending, shearing), depending on the cooker used. + */ + virtual uint32_t getNumPhases() const = 0; + + /** \brief Returns the number of rest lengths stored. + Each constraint uses the rest value to determine if the two connected particles need to be pulled together or pushed apart. + */ + virtual uint32_t getNumRestvalues() const = 0; + + /** \brief Returns the number of constraint stiffness values stored. + It is optional for a Fabric to have per constraint stiffness values provided. + This function will return 0 if no values are stored. + Stiffness per constraint values stored here can be used if more fine grain control is required (as opposed to the values stored in the cloth's phase configuration). + The Cloth 's phase configuration stiffness values will be ignored if stiffness per constraint values are used. + */ + virtual uint32_t getNumStiffnessValues() const = 0; + + + /** \brief Returns the number of sets stored. + Sets connect a phase to a range of indices. + */ + virtual uint32_t getNumSets() const = 0; + + /** \brief Returns the number of indices stored. + Each constraint has a pair of indices that indicate which particles it connects. + */ + virtual uint32_t getNumIndices() const = 0; + /// Returns the number of particles. + virtual uint32_t getNumParticles() const = 0; + /// Returns the number of Tethers stored. + virtual uint32_t getNumTethers() const = 0; + /// Returns the number of triangles that make up the cloth mesh. + virtual uint32_t getNumTriangles() const = 0; + + /** Scales all constraint rest lengths.*/ + virtual void scaleRestvalues(float) = 0; + /** Scales all tether lengths.*/ + virtual void scaleTetherLengths(float) = 0; + + void incRefCount() + { + ps::atomicIncrement(&mRefCount); + NV_CLOTH_ASSERT(mRefCount > 0); + } + + /// Returns true if the object is destroyed + bool decRefCount() + { + NV_CLOTH_ASSERT(mRefCount > 0); + int result = ps::atomicDecrement(&mRefCount); + if (result == 0) + { + delete this; + return true; + } + return false; + } + + protected: + int32_t mRefCount; +}; + +} // namespace cloth +} // namespace nv diff --git a/Source/ThirdParty/NvCloth/Factory.h b/Source/ThirdParty/NvCloth/Factory.h new file mode 100644 index 000000000..46a7adfe5 --- /dev/null +++ b/Source/ThirdParty/NvCloth/Factory.h @@ -0,0 +1,194 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +#include "NvCloth/Range.h" +#include +#include +#include "NvCloth/Allocator.h" + +typedef struct CUctx_st* CUcontext; + +namespace nv +{ +namespace cloth +{ +class DxContextManagerCallback; +class Factory; +} +} +NV_CLOTH_API(nv::cloth::Factory*) NvClothCreateFactoryCPU(); +NV_CLOTH_API(nv::cloth::Factory*) NvClothCreateFactoryCUDA(CUcontext); +NV_CLOTH_API(nv::cloth::Factory*) NvClothCreateFactoryDX11(nv::cloth::DxContextManagerCallback*); +NV_CLOTH_API(void) NvClothDestroyFactory(nv::cloth::Factory*); + +///Returns true if this dll was compiled with cuda support +NV_CLOTH_API(bool) NvClothCompiledWithCudaSupport(); +///Returns true if this dll was compiled with DX support +NV_CLOTH_API(bool) NvClothCompiledWithDxSupport(); + +namespace nv +{ +namespace cloth +{ + +class Fabric; +class Cloth; +class Solver; + +enum struct Platform +{ + CPU, + CUDA, + DX11 +}; + +/// abstract factory to create context-specific simulation components +/// such as cloth, solver, collision, etc. +class Factory : public UserAllocated +{ + protected: + Factory() {} + Factory(const Factory&); + Factory& operator = (const Factory&); + virtual ~Factory() {} + + friend NV_CLOTH_IMPORT void NV_CLOTH_CALL_CONV ::NvClothDestroyFactory(nv::cloth::Factory*); + + public: + virtual Platform getPlatform() const = 0; + + /** + \brief Create fabric data used to setup cloth object. + Look at the cooking extension for helper functions to create fabrics from meshes. + The returned fabric will have a refcount of 1. + @param numParticles number of particles, must be larger than any particle index + @param phaseIndices map from phase to set index + @param sets inclusive prefix sum of restvalue count per set + @param restvalues array of constraint rest values + @param indices array of particle index pair per constraint + */ + virtual Fabric* createFabric(uint32_t numParticles, Range phaseIndices, Range sets, + Range restvalues, Range stiffnessValues, Range indices, + Range anchors, Range tetherLengths, + Range triangles) = 0; + + /** + \brief Create cloth object. + @param particles initial particle positions. + @param fabric edge distance constraint structure + */ + virtual Cloth* createCloth(Range particles, Fabric& fabric) = 0; + + /** + \brief Create cloth solver object. + */ + virtual Solver* createSolver() = 0; + + /** + \brief Create a copy of a cloth instance + @param cloth the instance to be cloned, need not match the factory type + */ + virtual Cloth* clone(const Cloth& cloth) = 0; + + /** + \brief Extract original data from a fabric object. + Use the getNum* methods on Cloth to get the memory requirements before calling this function. + @param fabric to extract from, must match factory type + @param phaseIndices pre-allocated memory range to write phase => set indices + @param sets pre-allocated memory range to write sets + @param restvalues pre-allocated memory range to write restvalues + @param indices pre-allocated memory range to write indices + */ + virtual void extractFabricData(const Fabric& fabric, Range phaseIndices, Range sets, + Range restvalues, Range stiffnessValues, Range indices, Range anchors, + Range tetherLengths, Range triangles) const = 0; + + /** + \brief Extract current collision spheres and capsules from a cloth object. + Use the getNum* methods on Cloth to get the memory requirements before calling this function. + @param cloth the instance to extract from, must match factory type + @param spheres pre-allocated memory range to write spheres + @param capsules pre-allocated memory range to write capsules + @param planes pre-allocated memory range to write planes + @param convexes pre-allocated memory range to write convexes + @param triangles pre-allocated memory range to write triangles + */ + virtual void extractCollisionData(const Cloth& cloth, Range spheres, Range capsules, + Range planes, Range convexes, Range triangles) const = 0; + + /** + Extract current motion constraints from a cloth object + Use the getNum* methods on Cloth to get the memory requirements before calling this function. + @param cloth the instance to extract from, must match factory type + @param destConstraints pre-allocated memory range to write constraints + */ + virtual void extractMotionConstraints(const Cloth& cloth, Range destConstraints) const = 0; + + /** + Extract current separation constraints from a cloth object + @param cloth the instance to extract from, must match factory type + @param destConstraints pre-allocated memory range to write constraints + */ + virtual void extractSeparationConstraints(const Cloth& cloth, Range destConstraints) const = 0; + + /** + Extract current particle accelerations from a cloth object + @param cloth the instance to extract from, must match factory type + @param destAccelerations pre-allocated memory range to write accelerations + */ + virtual void extractParticleAccelerations(const Cloth& cloth, Range destAccelerations) const = 0; + + /** + Extract virtual particles from a cloth object + @param cloth the instance to extract from, must match factory type + @param destIndices pre-allocated memory range to write indices + @param destWeights pre-allocated memory range to write weights + */ + virtual void extractVirtualParticles(const Cloth& cloth, Range destIndices, + Range destWeights) const = 0; + + /** + Extract self collision indices from cloth object. + @param cloth the instance to extract from, must match factory type + @param destIndices pre-allocated memory range to write indices + */ + virtual void extractSelfCollisionIndices(const Cloth& cloth, Range destIndices) const = 0; + + /** + Extract particle rest positions from cloth object. + @param cloth the instance to extract from, must match factory type + @param destRestPositions pre-allocated memory range to write rest positions + */ + virtual void extractRestPositions(const Cloth& cloth, Range destRestPositions) const = 0; +}; + +} // namespace cloth +} // namespace nv diff --git a/Source/ThirdParty/NvCloth/License.txt b/Source/ThirdParty/NvCloth/License.txt new file mode 100644 index 000000000..174b4d88d --- /dev/null +++ b/Source/ThirdParty/NvCloth/License.txt @@ -0,0 +1,69 @@ +Nvidia Source Code License (1-Way Commercial) + +1. Definitions + +"Licensor" means any person or entity that distributes its Work. "Software" +means the original work of authorship made available under this License. "Work" +means the Software and any additions to or derivative works of the Software that +are made available under this License. The terms "reproduce," "reproduction," +"derivative works," and "distribution" have the meaning as provided under U.S. +copyright law; provided, however, that for the purposes of this License, +derivative works shall not include works that remain separable from, or merely +link (or bind by name) to the interfaces of, the Work. Works, including the +Software, are "made available" under this License by including in or with the +Work either (a) a copyright notice referencing the applicability of this License +to the Work, or (b) a copy of this License. + +2. License Grants + +2.1 Copyright Grant. Subject to the terms and conditions of this License, each +Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, +copyright license to reproduce, prepare derivative works of, publicly display, +publicly perform, sublicense and distribute its Work and any resulting +derivative works in any form. + +3. Limitations + +3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do +so under this License, (b) you include a complete copy of this License with your +distribution, and (c) you retain without modification any copyright, patent, +trademark, or attribution notices that are present in the Work. + +3.2 Derivative Works. You may specify that additional or different terms apply +to the use, reproduction, and distribution of your derivative works of the Work +("Your Terms") only if you identify the specific derivative works that are +subject to Your Terms. Notwithstanding Your Terms, this License (including the +redistribution requirements in Section 3.1) will continue to apply to the Work +itself. + +3.3 Patent Claims. If you bring or threaten to bring a patent claim against any +Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to +enforce any patents that you allege are infringed by any Work, then your rights +under this License from such Licensor (including the grant in Section 2.1) will +terminate immediately. + +3.4 Trademarks. This License does not grant any rights to use any Licensor's or +its affiliates' names, logos, or trademarks, except as necessary to reproduce +the notices described in this License. + +3.5 Termination. If you violate any term of this License, then your rights under +this License (including the grant in Section 2.1) will terminate +immediately. + +4. Disclaimer of Warranty. + +THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. +YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE. + +5. Limitation of Liability. + +EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, +WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY +LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, +INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, +THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF +GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR +MALFUNCTION, OR ANY OTHER COMMERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/Source/ThirdParty/NvCloth/NvCloth.Build.cs b/Source/ThirdParty/NvCloth/NvCloth.Build.cs new file mode 100644 index 000000000..ea2bc02c8 --- /dev/null +++ b/Source/ThirdParty/NvCloth/NvCloth.Build.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System.IO; +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// https://github.com/NVIDIAGameWorks/NvCloth +/// +public class NvCloth : DepsModule +{ + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.Custom; + LicenseFilePath = "License.txt"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PublicDefinitions.Add("WITH_CLOTH"); + options.PublicDefinitions.Add("NV_CLOTH_IMPORT="); + + var libName = "NvCloth_x64"; + switch (options.Platform.Target) + { + case TargetPlatform.PS4: + case TargetPlatform.PS5: + case TargetPlatform.Android: + libName = "NvCloth"; + break; + case TargetPlatform.Switch: + libName = "NvCloth"; + options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/Platforms/Switch/Binaries/Data/PhysX/physx/include")); + options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/Platforms/Switch/Binaries/Data/PhysX/physx/include/foundation")); + break; + } + AddLib(options, options.DepsFolder, libName); + } +} diff --git a/Source/ThirdParty/NvCloth/NvClothExt/ClothFabricCooker.h b/Source/ThirdParty/NvCloth/NvClothExt/ClothFabricCooker.h new file mode 100644 index 000000000..329c455e2 --- /dev/null +++ b/Source/ThirdParty/NvCloth/NvClothExt/ClothFabricCooker.h @@ -0,0 +1,225 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + + +#ifndef NV_CLOTH_EXTENSIONS_CLOTH_FABRIC_COOKER_H +#define NV_CLOTH_EXTENSIONS_CLOTH_FABRIC_COOKER_H + +/** \addtogroup extensions + @{ +*/ + +#include "ClothMeshDesc.h" +#include "NvCloth/Fabric.h" +#include "NvCloth/Factory.h" + +namespace nv +{ +namespace cloth +{ + +struct CookedData +{ + uint32_t mNumParticles; + Range mPhaseIndices; + Range mPhaseTypes; + Range mSets; + Range mRestvalues; + Range mStiffnessValues; + Range mIndices; + Range mAnchors; + Range mTetherLengths; + Range mTriangles; +}; + +/** +\brief Describe type of phase in cloth fabric. +\see Fabric for an explanation of concepts on phase and set. +*/ +struct ClothFabricPhaseType +{ + enum Enum + { + eINVALID, //!< invalid type + eVERTICAL, //!< resists stretching or compression, usually along the gravity + eHORIZONTAL, //!< resists stretching or compression, perpendicular to the gravity + eBENDING, //!< resists out-of-plane bending in angle-based formulation + eSHEARING, //!< resists in-plane shearing along (typically) diagonal edges, + eCOUNT // internal use only + }; +}; + +/** +\brief References a set of constraints that can be solved in parallel. +\see Fabric for an explanation of the concepts on phase and set. +*/ +struct ClothFabricPhase +{ + ClothFabricPhase(ClothFabricPhaseType::Enum type = + ClothFabricPhaseType::eINVALID, physx::PxU32 index = 0); + + /** + \brief Type of constraints to solve. + */ + ClothFabricPhaseType::Enum phaseType; + + /** + \brief Index of the set that contains the particle indices. + */ + physx::PxU32 setIndex; +}; + +PX_INLINE ClothFabricPhase::ClothFabricPhase( + ClothFabricPhaseType::Enum type, physx::PxU32 index) + : phaseType(type) + , setIndex(index) +{} + +/** +\brief References all the data required to create a fabric. +\see ClothFabricCooker.getDescriptor() +*/ +class ClothFabricDesc +{ +public: + /** \brief The number of particles needed when creating a PxCloth instance from the fabric. */ + physx::PxU32 nbParticles; + + /** \brief The number of solver phases. */ + physx::PxU32 nbPhases; + /** \brief Array defining which constraints to solve each phase. See #Fabric.getPhases(). */ + const ClothFabricPhase* phases; + + /** \brief The number of sets in the fabric. */ + physx::PxU32 nbSets; + /** \brief Array with an index per set which points one entry beyond the last constraint of the set. See #Fabric.getSets(). */ + const physx::PxU32* sets; + + /** \brief Array of particle indices which specifies the pair of constrained vertices. See #Fabric.getParticleIndices(). */ + const physx::PxU32* indices; + /** \brief Array of rest values for each constraint. See #Fabric.getRestvalues(). */ + const physx::PxReal* restvalues; + + /** \brief Size of tetherAnchors and tetherLengths arrays, needs to be multiple of nbParticles. */ + physx::PxU32 nbTethers; + /** \brief Array of particle indices specifying the tether anchors. See #Fabric.getTetherAnchors(). */ + const physx::PxU32* tetherAnchors; + /** \brief Array of rest distance between tethered particle pairs. See #Fabric.getTetherLengths(). */ + const physx::PxReal* tetherLengths; + + physx::PxU32 nbTriangles; + const physx::PxU32* triangles; + + /** + \brief constructor sets to default. + */ + PX_INLINE ClothFabricDesc(); + + /** + \brief (re)sets the structure to the default. + */ + PX_INLINE void setToDefault(); + + /** + \brief Returns true if the descriptor is valid. + \return True if the current settings are valid + */ + PX_INLINE bool isValid() const; +}; + +PX_INLINE ClothFabricDesc::ClothFabricDesc() +{ + setToDefault(); +} + +PX_INLINE void ClothFabricDesc::setToDefault() +{ + memset(this, 0, sizeof(ClothFabricDesc)); +} + +PX_INLINE bool ClothFabricDesc::isValid() const +{ + return nbParticles && nbPhases && phases && restvalues && nbSets + && sets && indices && (!nbTethers || (tetherAnchors && tetherLengths)) + && (!nbTriangles || triangles); +} + +///Use NvClothCreateFabricCooker() to create an implemented instance +class NV_CLOTH_IMPORT ClothFabricCooker : public UserAllocated +{ +public: + virtual ~ClothFabricCooker(){} + + /** + \brief Cooks a triangle mesh to a ClothFabricDesc. + \param desc The cloth mesh descriptor on which the generation of the cooked mesh depends. + \param gravity A normalized vector which specifies the direction of gravity. + This information allows the cooker to generate a fabric with higher quality simulation behavior. + The gravity vector should point in the direction gravity will be pulling towards in the most common situation/at rest. + e.g. For flags it might be beneficial to set the gravity horizontal if they are cooked in landscape orientation, as a flag will hang in portrait orientation at rest. + \param useGeodesicTether A flag to indicate whether to compute geodesic distance for tether constraints. + \note The geodesic option for tether only works for manifold input. For non-manifold input, a simple Euclidean distance will be used. + For more detailed cooker status for such cases, try running ClothGeodesicTetherCooker directly. + */ + virtual bool cook(const ClothMeshDesc& desc, physx::PxVec3 gravity, bool useGeodesicTether = true) = 0; + + /** \brief Returns fabric cooked data for creating fabrics. */ + virtual CookedData getCookedData() const = 0; + + /** \brief Returns the fabric descriptor to create the fabric. */ + virtual ClothFabricDesc getDescriptor() const = 0; + + /** \brief Saves the fabric data to a platform and version dependent stream. */ + virtual void save(physx::PxOutputStream& stream, bool platformMismatch) const = 0; +}; + +/** @} */ + +} // namespace cloth +} // namespace nv + + +NV_CLOTH_API(nv::cloth::ClothFabricCooker*) NvClothCreateFabricCooker(); + +/** +\brief Cooks a triangle mesh to a Fabric. + +\param factory The factory for which the cloth is cooked. +\param desc The cloth mesh descriptor on which the generation of the cooked mesh depends. +\param gravity A normalized vector which specifies the direction of gravity. +This information allows the cooker to generate a fabric with higher quality simulation behavior. +\param phaseTypes Optional array where phase type information can be writen to. +\param useGeodesicTether A flag to indicate whether to compute geodesic distance for tether constraints. +\return The created cloth fabric, or NULL if creation failed. +*/ +NV_CLOTH_API(nv::cloth::Fabric*) NvClothCookFabricFromMesh(nv::cloth::Factory* factory, + const nv::cloth::ClothMeshDesc& desc, const float gravity[3], + nv::cloth::Vector::Type* phaseTypes = nullptr, bool useGeodesicTether = true); + +#endif // NV_CLOTH_EXTENSIONS_CLOTH_FABRIC_COOKER_H diff --git a/Source/ThirdParty/NvCloth/NvClothExt/ClothMeshDesc.h b/Source/ThirdParty/NvCloth/NvClothExt/ClothMeshDesc.h new file mode 100644 index 000000000..9ec187c9c --- /dev/null +++ b/Source/ThirdParty/NvCloth/NvClothExt/ClothMeshDesc.h @@ -0,0 +1,211 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + + +#ifndef NV_CLOTH_EXTENSIONS_CLOTHMESHDESC +#define NV_CLOTH_EXTENSIONS_CLOTHMESHDESC +/** \addtogroup extensions +@{ +*/ + +#include "foundation/PxVec3.h" + +namespace nv +{ +namespace cloth +{ + +struct StridedData +{ + /** + \brief The offset in bytes between consecutive samples in the data. + + Default: 0 + */ + physx::PxU32 stride; + const void* data; + + StridedData() : stride( 0 ), data( NULL ) {} + + template + PX_INLINE const TDataType& at( physx::PxU32 idx ) const + { + physx::PxU32 theStride( stride ); + if ( theStride == 0 ) + theStride = sizeof( TDataType ); + physx::PxU32 offset( theStride * idx ); + return *(reinterpret_cast( reinterpret_cast< const physx::PxU8* >( data ) + offset )); + } +}; + +struct BoundedData : public StridedData +{ + physx::PxU32 count; + BoundedData() : count( 0 ) {} +}; + +/** +\brief Enum with flag values to be used in ClothMeshDesc. +*/ +struct MeshFlag +{ + enum Enum + { + e16_BIT_INDICES = (1<<1) //!< Denotes the use of 16-bit vertex indices + }; +}; + +/** +\brief Descriptor class for a cloth mesh. +*/ +class ClothMeshDesc +{ +public: + + /** + \brief Pointer to first vertex point. + */ + BoundedData points; + + /** + \brief Pointer to first stiffness value in stiffnes per vertex array. empty if unused. + */ + BoundedData pointsStiffness; + + /** + \brief Determines whether particle is simulated or static. + A positive value denotes that the particle is being simulated, zero denotes a static particle. + This data is used to generate tether and zero stretch constraints. + If invMasses.data is null, all particles are assumed to be simulated + and no tether and zero stretch constraints are being generated. + */ + BoundedData invMasses; + + /** + \brief Pointer to the first triangle. + + These are triplets of 0 based indices: + vert0 vert1 vert2 + vert0 vert1 vert2 + vert0 vert1 vert2 + ... + + where vert* is either a 32 or 16 bit unsigned integer. There are a total of 3*count indices. + The stride determines the byte offset to the next index triple. + + This is declared as a void pointer because it is actually either an physx::PxU16 or a physx::PxU32 pointer. + */ + BoundedData triangles; + + /** + \brief Pointer to the first quad. + + These are quadruples of 0 based indices: + vert0 vert1 vert2 vert3 + vert0 vert1 vert2 vert3 + vert0 vert1 vert2 vert3 + ... + + where vert* is either a 32 or 16 bit unsigned integer. There are a total of 4*count indices. + The stride determines the byte offset to the next index quadruple. + + This is declared as a void pointer because it is actually either an physx::PxU16 or a physx::PxU32 pointer. + */ + BoundedData quads; + + /** + \brief Flags bits, combined from values of the enum ::MeshFlag + */ + unsigned int flags; + + /** + \brief constructor sets to default. + */ + PX_INLINE ClothMeshDesc(); + /** + \brief (re)sets the structure to the default. + */ + PX_INLINE void setToDefault(); + /** + \brief Returns true if the descriptor is valid. + \return True if the current settings are valid + */ + PX_INLINE bool isValid() const; +}; + +PX_INLINE ClothMeshDesc::ClothMeshDesc() +{ + flags = 0; +} + +PX_INLINE void ClothMeshDesc::setToDefault() +{ + *this = ClothMeshDesc(); +} + +PX_INLINE bool ClothMeshDesc::isValid() const +{ + if (points.count < 3) // at least 1 triangle + return false; + if ((pointsStiffness.count != points.count) && pointsStiffness.count != 0) + return false; // either all or none of the points can have stiffness information + if (points.count > 0xffff && flags & MeshFlag::e16_BIT_INDICES) + return false; + if (!points.data) + return false; + if (points.stride < sizeof(physx::PxVec3)) // should be at least one point + return false; + + if (invMasses.data && invMasses.stride < sizeof(float)) + return false; + if (invMasses.data && invMasses.count != points.count) + return false; + + if (!triangles.count && !quads.count) // no support for non-indexed mesh + return false; + if (triangles.count && !triangles.data) + return false; + if (quads.count && !quads.data) + return false; + + physx::PxU32 indexSize = (flags & MeshFlag::e16_BIT_INDICES) ? sizeof(physx::PxU16) : sizeof(physx::PxU32); + if (triangles.count && triangles.stride < indexSize*3) + return false; + if (quads.count && quads.stride < indexSize*4) + return false; + + return true; +} + +} // namespace cloth +} // namespace nv + + +/** @} */ +#endif diff --git a/Source/ThirdParty/NvCloth/NvClothExt/ClothMeshQuadifier.h b/Source/ThirdParty/NvCloth/NvClothExt/ClothMeshQuadifier.h new file mode 100644 index 000000000..731d361d8 --- /dev/null +++ b/Source/ThirdParty/NvCloth/NvClothExt/ClothMeshQuadifier.h @@ -0,0 +1,78 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + + +#ifndef NV_CLOTH_EXTENSIONS_CLOTH_EDGE_QUADIFIER_H +#define NV_CLOTH_EXTENSIONS_CLOTH_EDGE_QUADIFIER_H + +/** \addtogroup extensions +@{ +*/ + +#include "ClothMeshDesc.h" +#include "NvCloth/Callbacks.h" +#include "NvCloth/Allocator.h" + +namespace nv +{ +namespace cloth +{ + +class ClothMeshQuadifier : public UserAllocated +{ +public: + virtual ~ClothMeshQuadifier(){} + + /** + \brief Convert triangles of ClothMeshDesc to quads. + \details In NvCloth, quad dominant mesh representations are preferable to pre-triangulated versions. + In cases where the mesh has been already triangulated, this class provides a meachanism to + convert (quadify) some triangles back to quad representations. + \see ClothFabricCooker + \param desc The cloth mesh descriptor prepared for cooking + */ + virtual bool quadify(const ClothMeshDesc& desc) = 0; + + /** + \brief Returns a mesh descriptor with some triangle pairs converted to quads. + \note The returned descriptor is valid only within the lifespan of ClothMeshQuadifier class. + */ + + virtual ClothMeshDesc getDescriptor() const = 0; + +}; + +} // namespace cloth +} // namespace nv + +NV_CLOTH_API(nv::cloth::ClothMeshQuadifier*) NvClothCreateMeshQuadifier(); + +/** @} */ + +#endif // NV_CLOTH_EXTENSIONS_CLOTH_EDGE_QUADIFIER_H diff --git a/Source/ThirdParty/NvCloth/NvClothExt/ClothTetherCooker.h b/Source/ThirdParty/NvCloth/NvClothExt/ClothTetherCooker.h new file mode 100644 index 000000000..ce20f6c25 --- /dev/null +++ b/Source/ThirdParty/NvCloth/NvClothExt/ClothTetherCooker.h @@ -0,0 +1,94 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + + +#ifndef NV_CLOTH_EXTENSIONS_CLOTH_TETHER_COOKER_H +#define NV_CLOTH_EXTENSIONS_CLOTH_TETHER_COOKER_H + +/** \addtogroup extensions +@{ +*/ + +#include "ClothMeshDesc.h" +#include "NvCloth/Allocator.h" + +namespace nv +{ +namespace cloth +{ + +class ClothTetherCooker : public UserAllocated +{ +public: + virtual ~ClothTetherCooker(){} + + /** + \brief Compute tether data from ClothMeshDesc with simple distance measure. + \details The tether constraint in NvCloth requires rest distance and anchor index to be precomputed during cooking time. + This cooker computes a simple Euclidean distance to closest anchor point. + The Euclidean distance measure works reasonably for flat cloth and flags and computation time is very fast. + With this cooker, there is only one tether anchor point per particle. + \see ClothTetherGeodesicCooker for more accurate distance estimation. + \param desc The cloth mesh descriptor prepared for cooking + */ + virtual bool cook(const ClothMeshDesc &desc) = 0; + + /** + \brief Returns cooker status + \details This function returns cooker status after cooker computation is done. + A non-zero return value indicates a failure. + */ + virtual uint32_t getCookerStatus() const = 0; //From APEX + + /** + \brief Returns number of tether anchors per particle + \note Returned number indicates the maximum anchors. + If some particles are assigned fewer anchors, the anchor indices will be physx::PxU32(-1) + \note If there is no attached point in the input mesh descriptor, this will return 0 and no tether data will be generated. + */ + virtual physx::PxU32 getNbTethersPerParticle() const = 0; + + /** + \brief Returns computed tether data. + \details This function returns anchor indices for each particle as well as desired distance between the tether anchor and the particle. + The user buffers should be at least as large as number of particles. + */ + virtual void getTetherData(physx::PxU32* userTetherAnchors, physx::PxReal* userTetherLengths) const = 0; + +}; + +} // namespace cloth +} // namespace nv + +NV_CLOTH_API(nv::cloth::ClothTetherCooker*) NvClothCreateSimpleTetherCooker(); +NV_CLOTH_API(nv::cloth::ClothTetherCooker*) NvClothCreateGeodesicTetherCooker(); + +/** @} */ + +#endif // NV_CLOTH_EXTENSIONS_CLOTH_TETHER_COOKER_H diff --git a/Source/ThirdParty/NvCloth/PhaseConfig.h b/Source/ThirdParty/NvCloth/PhaseConfig.h new file mode 100644 index 000000000..29fb73573 --- /dev/null +++ b/Source/ThirdParty/NvCloth/PhaseConfig.h @@ -0,0 +1,62 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +#include + +namespace nv +{ +namespace cloth +{ + +struct PhaseConfig +{ + PhaseConfig(uint16_t index = uint16_t(-1)) + : mStiffness(1.0f) + , mStiffnessMultiplier(1.0f) + , mCompressionLimit(1.0f) + , mStretchLimit(1.0f) + , mPhaseIndex(index) + , mPadding(0xffff) + { + } + + //These 4 floats need to be in order as they are loaded in to simd vectors in the solver + float mStiffness; // target convergence rate per iteration (1/solverFrequency) + float mStiffnessMultiplier; + float mCompressionLimit; + float mStretchLimit; + + uint16_t mPhaseIndex; + uint16_t mPadding; +}; + +} // namespace cloth +} // namespace nv diff --git a/Source/ThirdParty/NvCloth/Range.h b/Source/ThirdParty/NvCloth/Range.h new file mode 100644 index 000000000..a1e41d425 --- /dev/null +++ b/Source/ThirdParty/NvCloth/Range.h @@ -0,0 +1,155 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +#include "NvCloth/Callbacks.h" + +namespace nv +{ +namespace cloth +{ + +template +struct Range +{ + /** \brief Construct an empty range. + */ + Range(); + + /** \brief Construct a range (array like container) using existing memory. + Range doesn't take ownership of this memory. + Interface works similar to std::vector. + @param begin start of the memory + @param end end of the memory range, point to one element past the last valid element. + */ + Range(T* begin, T* end); + + template + Range(const Range& other); + + uint32_t size() const; + bool empty() const; + + void popFront(); + void popBack(); + + T* begin() const; + T* end() const; + + T& front() const; + T& back() const; + + T& operator[](uint32_t i) const; + + private: + T* mBegin; + T* mEnd; // past last element (like std::vector::end()) +}; + +template +Range::Range() +: mBegin(0), mEnd(0) +{ +} + +template +Range::Range(T* begin, T* end) +: mBegin(begin), mEnd(end) +{ +} + +template +template +Range::Range(const Range& other) +: mBegin(other.begin()), mEnd(other.end()) +{ +} + +template +uint32_t Range::size() const +{ + return uint32_t(mEnd - mBegin); +} + +template +bool Range::empty() const +{ + return mBegin >= mEnd; +} + +template +void Range::popFront() +{ + NV_CLOTH_ASSERT(mBegin < mEnd); + ++mBegin; +} + +template +void Range::popBack() +{ + NV_CLOTH_ASSERT(mBegin < mEnd); + --mEnd; +} + +template +T* Range::begin() const +{ + return mBegin; +} + +template +T* Range::end() const +{ + return mEnd; +} + +template +T& Range::front() const +{ + NV_CLOTH_ASSERT(mBegin < mEnd); + return *mBegin; +} + +template +T& Range::back() const +{ + NV_CLOTH_ASSERT(mBegin < mEnd); + return mEnd[-1]; +} + +template +T& Range::operator[](uint32_t i) const +{ + NV_CLOTH_ASSERT(mBegin + i < mEnd); + return mBegin[i]; +} + +} // namespace cloth +} // namespace nv diff --git a/Source/ThirdParty/NvCloth/Solver.h b/Source/ThirdParty/NvCloth/Solver.h new file mode 100644 index 000000000..0d2bbb816 --- /dev/null +++ b/Source/ThirdParty/NvCloth/Solver.h @@ -0,0 +1,111 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#pragma once + +#include "NvCloth/Allocator.h" +#include "NvCloth/Range.h" +#include "NvCloth/ps/PsArray.h" + +namespace nv +{ +namespace cloth +{ + +class Cloth; + +// called during inter-collision, user0 and user1 are the user data from each cloth +typedef bool (*InterCollisionFilter)(void* user0, void* user1); + +/// base class for solvers +class Solver : public UserAllocated +{ + protected: + Solver() {} + Solver(const Solver&); + Solver& operator = (const Solver&); + + public: + virtual ~Solver() {} + + /// Adds cloth object. + virtual void addCloth(Cloth* cloth) = 0; + + /// Adds an array of cloth objects. + virtual void addCloths(Range cloths) = 0; + + /// Removes cloth object. + virtual void removeCloth(Cloth* cloth) = 0; + + /// Returns the numer of cloths added to the solver. + virtual int getNumCloths() const = 0; + + /// Returns the pointer to the first cloth added to the solver + virtual Cloth * const * getClothList() const = 0; + + // functions executing the simulation work. + /** \brief Begins a simulation frame. + Returns false if there is nothing to simulate. + Use simulateChunk() after calling this function to do the computation. + @param dt The delta time for this frame. + */ + virtual bool beginSimulation(float dt) = 0; + + /** \brief Do the computationally heavy part of the simulation. + Call this function getSimulationChunkCount() times to do the entire simulation. + This function can be called from multiple threads in parallel. + All Chunks need to be simulated before ending the frame. + */ + virtual void simulateChunk(int idx) = 0; + + /** \brief Finishes up the simulation. + This function can be expensive if inter-collision is enabled. + */ + virtual void endSimulation() = 0; + + /** \brief Returns the number of chunks that need to be simulated this frame. + */ + virtual int getSimulationChunkCount() const = 0; + + /// inter-collision parameters + /// Note that using intercollision with more than 32 cloths added to the solver will cause undefined behavior + virtual void setInterCollisionDistance(float distance) = 0; + virtual float getInterCollisionDistance() const = 0; + virtual void setInterCollisionStiffness(float stiffness) = 0; + virtual float getInterCollisionStiffness() const = 0; + virtual void setInterCollisionNbIterations(uint32_t nbIterations) = 0; + virtual uint32_t getInterCollisionNbIterations() const = 0; + virtual void setInterCollisionFilter(InterCollisionFilter filter) = 0; + + /// Returns true if an unrecoverable error has occurred. + virtual bool hasError() const = 0; +}; + +} // namespace cloth +} // namespace nv diff --git a/Source/ThirdParty/NvCloth/ps/Ps.h b/Source/ThirdParty/NvCloth/ps/Ps.h new file mode 100644 index 000000000..1c1843fe0 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/Ps.h @@ -0,0 +1,75 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PS_H +#define PSFOUNDATION_PS_H + +/*! \file top level include file for shared foundation */ + +#include "foundation/Px.h" + +/** +Platform specific defines +*/ +#if PX_WINDOWS_FAMILY || PX_XBOXONE +#pragma intrinsic(memcmp) +#pragma intrinsic(memcpy) +#pragma intrinsic(memset) +#pragma intrinsic(abs) +#pragma intrinsic(labs) +#endif + +// An expression that should expand to nothing in non PX_CHECKED builds. +// We currently use this only for tagging the purpose of containers for memory use tracking. +#if PX_CHECKED +#define PX_DEBUG_EXP(x) (x) +#else +#define PX_DEBUG_EXP(x) +#endif + +#define PX_SIGN_BITMASK 0x80000000 + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +// Int-as-bool type - has some uses for efficiency and with SIMD +typedef int IntBool; +static const IntBool IntFalse = 0; +static const IntBool IntTrue = 1; + +} +} +} + +#endif // #ifndef PSFOUNDATION_PS_H diff --git a/Source/ThirdParty/NvCloth/ps/PsAlignedMalloc.h b/Source/ThirdParty/NvCloth/ps/PsAlignedMalloc.h new file mode 100644 index 000000000..5591acbcd --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsAlignedMalloc.h @@ -0,0 +1,93 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSALIGNEDMALLOC_H +#define PSFOUNDATION_PSALIGNEDMALLOC_H + +#include "PsUserAllocated.h" + +/*! +Allocate aligned memory. +Alignment must be a power of 2! +-- should be templated by a base allocator +*/ + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +/** +Allocator, which is used to access the global PxAllocatorCallback instance +(used for dynamic data types template instantiation), which can align memory +*/ + +// SCS: AlignedMalloc with 3 params not found, seems not used on PC either +// disabled for now to avoid GCC error + +template +class AlignedAllocator : public BaseAllocator +{ + public: + AlignedAllocator(const BaseAllocator& base = BaseAllocator()) : BaseAllocator(base) + { + } + + void* allocate(size_t size, const char* file, int line) + { + size_t pad = N - 1 + sizeof(size_t); // store offset for delete. + uint8_t* base = reinterpret_cast(BaseAllocator::allocate(size + pad, file, line)); + if(!base) + return NULL; + + uint8_t* ptr = reinterpret_cast(size_t(base + pad) & ~(size_t(N) - 1)); // aligned pointer, ensuring N + // is a size_t + // wide mask + reinterpret_cast(ptr)[-1] = size_t(ptr - base); // store offset + + return ptr; + } + void deallocate(void* ptr) + { + if(ptr == NULL) + return; + + uint8_t* base = reinterpret_cast(ptr) - reinterpret_cast(ptr)[-1]; + BaseAllocator::deallocate(base); + } +}; + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSALIGNEDMALLOC_H diff --git a/Source/ThirdParty/NvCloth/ps/PsAllocator.h b/Source/ThirdParty/NvCloth/ps/PsAllocator.h new file mode 100644 index 000000000..4d2f53852 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsAllocator.h @@ -0,0 +1,351 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSALLOCATOR_H +#define PSFOUNDATION_PSALLOCATOR_H + +#include "foundation/PxAllocatorCallback.h" +//#include "foundation/PxFoundation.h" +#include "Ps.h" +#include "../Callbacks.h" + +#if(PX_WINDOWS_FAMILY || PX_XBOXONE) +#include + #if PX_VC >= 16 + #include + #else + #include + #endif +#endif +#if(PX_APPLE_FAMILY) +#include +#endif + +#include + +// Allocation macros going through user allocator +#define PX_ALLOC(n, name) nv::cloth::NonTrackingAllocator().allocate(n, __FILE__, __LINE__) +#define PX_ALLOC_TEMP(n, name) PX_ALLOC(n, name) +#define PX_FREE(x) nv::cloth::NonTrackingAllocator().deallocate(x) +#define PX_FREE_AND_RESET(x) \ + { \ + PX_FREE(x); \ + x = 0; \ + } + +// The following macros support plain-old-types and classes derived from UserAllocated. +#define PX_NEW(T) new (nv::cloth::ReflectionAllocator(), __FILE__, __LINE__) T +#define PX_NEW_TEMP(T) PX_NEW(T) +#define PX_DELETE(x) delete x +#define PX_DELETE_AND_RESET(x) \ + { \ + PX_DELETE(x); \ + x = 0; \ + } +#define PX_DELETE_POD(x) \ + { \ + PX_FREE(x); \ + x = 0; \ + } +#define PX_DELETE_ARRAY(x) \ + { \ + PX_DELETE([] x); \ + x = 0; \ + } + +// aligned allocation +#define PX_ALIGNED16_ALLOC(n) nv::cloth::AlignedAllocator<16>().allocate(n, __FILE__, __LINE__) +#define PX_ALIGNED16_FREE(x) nv::cloth::AlignedAllocator<16>().deallocate(x) + +//! placement new macro to make it easy to spot bad use of 'new' +#define PX_PLACEMENT_NEW(p, T) new (p) T + +#if PX_DEBUG || PX_CHECKED +#define PX_USE_NAMED_ALLOCATOR 1 +#else +#define PX_USE_NAMED_ALLOCATOR 0 +#endif + +// Don't use inline for alloca !!! +#if PX_WINDOWS_FAMILY +#include +#define PxAlloca(x) _alloca(x) +#elif PX_LINUX || PX_ANDROID +#include +#define PxAlloca(x) alloca(x) +#elif PX_APPLE_FAMILY +#include +#define PxAlloca(x) alloca(x) +#elif PX_PS4 || PX_PS5 +#include +#define PxAlloca(x) alloca(x) +#elif PX_XBOXONE +#include +#define PxAlloca(x) alloca(x) +#elif PX_SWITCH +#include +#define PxAlloca(x) alloca(x) +#endif + +#define PxAllocaAligned(x, alignment) ((size_t(PxAlloca(x + alignment)) + (alignment - 1)) & ~size_t(alignment - 1)) + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +//NV_CLOTH_IMPORT PxAllocatorCallback& getAllocator(); + +/** +Allocator used to access the global PxAllocatorCallback instance without providing additional information. +*/ + +class NV_CLOTH_IMPORT Allocator +{ + public: + Allocator(const char* = 0) + { + } + void* allocate(size_t size, const char* file, int line); + void deallocate(void* ptr); +}; + +/* + * Bootstrap allocator using malloc/free. + * Don't use unless your objects get allocated before foundation is initialized. + */ +class RawAllocator +{ + public: + RawAllocator(const char* = 0) + { + } + void* allocate(size_t size, const char*, int) + { + // malloc returns valid pointer for size==0, no need to check + return ::malloc(size); + } + void deallocate(void* ptr) + { + // free(0) is guaranteed to have no side effect, no need to check + ::free(ptr); + } +}; + +/* + * Allocator that simply calls straight back to the application without tracking. + * This is used by the heap (Foundation::mNamedAllocMap) that tracks allocations + * because it needs to be able to grow as a result of an allocation. + * Making the hash table re-entrant to deal with this may not make sense. + */ +class NonTrackingAllocator +{ + public: + PX_FORCE_INLINE NonTrackingAllocator(const char* = 0) + { + } + PX_FORCE_INLINE void* allocate(size_t size, const char* file, int line) + { + return !size ? 0 : GetNvClothAllocator()->allocate(size, "NonTrackedAlloc", file, line); + } + PX_FORCE_INLINE void deallocate(void* ptr) + { + if(ptr) + GetNvClothAllocator()->deallocate(ptr); + } +}; + +/* +\brief Virtual allocator callback used to provide run-time defined allocators to foundation types like Array or Bitmap. + This is used by VirtualAllocator +*/ +class VirtualAllocatorCallback +{ + public: + VirtualAllocatorCallback() + { + } + virtual ~VirtualAllocatorCallback() + { + } + virtual void* allocate(const size_t size, const char* file, const int line) = 0; + virtual void deallocate(void* ptr) = 0; +}; + +/* +\brief Virtual allocator to be used by foundation types to provide run-time defined allocators. +Due to the fact that Array extends its allocator, rather than contains a reference/pointer to it, the VirtualAllocator +must +be a concrete type containing a pointer to a virtual callback. The callback may not be available at instantiation time, +therefore +methods are provided to set the callback later. +*/ +class VirtualAllocator +{ + public: + VirtualAllocator(VirtualAllocatorCallback* callback = NULL) : mCallback(callback) + { + } + + void* allocate(const size_t size, const char* file, const int line) + { + NV_CLOTH_ASSERT(mCallback); + if(size) + return mCallback->allocate(size, file, line); + return NULL; + } + void deallocate(void* ptr) + { + NV_CLOTH_ASSERT(mCallback); + if(ptr) + mCallback->deallocate(ptr); + } + + void setCallback(VirtualAllocatorCallback* callback) + { + mCallback = callback; + } + VirtualAllocatorCallback* getCallback() + { + return mCallback; + } + + private: + VirtualAllocatorCallback* mCallback; + VirtualAllocator& operator=(const VirtualAllocator&); +}; + +/** +Allocator used to access the global PxAllocatorCallback instance using a static name derived from T. +*/ + +template +class ReflectionAllocator +{ + static const char* getName() + { + if(true) + return ""; +#if PX_GCC_FAMILY + return __PRETTY_FUNCTION__; +#else + // name() calls malloc(), raw_name() wouldn't + return typeid(T).name(); +#endif + } + + public: + ReflectionAllocator(const physx::PxEMPTY) + { + } + ReflectionAllocator(const char* = 0) + { + } + inline ReflectionAllocator(const ReflectionAllocator&) + { + } + void* allocate(size_t size, const char* filename, int line) + { + return size ? GetNvClothAllocator()->allocate(size, getName(), filename, line) : 0; + } + void deallocate(void* ptr) + { + if(ptr) + GetNvClothAllocator()->deallocate(ptr); + } +}; + +template +struct AllocatorTraits +{ + typedef ReflectionAllocator Type; +}; + +// if you get a build error here, you are trying to PX_NEW a class +// that is neither plain-old-type nor derived from UserAllocated +template +union EnableIfPod +{ + int i; + T t; + typedef X Type; +}; + +} // namespace ps +} // namespace cloth +} // namespace nv + +// Global placement new for ReflectionAllocator templated by +// plain-old-type. Allows using PX_NEW for pointers and built-in-types. +// +// ATTENTION: You need to use PX_DELETE_POD or PX_FREE to deallocate +// memory, not PX_DELETE. PX_DELETE_POD redirects to PX_FREE. +// +// Rationale: PX_DELETE uses global operator delete(void*), which we dont' want to overload. +// Any other definition of PX_DELETE couldn't support array syntax 'PX_DELETE([]a);'. +// PX_DELETE_POD was preferred over PX_DELETE_ARRAY because it is used +// less often and applies to both single instances and arrays. +template +PX_INLINE void* operator new(size_t size, nv::cloth::ps::ReflectionAllocator alloc, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line) +{ + return alloc.allocate(size, fileName, line); +} + +template +PX_INLINE void* operator new [](size_t size, nv::cloth::ps::ReflectionAllocator alloc, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line) +{ return alloc.allocate(size, fileName, line); } + +// If construction after placement new throws, this placement delete is being called. +template +PX_INLINE void operator delete(void* ptr, nv::cloth::ps::ReflectionAllocator alloc, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line) +{ + PX_UNUSED(fileName); + PX_UNUSED(line); + + alloc.deallocate(ptr); +} + +// If construction after placement new throws, this placement delete is being called. +template +PX_INLINE void operator delete [](void* ptr, nv::cloth::ps::ReflectionAllocator alloc, const char* fileName, + typename nv::cloth::ps::EnableIfPod::Type line) +{ + PX_UNUSED(fileName); + PX_UNUSED(line); + + alloc.deallocate(ptr); +} + +#endif // #ifndef PSFOUNDATION_PSALLOCATOR_H diff --git a/Source/ThirdParty/NvCloth/ps/PsArray.h b/Source/ThirdParty/NvCloth/ps/PsArray.h new file mode 100644 index 000000000..a4d296b7b --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsArray.h @@ -0,0 +1,726 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSARRAY_H +#define PSFOUNDATION_PSARRAY_H + +#include "../Callbacks.h" +#include "foundation/PxIntrinsics.h" +#include "PsBasicTemplates.h" +#include "NvCloth/ps/PsAllocator.h" + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +template +void exportArray(Serializer& stream, const void* data, uint32_t size, uint32_t sizeOfElement, uint32_t capacity); +char* importArray(char* address, void** data, uint32_t size, uint32_t sizeOfElement, uint32_t capacity); + +/*! +An array is a sequential container. + +Implementation note +* entries between 0 and size are valid objects +* we use inheritance to build this because the array is included inline in a lot + of objects and we want the allocator to take no space if it's not stateful, which + aggregation doesn't allow. Also, we want the metadata at the front for the inline + case where the allocator contains some inline storage space +*/ +template ::Type> +class Array : protected Alloc +{ + public: + typedef T* Iterator; + typedef const T* ConstIterator; + + explicit Array(const physx::PxEMPTY v) : Alloc(v) + { + if(mData) + mCapacity |= PX_SIGN_BITMASK; + } + + /*! + Default array constructor. Initialize an empty array + */ + PX_INLINE explicit Array(const Alloc& alloc = Alloc()) : Alloc(alloc), mData(0), mSize(0), mCapacity(0) + { + } + + /*! + Initialize array with given capacity + */ + PX_INLINE explicit Array(uint32_t size, const T& a = T(), const Alloc& alloc = Alloc()) + : Alloc(alloc), mData(0), mSize(0), mCapacity(0) + { + resize(size, a); + } + + /*! + Copy-constructor. Copy all entries from other array + */ + template + PX_INLINE explicit Array(const Array& other, const Alloc& alloc = Alloc()) + : Alloc(alloc) + { + copy(other); + } + + // This is necessary else the basic default copy constructor is used in the case of both arrays being of the same + // template instance + // The C++ standard clearly states that a template constructor is never a copy constructor [2]. In other words, + // the presence of a template constructor does not suppress the implicit declaration of the copy constructor. + // Also never make a copy constructor explicit, or copy-initialization* will no longer work. This is because + // 'binding an rvalue to a const reference requires an accessible copy constructor' (http://gcc.gnu.org/bugs/) + // *http://stackoverflow.com/questions/1051379/is-there-a-difference-in-c-between-copy-initialization-and-assignment-initializ + PX_INLINE Array(const Array& other, const Alloc& alloc = Alloc()) : Alloc(alloc) + { + copy(other); + } + + /*! + Initialize array with given length + */ + PX_INLINE explicit Array(const T* first, const T* last, const Alloc& alloc = Alloc()) + : Alloc(alloc), mSize(last < first ? 0 : uint32_t(last - first)), mCapacity(mSize) + { + mData = allocate(mSize); + copy(mData, mData + mSize, first); + } + + /*! + Destructor + */ + PX_INLINE ~Array() + { + destroy(mData, mData + mSize); + + if(capacity() && !isInUserMemory()) + deallocate(mData); + } + + /*! + Assignment operator. Copy content (deep-copy) + */ + template + PX_INLINE Array& operator=(const Array& rhs) + { + if(&rhs == this) + return *this; + + clear(); + reserve(rhs.mSize); + copy(mData, mData + rhs.mSize, rhs.mData); + + mSize = rhs.mSize; + return *this; + } + + PX_INLINE Array& operator=(const Array& t) // Needs to be declared, see comment at copy-constructor + { + return operator=(t); + } + + /*! + Array indexing operator. + \param i + The index of the element that will be returned. + \return + The element i in the array. + */ + PX_FORCE_INLINE const T& operator[](uint32_t i) const + { + NV_CLOTH_ASSERT(i < mSize); + return mData[i]; + } + + /*! + Array indexing operator. + \param i + The index of the element that will be returned. + \return + The element i in the array. + */ + PX_FORCE_INLINE T& operator[](uint32_t i) + { + NV_CLOTH_ASSERT(i < mSize); + return mData[i]; + } + + /*! + Returns a pointer to the initial element of the array. + \return + a pointer to the initial element of the array. + */ + PX_FORCE_INLINE ConstIterator begin() const + { + return mData; + } + + PX_FORCE_INLINE Iterator begin() + { + return mData; + } + + /*! + Returns an iterator beyond the last element of the array. Do not dereference. + \return + a pointer to the element beyond the last element of the array. + */ + + PX_FORCE_INLINE ConstIterator end() const + { + return mData + mSize; + } + + PX_FORCE_INLINE Iterator end() + { + return mData + mSize; + } + + /*! + Returns a reference to the first element of the array. Undefined if the array is empty. + \return a reference to the first element of the array + */ + + PX_FORCE_INLINE const T& front() const + { + NV_CLOTH_ASSERT(mSize); + return mData[0]; + } + + PX_FORCE_INLINE T& front() + { + NV_CLOTH_ASSERT(mSize); + return mData[0]; + } + + /*! + Returns a reference to the last element of the array. Undefined if the array is empty + \return a reference to the last element of the array + */ + + PX_FORCE_INLINE const T& back() const + { + NV_CLOTH_ASSERT(mSize); + return mData[mSize - 1]; + } + + PX_FORCE_INLINE T& back() + { + NV_CLOTH_ASSERT(mSize); + return mData[mSize - 1]; + } + + /*! + Returns the number of entries in the array. This can, and probably will, + differ from the array capacity. + \return + The number of of entries in the array. + */ + PX_FORCE_INLINE uint32_t size() const + { + return mSize; + } + + /*! + Clears the array. + */ + PX_INLINE void clear() + { + destroy(mData, mData + mSize); + mSize = 0; + } + + /*! + Returns whether the array is empty (i.e. whether its size is 0). + \return + true if the array is empty + */ + PX_FORCE_INLINE bool empty() const + { + return mSize == 0; + } + + /*! + Finds the first occurrence of an element in the array. + \param a + The element to find. + */ + + PX_INLINE Iterator find(const T& a) + { + uint32_t index; + for(index = 0; index < mSize && mData[index] != a; index++) + ; + return mData + index; + } + + PX_INLINE ConstIterator find(const T& a) const + { + uint32_t index; + for(index = 0; index < mSize && mData[index] != a; index++) + ; + return mData + index; + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Adds one element to the end of the array. Operation is O(1). + \param a + The element that will be added to this array. + */ + ///////////////////////////////////////////////////////////////////////// + + PX_FORCE_INLINE T& pushBack(const T& a) + { + if(capacity() <= mSize) + return growAndPushBack(a); + + PX_PLACEMENT_NEW(reinterpret_cast(mData + mSize), T)(a); + + return mData[mSize++]; + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Returns the element at the end of the array. Only legal if the array is non-empty. + */ + ///////////////////////////////////////////////////////////////////////// + PX_INLINE T popBack() + { + NV_CLOTH_ASSERT(mSize); + T t = mData[mSize - 1]; + + mData[--mSize].~T(); + + return t; + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Construct one element at the end of the array. Operation is O(1). + */ + ///////////////////////////////////////////////////////////////////////// + PX_INLINE T& insert() + { + if(capacity() <= mSize) + grow(capacityIncrement()); + + T* ptr = mData + mSize++; + new (ptr) T; // not 'T()' because PODs should not get default-initialized. + return *ptr; + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Subtracts the element on position i from the array and replace it with + the last element. + Operation is O(1) + \param i + The position of the element that will be subtracted from this array. + */ + ///////////////////////////////////////////////////////////////////////// + PX_INLINE void replaceWithLast(uint32_t i) + { + NV_CLOTH_ASSERT(i < mSize); + mData[i] = mData[--mSize]; + + mData[mSize].~T(); + } + + PX_INLINE void replaceWithLast(Iterator i) + { + replaceWithLast(static_cast(i - mData)); + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Replaces the first occurrence of the element a with the last element + Operation is O(n) + \param a + The position of the element that will be subtracted from this array. + \return true if the element has been removed. + */ + ///////////////////////////////////////////////////////////////////////// + + PX_INLINE bool findAndReplaceWithLast(const T& a) + { + uint32_t index = 0; + while(index < mSize && mData[index] != a) + ++index; + if(index == mSize) + return false; + replaceWithLast(index); + return true; + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Subtracts the element on position i from the array. Shift the entire + array one step. + Operation is O(n) + \param i + The position of the element that will be subtracted from this array. + */ + ///////////////////////////////////////////////////////////////////////// + PX_INLINE void remove(uint32_t i) + { + NV_CLOTH_ASSERT(i < mSize); + + T* it = mData + i; + it->~T(); + while (++i < mSize) + { + new (it) T(mData[i]); + ++it; + it->~T(); + } + --mSize; + } + + ///////////////////////////////////////////////////////////////////////// + /*! + Removes a range from the array. Shifts the array so order is maintained. + Operation is O(n) + \param begin + The starting position of the element that will be subtracted from this array. + \param count + The number of elments that will be subtracted from this array. + */ + ///////////////////////////////////////////////////////////////////////// + PX_INLINE void removeRange(uint32_t begin, uint32_t count) + { + NV_CLOTH_ASSERT(begin < mSize); + NV_CLOTH_ASSERT((begin + count) <= mSize); + + for(uint32_t i = 0; i < count; i++) + mData[begin + i].~T(); // call the destructor on the ones being removed first. + + T* dest = &mData[begin]; // location we are copying the tail end objects to + T* src = &mData[begin + count]; // start of tail objects + uint32_t move_count = mSize - (begin + count); // compute remainder that needs to be copied down + + for(uint32_t i = 0; i < move_count; i++) + { + new (dest) T(*src); // copy the old one to the new location + src->~T(); // call the destructor on the old location + dest++; + src++; + } + mSize -= count; + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Resize array + */ + ////////////////////////////////////////////////////////////////////////// + PX_NOINLINE void resize(const uint32_t size, const T& a = T()); + + PX_NOINLINE void resizeUninitialized(const uint32_t size); + + ////////////////////////////////////////////////////////////////////////// + /*! + Resize array such that only as much memory is allocated to hold the + existing elements + */ + ////////////////////////////////////////////////////////////////////////// + PX_INLINE void shrink() + { + recreate(mSize); + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Deletes all array elements and frees memory. + */ + ////////////////////////////////////////////////////////////////////////// + PX_INLINE void reset() + { + resize(0); + shrink(); + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Ensure that the array has at least size capacity. + */ + ////////////////////////////////////////////////////////////////////////// + PX_INLINE void reserve(const uint32_t capacity) + { + if(capacity > this->capacity()) + grow(capacity); + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Query the capacity(allocated mem) for the array. + */ + ////////////////////////////////////////////////////////////////////////// + PX_FORCE_INLINE uint32_t capacity() const + { + return mCapacity & ~PX_SIGN_BITMASK; + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Unsafe function to force the size of the array + */ + ////////////////////////////////////////////////////////////////////////// + PX_FORCE_INLINE void forceSize_Unsafe(uint32_t size) + { + NV_CLOTH_ASSERT(size <= mCapacity); + mSize = size; + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Swap contents of an array without allocating temporary storage + */ + ////////////////////////////////////////////////////////////////////////// + PX_INLINE void swap(Array& other) + { + ps::swap(mData, other.mData); + ps::swap(mSize, other.mSize); + ps::swap(mCapacity, other.mCapacity); + } + + ////////////////////////////////////////////////////////////////////////// + /*! + Assign a range of values to this vector (resizes to length of range) + */ + ////////////////////////////////////////////////////////////////////////// + PX_INLINE void assign(const T* first, const T* last) + { + resizeUninitialized(uint32_t(last - first)); + copy(begin(), end(), first); + } + + // We need one bit to mark arrays that have been deserialized from a user-provided memory block. + // For alignment & memory saving purpose we store that bit in the rarely used capacity member. + PX_FORCE_INLINE uint32_t isInUserMemory() const + { + return mCapacity & PX_SIGN_BITMASK; + } + + /// return reference to allocator + PX_INLINE Alloc& getAllocator() + { + return *this; + } + + protected: + // constructor for where we don't own the memory + Array(T* memory, uint32_t size, uint32_t capacity, const Alloc& alloc = Alloc()) + : Alloc(alloc), mData(memory), mSize(size), mCapacity(capacity | PX_SIGN_BITMASK) + { + } + + template + PX_NOINLINE void copy(const Array& other); + + PX_INLINE T* allocate(uint32_t size) + { + if(size > 0) + { + T* p = reinterpret_cast(Alloc::allocate(sizeof(T) * size, __FILE__, __LINE__)); +/** +Mark a specified amount of memory with 0xcd pattern. This is used to check that the meta data +definition for serialized classes is complete in checked builds. +*/ +#if PX_CHECKED + if(p) + { + for(uint32_t i = 0; i < (sizeof(T) * size); ++i) + reinterpret_cast(p)[i] = 0xcd; + } +#endif + return p; + } + return 0; + } + + PX_INLINE void deallocate(void* mem) + { + Alloc::deallocate(mem); + } + + static PX_INLINE void create(T* first, T* last, const T& a) + { + for(; first < last; ++first) + ::new (first) T(a); + } + + static PX_INLINE void copy(T* first, T* last, const T* src) + { + if(last <= first) + return; + + for(; first < last; ++first, ++src) + ::new (first) T(*src); + } + + static PX_INLINE void destroy(T* first, T* last) + { + for(; first < last; ++first) + first->~T(); + } + + /*! + Called when pushBack() needs to grow the array. + \param a The element that will be added to this array. + */ + PX_NOINLINE T& growAndPushBack(const T& a); + + /*! + Resizes the available memory for the array. + + \param capacity + The number of entries that the set should be able to hold. + */ + PX_INLINE void grow(uint32_t capacity) + { + NV_CLOTH_ASSERT(this->capacity() < capacity); + recreate(capacity); + } + + /*! + Creates a new memory block, copies all entries to the new block and destroys old entries. + + \param capacity + The number of entries that the set should be able to hold. + */ + PX_NOINLINE void recreate(uint32_t capacity); + + // The idea here is to prevent accidental bugs with pushBack or insert. Unfortunately + // it interacts badly with InlineArrays with smaller inline allocations. + // TODO(dsequeira): policy template arg, this is exactly what they're for. + PX_INLINE uint32_t capacityIncrement() const + { + const uint32_t capacity = this->capacity(); + return capacity == 0 ? 1 : capacity * 2; + } + + T* mData; + uint32_t mSize; + uint32_t mCapacity; +}; + +template +PX_NOINLINE void Array::resize(const uint32_t size, const T& a) +{ + reserve(size); + create(mData + mSize, mData + size, a); + destroy(mData + size, mData + mSize); + mSize = size; +} + +template +template +PX_NOINLINE void Array::copy(const Array& other) +{ + if(!other.empty()) + { + mData = allocate(mSize = mCapacity = other.size()); + copy(mData, mData + mSize, other.begin()); + } + else + { + mData = NULL; + mSize = 0; + mCapacity = 0; + } + + // mData = allocate(other.mSize); + // mSize = other.mSize; + // mCapacity = other.mSize; + // copy(mData, mData + mSize, other.mData); +} + +template +PX_NOINLINE void Array::resizeUninitialized(const uint32_t size) +{ + reserve(size); + mSize = size; +} + +template +PX_NOINLINE T& Array::growAndPushBack(const T& a) +{ + uint32_t capacity = capacityIncrement(); + + T* newData = allocate(capacity); + NV_CLOTH_ASSERT((!capacity) || (newData && (newData != mData))); + copy(newData, newData + mSize, mData); + + // inserting element before destroying old array + // avoids referencing destroyed object when duplicating array element. + PX_PLACEMENT_NEW(reinterpret_cast(newData + mSize), T)(a); + + destroy(mData, mData + mSize); + if(!isInUserMemory()) + deallocate(mData); + + mData = newData; + mCapacity = capacity; + + return mData[mSize++]; +} + +template +PX_NOINLINE void Array::recreate(uint32_t capacity) +{ + T* newData = allocate(capacity); + NV_CLOTH_ASSERT((!capacity) || (newData && (newData != mData))); + + copy(newData, newData + mSize, mData); + destroy(mData, mData + mSize); + if(!isInUserMemory()) + deallocate(mData); + + mData = newData; + mCapacity = capacity; +} + +template +PX_INLINE void swap(Array& x, Array& y) +{ + x.swap(y); +} + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSARRAY_H diff --git a/Source/ThirdParty/NvCloth/ps/PsAtomic.h b/Source/ThirdParty/NvCloth/ps/PsAtomic.h new file mode 100644 index 000000000..65f12ef7a --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsAtomic.h @@ -0,0 +1,69 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSATOMIC_H +#define PSFOUNDATION_PSATOMIC_H + +#include "NvCloth/ps/Ps.h" +#include "NvCloth/Callbacks.h" + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +/* set *dest equal to val. Return the old value of *dest */ +NV_CLOTH_IMPORT int32_t atomicExchange(volatile int32_t* dest, int32_t val); + +/* if *dest == comp, replace with exch. Return original value of *dest */ +NV_CLOTH_IMPORT int32_t atomicCompareExchange(volatile int32_t* dest, int32_t exch, int32_t comp); + +/* if *dest == comp, replace with exch. Return original value of *dest */ +NV_CLOTH_IMPORT void* atomicCompareExchangePointer(volatile void** dest, void* exch, void* comp); + +/* increment the specified location. Return the incremented value */ +NV_CLOTH_IMPORT int32_t atomicIncrement(volatile int32_t* val); + +/* decrement the specified location. Return the decremented value */ +NV_CLOTH_IMPORT int32_t atomicDecrement(volatile int32_t* val); + +/* add delta to *val. Return the new value */ +NV_CLOTH_IMPORT int32_t atomicAdd(volatile int32_t* val, int32_t delta); + +/* compute the maximum of dest and val. Return the new value */ +NV_CLOTH_IMPORT int32_t atomicMax(volatile int32_t* val, int32_t val2); + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSATOMIC_H diff --git a/Source/ThirdParty/NvCloth/ps/PsBasicTemplates.h b/Source/ThirdParty/NvCloth/ps/PsBasicTemplates.h new file mode 100644 index 000000000..c8ef56c70 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsBasicTemplates.h @@ -0,0 +1,151 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSBASICTEMPLATES_H +#define PSFOUNDATION_PSBASICTEMPLATES_H + +#include "Ps.h" + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +template +struct Equal +{ + bool operator()(const A& a, const A& b) const + { + return a == b; + } +}; + +template +struct Less +{ + bool operator()(const A& a, const A& b) const + { + return a < b; + } +}; + +template +struct Greater +{ + bool operator()(const A& a, const A& b) const + { + return a > b; + } +}; + +template +class Pair +{ + public: + F first; + S second; + Pair() : first(F()), second(S()) + { + } + Pair(const F& f, const S& s) : first(f), second(s) + { + } + Pair(const Pair& p) : first(p.first), second(p.second) + { + } + // CN - fix for /.../PsBasicTemplates.h(61) : warning C4512: 'nv::cloth::Pair' : assignment operator could + // not be generated + Pair& operator=(const Pair& p) + { + first = p.first; + second = p.second; + return *this; + } + bool operator==(const Pair& p) const + { + return first == p.first && second == p.second; + } + bool operator<(const Pair& p) const + { + if(first < p.first) + return true; + else + return !(p.first < first) && (second < p.second); + } +}; + +template +struct LogTwo +{ + static const unsigned int value = LogTwo<(A >> 1)>::value + 1; +}; +template <> +struct LogTwo<1> +{ + static const unsigned int value = 0; +}; + +template +struct UnConst +{ + typedef T Type; +}; +template +struct UnConst +{ + typedef T Type; +}; + +template +T pointerOffset(void* p, ptrdiff_t offset) +{ + return reinterpret_cast(reinterpret_cast(p) + offset); +} +template +T pointerOffset(const void* p, ptrdiff_t offset) +{ + return reinterpret_cast(reinterpret_cast(p) + offset); +} + +template +PX_CUDA_CALLABLE PX_INLINE void swap(T& x, T& y) +{ + const T tmp = x; + x = y; + y = tmp; +} + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSBASICTEMPLATES_H diff --git a/Source/ThirdParty/NvCloth/ps/PsBitUtils.h b/Source/ThirdParty/NvCloth/ps/PsBitUtils.h new file mode 100644 index 000000000..f25e32ce1 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsBitUtils.h @@ -0,0 +1,114 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSBITUTILS_H +#define PSFOUNDATION_PSBITUTILS_H + +#include "foundation/PxIntrinsics.h" +#include "../Callbacks.h" +#include "PsIntrinsics.h" +#include "Ps.h" + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +PX_INLINE uint32_t bitCount(uint32_t v) +{ + // from http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + uint32_t const w = v - ((v >> 1) & 0x55555555); + uint32_t const x = (w & 0x33333333) + ((w >> 2) & 0x33333333); + return (((x + (x >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; +} + +PX_INLINE bool isPowerOfTwo(uint32_t x) +{ + return x != 0 && (x & (x - 1)) == 0; +} + +// "Next Largest Power of 2 +// Given a binary integer value x, the next largest power of 2 can be computed by a SWAR algorithm +// that recursively "folds" the upper bits into the lower bits. This process yields a bit vector with +// the same most significant 1 as x, but all 1's below it. Adding 1 to that value yields the next +// largest power of 2. For a 32-bit value:" +PX_INLINE uint32_t nextPowerOfTwo(uint32_t x) +{ + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return x + 1; +} + +/*! +Return the index of the highest set bit. Not valid for zero arg. +*/ + +PX_INLINE uint32_t lowestSetBit(uint32_t x) +{ + NV_CLOTH_ASSERT(x); + return PxLowestSetBitUnsafe(x); +} + +/*! +Return the index of the highest set bit. Not valid for zero arg. +*/ + +PX_INLINE uint32_t highestSetBit(uint32_t x) +{ + NV_CLOTH_ASSERT(x); + return PxHighestSetBitUnsafe(x); +} + +// Helper function to approximate log2 of an integer value +// assumes that the input is actually power of two. +// todo: replace 2 usages with 'highestSetBit' +PX_INLINE uint32_t ilog2(uint32_t num) +{ + for(uint32_t i = 0; i < 32; i++) + { + num >>= 1; + if(num == 0) + return i; + } + + NV_CLOTH_ASSERT(0); + return uint32_t(-1); +} + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSBITUTILS_H diff --git a/Source/ThirdParty/NvCloth/ps/PsHash.h b/Source/ThirdParty/NvCloth/ps/PsHash.h new file mode 100644 index 000000000..92c323df1 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsHash.h @@ -0,0 +1,167 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSHASH_H +#define PSFOUNDATION_PSHASH_H + +#include "Ps.h" +#include "PsBasicTemplates.h" + +#if PX_VC +#pragma warning(push) +#pragma warning(disable : 4302) +#endif + +#if PX_LINUX +#include "foundation/PxSimpleTypes.h" +#endif + +/*! +Central definition of hash functions +*/ + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +// Hash functions + +// Thomas Wang's 32 bit mix +// http://www.cris.com/~Ttwang/tech/inthash.htm +PX_FORCE_INLINE uint32_t hash(const uint32_t key) +{ + uint32_t k = key; + k += ~(k << 15); + k ^= (k >> 10); + k += (k << 3); + k ^= (k >> 6); + k += ~(k << 11); + k ^= (k >> 16); + return uint32_t(k); +} + +PX_FORCE_INLINE uint32_t hash(const int32_t key) +{ + return hash(uint32_t(key)); +} + +// Thomas Wang's 64 bit mix +// http://www.cris.com/~Ttwang/tech/inthash.htm +PX_FORCE_INLINE uint32_t hash(const uint64_t key) +{ + uint64_t k = key; + k += ~(k << 32); + k ^= (k >> 22); + k += ~(k << 13); + k ^= (k >> 8); + k += (k << 3); + k ^= (k >> 15); + k += ~(k << 27); + k ^= (k >> 31); + return uint32_t(UINT32_MAX & k); +} + +#if PX_APPLE_FAMILY +// hash for size_t, to make gcc happy +PX_INLINE uint32_t hash(const size_t key) +{ +#if PX_P64_FAMILY + return hash(uint64_t(key)); +#else + return hash(uint32_t(key)); +#endif +} +#endif + +// Hash function for pointers +PX_INLINE uint32_t hash(const void* ptr) +{ +#if PX_P64_FAMILY + return hash(uint64_t(ptr)); +#else + return hash(uint32_t(UINT32_MAX & size_t(ptr))); +#endif +} + +// Hash function for pairs +template +PX_INLINE uint32_t hash(const Pair& p) +{ + uint32_t seed = 0x876543; + uint32_t m = 1000007; + return hash(p.second) ^ (m * (hash(p.first) ^ (m * seed))); +} + +// hash object for hash map template parameter +template +struct Hash +{ + uint32_t operator()(const Key& k) const + { + return hash(k); + } + bool equal(const Key& k0, const Key& k1) const + { + return k0 == k1; + } +}; + +// specialization for strings +template <> +struct Hash +{ + public: + uint32_t operator()(const char* _string) const + { + // "DJB" string hash + const uint8_t* string = reinterpret_cast(_string); + uint32_t h = 5381; + for(const uint8_t* ptr = string; *ptr; ptr++) + h = ((h << 5) + h) ^ uint32_t(*ptr); + return h; + } + bool equal(const char* string0, const char* string1) const + { + return !strcmp(string0, string1); + } +}; + +} // namespace ps +} // namespace cloth +} // namespace nv + +#if PX_VC +#pragma warning(pop) +#endif + +#endif // #ifndef PSFOUNDATION_PSHASH_H diff --git a/Source/ThirdParty/NvCloth/ps/PsHashInternals.h b/Source/ThirdParty/NvCloth/ps/PsHashInternals.h new file mode 100644 index 000000000..7e823e8d6 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsHashInternals.h @@ -0,0 +1,800 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSHASHINTERNALS_H +#define PSFOUNDATION_PSHASHINTERNALS_H + +#include "PsBasicTemplates.h" +#include "PsArray.h" +#include "PsBitUtils.h" +#include "PsHash.h" +#include "foundation/PxIntrinsics.h" + +#if PX_VC +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#endif +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +namespace internal +{ +template +class HashBase : private Allocator +{ + void init(uint32_t initialTableSize, float loadFactor) + { + mBuffer = NULL; + mEntries = NULL; + mEntriesNext = NULL; + mHash = NULL; + mEntriesCapacity = 0; + mHashSize = 0; + mLoadFactor = loadFactor; + mFreeList = uint32_t(EOL); + mTimestamp = 0; + mEntriesCount = 0; + + if(initialTableSize) + reserveInternal(initialTableSize); + } + + public: + typedef Entry EntryType; + + HashBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : Allocator(PX_DEBUG_EXP("hashBase")) + { + init(initialTableSize, loadFactor); + } + + HashBase(uint32_t initialTableSize, float loadFactor, const Allocator& alloc) : Allocator(alloc) + { + init(initialTableSize, loadFactor); + } + + HashBase(const Allocator& alloc) : Allocator(alloc) + { + init(64, 0.75f); + } + + ~HashBase() + { + destroy(); // No need to clear() + + if(mBuffer) + Allocator::deallocate(mBuffer); + } + + static const uint32_t EOL = 0xffffffff; + + PX_INLINE Entry* create(const Key& k, bool& exists) + { + uint32_t h = 0; + if(mHashSize) + { + h = hash(k); + uint32_t index = mHash[h]; + while(index != EOL && !HashFn().equal(GetKey()(mEntries[index]), k)) + index = mEntriesNext[index]; + exists = index != EOL; + if(exists) + return mEntries + index; + } + else + exists = false; + + if(freeListEmpty()) + { + grow(); + h = hash(k); + } + + uint32_t entryIndex = freeListGetNext(); + + mEntriesNext[entryIndex] = mHash[h]; + mHash[h] = entryIndex; + + mEntriesCount++; + mTimestamp++; + + return mEntries + entryIndex; + } + + PX_INLINE const Entry* find(const Key& k) const + { + if(!mEntriesCount) + return NULL; + + const uint32_t h = hash(k); + uint32_t index = mHash[h]; + while(index != EOL && !HashFn().equal(GetKey()(mEntries[index]), k)) + index = mEntriesNext[index]; + return index != EOL ? mEntries + index : NULL; + } + + PX_INLINE bool erase(const Key& k, Entry& e) + { + if(!mEntriesCount) + return false; + + const uint32_t h = hash(k); + uint32_t* ptr = mHash + h; + while(*ptr != EOL && !HashFn().equal(GetKey()(mEntries[*ptr]), k)) + ptr = mEntriesNext + *ptr; + + if(*ptr == EOL) + return false; + + PX_PLACEMENT_NEW(&e, Entry)(mEntries[*ptr]); + + return eraseInternal(ptr); + } + + PX_INLINE bool erase(const Key& k) + { + if(!mEntriesCount) + return false; + + const uint32_t h = hash(k); + uint32_t* ptr = mHash + h; + while(*ptr != EOL && !HashFn().equal(GetKey()(mEntries[*ptr]), k)) + ptr = mEntriesNext + *ptr; + + if(*ptr == EOL) + return false; + + return eraseInternal(ptr); + } + + PX_INLINE uint32_t size() const + { + return mEntriesCount; + } + + PX_INLINE uint32_t capacity() const + { + return mHashSize; + } + + void clear() + { + if(!mHashSize || mEntriesCount == 0) + return; + + destroy(); + + physx::intrinsics::memSet(mHash, EOL, mHashSize * sizeof(uint32_t)); + + const uint32_t sizeMinus1 = mEntriesCapacity - 1; + for(uint32_t i = 0; i < sizeMinus1; i++) + { + PxPrefetchLine(mEntriesNext + i, 128); + mEntriesNext[i] = i + 1; + } + mEntriesNext[mEntriesCapacity - 1] = uint32_t(EOL); + mFreeList = 0; + mEntriesCount = 0; + } + + void reserve(uint32_t size) + { + if(size > mHashSize) + reserveInternal(size); + } + + PX_INLINE const Entry* getEntries() const + { + return mEntries; + } + + PX_INLINE Entry* insertUnique(const Key& k) + { + NV_CLOTH_ASSERT(find(k) == NULL); + uint32_t h = hash(k); + + uint32_t entryIndex = freeListGetNext(); + + mEntriesNext[entryIndex] = mHash[h]; + mHash[h] = entryIndex; + + mEntriesCount++; + mTimestamp++; + + return mEntries + entryIndex; + } + + private: + void destroy() + { + for(uint32_t i = 0; i < mHashSize; i++) + { + for(uint32_t j = mHash[i]; j != EOL; j = mEntriesNext[j]) + mEntries[j].~Entry(); + } + } + + template + PX_NOINLINE void copy(const HashBase& other); + + // free list management - if we're coalescing, then we use mFreeList to hold + // the top of the free list and it should always be equal to size(). Otherwise, + // we build a free list in the next() pointers. + + PX_INLINE void freeListAdd(uint32_t index) + { + if(compacting) + { + mFreeList--; + NV_CLOTH_ASSERT(mFreeList == mEntriesCount); + } + else + { + mEntriesNext[index] = mFreeList; + mFreeList = index; + } + } + + PX_INLINE void freeListAdd(uint32_t start, uint32_t end) + { + if(!compacting) + { + for(uint32_t i = start; i < end - 1; i++) // add the new entries to the free list + mEntriesNext[i] = i + 1; + + // link in old free list + mEntriesNext[end - 1] = mFreeList; + NV_CLOTH_ASSERT(mFreeList != end - 1); + mFreeList = start; + } + else if(mFreeList == EOL) // don't reset the free ptr for the compacting hash unless it's empty + mFreeList = start; + } + + PX_INLINE uint32_t freeListGetNext() + { + NV_CLOTH_ASSERT(!freeListEmpty()); + if(compacting) + { + NV_CLOTH_ASSERT(mFreeList == mEntriesCount); + return mFreeList++; + } + else + { + uint32_t entryIndex = mFreeList; + mFreeList = mEntriesNext[mFreeList]; + return entryIndex; + } + } + + PX_INLINE bool freeListEmpty() const + { + if(compacting) + return mEntriesCount == mEntriesCapacity; + else + return mFreeList == EOL; + } + + PX_INLINE void replaceWithLast(uint32_t index) + { + PX_PLACEMENT_NEW(mEntries + index, Entry)(mEntries[mEntriesCount]); + mEntries[mEntriesCount].~Entry(); + mEntriesNext[index] = mEntriesNext[mEntriesCount]; + + uint32_t h = hash(GetKey()(mEntries[index])); + uint32_t* ptr; + for(ptr = mHash + h; *ptr != mEntriesCount; ptr = mEntriesNext + *ptr) + NV_CLOTH_ASSERT(*ptr != EOL); + *ptr = index; + } + + PX_INLINE uint32_t hash(const Key& k, uint32_t hashSize) const + { + return HashFn()(k) & (hashSize - 1); + } + + PX_INLINE uint32_t hash(const Key& k) const + { + return hash(k, mHashSize); + } + + PX_INLINE bool eraseInternal(uint32_t* ptr) + { + const uint32_t index = *ptr; + + *ptr = mEntriesNext[index]; + + mEntries[index].~Entry(); + + mEntriesCount--; + mTimestamp++; + + if (compacting && index != mEntriesCount) + replaceWithLast(index); + + freeListAdd(index); + return true; + } + + void reserveInternal(uint32_t size) + { + if(!isPowerOfTwo(size)) + size = nextPowerOfTwo(size); + + NV_CLOTH_ASSERT(!(size & (size - 1))); + + // decide whether iteration can be done on the entries directly + bool resizeCompact = compacting || freeListEmpty(); + + // define new table sizes + uint32_t oldEntriesCapacity = mEntriesCapacity; + uint32_t newEntriesCapacity = uint32_t(float(size) * mLoadFactor); + uint32_t newHashSize = size; + + // allocate new common buffer and setup pointers to new tables + uint8_t* newBuffer; + uint32_t* newHash; + uint32_t* newEntriesNext; + Entry* newEntries; + { + uint32_t newHashByteOffset = 0; + uint32_t newEntriesNextBytesOffset = newHashByteOffset + newHashSize * sizeof(uint32_t); + uint32_t newEntriesByteOffset = newEntriesNextBytesOffset + newEntriesCapacity * sizeof(uint32_t); + newEntriesByteOffset += (16 - (newEntriesByteOffset & 15)) & 15; + uint32_t newBufferByteSize = newEntriesByteOffset + newEntriesCapacity * sizeof(Entry); + + newBuffer = reinterpret_cast(Allocator::allocate(newBufferByteSize, __FILE__, __LINE__)); + NV_CLOTH_ASSERT(newBuffer); + + newHash = reinterpret_cast(newBuffer + newHashByteOffset); + newEntriesNext = reinterpret_cast(newBuffer + newEntriesNextBytesOffset); + newEntries = reinterpret_cast(newBuffer + newEntriesByteOffset); + } + + // initialize new hash table + physx::intrinsics::memSet(newHash, uint32_t(EOL), newHashSize * sizeof(uint32_t)); + + // iterate over old entries, re-hash and create new entries + if(resizeCompact) + { + // check that old free list is empty - we don't need to copy the next entries + NV_CLOTH_ASSERT(compacting || mFreeList == EOL); + + for(uint32_t index = 0; index < mEntriesCount; ++index) + { + uint32_t h = hash(GetKey()(mEntries[index]), newHashSize); + newEntriesNext[index] = newHash[h]; + newHash[h] = index; + + PX_PLACEMENT_NEW(newEntries + index, Entry)(mEntries[index]); + mEntries[index].~Entry(); + } + } + else + { + // copy old free list, only required for non compact resizing + physx::intrinsics::memCopy(newEntriesNext, mEntriesNext, mEntriesCapacity * sizeof(uint32_t)); + + for(uint32_t bucket = 0; bucket < mHashSize; bucket++) + { + uint32_t index = mHash[bucket]; + while(index != EOL) + { + uint32_t h = hash(GetKey()(mEntries[index]), newHashSize); + newEntriesNext[index] = newHash[h]; + NV_CLOTH_ASSERT(index != newHash[h]); + + newHash[h] = index; + + PX_PLACEMENT_NEW(newEntries + index, Entry)(mEntries[index]); + mEntries[index].~Entry(); + + index = mEntriesNext[index]; + } + } + } + + // swap buffer and pointers + Allocator::deallocate(mBuffer); + mBuffer = newBuffer; + mHash = newHash; + mHashSize = newHashSize; + mEntriesNext = newEntriesNext; + mEntries = newEntries; + mEntriesCapacity = newEntriesCapacity; + + freeListAdd(oldEntriesCapacity, newEntriesCapacity); + } + + void grow() + { + NV_CLOTH_ASSERT((mFreeList == EOL) || (compacting && (mEntriesCount == mEntriesCapacity))); + + uint32_t size = mHashSize == 0 ? 16 : mHashSize * 2; + reserve(size); + } + + uint8_t* mBuffer; + Entry* mEntries; + uint32_t* mEntriesNext; // same size as mEntries + uint32_t* mHash; + uint32_t mEntriesCapacity; + uint32_t mHashSize; + float mLoadFactor; + uint32_t mFreeList; + uint32_t mTimestamp; + uint32_t mEntriesCount; // number of entries + + public: + class Iter + { + public: + PX_INLINE Iter(HashBase& b) : mBucket(0), mEntry(uint32_t(b.EOL)), mTimestamp(b.mTimestamp), mBase(b) + { + if(mBase.mEntriesCapacity > 0) + { + mEntry = mBase.mHash[0]; + skip(); + } + } + + PX_INLINE void check() const + { + NV_CLOTH_ASSERT(mTimestamp == mBase.mTimestamp); + } + PX_INLINE const Entry& operator*() const + { + check(); + return mBase.mEntries[mEntry]; + } + PX_INLINE Entry& operator*() + { + check(); + return mBase.mEntries[mEntry]; + } + PX_INLINE const Entry* operator->() const + { + check(); + return mBase.mEntries + mEntry; + } + PX_INLINE Entry* operator->() + { + check(); + return mBase.mEntries + mEntry; + } + PX_INLINE Iter operator++() + { + check(); + advance(); + return *this; + } + PX_INLINE Iter operator++(int) + { + check(); + Iter i = *this; + advance(); + return i; + } + PX_INLINE bool done() const + { + check(); + return mEntry == mBase.EOL; + } + + private: + PX_INLINE void advance() + { + mEntry = mBase.mEntriesNext[mEntry]; + skip(); + } + PX_INLINE void skip() + { + while(mEntry == mBase.EOL) + { + if(++mBucket == mBase.mHashSize) + break; + mEntry = mBase.mHash[mBucket]; + } + } + + Iter& operator=(const Iter&); + + uint32_t mBucket; + uint32_t mEntry; + uint32_t mTimestamp; + HashBase& mBase; + }; + + /*! + Iterate over entries in a hash base and allow entry erase while iterating + */ + class EraseIterator + { + public: + PX_INLINE EraseIterator(HashBase& b): mBase(b) + { + reset(); + } + + PX_INLINE Entry* eraseCurrentGetNext(bool eraseCurrent) + { + if(eraseCurrent && mCurrentEntryIndexPtr) + { + mBase.eraseInternal(mCurrentEntryIndexPtr); + // if next was valid return the same ptr, if next was EOL search new hash entry + if(*mCurrentEntryIndexPtr != mBase.EOL) + return mBase.mEntries + *mCurrentEntryIndexPtr; + else + return traverseHashEntries(); + } + + // traverse mHash to find next entry + if(mCurrentEntryIndexPtr == NULL) + return traverseHashEntries(); + + const uint32_t index = *mCurrentEntryIndexPtr; + if(mBase.mEntriesNext[index] == mBase.EOL) + { + return traverseHashEntries(); + } + else + { + mCurrentEntryIndexPtr = mBase.mEntriesNext + index; + return mBase.mEntries + *mCurrentEntryIndexPtr; + } + } + + PX_INLINE void reset() + { + mCurrentHashIndex = 0; + mCurrentEntryIndexPtr = NULL; + } + + private: + PX_INLINE Entry* traverseHashEntries() + { + mCurrentEntryIndexPtr = NULL; + while (mCurrentEntryIndexPtr == NULL && mCurrentHashIndex < mBase.mHashSize) + { + if (mBase.mHash[mCurrentHashIndex] != mBase.EOL) + { + mCurrentEntryIndexPtr = mBase.mHash + mCurrentHashIndex; + mCurrentHashIndex++; + return mBase.mEntries + *mCurrentEntryIndexPtr; + } + else + { + mCurrentHashIndex++; + } + } + return NULL; + } + + EraseIterator& operator=(const EraseIterator&); + private: + uint32_t* mCurrentEntryIndexPtr; + uint32_t mCurrentHashIndex; + HashBase& mBase; + }; +}; + +template +template +PX_NOINLINE void +HashBase::copy(const HashBase& other) +{ + reserve(other.mEntriesCount); + + for(uint32_t i = 0; i < other.mEntriesCount; i++) + { + for(uint32_t j = other.mHash[i]; j != EOL; j = other.mEntriesNext[j]) + { + const Entry& otherEntry = other.mEntries[j]; + + bool exists; + Entry* newEntry = create(GK()(otherEntry), exists); + NV_CLOTH_ASSERT(!exists); + + PX_PLACEMENT_NEW(newEntry, Entry)(otherEntry); + } + } +} + +template ::Type, bool Coalesced = false> +class HashSetBase +{ + PX_NOCOPY(HashSetBase) + public: + struct GetKey + { + PX_INLINE const Key& operator()(const Key& e) + { + return e; + } + }; + + typedef HashBase BaseMap; + typedef typename BaseMap::Iter Iterator; + + HashSetBase(uint32_t initialTableSize, float loadFactor, const Allocator& alloc) + : mBase(initialTableSize, loadFactor, alloc) + { + } + + HashSetBase(const Allocator& alloc) : mBase(64, 0.75f, alloc) + { + } + + HashSetBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : mBase(initialTableSize, loadFactor) + { + } + + bool insert(const Key& k) + { + bool exists; + Key* e = mBase.create(k, exists); + if(!exists) + PX_PLACEMENT_NEW(e, Key)(k); + return !exists; + } + + PX_INLINE bool contains(const Key& k) const + { + return mBase.find(k) != 0; + } + PX_INLINE bool erase(const Key& k) + { + return mBase.erase(k); + } + PX_INLINE uint32_t size() const + { + return mBase.size(); + } + PX_INLINE uint32_t capacity() const + { + return mBase.capacity(); + } + PX_INLINE void reserve(uint32_t size) + { + mBase.reserve(size); + } + PX_INLINE void clear() + { + mBase.clear(); + } + + protected: + BaseMap mBase; +}; + +template >::Type> +class HashMapBase +{ + PX_NOCOPY(HashMapBase) + public: + typedef Pair Entry; + + struct GetKey + { + PX_INLINE const Key& operator()(const Entry& e) + { + return e.first; + } + }; + + typedef HashBase BaseMap; + typedef typename BaseMap::Iter Iterator; + typedef typename BaseMap::EraseIterator EraseIterator; + + HashMapBase(uint32_t initialTableSize, float loadFactor, const Allocator& alloc) + : mBase(initialTableSize, loadFactor, alloc) + { + } + + HashMapBase(const Allocator& alloc) : mBase(64, 0.75f, alloc) + { + } + + HashMapBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : mBase(initialTableSize, loadFactor) + { + } + + bool insert(const Key /*&*/ k, const Value /*&*/ v) + { + bool exists; + Entry* e = mBase.create(k, exists); + if(!exists) + PX_PLACEMENT_NEW(e, Entry)(k, v); + return !exists; + } + + Value& operator[](const Key& k) + { + bool exists; + Entry* e = mBase.create(k, exists); + if(!exists) + PX_PLACEMENT_NEW(e, Entry)(k, Value()); + + return e->second; + } + + PX_INLINE const Entry* find(const Key& k) const + { + return mBase.find(k); + } + PX_INLINE bool erase(const Key& k) + { + return mBase.erase(k); + } + PX_INLINE bool erase(const Key& k, Entry& e) + { + return mBase.erase(k, e); + } + PX_INLINE uint32_t size() const + { + return mBase.size(); + } + PX_INLINE uint32_t capacity() const + { + return mBase.capacity(); + } + PX_INLINE Iterator getIterator() + { + return Iterator(mBase); + } + PX_INLINE EraseIterator getEraseIterator() + { + return EraseIterator(mBase); + } + PX_INLINE void reserve(uint32_t size) + { + mBase.reserve(size); + } + PX_INLINE void clear() + { + mBase.clear(); + } + + protected: + BaseMap mBase; +}; +} + +} // namespace ps +} // namespace cloth +} // namespace nv + +#if PX_VC +#pragma warning(pop) +#endif +#endif // #ifndef PSFOUNDATION_PSHASHINTERNALS_H diff --git a/Source/ThirdParty/NvCloth/ps/PsHashMap.h b/Source/ThirdParty/NvCloth/ps/PsHashMap.h new file mode 100644 index 000000000..229b35aaa --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsHashMap.h @@ -0,0 +1,123 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSHASHMAP_H +#define PSFOUNDATION_PSHASHMAP_H + +#include "PsHashInternals.h" + +// TODO: make this doxy-format +// +// This header defines two hash maps. Hash maps +// * support custom initial table sizes (rounded up internally to power-of-2) +// * support custom static allocator objects +// * auto-resize, based on a load factor (i.e. a 64-entry .75 load factor hash will resize +// when the 49th element is inserted) +// * are based on open hashing +// * have O(1) contains, erase +// +// Maps have STL-like copying semantics, and properly initialize and destruct copies of objects +// +// There are two forms of map: coalesced and uncoalesced. Coalesced maps keep the entries in the +// initial segment of an array, so are fast to iterate over; however deletion is approximately +// twice as expensive. +// +// HashMap: +// bool insert(const Key& k, const Value& v) O(1) amortized (exponential resize policy) +// Value & operator[](const Key& k) O(1) for existing objects, else O(1) amortized +// const Entry * find(const Key& k); O(1) +// bool erase(const T& k); O(1) +// uint32_t size(); constant +// void reserve(uint32_t size); O(MAX(currentOccupancy,size)) +// void clear(); O(currentOccupancy) (with zero constant for objects +// without +// destructors) +// Iterator getIterator(); +// +// operator[] creates an entry if one does not exist, initializing with the default constructor. +// CoalescedHashMap does not support getIterator, but instead supports +// const Key *getEntries(); +// +// Use of iterators: +// +// for(HashMap::Iterator iter = test.getIterator(); !iter.done(); ++iter) +// myFunction(iter->first, iter->second); + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +template , class Allocator = NonTrackingAllocator> +class HashMap : public internal::HashMapBase +{ + public: + typedef internal::HashMapBase HashMapBase; + typedef typename HashMapBase::Iterator Iterator; + + HashMap(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : HashMapBase(initialTableSize, loadFactor) + { + } + HashMap(uint32_t initialTableSize, float loadFactor, const Allocator& alloc) + : HashMapBase(initialTableSize, loadFactor, alloc) + { + } + HashMap(const Allocator& alloc) : HashMapBase(64, 0.75f, alloc) + { + } + Iterator getIterator() + { + return Iterator(HashMapBase::mBase); + } +}; + +template , class Allocator = NonTrackingAllocator> +class CoalescedHashMap : public internal::HashMapBase +{ + public: + typedef internal::HashMapBase HashMapBase; + + CoalescedHashMap(uint32_t initialTableSize = 64, float loadFactor = 0.75f) + : HashMapBase(initialTableSize, loadFactor) + { + } + const Pair* getEntries() const + { + return HashMapBase::mBase.getEntries(); + } +}; + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSHASHMAP_H diff --git a/Source/ThirdParty/NvCloth/ps/PsIntrinsics.h b/Source/ThirdParty/NvCloth/ps/PsIntrinsics.h new file mode 100644 index 000000000..5f5d35d16 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsIntrinsics.h @@ -0,0 +1,47 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSINTRINSICS_H +#define PSFOUNDATION_PSINTRINSICS_H + +#include "foundation/PxPreprocessor.h" + +#if PX_WINDOWS_FAMILY +#include "windows/PsWindowsIntrinsics.h" +#elif(PX_LINUX || PX_ANDROID || PX_APPLE_FAMILY || PX_PS4 || PX_PS5) +#include "unix/PsUnixIntrinsics.h" +#elif PX_XBOXONE +#include "XboxOne/PsXboxOneIntrinsics.h" +#elif PX_SWITCH +#include "switch/PsSwitchIntrinsics.h" +#else +#error "Platform not supported!" +#endif + +#endif // #ifndef PSFOUNDATION_PSINTRINSICS_H diff --git a/Source/ThirdParty/NvCloth/ps/PsMathUtils.h b/Source/ThirdParty/NvCloth/ps/PsMathUtils.h new file mode 100644 index 000000000..b2c4602c0 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsMathUtils.h @@ -0,0 +1,699 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSMATHUTILS_H +#define PSFOUNDATION_PSMATHUTILS_H + +#include "foundation/PxPreprocessor.h" +#include "foundation/PxTransform.h" +#include "foundation/PxMat33.h" +#include "NvCloth/ps/Ps.h" +#include "NvCloth/ps/PsIntrinsics.h" +#include "NvCloth/Callbacks.h" + +// General guideline is: if it's an abstract math function, it belongs here. +// If it's a math function where the inputs have specific semantics (e.g. +// separateSwingTwist) it doesn't. + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +/** +\brief sign returns the sign of its argument. The sign of zero is undefined. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 sign(const physx::PxF32 a) +{ + return physx::intrinsics::sign(a); +} + +/** +\brief sign returns the sign of its argument. The sign of zero is undefined. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 sign(const physx::PxF64 a) +{ + return (a >= 0.0) ? 1.0 : -1.0; +} + +/** +\brief sign returns the sign of its argument. The sign of zero is undefined. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxI32 sign(const physx::PxI32 a) +{ + return (a >= 0) ? 1 : -1; +} + +/** +\brief Returns true if the two numbers are within eps of each other. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE bool equals(const physx::PxF32 a, const physx::PxF32 b, const physx::PxF32 eps) +{ + return (physx::PxAbs(a - b) < eps); +} + +/** +\brief Returns true if the two numbers are within eps of each other. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE bool equals(const physx::PxF64 a, const physx::PxF64 b, const physx::PxF64 eps) +{ + return (physx::PxAbs(a - b) < eps); +} + +/** +\brief The floor function returns a floating-point value representing the largest integer that is less than or equal to +x. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 floor(const physx::PxF32 a) +{ + return ::floorf(a); +} + +/** +\brief The floor function returns a floating-point value representing the largest integer that is less than or equal to +x. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 floor(const physx::PxF64 a) +{ + return ::floor(a); +} + +/** +\brief The ceil function returns a single value representing the smallest integer that is greater than or equal to x. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 ceil(const physx::PxF32 a) +{ + return ::ceilf(a); +} + +/** +\brief The ceil function returns a double value representing the smallest integer that is greater than or equal to x. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 ceil(const physx::PxF64 a) +{ + return ::ceil(a); +} + +/** +\brief mod returns the floating-point remainder of x / y. + +If the value of y is 0.0, mod returns a quiet NaN. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 mod(const physx::PxF32 x, const physx::PxF32 y) +{ + return physx::PxF32(::fmodf(x, y)); +} + +/** +\brief mod returns the floating-point remainder of x / y. + +If the value of y is 0.0, mod returns a quiet NaN. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 mod(const physx::PxF64 x, const physx::PxF64 y) +{ + return ::fmod(x, y); +} + +/** +\brief Square. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 sqr(const physx::PxF32 a) +{ + return a * a; +} + +/** +\brief Square. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 sqr(const physx::PxF64 a) +{ + return a * a; +} + +/** +\brief Calculates x raised to the power of y. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 pow(const physx::PxF32 x, const physx::PxF32 y) +{ + return ::powf(x, y); +} + +/** +\brief Calculates x raised to the power of y. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 pow(const physx::PxF64 x, const physx::PxF64 y) +{ + return ::pow(x, y); +} + +/** +\brief Calculates e^n +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 exp(const physx::PxF32 a) +{ + return ::expf(a); +} +/** + +\brief Calculates e^n +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 exp(const physx::PxF64 a) +{ + return ::exp(a); +} + +/** +\brief Calculates 2^n +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 exp2(const physx::PxF32 a) +{ + return ::expf(a * 0.693147180559945309417f); +} +/** + +\brief Calculates 2^n +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 exp2(const physx::PxF64 a) +{ + return ::exp(a * 0.693147180559945309417); +} + +/** +\brief Calculates logarithms. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 logE(const physx::PxF32 a) +{ + return ::logf(a); +} + +/** +\brief Calculates logarithms. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 logE(const physx::PxF64 a) +{ + return ::log(a); +} + +/** +\brief Calculates logarithms. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 log2(const physx::PxF32 a) +{ + return ::logf(a) / 0.693147180559945309417f; +} + +/** +\brief Calculates logarithms. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 log2(const physx::PxF64 a) +{ + return ::log(a) / 0.693147180559945309417; +} + +/** +\brief Calculates logarithms. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 log10(const physx::PxF32 a) +{ + return ::log10f(a); +} + +/** +\brief Calculates logarithms. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 log10(const physx::PxF64 a) +{ + return ::log10(a); +} + +/** +\brief Converts degrees to radians. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 degToRad(const physx::PxF32 a) +{ + return 0.01745329251994329547f * a; +} + +/** +\brief Converts degrees to radians. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 degToRad(const physx::PxF64 a) +{ + return 0.01745329251994329547 * a; +} + +/** +\brief Converts radians to degrees. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 radToDeg(const physx::PxF32 a) +{ + return 57.29577951308232286465f * a; +} + +/** +\brief Converts radians to degrees. +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF64 radToDeg(const physx::PxF64 a) +{ + return 57.29577951308232286465 * a; +} + +//! \brief compute sine and cosine at the same time. There is a 'fsincos' on PC that we probably want to use here +PX_CUDA_CALLABLE PX_FORCE_INLINE void sincos(const physx::PxF32 radians, physx::PxF32& sin, physx::PxF32& cos) +{ + /* something like: + _asm fld Local + _asm fsincos + _asm fstp LocalCos + _asm fstp LocalSin + */ + sin = physx::PxSin(radians); + cos = physx::PxCos(radians); +} + +/** +\brief uniform random number in [a,b] +*/ +PX_FORCE_INLINE physx::PxI32 rand(const physx::PxI32 a, const physx::PxI32 b) +{ + return a + physx::PxI32(::rand() % (b - a + 1)); +} + +/** +\brief uniform random number in [a,b] +*/ +PX_FORCE_INLINE physx::PxF32 rand(const physx::PxF32 a, const physx::PxF32 b) +{ + return a + (b - a) * ::rand() / RAND_MAX; +} + +//! \brief return angle between two vectors in radians +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxF32 angle(const physx::PxVec3& v0, const physx::PxVec3& v1) +{ + const physx::PxF32 cos = v0.dot(v1); // |v0|*|v1|*Cos(Angle) + const physx::PxF32 sin = (v0.cross(v1)).magnitude(); // |v0|*|v1|*Sin(Angle) + return physx::PxAtan2(sin, cos); +} + +//! If possible use instead fsel on the dot product /*fsel(d.dot(p),onething,anotherthing);*/ +//! Compares orientations (more readable, user-friendly function) +PX_CUDA_CALLABLE PX_FORCE_INLINE bool sameDirection(const physx::PxVec3& d, const physx::PxVec3& p) +{ + return d.dot(p) >= 0.0f; +} + +//! Checks 2 values have different signs +PX_CUDA_CALLABLE PX_FORCE_INLINE IntBool differentSign(physx::PxReal f0, physx::PxReal f1) +{ +#if !PX_EMSCRIPTEN + union + { + physx::PxU32 u; + physx::PxReal f; + } u1, u2; + u1.f = f0; + u2.f = f1; + return IntBool((u1.u ^ u2.u) & PX_SIGN_BITMASK); +#else + // javascript floats are 64-bits... + return IntBool( (f0*f1) < 0.0f ); +#endif +} + +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxMat33 star(const physx::PxVec3& v) +{ + return physx::PxMat33(physx::PxVec3(0, v.z, -v.y), physx::PxVec3(-v.z, 0, v.x), physx::PxVec3(v.y, -v.x, 0)); +} + +PX_CUDA_CALLABLE PX_INLINE physx::PxVec3 log(const physx::PxQuat& q) +{ + const physx::PxReal s = q.getImaginaryPart().magnitude(); + if(s < 1e-12f) + return physx::PxVec3(0.0f); + // force the half-angle to have magnitude <= pi/2 + physx::PxReal halfAngle = q.w < 0 ? physx::PxAtan2(-s, -q.w) : physx::PxAtan2(s, q.w); + NV_CLOTH_ASSERT(halfAngle >= -physx::PxPi / 2 && halfAngle <= physx::PxPi / 2); + + return q.getImaginaryPart().getNormalized() * 2.f * halfAngle; +} + +PX_CUDA_CALLABLE PX_INLINE physx::PxQuat exp(const physx::PxVec3& v) +{ + const physx::PxReal m = v.magnitudeSquared(); + return m < 1e-24f ? physx::PxQuat(physx::PxIdentity) : physx::PxQuat(physx::PxSqrt(m), v * physx::PxRecipSqrt(m)); +} + +// quat to rotate v0 t0 v1 +PX_CUDA_CALLABLE PX_INLINE physx::PxQuat rotationArc(const physx::PxVec3& v0, const physx::PxVec3& v1) +{ + const physx::PxVec3 cross = v0.cross(v1); + const physx::PxReal d = v0.dot(v1); + if(d <= -0.99999f) + return (physx::PxAbs(v0.x) < 0.1f ? physx::PxQuat(0.0f, v0.z, -v0.y, 0.0f) : physx::PxQuat(v0.y, -v0.x, 0.0, 0.0)).getNormalized(); + + const physx::PxReal s = physx::PxSqrt((1 + d) * 2), r = 1 / s; + + return physx::PxQuat(cross.x * r, cross.y * r, cross.z * r, s * 0.5f).getNormalized(); +} + +/** +\brief returns largest axis +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxU32 largestAxis(const physx::PxVec3& v) +{ + physx::PxU32 m = physx::PxU32(v.y > v.x ? 1 : 0); + return v.z > v[m] ? 2 : m; +} + +/** +\brief returns indices for the largest axis and 2 other axii +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxU32 largestAxis(const physx::PxVec3& v, physx::PxU32& other1, physx::PxU32& other2) +{ + if(v.x >= physx::PxMax(v.y, v.z)) + { + other1 = 1; + other2 = 2; + return 0; + } + else if(v.y >= v.z) + { + other1 = 0; + other2 = 2; + return 1; + } + else + { + other1 = 0; + other2 = 1; + return 2; + } +} + +/** +\brief returns axis with smallest absolute value +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxU32 closestAxis(const physx::PxVec3& v) +{ + physx::PxU32 m = physx::PxU32(physx::PxAbs(v.y) > physx::PxAbs(v.x) ? 1 : 0); + return physx::PxAbs(v.z) > physx::PxAbs(v[m]) ? 2 : m; +} + +PX_CUDA_CALLABLE PX_INLINE physx::PxU32 closestAxis(const physx::PxVec3& v, physx::PxU32& j, physx::PxU32& k) +{ + // find largest 2D plane projection + const physx::PxF32 absPx = physx::PxAbs(v.x); + const physx::PxF32 absNy = physx::PxAbs(v.y); + const physx::PxF32 absNz = physx::PxAbs(v.z); + + physx::PxU32 m = 0; // x biggest axis + j = 1; + k = 2; + if(absNy > absPx && absNy > absNz) + { + // y biggest + j = 2; + k = 0; + m = 1; + } + else if(absNz > absPx) + { + // z biggest + j = 0; + k = 1; + m = 2; + } + return m; +} + +/*! +Extend an edge along its length by a factor +*/ +PX_CUDA_CALLABLE PX_FORCE_INLINE void makeFatEdge(physx::PxVec3& p0, physx::PxVec3& p1, physx::PxReal fatCoeff) +{ + physx::PxVec3 delta = p1 - p0; + + const physx::PxReal m = delta.magnitude(); + if(m > 0.0f) + { + delta *= fatCoeff / m; + p0 -= delta; + p1 += delta; + } +} + +//! Compute point as combination of barycentric coordinates +PX_CUDA_CALLABLE PX_FORCE_INLINE physx::PxVec3 +computeBarycentricPoint(const physx::PxVec3& p0, const physx::PxVec3& p1, const physx::PxVec3& p2, physx::PxReal u, physx::PxReal v) +{ + // This seems to confuse the compiler... + // return (1.0f - u - v)*p0 + u*p1 + v*p2; + const physx::PxF32 w = 1.0f - u - v; + return physx::PxVec3(w * p0.x + u * p1.x + v * p2.x, w * p0.y + u * p1.y + v * p2.y, w * p0.z + u * p1.z + v * p2.z); +} + +// generates a pair of quaternions (swing, twist) such that in = swing * twist, with +// swing.x = 0 +// twist.y = twist.z = 0, and twist is a unit quat +PX_FORCE_INLINE void separateSwingTwist(const physx::PxQuat& q, physx::PxQuat& swing, physx::PxQuat& twist) +{ + twist = q.x != 0.0f ? physx::PxQuat(q.x, 0, 0, q.w).getNormalized() : physx::PxQuat(physx::PxIdentity); + swing = q * twist.getConjugate(); +} + +// generate two tangent vectors to a given normal +PX_FORCE_INLINE void normalToTangents(const physx::PxVec3& normal, physx::PxVec3& tangent0, physx::PxVec3& tangent1) +{ + tangent0 = physx::PxAbs(normal.x) < 0.70710678f ? physx::PxVec3(0, -normal.z, normal.y) : physx::PxVec3(-normal.y, normal.x, 0); + tangent0.normalize(); + tangent1 = normal.cross(tangent0); +} + +/** +\brief computes a oriented bounding box around the scaled basis. +\param basis Input = skewed basis, Output = (normalized) orthogonal basis. +\return Bounding box extent. +*/ +NV_CLOTH_IMPORT physx::PxVec3 optimizeBoundingBox(physx::PxMat33& basis); + +NV_CLOTH_IMPORT physx::PxQuat slerp(const physx::PxReal t, const physx::PxQuat& left, const physx::PxQuat& right); + +PX_CUDA_CALLABLE PX_INLINE physx::PxVec3 ellipseClamp(const physx::PxVec3& point, const physx::PxVec3& radii) +{ + // This function need to be implemented in the header file because + // it is included in a spu shader program. + + // finds the closest point on the ellipse to a given point + + // (p.y, p.z) is the input point + // (e.y, e.z) are the radii of the ellipse + + // lagrange multiplier method with Newton/Halley hybrid root-finder. + // see http://www.geometrictools.com/Documentation/DistancePointToEllipse2.pdf + // for proof of Newton step robustness and initial estimate. + // Halley converges much faster but sometimes overshoots - when that happens we take + // a newton step instead + + // converges in 1-2 iterations where D&C works well, and it's good with 4 iterations + // with any ellipse that isn't completely crazy + + const physx::PxU32 MAX_ITERATIONS = 20; + const physx::PxReal convergenceThreshold = 1e-4f; + + // iteration requires first quadrant but we recover generality later + + physx::PxVec3 q(0, physx::PxAbs(point.y), physx::PxAbs(point.z)); + const physx::PxReal tinyEps = 1e-6f; // very close to minor axis is numerically problematic but trivial + if(radii.y >= radii.z) + { + if(q.z < tinyEps) + return physx::PxVec3(0, point.y > 0 ? radii.y : -radii.y, 0); + } + else + { + if(q.y < tinyEps) + return physx::PxVec3(0, 0, point.z > 0 ? radii.z : -radii.z); + } + + physx::PxVec3 denom, e2 = radii.multiply(radii), eq = radii.multiply(q); + + // we can use any initial guess which is > maximum(-e.y^2,-e.z^2) and for which f(t) is > 0. + // this guess works well near the axes, but is weak along the diagonals. + + physx::PxReal t = physx::PxMax(eq.y - e2.y, eq.z - e2.z); + + for(physx::PxU32 i = 0; i < MAX_ITERATIONS; i++) + { + denom = physx::PxVec3(0, 1 / (t + e2.y), 1 / (t + e2.z)); + physx::PxVec3 denom2 = eq.multiply(denom); + + physx::PxVec3 fv = denom2.multiply(denom2); + physx::PxReal f = fv.y + fv.z - 1; + + // although in exact arithmetic we are guaranteed f>0, we can get here + // on the first iteration via catastrophic cancellation if the point is + // very close to the origin. In that case we just behave as if f=0 + + if(f < convergenceThreshold) + return e2.multiply(point).multiply(denom); + + physx::PxReal df = fv.dot(denom) * -2.0f; + t = t - f / df; + } + + // we didn't converge, so clamp what we have + physx::PxVec3 r = e2.multiply(point).multiply(denom); + return r * physx::PxRecipSqrt(sqr(r.y / radii.y) + sqr(r.z / radii.z)); +} + +PX_CUDA_CALLABLE PX_INLINE physx::PxReal tanHalf(physx::PxReal sin, physx::PxReal cos) +{ + return sin / (1 + cos); +} + +PX_INLINE physx::PxQuat quatFromTanQVector(const physx::PxVec3& v) +{ + physx::PxReal v2 = v.dot(v); + if(v2 < 1e-12f) + return physx::PxQuat(physx::PxIdentity); + physx::PxReal d = 1 / (1 + v2); + return physx::PxQuat(v.x * 2, v.y * 2, v.z * 2, 1 - v2) * d; +} + +PX_FORCE_INLINE physx::PxVec3 cross100(const physx::PxVec3& b) +{ + return physx::PxVec3(0.0f, -b.z, b.y); +} +PX_FORCE_INLINE physx::PxVec3 cross010(const physx::PxVec3& b) +{ + return physx::PxVec3(b.z, 0.0f, -b.x); +} +PX_FORCE_INLINE physx::PxVec3 cross001(const physx::PxVec3& b) +{ + return physx::PxVec3(-b.y, b.x, 0.0f); +} + +PX_INLINE void decomposeVector(physx::PxVec3& normalCompo, physx::PxVec3& tangentCompo, const physx::PxVec3& outwardDir, + const physx::PxVec3& outwardNormal) +{ + normalCompo = outwardNormal * (outwardDir.dot(outwardNormal)); + tangentCompo = outwardDir - normalCompo; +} + +//! \brief Return (i+1)%3 +// Avoid variable shift for XBox: +// PX_INLINE physx::PxU32 Ps::getNextIndex3(physx::PxU32 i) { return (1<> 1)) & 3; +} + +PX_INLINE physx::PxMat33 rotFrom2Vectors(const physx::PxVec3& from, const physx::PxVec3& to) +{ + // See bottom of http://www.euclideanspace.com/maths/algebra/matrix/orthogonal/rotation/index.htm + + // Early exit if to = from + if((from - to).magnitudeSquared() < 1e-4f) + return physx::PxMat33(physx::PxIdentity); + + // Early exit if to = -from + if((from + to).magnitudeSquared() < 1e-4f) + return physx::PxMat33::createDiagonal(physx::PxVec3(1.0f, -1.0f, -1.0f)); + + physx::PxVec3 n = from.cross(to); + + physx::PxReal C = from.dot(to), S = physx::PxSqrt(1 - C * C), CC = 1 - C; + + physx::PxReal xx = n.x * n.x, yy = n.y * n.y, zz = n.z * n.z, xy = n.x * n.y, yz = n.y * n.z, xz = n.x * n.z; + + physx::PxMat33 R; + + R(0, 0) = 1 + CC * (xx - 1); + R(0, 1) = -n.z * S + CC * xy; + R(0, 2) = n.y * S + CC * xz; + + R(1, 0) = n.z * S + CC * xy; + R(1, 1) = 1 + CC * (yy - 1); + R(1, 2) = -n.x * S + CC * yz; + + R(2, 0) = -n.y * S + CC * xz; + R(2, 1) = n.x * S + CC * yz; + R(2, 2) = 1 + CC * (zz - 1); + + return R; +} + +NV_CLOTH_IMPORT void integrateTransform(const physx::PxTransform& curTrans, const physx::PxVec3& linvel, const physx::PxVec3& angvel, + physx::PxReal timeStep, physx::PxTransform& result); + +PX_INLINE void computeBasis(const physx::PxVec3& dir, physx::PxVec3& right, physx::PxVec3& up) +{ + // Derive two remaining vectors + if(physx::PxAbs(dir.y) <= 0.9999f) + { + right = physx::PxVec3(dir.z, 0.0f, -dir.x); + right.normalize(); + + // PT: normalize not needed for 'up' because dir & right are unit vectors, + // and by construction the angle between them is 90 degrees (i.e. sin(angle)=1) + up = physx::PxVec3(dir.y * right.z, dir.z * right.x - dir.x * right.z, -dir.y * right.x); + } + else + { + right = physx::PxVec3(1.0f, 0.0f, 0.0f); + + up = physx::PxVec3(0.0f, dir.z, -dir.y); + up.normalize(); + } +} + +PX_INLINE void computeBasis(const physx::PxVec3& p0, const physx::PxVec3& p1, physx::PxVec3& dir, physx::PxVec3& right, physx::PxVec3& up) +{ + // Compute the new direction vector + dir = p1 - p0; + dir.normalize(); + + // Derive two remaining vectors + computeBasis(dir, right, up); +} + +PX_FORCE_INLINE bool isAlmostZero(const physx::PxVec3& v) +{ + if(physx::PxAbs(v.x) > 1e-6f || physx::PxAbs(v.y) > 1e-6f || physx::PxAbs(v.z) > 1e-6f) + return false; + return true; +} +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif diff --git a/Source/ThirdParty/NvCloth/ps/PsUserAllocated.h b/Source/ThirdParty/NvCloth/ps/PsUserAllocated.h new file mode 100644 index 000000000..bcf3928bb --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/PsUserAllocated.h @@ -0,0 +1,97 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSUSERALLOCATED_H +#define PSFOUNDATION_PSUSERALLOCATED_H + +#include "PsAllocator.h" + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ +/** +Provides new and delete using a UserAllocator. +Guarantees that 'delete x;' uses the UserAllocator too. +*/ +class UserAllocated +{ + public: + // PX_SERIALIZATION + PX_INLINE void* operator new(size_t, void* address) + { + return address; + } + //~PX_SERIALIZATION + // Matching operator delete to the above operator new. Don't ask me + // how this makes any sense - Nuernberger. + PX_INLINE void operator delete(void*, void*) + { + } + + template + PX_INLINE void* operator new(size_t size, Alloc alloc, const char* fileName, int line) + { + return alloc.allocate(size, fileName, line); + } + template + PX_INLINE void* operator new [](size_t size, Alloc alloc, const char* fileName, int line) + { return alloc.allocate(size, fileName, line); } + + // placement delete + template + PX_INLINE void operator delete(void* ptr, Alloc alloc, const char* fileName, int line) + { + PX_UNUSED(fileName); + PX_UNUSED(line); + alloc.deallocate(ptr); + } + template + PX_INLINE void operator delete [](void* ptr, Alloc alloc, const char* fileName, int line) + { + PX_UNUSED(fileName); + PX_UNUSED(line); + alloc.deallocate(ptr); + } PX_INLINE void + operator delete(void* ptr) + { + NonTrackingAllocator().deallocate(ptr); + } + PX_INLINE void operator delete [](void* ptr) + { NonTrackingAllocator().deallocate(ptr); } +}; +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSUSERALLOCATED_H diff --git a/Source/ThirdParty/NvCloth/ps/unix/PsUnixIntrinsics.h b/Source/ThirdParty/NvCloth/ps/unix/PsUnixIntrinsics.h new file mode 100644 index 000000000..2a7401764 --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/unix/PsUnixIntrinsics.h @@ -0,0 +1,138 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSUNIXINTRINSICS_H +#define PSFOUNDATION_PSUNIXINTRINSICS_H + +#include "NvCloth/ps/Ps.h" +#include "NvCloth/Callbacks.h" +#include + +#if PX_ANDROID || (PX_LINUX && !(PX_X64 || PX_X64)) // x86[_64] Linux uses inline assembly for debug break +#include // for Ns::debugBreak() { raise(SIGTRAP); } +#endif + +#if 0 +#include +#endif + +// this file is for internal intrinsics - that is, intrinsics that are used in +// cross platform code but do not appear in the API + +#if !(PX_LINUX || PX_ANDROID || PX_PS4 || PX_PS5 || PX_APPLE_FAMILY) +#error "This file should only be included by unix builds!!" +#endif + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ + + +PX_FORCE_INLINE void PxMemoryBarrier() +{ + __sync_synchronize(); +} + +/*! +Return the index of the highest set bit. Undefined for zero arg. +*/ +PX_INLINE uint32_t PxHighestSetBitUnsafe(uint32_t v) +{ + + return 31 - __builtin_clz(v); +} + +/*! +Return the index of the highest set bit. Undefined for zero arg. +*/ +PX_INLINE int32_t PxLowestSetBitUnsafe(uint32_t v) +{ + return __builtin_ctz(v); +} + +/*! +Returns the index of the highest set bit. Returns 32 for v=0. +*/ +PX_INLINE uint32_t PxCountLeadingZeros(uint32_t v) +{ + if(v) + return __builtin_clz(v); + else + return 32; +} + +/*! +Prefetch aligned 64B x86, 32b ARM around \c ptr+offset. +*/ +PX_FORCE_INLINE void PxPrefetchLine(const void* ptr, uint32_t offset = 0) +{ + __builtin_prefetch(reinterpret_cast(ptr) + offset, 0, 3); +} + +/*! +Prefetch \c count bytes starting at \c ptr. +*/ +#if PX_ANDROID || PX_IOS +PX_FORCE_INLINE void prefetch(const void* ptr, uint32_t count = 1) +{ + const char* cp = static_cast(ptr); + size_t p = reinterpret_cast(ptr); + uint32_t startLine = uint32_t(p >> 5), endLine = uint32_t((p + count - 1) >> 5); + uint32_t lines = endLine - startLine + 1; + do + { + PxPrefetchLine(cp); + cp += 32; + } while(--lines); +} +#else +PX_FORCE_INLINE void prefetch(const void* ptr, uint32_t count = 1) +{ + const char* cp = reinterpret_cast(ptr); + uint64_t p = size_t(ptr); + uint64_t startLine = p >> 6, endLine = (p + count - 1) >> 6; + uint64_t lines = endLine - startLine + 1; + do + { + PxPrefetchLine(cp); + cp += 64; + } while(--lines); +} +#endif + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSUNIXINTRINSICS_H diff --git a/Source/ThirdParty/NvCloth/ps/windows/PsWindowsIntrinsics.h b/Source/ThirdParty/NvCloth/ps/windows/PsWindowsIntrinsics.h new file mode 100644 index 000000000..5eaaac40f --- /dev/null +++ b/Source/ThirdParty/NvCloth/ps/windows/PsWindowsIntrinsics.h @@ -0,0 +1,173 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved. +// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. +// Copyright (c) 2001-2004 NovodeX AG. All rights reserved. + +#ifndef PSFOUNDATION_PSWINDOWSINTRINSICS_H +#define PSFOUNDATION_PSWINDOWSINTRINSICS_H + +#include "Ps.h" + +// this file is for internal intrinsics - that is, intrinsics that are used in +// cross platform code but do not appear in the API + +#if !PX_WINDOWS_FAMILY +#error "This file should only be included by Windows builds!!" +#endif + +#pragma warning(push) +//'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' +#pragma warning(disable : 4668) +#if PX_VC == 10 +#pragma warning(disable : 4987) // nonstandard extension used: 'throw (...)' +#endif +#include +#pragma warning(pop) + +#pragma warning(push) +#pragma warning(disable : 4985) // 'symbol name': attributes not present on previous declaration +#include +#pragma warning(pop) + +#include +#include + +#pragma intrinsic(_BitScanForward) +#pragma intrinsic(_BitScanReverse) + +/** \brief NVidia namespace */ +namespace nv +{ +/** \brief nvcloth namespace */ +namespace cloth +{ +namespace ps +{ + +/* +* Implements a memory barrier +*/ +PX_FORCE_INLINE void PxMemoryBarrier() +{ + _ReadWriteBarrier(); + /* long Barrier; + __asm { + xchg Barrier, eax + }*/ +} + +/*! +Returns the index of the highest set bit. Not valid for zero arg. +*/ +PX_FORCE_INLINE uint32_t PxHighestSetBitUnsafe(uint32_t v) +{ + unsigned long retval; + _BitScanReverse(&retval, v); + return retval; +} + +/*! +Returns the index of the highest set bit. Undefined for zero arg. +*/ +PX_FORCE_INLINE uint32_t PxLowestSetBitUnsafe(uint32_t v) +{ + unsigned long retval; + _BitScanForward(&retval, v); + return retval; +} + +/*! +Returns the number of leading zeros in v. Returns 32 for v=0. +*/ +PX_FORCE_INLINE uint32_t PxCountLeadingZeros(uint32_t v) +{ + if(v) + { + unsigned long bsr = (unsigned long)-1; + _BitScanReverse(&bsr, v); + return 31 - bsr; + } + else + return 32; +} + +/*! +Prefetch aligned cache size around \c ptr+offset. +*/ +#if !PX_ARM +PX_FORCE_INLINE void PxPrefetchLine(const void* ptr, uint32_t offset = 0) +{ + // cache line on X86/X64 is 64-bytes so a 128-byte prefetch would require 2 prefetches. + // However, we can only dispatch a limited number of prefetch instructions so we opt to prefetch just 1 cache line + /*_mm_prefetch(((const char*)ptr + offset), _MM_HINT_T0);*/ + // We get slightly better performance prefetching to non-temporal addresses instead of all cache levels + _mm_prefetch(((const char*)ptr + offset), _MM_HINT_NTA); +} +#else +PX_FORCE_INLINE void PxPrefetchLine(const void* ptr, uint32_t offset = 0) +{ + // arm does have 32b cache line size + __prefetch(((const char*)ptr + offset)); +} +#endif + +/*! +Prefetch \c count bytes starting at \c ptr. +*/ +#if !PX_ARM +PX_FORCE_INLINE void PxPrefetch(const void* ptr, uint32_t count = 1) +{ + const char* cp = (char*)ptr; + uint64_t p = size_t(ptr); + uint64_t startLine = p >> 6, endLine = (p + count - 1) >> 6; + uint64_t lines = endLine - startLine + 1; + do + { + PxPrefetchLine(cp); + cp += 64; + } while(--lines); +} +#else +PX_FORCE_INLINE void PxPrefetch(const void* ptr, uint32_t count = 1) +{ + const char* cp = (char*)ptr; + uint32_t p = size_t(ptr); + uint32_t startLine = p >> 5, endLine = (p + count - 1) >> 5; + uint32_t lines = endLine - startLine + 1; + do + { + PxPrefetchLine(cp); + cp += 32; + } while(--lines); +} +#endif + +} // namespace ps +} // namespace cloth +} // namespace nv + +#endif // #ifndef PSFOUNDATION_PSWINDOWSINTRINSICS_H diff --git a/Source/Tools/Flax.Build/Deps/Dependency.cs b/Source/Tools/Flax.Build/Deps/Dependency.cs index 29b9fd58a..46422179e 100644 --- a/Source/Tools/Flax.Build/Deps/Dependency.cs +++ b/Source/Tools/Flax.Build/Deps/Dependency.cs @@ -275,18 +275,18 @@ namespace Flax.Deps cmdLine = string.Format("CMakeLists.txt -G \"Visual Studio 17 2022\" -A {0}", arch); break; } - case TargetPlatform.Linux: case TargetPlatform.PS4: + cmdLine = "CMakeLists.txt -DCMAKE_GENERATOR_PLATFORM=ORBIS -G \"Visual Studio 15 2017\""; + break; case TargetPlatform.PS5: - { + cmdLine = "CMakeLists.txt -DCMAKE_GENERATOR_PLATFORM=PROSPERO -G \"Visual Studio 16 2019\""; + break; + case TargetPlatform.Linux: cmdLine = "CMakeLists.txt"; break; - } case TargetPlatform.Switch: - { cmdLine = string.Format("-DCMAKE_TOOLCHAIN_FILE=\"{1}\\Source\\Platforms\\Switch\\Binaries\\Data\\Switch.cmake\" -G \"NMake Makefiles\" -DCMAKE_MAKE_PROGRAM=\"{0}..\\..\\VC\\bin\\nmake.exe\"", Environment.GetEnvironmentVariable("VS140COMNTOOLS"), Globals.EngineRoot); break; - } case TargetPlatform.Android: { var ndk = AndroidNdk.Instance.RootPath; From f81989a17148fa00056d2c2ab84f7426680c654c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Jul 2023 10:38:36 +0200 Subject: [PATCH 022/294] Add **Cloth** simulation with physics --- Source/Engine/Level/Actors/AnimatedModel.cpp | 4 +- Source/Engine/Level/Actors/StaticModel.cpp | 4 +- Source/Engine/Physics/Actors/Cloth.cpp | 492 ++++++++++++++++ Source/Engine/Physics/Actors/Cloth.h | 307 ++++++++++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 547 ++++++++++++++++++ Source/Engine/Physics/Physics.Build.cs | 12 + Source/Engine/Physics/PhysicsBackend.h | 31 +- Source/ThirdParty/PhysX/PhysX.Build.cs | 2 +- .../Flax.Build/Deps/Dependencies/NvCloth.cs | 193 ++++++ 9 files changed, 1586 insertions(+), 6 deletions(-) create mode 100644 Source/Engine/Physics/Actors/Cloth.cpp create mode 100644 Source/Engine/Physics/Actors/Cloth.h create mode 100644 Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 728758736..e5c4c5a09 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -690,12 +690,12 @@ void AnimatedModel::OnActiveInTreeChanged() void AnimatedModel::UpdateBounds() { - auto model = SkinnedModel.Get(); + const auto model = SkinnedModel.Get(); if (CustomBounds.GetSize().LengthSquared() > 0.01f) { BoundingBox::Transform(CustomBounds, _transform, _box); } - else if (model && model->IsLoaded()) + else if (model && model->IsLoaded() && model->LODs.Count() != 0) { BoundingBox box = model->LODs[0].GetBox(_transform, _deformation); if (GraphInstance.NodesPose.Count() != 0) diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 17d5ca805..3ab88cdce 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -270,8 +270,8 @@ void StaticModel::OnModelResidencyChanged() void StaticModel::UpdateBounds() { - auto model = Model.Get(); - if (model && model->IsLoaded()) + const auto model = Model.Get(); + if (model && model->IsLoaded() && model->LODs.Count() != 0) { Transform transform = _transform; transform.Scale *= _boundsScale; diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp new file mode 100644 index 000000000..a16f5b8af --- /dev/null +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -0,0 +1,492 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "Cloth.h" +#include "Engine/Core/Log.h" +#include "Engine/Graphics/Models/MeshBase.h" +#include "Engine/Graphics/Models/MeshDeformation.h" +#include "Engine/Physics/PhysicsBackend.h" +#include "Engine/Physics/PhysicsScene.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Level/Actors/AnimatedModel.h" +#if USE_EDITOR +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Debug/DebugDraw.h" +#endif + +Cloth::Cloth(const SpawnParams& params) + : Actor(params) +{ + // Use the first mesh by default + _mesh.LODIndex = _mesh.MeshIndex = 0; +} + +ModelInstanceActor::MeshReference Cloth::GetMesh() const +{ + auto value = _mesh; + value.Actor = Cast(GetParent()); // Force to use cloth's parent only + return value; +} + +void Cloth::SetMesh(const ModelInstanceActor::MeshReference& value) +{ + if (_mesh.LODIndex == value.LODIndex && _mesh.MeshIndex == value.MeshIndex) + return; + + // Remove mesh deformer (mesh index/lod changes) + if (_meshDeformation) + { + Function deformer; + deformer.Bind(this); + _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _meshDeformation = nullptr; + } + + _mesh = value; + _mesh.Actor = nullptr; // Don't store this reference + Rebuild(); +} + +void Cloth::SetForce(const ForceSettings& value) +{ + _forceSettings = value; +#if WITH_CLOTH + if (_cloth) + PhysicsBackend::SetClothForceSettings(_cloth, &value); +#endif +} + +void Cloth::SetCollision(const CollisionSettings& value) +{ + _collisionSettings = value; +#if WITH_CLOTH + if (_cloth) + PhysicsBackend::SetClothCollisionSettings(_cloth, &value); +#endif +} + +void Cloth::SetSimulation(const SimulationSettings& value) +{ + _simulationSettings = value; +#if WITH_CLOTH + if (_cloth) + PhysicsBackend::SetClothSimulationSettings(_cloth, &value); +#endif +} + +void Cloth::SetFabric(const FabricSettings& value) +{ + _fabricSettings = value; +#if WITH_CLOTH + if (_cloth) + PhysicsBackend::SetClothFabricSettings(_cloth, &value); +#endif +} + +void Cloth::Rebuild() +{ +#if WITH_CLOTH + if (_cloth) + { + // Remove old + if (IsDuringPlay()) + PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); + DestroyCloth(); + + // Create new + CreateCloth(); + if (IsDuringPlay()) + PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); + } +#endif +} + +void Cloth::ClearInteria() +{ +#if WITH_CLOTH + if (_cloth) + PhysicsBackend::ClearClothInertia(_cloth); +#endif +} + +void Cloth::Serialize(SerializeStream& stream, const void* otherObj) +{ + Actor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(Cloth); + + SERIALIZE_MEMBER(Mesh, _mesh); + SERIALIZE_MEMBER(Force, _forceSettings); + SERIALIZE_MEMBER(Collision, _collisionSettings); + SERIALIZE_MEMBER(Simulation, _simulationSettings); + SERIALIZE_MEMBER(Fabric, _fabricSettings); +} + +void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + Actor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(Mesh, _mesh); + _mesh.Actor = nullptr; // Don't store this reference + DESERIALIZE_MEMBER(Force, _forceSettings); + DESERIALIZE_MEMBER(Collision, _collisionSettings); + DESERIALIZE_MEMBER(Simulation, _simulationSettings); + DESERIALIZE_MEMBER(Fabric, _fabricSettings); +} + +#if USE_EDITOR + +void Cloth::DrawPhysicsDebug(RenderView& view) +{ +#if WITH_CLOTH + if (_cloth) + { + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return; + BytesContainer indicesData; + int32 indicesCount = 0; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) + return; + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothCurrentParticles(_cloth); + const Transform transform = GetTransform(); + const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); + const int32 trianglesCount = indicesCount / 3; + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + int32 i0, i1, i2; + if (indices16bit) + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + else + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); + const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); + const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); + // TODO: highlight immovable cloth particles with a different color + DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Pink, 0, true); + } + PhysicsBackend::UnlockClothParticles(_cloth); + } +#endif +} + +void Cloth::OnDebugDrawSelected() +{ +#if WITH_CLOTH + if (_cloth) + { + DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true); + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return; + BytesContainer indicesData; + int32 indicesCount = 0; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) + return; + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothCurrentParticles(_cloth); + const Transform transform = GetTransform(); + const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); + const int32 trianglesCount = indicesCount / 3; + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + int32 i0, i1, i2; + if (indices16bit) + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + else + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); + const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); + const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); + // TODO: highlight immovable cloth particles with a different color + DEBUG_DRAW_LINE(v0, v1, Color::White, 0, false); + DEBUG_DRAW_LINE(v1, v2, Color::White, 0, false); + DEBUG_DRAW_LINE(v2, v0, Color::White, 0, false); + } + PhysicsBackend::UnlockClothParticles(_cloth); + } +#endif + + Actor::OnDebugDrawSelected(); +} + +#endif + +void Cloth::BeginPlay(SceneBeginData* data) +{ + if (CreateCloth()) + { + LOG(Error, "Failed to create cloth '{0}'", GetNamePath()); + } + + Actor::BeginPlay(data); +} + +void Cloth::EndPlay() +{ + Actor::EndPlay(); + + if (_cloth) + { + DestroyCloth(); + } +} + +void Cloth::OnEnable() +{ +#if USE_EDITOR + GetSceneRendering()->AddPhysicsDebug(this); +#endif +#if WITH_CLOTH + if (_cloth) + { + PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); + } +#endif + + Actor::OnEnable(); +} + +void Cloth::OnDisable() +{ + Actor::OnDisable(); + +#if WITH_CLOTH + if (_cloth) + { + PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); + } +#endif +#if USE_EDITOR + GetSceneRendering()->RemovePhysicsDebug(this); +#endif +} + +void Cloth::OnParentChanged() +{ + Actor::OnParentChanged(); + + Rebuild(); +} + +void Cloth::OnTransformChanged() +{ + Actor::OnTransformChanged(); + +#if WITH_CLOTH + if (_cloth) + { + // Move cloth but consider this as teleport if the position delta is significant + const float minTeleportDistanceSq = Math::Square(1000.0f); + const bool teleport = Vector3::DistanceSquared(_cachedPosition, _transform.Translation) >= minTeleportDistanceSq; + _cachedPosition = _transform.Translation; + PhysicsBackend::SetClothTransform(_cloth, _transform, teleport); + } + else +#endif + { + _box = BoundingBox(_transform.Translation); + _sphere = BoundingSphere(_transform.Translation, 0.0f); + } +} + +void Cloth::OnPhysicsSceneChanged(PhysicsScene* previous) +{ + Actor::OnPhysicsSceneChanged(previous); + +#if WITH_CLOTH + if (_cloth) + { + PhysicsBackend::RemoveCloth(previous->GetPhysicsScene(), _cloth); + void* scene = GetPhysicsScene()->GetPhysicsScene(); + PhysicsBackend::AddCloth(scene, _cloth); + } +#endif +} + +bool Cloth::CreateCloth() +{ +#if WITH_CLOTH + PROFILE_CPU(); + + // Get mesh data + // TODO: consider making it via async task so physics can wait on the cloth setup from mesh data just before next fixed update which gives more time when loading scene + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return false; + PhysicsClothDesc desc; + desc.Actor = this; + BytesContainer data; + int32 count; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count)) + return true; + desc.VerticesData = data.Get(); + desc.VerticesCount = count; + desc.VerticesStride = data.Length() / count; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, data, count)) + return true; + desc.IndicesData = data.Get(); + desc.IndicesCount = count; + desc.IndicesStride = data.Length() / count; + + // Create cloth + ASSERT(_cloth == nullptr); + _cloth = PhysicsBackend::CreateCloth(desc); + if (_cloth == nullptr) + return true; + _cachedPosition = _transform.Translation; + PhysicsBackend::SetClothForceSettings(_cloth, &_forceSettings); + PhysicsBackend::SetClothCollisionSettings(_cloth, &_collisionSettings); + PhysicsBackend::SetClothSimulationSettings(_cloth, &_simulationSettings); + PhysicsBackend::SetClothFabricSettings(_cloth, &_fabricSettings); + PhysicsBackend::SetClothTransform(_cloth, _transform, true); + PhysicsBackend::ClearClothInertia(_cloth); + + // Add cloth mesh deformer + if (auto* deformation = mesh.Actor->GetMeshDeformation()) + { + Function deformer; + deformer.Bind(this); + deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _meshDeformation = deformation; + } +#endif + + return false; +} + +void Cloth::DestroyCloth() +{ +#if WITH_CLOTH + if (_meshDeformation) + { + Function deformer; + deformer.Bind(this); + _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _meshDeformation = nullptr; + } + PhysicsBackend::DestroyCloth(_cloth); + _cloth = nullptr; +#endif +} + +void Cloth::OnUpdated() +{ + if (_meshDeformation) + { + // Mark mesh as dirty + const Matrix invWorld = Matrix::Invert(_transform.GetWorld()); + BoundingBox localBounds; + BoundingBox::Transform(_box, invWorld, localBounds); + _meshDeformation->Dirty(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, localBounds); + + // Update bounds (for mesh culling) + auto* actor = (ModelInstanceActor*)GetParent(); + actor->UpdateBounds(); + } +} + +void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformation) +{ +#if WITH_CLOTH + PROFILE_CPU_NAMED("Cloth"); + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothCurrentParticles(_cloth); + + // Update mesh vertices based on the cloth particles positions + auto vbData = deformation.VertexBuffer.Data.Get(); + auto vbCount = (uint32)mesh->GetVertexCount(); + auto vbStride = (uint32)deformation.VertexBuffer.Data.Count() / vbCount; + // TODO: add support for mesh vertex data layout descriptor instead hardcoded position data at the beginning of VB0 + ASSERT((uint32)particles.Length() >= vbCount); + if (auto* animatedModel = Cast(GetParent())) + { + if (animatedModel->GraphInstance.NodesPose.IsEmpty()) + { + // Delay unit skinning data is ready + PhysicsBackend::UnlockClothParticles(_cloth); + _meshDeformation->Dirty(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0); + return; + } + + // TODO: optimize memory allocs (eg. get pose as Span for readonly) + Array pose; + animatedModel->GetCurrentPose(pose); + const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; + + // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones + ASSERT(vbStride == sizeof(VB0SkinnedElementType)); + for (uint32 i = 0; i < vbCount; i++) + { + VB0SkinnedElementType& vb0 = *(VB0SkinnedElementType*)vbData; + + // Calculate skinned vertex matrix from bones blending + const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); + // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly + Matrix matrix; + const SkeletonBone& bone0 = skeleton.Bones[vb0.BlendIndices.R]; + Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); + Matrix boneMatrix = matrix * blendWeights.X; + if (blendWeights.Y > 0.0f) + { + const SkeletonBone& bone1 = skeleton.Bones[vb0.BlendIndices.G]; + Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.Y; + } + if (blendWeights.Z > 0.0f) + { + const SkeletonBone& bone2 = skeleton.Bones[vb0.BlendIndices.B]; + Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.Z; + } + if (blendWeights.W > 0.0f) + { + const SkeletonBone& bone3 = skeleton.Bones[vb0.BlendIndices.A]; + Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.W; + } + + // Set vertex position so it will match cloth particle pos after skinning with bone matrix + Matrix boneMatrixInv; + Matrix::Invert(boneMatrix, boneMatrixInv); + Float3 pos = *(Float3*)&particles.Get()[i]; + vb0.Position = Float3::Transform(pos, boneMatrixInv); + + vbData += vbStride; + } + } + else + { + for (uint32 i = 0; i < vbCount; i++) + { + *((Float3*)vbData) = *(Float3*)&particles.Get()[i]; + vbData += vbStride; + } + } + + // Mark whole mesh as modified + deformation.DirtyMinIndex = 0; + deformation.DirtyMaxIndex = vbCount; + + PhysicsBackend::UnlockClothParticles(_cloth); +#endif +} diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h new file mode 100644 index 000000000..588a4f4fd --- /dev/null +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -0,0 +1,307 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actor.h" +#include "Engine/Level/Actors/ModelInstanceActor.h" + +/// +/// Physical simulation actor for cloth objects made of vertices that are simulated as cloth particles with physical properties, forces, and constraints to affect cloth behavior. +/// +API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor +{ + friend class PhysicsBackend; + DECLARE_SCENE_OBJECT(Cloth); + + /// + /// Cloth response to forces settings. + /// + API_STRUCT() struct ForceSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(ForceSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Scale multiplier applied to the gravity of cloth particles (scales the global gravity force). + /// + API_FIELD() float GravityScale = 1.0f; + + /// + /// Damping of cloth particle velocity. 0: velocity is unaffected. 1: velocity is zeroed. + /// + API_FIELD(Attributes="Limit(0, 1)") float Damping = 0.4f; + + /// + /// Portion of velocity applied to cloth particles. 0: cloth particles are unaffected. 1: damped global cloth particle velocity. + /// + API_FIELD(Attributes="Limit(0, 1)") float LinearDrag = 0.2f; + + /// + /// Portion of angular velocity applied to turning cloth particles. 0: cloth particles are unaffected. 1: damped global cloth particle angular velocity. + /// + API_FIELD(Attributes="Limit(0, 1)") float AngularDrag = 0.2f; + + /// + /// Portion of linear acceleration applied to cloth particles. 0: cloth particles are unaffected. 1: physically correct linear acceleration. + /// + API_FIELD(Attributes="Limit(0, 1)") float LinearInertia = 1.0f; + + /// + /// Portion of angular acceleration applied to turning cloth particles. 0: cloth particles are unaffected. 1: physically correct angular acceleration. + /// + API_FIELD(Attributes="Limit(0, 1)") float AngularInertia = 1.0f; + + /// + /// Portion of angular velocity applied to turning cloth particles. 0: cloth particles are unaffected. 1: physically correct angular velocity. + /// + API_FIELD(Attributes="Limit(0, 1)") float CentrifugalInertia = 1.0f; + + /// + /// Defines how much drag air applies to the cloth particles. Set to 0 to disable wind. + /// + API_FIELD(Attributes="Limit(0, 1)") float AirDragCoefficient = 0.0f; + + /// + /// Defines how much lift air applies to the cloth particles. Set to 0 to disable wind. + /// + API_FIELD(Attributes="Limit(0, 1)") float AirLiftCoefficient = 0.0f; + + /// + /// Defines fluid density of air used for drag and lift calculations. + /// + API_FIELD(Attributes="Limit(0)") float AirDensity = 1.0f; + }; + + /// + /// Cloth response to collisions settings. + /// + API_STRUCT() struct CollisionSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(CollisionSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Controls the amount of friction between cloth particles and colliders. 0: friction disabled. + /// + API_FIELD(Attributes="Limit(0)") float Friction = 0.1f; + + /// + /// Controls how quickly cloth particle mass is increased during collisions. 0: mass scale disabled. + /// + API_FIELD(Attributes="Limit(0)") float MassScale = 0.0f; + + /// + /// Enables collisions with scene geometry (both dynamic and static). Disable this to improve performance of cloth that doesn't need to collide. + /// + API_FIELD() bool SceneCollisions = true; + + /// + /// Enables Continuous Collision Detection (CCD) that improves collision by computing the time of impact between cloth particles and colliders. The increase in quality can impact performance. + /// + API_FIELD() bool ContinuousCollisionDetection = false; + + /// + /// The minimum distance that the colliding cloth particles must maintain from each other in meters. 0: self collision disabled. + /// + API_FIELD(Attributes="Limit(0)") float SelfCollisionDistance = 0.0f; + + /// + /// Stiffness for the self collision constraints. 0: self collision disabled. + /// + API_FIELD(Attributes="Limit(0)") float SelfCollisionStiffness = 0.2f; + }; + + /// + /// Cloth simulation settings. + /// + API_STRUCT() struct SimulationSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(SimulationSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Target cloth solver iterations per second. The executed number of iterations per second may vary dependent on many performance factors. However, at least one iteration per frame is solved regardless of the value set. + /// + API_FIELD() float SolverFrequency = 300.0f; + + /// + /// Wind velocity vector (direction and magnitude) in world coordinates. A greater magnitude applies a stronger wind force. Ensure that Air Drag and Air Lift coefficients are non-zero in order to apply wind force. + /// + API_FIELD() Vector3 WindVelocity = Vector3::Zero; + }; + + /// + /// Cloth's fabric settings (material's stiffness and compression response) for a single axis. + /// + API_STRUCT() struct FabricAxisSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(FabricAxisSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Stiffness value for stretch and compression constraints. 0: disables it. + /// + API_FIELD(Attributes="Limit(0)") float Stiffness = 1.0f; + + /// + /// Scale value for stretch and compression constraints. 0: no stretch and compression constraints applied. 1: fully apply stretch and compression constraints. + /// + API_FIELD(Attributes="Limit(0, 1)") float StiffnessMultiplier = 1.0f; + + /// + /// Compression limit for constraints. + /// + API_FIELD(Attributes="Limit(0)") float CompressionLimit = 1.0f; + + /// + /// Stretch limit for constraints. + /// + API_FIELD(Attributes="Limit(0)") float StretchLimit = 1.0f; + }; + + /// + /// Cloth's fabric settings (material's stiffness and compression response). + /// + API_STRUCT() struct FabricSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(FabricStiffnessSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Vertical constraints for stretching or compression (along the gravity). + /// + API_FIELD() FabricAxisSettings Vertical; + + /// + /// Horizontal constraints for stretching or compression (perpendicular to the gravity). + /// + API_FIELD() FabricAxisSettings Horizontal; + + /// + /// Bending constraints for out-of-plane bending in angle-based formulation. + /// + API_FIELD() FabricAxisSettings Bending; + + /// + /// Shearing constraints for plane shearing along (typically) diagonal edges. + /// + API_FIELD() FabricAxisSettings Shearing; + }; + +private: + void* _cloth = nullptr; + ForceSettings _forceSettings; + CollisionSettings _collisionSettings; + SimulationSettings _simulationSettings; + FabricSettings _fabricSettings; + Vector3 _cachedPosition = Vector3::Zero; + ModelInstanceActor::MeshReference _mesh; + MeshDeformation* _meshDeformation = nullptr; + +public: + /// + /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). + /// + /// + API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")") + ModelInstanceActor::MeshReference GetMesh() const; + + /// + /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD). + /// + API_PROPERTY() void SetMesh(const ModelInstanceActor::MeshReference& value); + + /// + /// Gets the cloth response to forces settings. + /// + API_PROPERTY(Attributes="EditorOrder(10), EditorDisplay(\"Cloth\")") + FORCE_INLINE ForceSettings GetForce() const + { + return _forceSettings; + } + + /// + /// Sets the cloth response to forces settings. + /// + API_PROPERTY() void SetForce(const ForceSettings& value); + + /// + /// Gets the cloth response to collisions settings. + /// + API_PROPERTY(Attributes="EditorOrder(20), EditorDisplay(\"Cloth\")") + FORCE_INLINE CollisionSettings GetCollision() const + { + return _collisionSettings; + } + + /// + /// Sets the cloth response to collisions settings. + /// + API_PROPERTY() void SetCollision(const CollisionSettings& value); + + /// + /// Gets the cloth simulation settings. + /// + API_PROPERTY(Attributes="EditorOrder(30), EditorDisplay(\"Cloth\")") + FORCE_INLINE SimulationSettings GetSimulation() const + { + return _simulationSettings; + } + + /// + /// Sets the cloth simulation settings. + /// + API_PROPERTY() void SetSimulation(const SimulationSettings& value); + + /// + /// Gets the cloth's fabric settings (material's stiffness and compression response). + /// + API_PROPERTY(Attributes="EditorOrder(40), EditorDisplay(\"Cloth\")") + FORCE_INLINE FabricSettings GetFabric() const + { + return _fabricSettings; + } + + /// + /// Sets the cloth's fabric settings (material's stiffness and compression response). + /// + API_PROPERTY() void SetFabric(const FabricSettings& value); + +public: + /// + /// Recreates the cloth by removing current instance data and creating a new physical cloth object. Does nothing if cloth was not created (eg. no parent mesh). + /// + API_FUNCTION() void Rebuild(); + + /// + /// Sets the inertia derived from transform change to zero (once). It will reset any cloth object movement effects as it was teleported. + /// + API_FUNCTION() void ClearInteria(); + +public: + // [Actor] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + // [Actor] +#if USE_EDITOR + void OnDebugDrawSelected() override; +#endif + void BeginPlay(SceneBeginData* data) override; + void EndPlay() override; + void OnEnable() override; + void OnDisable() override; + void OnParentChanged() override; + void OnTransformChanged() override; + void OnPhysicsSceneChanged(PhysicsScene* previous) override; + +private: +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view); +#endif + bool CreateCloth(); + void DestroyCloth(); + void OnUpdated(); + void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); +}; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 41430fb4d..e33c66efe 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -38,6 +38,17 @@ #include #include #endif +#if WITH_CLOTH +#include "Engine/Physics/Actors/Cloth.h" +#include +#include +#include +#include +#include +#include +#define MAX_CLOTH_SPHERE_COUNT 32 +#define MAX_CLOTH_PLANE_COUNT 32 +#endif #if WITH_PVD #include #endif @@ -79,6 +90,9 @@ struct ScenePhysX PxBatchQueryExt* WheelRaycastBatchQuery = nullptr; int32 WheelRaycastBatchQuerySize = 0; #endif +#if WITH_CLOTH + nv::cloth::Solver* ClothSolver = nullptr; +#endif }; class AllocatorPhysX : public PxAllocatorCallback @@ -103,6 +117,67 @@ class ErrorPhysX : public PxErrorCallback } }; +#if WITH_CLOTH + +class AssertPhysX : public nv::cloth::PxAssertHandler +{ +public: + void operator()(const char* exp, const char* file, int line, bool& ignore) override + { + Platform::Error(String(exp)); + Platform::Crash(line, file); + } +}; + +class ProfilerPhysX : public physx::PxProfilerCallback +{ +public: + void* zoneStart(const char* eventName, bool detached, uint64_t contextId) override + { + return nullptr; + } + + void zoneEnd(void* profilerData, const char* eventName, bool detached, uint64_t contextId) override + { + } +}; + +struct FabricSettings +{ + int32 Refs; + nv::cloth::Vector::Type PhraseTypes; +}; + +struct ClothSettings +{ + bool SceneCollisions = false; + float GravityScale = 1.0f; + Cloth* Actor; + + void UpdateBounds(const nv::cloth::Cloth* clothPhysX) const + { + // Get cloth particles bounds (in local-space) + const PxVec3& clothBoundsPos = clothPhysX->getBoundingBoxCenter(); + const PxVec3& clothBoundsSize = clothPhysX->getBoundingBoxScale(); + BoundingBox localBounds; + BoundingBox::FromPoints(P2C(clothBoundsPos - clothBoundsSize), P2C(clothBoundsPos + clothBoundsSize), localBounds); + + // Transform local-space bounds into world-space + const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation()); + const Transform clothTrans(P2C(clothPose.p), P2C(clothPose.q)); + Vector3 boundsCorners[8]; + localBounds.GetCorners(boundsCorners); + for (Vector3& c : boundsCorners) + clothTrans.LocalToWorld(c, c); + + // Setup bounds + BoundingBox::FromPoints(boundsCorners, 8, const_cast(Actor->GetBox())); + BoundingSphere::FromBox(Actor->GetBox(), const_cast(Actor->GetSphere())); + } +}; + +#endif + class QueryFilterPhysX : public PxQueryFilterCallback { PxQueryHitType::Enum preFilter(const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags) override @@ -379,6 +454,10 @@ namespace PxMaterial* DefaultMaterial = nullptr; AllocatorPhysX AllocatorCallback; ErrorPhysX ErrorCallback; +#if WITH_CLOTH + AssertPhysX AssertCallback; + ProfilerPhysX ProfilerCallback; +#endif PxTolerancesScale ToleranceScale; QueryFilterPhysX QueryFilter; CharacterQueryFilterPhysX CharacterQueryFilter; @@ -402,6 +481,22 @@ namespace Array WheelTireTypes; WheelFilterPhysX WheelRaycastFilter; #endif + +#if WITH_CLOTH + nv::cloth::Factory* ClothFactory = nullptr; + Dictionary Fabrics; + Dictionary Cloths; +#endif +} + +FORCE_INLINE PxPlane transform(const PxPlane& plane, const PxMat33& inverse) +{ + PxPlane result; + result.n.x = plane.n.x * inverse.column0.x + plane.n.y * inverse.column0.y + plane.n.z * inverse.column0.z; + result.n.y = plane.n.x * inverse.column1.x + plane.n.y * inverse.column1.y + plane.n.z * inverse.column1.z; + result.n.z = plane.n.x * inverse.column2.x + plane.n.y * inverse.column2.y + plane.n.z * inverse.column2.z; + result.d = plane.d; + return result; } PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) @@ -777,6 +872,13 @@ void PhysicsBackend::Shutdown() RELEASE_PHYSX(DefaultMaterial); // Shutdown PhysX +#if WITH_CLOTH + if (ClothFactory) + { + NvClothDestroyFactory(ClothFactory); + ClothFactory = nullptr; + } +#endif #if WITH_VEHICLE if (VehicleSDKInitialized) { @@ -881,6 +983,13 @@ void PhysicsBackend::DestroyScene(void* scene) #if WITH_VEHICLE RELEASE_PHYSX(scenePhysX->WheelRaycastBatchQuery); scenePhysX->WheelRaycastBatchQuerySize = 0; +#endif +#if WITH_CLOTH + if (scenePhysX->ClothSolver) + { + NV_CLOTH_DELETE(scenePhysX->ClothSolver); + scenePhysX->ClothSolver = nullptr; + } #endif RELEASE_PHYSX(scenePhysX->ControllerManager); SAFE_DELETE(scenePhysX->CpuDispatcher); @@ -1249,6 +1358,203 @@ void PhysicsBackend::EndSimulateScene(void* scene) } } +#if WITH_CLOTH + nv::cloth::Solver* clothSolver = scenePhysX->ClothSolver; + if (clothSolver && clothSolver->getNumCloths() != 0) + { + PROFILE_CPU_NAMED("Physics.Cloth"); + const int32 clothsCount = scenePhysX->ClothSolver->getNumCloths(); + nv::cloth::Cloth* const* cloths = scenePhysX->ClothSolver->getClothList(); + // TODO: jobify to run in async (eg. with vehicles update and events setup) + + { + PROFILE_CPU_NAMED("Collisions"); + const bool hitTriggers = false; + const bool blockSingle = false; + PxQueryFilterData filterData; + filterData.flags |= PxQueryFlag::ePREFILTER; + filterData.data.word1 = blockSingle ? 1 : 0; + filterData.data.word2 = hitTriggers ? 1 : 0; + for (int32 i = 0; i < clothsCount; i++) + { + auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; + const auto& clothSettings = Cloths[clothPhysX]; + + // Setup automatic scene collisions with colliders around the cloth + if (clothSettings.SceneCollisions) + { + // Reset existing colliders + clothPhysX->setSpheres(nv::cloth::Range(), 0, clothPhysX->getNumSpheres()); + clothPhysX->setPlanes(nv::cloth::Range(), 0, clothPhysX->getNumPlanes()); + clothPhysX->setTriangles(nv::cloth::Range(), 0, clothPhysX->getNumTriangles()); + + filterData.data.word0 = Physics::LayerMasks[clothSettings.Actor->GetLayer()]; + const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation()); + const PxVec3 clothBoundsPos = clothPhysX->getBoundingBoxCenter(); + const PxVec3 clothBoundsSize = clothPhysX->getBoundingBoxScale(); + const PxTransform overlapPose(clothPose.transform(clothBoundsPos), clothPose.q); + const float boundsMargin = 1.6f; + const PxSphereGeometry overlapGeo(clothBoundsSize.magnitude() * boundsMargin); + + // Find any colliders around the cloth + DynamicHitBuffer buffer; + if (scenePhysX->Scene->overlap(overlapGeo, overlapPose, buffer, filterData, &QueryFilter)) + { + for (uint32 j = 0; j < buffer.getNbTouches(); j++) + { + const auto& hit = buffer.getTouch(j); + if (hit.shape) + { + const PxGeometry& geo = hit.shape->getGeometry(); + const PxTransform shapeToCloth = clothPose.transformInv(hit.actor->getGlobalPose().transform(hit.shape->getLocalPose())); + // TODO: maybe use shared spheres/planes buffers for batched assigning? + switch (geo.getType()) + { + case PxGeometryType::eSPHERE: + { + const PxSphereGeometry& geoSphere = (const PxSphereGeometry&)geo; + const PxVec4 packedSphere(shapeToCloth.p, geoSphere.radius); + const nv::cloth::Range sphereRange(&packedSphere, &packedSphere + 1); + const uint32_t spheresCount = clothPhysX->getNumSpheres(); + if (spheresCount + 1 > MAX_CLOTH_SPHERE_COUNT) + break; + clothPhysX->setSpheres(sphereRange, spheresCount, spheresCount); + break; + } + case PxGeometryType::eCAPSULE: + { + const PxCapsuleGeometry& geomCapsule = (const PxCapsuleGeometry&)geo; + const PxVec4 packedSpheres[2] = { + PxVec4(shapeToCloth.transform(PxVec3(+geomCapsule.halfHeight, 0, 0)), geomCapsule.radius), + PxVec4(shapeToCloth.transform(PxVec3(-geomCapsule.halfHeight, 0, 0)), geomCapsule.radius) + }; + const nv::cloth::Range sphereRange(packedSpheres, packedSpheres + 2); + const uint32_t spheresCount = clothPhysX->getNumSpheres(); + if (spheresCount + 2 > MAX_CLOTH_SPHERE_COUNT) + break; + clothPhysX->setSpheres(sphereRange, spheresCount, spheresCount); + const uint32_t packedCapsules[2] = { spheresCount, spheresCount + 1 }; + const int32 capsulesCount = clothPhysX->getNumCapsules(); + clothPhysX->setCapsules(nv::cloth::Range(packedCapsules, packedCapsules + 2), capsulesCount, capsulesCount); + break; + } + case PxGeometryType::eBOX: + { + const PxBoxGeometry& geomBox = (const PxBoxGeometry&)geo; + const uint32_t planesCount = clothPhysX->getNumPlanes(); + if (planesCount + 6 > MAX_CLOTH_PLANE_COUNT) + break; + const PxPlane packedPlanes[6] = { + PxPlane(PxVec3(1, 0, 0), -geomBox.halfExtents.x).transform(shapeToCloth), + PxPlane(PxVec3(-1, 0, 0), -geomBox.halfExtents.x).transform(shapeToCloth), + PxPlane(PxVec3(0, 1, 0), -geomBox.halfExtents.y).transform(shapeToCloth), + PxPlane(PxVec3(0, -1, 0), -geomBox.halfExtents.y).transform(shapeToCloth), + PxPlane(PxVec3(0, 0, 1), -geomBox.halfExtents.z).transform(shapeToCloth), + PxPlane(PxVec3(0, 0, -1), -geomBox.halfExtents.z).transform(shapeToCloth) + }; + clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)packedPlanes, (const PxVec4*)packedPlanes + 6), planesCount, planesCount); + const PxU32 convexMask = PxU32(0x3f << planesCount); + const uint32_t convexesCount = clothPhysX->getNumConvexes(); + clothPhysX->setConvexes(nv::cloth::Range(&convexMask, &convexMask + 1), convexesCount, convexesCount); + break; + } + case PxGeometryType::eCONVEXMESH: + { + const PxConvexMeshGeometry& geomConvexMesh = (const PxConvexMeshGeometry&)geo; + const PxU32 convexPlanesCount = geomConvexMesh.convexMesh->getNbPolygons(); + const uint32_t planesCount = clothPhysX->getNumPlanes(); + if (planesCount + convexPlanesCount > MAX_CLOTH_PLANE_COUNT) + break; + const PxMat33 convexToShapeInv = geomConvexMesh.scale.toMat33().getInverse(); + // TODO: merge convexToShapeInv with shapeToCloth to have a single matrix multiplication + PxPlane planes[MAX_CLOTH_PLANE_COUNT]; + for (PxU32 k = 0; k < convexPlanesCount; k++) + { + PxHullPolygon polygon; + geomConvexMesh.convexMesh->getPolygonData(k, polygon); + planes[k] = transform(reinterpret_cast(polygon.mPlane), convexToShapeInv).transform(shapeToCloth); + } + clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)planes, (const PxVec4*)planes + convexPlanesCount), planesCount, planesCount); + const PxU32 convexMask = PxU32(((1 << convexPlanesCount) - 1) << planesCount); + const uint32_t convexesCount = clothPhysX->getNumConvexes(); + clothPhysX->setConvexes(nv::cloth::Range(&convexMask, &convexMask + 1), convexesCount, convexesCount); + break; + } + // Cloth vs Triangle collisions are too slow for real-time use +#if 0 + case PxGeometryType::eTRIANGLEMESH: + { + const PxTriangleMeshGeometry& geomTriangleMesh = (const PxTriangleMeshGeometry&)geo; + if (geomTriangleMesh.triangleMesh->getNbTriangles() >= 1024) + break; // Ignore too-tessellated meshes due to poor solver performance + // TODO: use shared memory allocators maybe? maybe per-frame stack allocator? + Array vertices; + vertices.Add(geomTriangleMesh.triangleMesh->getVertices(), geomTriangleMesh.triangleMesh->getNbVertices()); + const PxMat33 triangleMeshToShape = geomTriangleMesh.scale.toMat33(); + // TODO: merge triangleMeshToShape with shapeToCloth to have a single matrix multiplication + for (int32 k = 0; k < vertices.Count(); k++) + { + PxVec3& v = vertices.Get()[k]; + v = shapeToCloth.transform(triangleMeshToShape.transform(v)); + } + Array triangles; + triangles.Resize(geomTriangleMesh.triangleMesh->getNbTriangles() * 3); + if (geomTriangleMesh.triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::e16_BIT_INDICES) + { + auto indices = (const uint16*)geomTriangleMesh.triangleMesh->getTriangles(); + for (int32 k = 0; k < triangles.Count(); k++) + triangles.Get()[k] = vertices.Get()[indices[k]]; + } + else + { + auto indices = (const uint32*)geomTriangleMesh.triangleMesh->getTriangles(); + for (int32 k = 0; k < triangles.Count(); k++) + triangles.Get()[k] = vertices.Get()[indices[k]]; + } + const uint32_t trianglesCount = clothPhysX->getNumTriangles(); + clothPhysX->setTriangles(nv::cloth::Range(triangles.begin(), triangles.end()), trianglesCount, trianglesCount); + break; + } + case PxGeometryType::eHEIGHTFIELD: + { + const PxHeightFieldGeometry& geomHeightField = (const PxHeightFieldGeometry&)geo; + // TODO: heightfield collisions (gather triangles only nearby cloth to not blow sim perf) + break; + } +#endif + } + } + } + } + } + } + } + + { + PROFILE_CPU_NAMED("Simulation"); + if (clothSolver->beginSimulation(scenePhysX->LastDeltaTime)) + { + const int32 count = clothSolver->getSimulationChunkCount(); + for (int32 i = 0; i < count; i++) + { + clothSolver->simulateChunk(i); + } + clothSolver->endSimulation(); + } + } + + { + PROFILE_CPU_NAMED("Post"); + for (int32 i = 0; i < clothsCount; i++) + { + auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; + const auto& clothSettings = Cloths[clothPhysX]; + clothSettings.UpdateBounds(clothPhysX); + clothSettings.Actor->OnUpdated(); + } + } + } + { PROFILE_CPU_NAMED("Physics.SendEvents"); @@ -1257,6 +1563,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) scenePhysX->EventsCallback.SendCollisionEvents(); scenePhysX->EventsCallback.SendJointEvents(); } +#endif } Vector3 PhysicsBackend::GetSceneGravity(void* scene) @@ -1269,6 +1576,19 @@ void PhysicsBackend::SetSceneGravity(void* scene, const Vector3& value) { auto scenePhysX = (ScenePhysX*)scene; scenePhysX->Scene->setGravity(C2P(value)); +#if WITH_CLOTH + if (scenePhysX->ClothSolver) + { + const int32 clothsCount = scenePhysX->ClothSolver->getNumCloths(); + nv::cloth::Cloth* const* cloths = scenePhysX->ClothSolver->getClothList(); + for (int32 i = 0; i < clothsCount; i++) + { + auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; + const auto& clothSettings = Cloths[clothPhysX]; + clothPhysX->setGravity(C2P(value * clothSettings.GravityScale)); + } + } +#endif } bool PhysicsBackend::GetSceneEnableCCD(void* scene) @@ -1302,6 +1622,7 @@ void PhysicsBackend::SetSceneOrigin(void* scene, const Vector3& oldOrigin, const scenePhysX->Origin = newOrigin; scenePhysX->Scene->shiftOrigin(shift); scenePhysX->ControllerManager->shiftOrigin(shift); +#if WITH_VEHICLE WheelVehiclesCache.Clear(); for (auto wheelVehicle : scenePhysX->WheelVehicles) { @@ -1312,6 +1633,18 @@ void PhysicsBackend::SetSceneOrigin(void* scene, const Vector3& oldOrigin, const WheelVehiclesCache.Add(drive); } PxVehicleShiftOrigin(shift, WheelVehiclesCache.Count(), WheelVehiclesCache.Get()); +#endif +#if WITH_CLOTH + if (scenePhysX->ClothSolver) + { + const int32 clothsCount = scenePhysX->ClothSolver->getNumCloths(); + nv::cloth::Cloth* const* cloths = scenePhysX->ClothSolver->getClothList(); + for (int32 i = 0; i < clothsCount; i++) + { + cloths[i]->teleport(shift); + } + } +#endif SceneOrigins[scenePhysX->Scene] = newOrigin; } @@ -2931,6 +3264,220 @@ void PhysicsBackend::RemoveVehicle(void* scene, WheeledVehicle* actor) #endif +#if WITH_CLOTH + +void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) +{ + // Lazy-init NvCloth + if (ClothFactory == nullptr) + { + nv::cloth::InitializeNvCloth(&AllocatorCallback, &ErrorCallback, &AssertCallback, &ProfilerCallback); + ClothFactory = NvClothCreateFactoryCPU(); + ASSERT(ClothFactory); + } + + // Cook fabric from the mesh data + nv::cloth::ClothMeshDesc meshDesc; + meshDesc.points.data = desc.VerticesData; + meshDesc.points.stride = desc.VerticesStride; + meshDesc.points.count = desc.VerticesCount; + meshDesc.triangles.data = desc.IndicesData; + meshDesc.triangles.stride = desc.IndicesStride * 3; + meshDesc.triangles.count = desc.IndicesCount / 3; + if (desc.IndicesStride == sizeof(uint16)) + meshDesc.flags |= nv::cloth::MeshFlag::e16_BIT_INDICES; + // TODO: provide invMasses data + const Float3 gravity(PhysicsSettings::Get()->DefaultGravity); + nv::cloth::Vector::Type phaseTypeInfo; + // TODO: automatically reuse fabric from existing cloths (simply check for input data used for computations to improve perf when duplicating cloths or with prefab) + nv::cloth::Fabric* fabric = NvClothCookFabricFromMesh(ClothFactory, meshDesc, gravity.Raw, &phaseTypeInfo); + if (!fabric) + { + LOG(Error, "NvClothCookFabricFromMesh failed"); + return nullptr; + } + + // Create cloth object + static_assert(sizeof(Float4) == sizeof(PxVec4), "Size mismatch"); + Array initialState; + // TODO: provide initial state for cloth from the owner (eg. current skinned mesh position) + initialState.Resize((int32)desc.VerticesCount); + for (uint32 i = 0; i < desc.VerticesCount; i++) + initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), 1.0f); // TODO: set .w to invMass of that vertex + const nv::cloth::Range initialParticlesRange((PxVec4*)initialState.Get(), (PxVec4*)initialState.Get() + initialState.Count()); + nv::cloth::Cloth* clothPhysX = ClothFactory->createCloth(initialParticlesRange, *fabric); + fabric->decRefCount(); + if (!clothPhysX) + { + LOG(Error, "createCloth failed"); + return nullptr; + } + + // Setup settings + FabricSettings fabricSettings; + fabricSettings.Refs = 1; + fabricSettings.PhraseTypes.swap(phaseTypeInfo); + Fabrics.Add(fabric, fabricSettings); + ClothSettings clothSettings; + clothSettings.Actor = desc.Actor; + clothSettings.UpdateBounds(clothPhysX); + Cloths.Add(clothPhysX, clothSettings); + + return clothPhysX; +} + +void PhysicsBackend::DestroyCloth(void* cloth) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + if (--Fabrics[&clothPhysX->getFabric()].Refs == 0) + Fabrics.Remove(&clothPhysX->getFabric()); + Cloths.Remove(clothPhysX); + NV_CLOTH_DELETE(clothPhysX); +} + +void PhysicsBackend::SetClothForceSettings(void* cloth, const void* settingsPtr) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + const auto& settings = *(const Cloth::ForceSettings*)settingsPtr; + clothPhysX->setGravity(C2P(PhysicsSettings::Get()->DefaultGravity * settings.GravityScale)); + clothPhysX->setDamping(PxVec3(settings.Damping)); + clothPhysX->setLinearDrag(PxVec3(settings.LinearDrag)); + clothPhysX->setAngularDrag(PxVec3(settings.AngularDrag)); + clothPhysX->setLinearInertia(PxVec3(settings.LinearInertia)); + clothPhysX->setAngularInertia(PxVec3(settings.AngularInertia)); + clothPhysX->setCentrifugalInertia(PxVec3(settings.CentrifugalInertia)); + clothPhysX->setDragCoefficient(Math::Saturate(settings.AirDragCoefficient)); + clothPhysX->setLiftCoefficient(Math::Saturate(settings.AirLiftCoefficient)); + clothPhysX->setFluidDensity(Math::Max(settings.AirDensity, ZeroTolerance)); + auto& clothSettings = Cloths[clothPhysX]; + clothSettings.GravityScale = settings.GravityScale; +} + +void PhysicsBackend::SetClothCollisionSettings(void* cloth, const void* settingsPtr) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + const auto& settings = *(const Cloth::CollisionSettings*)settingsPtr; + clothPhysX->setFriction(settings.Friction); + clothPhysX->setCollisionMassScale(settings.MassScale); + clothPhysX->enableContinuousCollision(settings.ContinuousCollisionDetection); + clothPhysX->setSelfCollisionDistance(settings.SelfCollisionDistance); + clothPhysX->setSelfCollisionStiffness(settings.SelfCollisionStiffness); + auto& clothSettings = Cloths[clothPhysX]; + if (clothSettings.SceneCollisions != settings.SceneCollisions && !settings.SceneCollisions) + { + // Remove colliders + clothPhysX->setSpheres(nv::cloth::Range(), 0, clothPhysX->getNumSpheres()); + clothPhysX->setPlanes(nv::cloth::Range(), 0, clothPhysX->getNumPlanes()); + clothPhysX->setTriangles(nv::cloth::Range(), 0, clothPhysX->getNumTriangles()); + } + clothSettings.SceneCollisions = settings.SceneCollisions; +} + +void PhysicsBackend::SetClothSimulationSettings(void* cloth, const void* settingsPtr) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + const auto& settings = *(const Cloth::SimulationSettings*)settingsPtr; + clothPhysX->setSolverFrequency(settings.SolverFrequency); + clothPhysX->setWindVelocity(C2P(settings.WindVelocity)); +} + +void PhysicsBackend::SetClothFabricSettings(void* cloth, const void* settingsPtr) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + const auto& settings = *(const Cloth::FabricSettings*)settingsPtr; + const auto& fabricSettings = Fabrics[&clothPhysX->getFabric()]; + Array configs; + configs.Resize(fabricSettings.PhraseTypes.size()); + for (int32 i = 0; i < configs.Count(); i++) + { + auto& config = configs[i]; + config.mPhaseIndex = i; + const Cloth::FabricAxisSettings* axisSettings; + switch (fabricSettings.PhraseTypes[i]) + { + case nv::cloth::ClothFabricPhaseType::eVERTICAL: + axisSettings = &settings.Vertical; + break; + case nv::cloth::ClothFabricPhaseType::eHORIZONTAL: + axisSettings = &settings.Horizontal; + break; + case nv::cloth::ClothFabricPhaseType::eBENDING: + axisSettings = &settings.Bending; + break; + case nv::cloth::ClothFabricPhaseType::eSHEARING: + axisSettings = &settings.Shearing; + break; + } + config.mStiffness = axisSettings->Stiffness; + config.mStiffnessMultiplier = axisSettings->StiffnessMultiplier; + config.mCompressionLimit = axisSettings->CompressionLimit; + config.mStretchLimit = axisSettings->StretchLimit; + } + clothPhysX->setPhaseConfig(nv::cloth::Range(configs.begin(), configs.end())); +} + +void PhysicsBackend::SetClothTransform(void* cloth, const Transform& transform, bool teleport) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + if (teleport) + { + clothPhysX->teleportToLocation(C2P(transform.Translation), C2P(transform.Orientation)); + } + else + { + clothPhysX->setTranslation(C2P(transform.Translation)); + clothPhysX->setRotation(C2P(transform.Orientation)); + } + const auto& clothSettings = Cloths[clothPhysX]; + clothSettings.UpdateBounds(clothPhysX); +} + +void PhysicsBackend::ClearClothInertia(void* cloth) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + clothPhysX->clearInertia(); +} + +void PhysicsBackend::LockClothParticles(void* cloth) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + clothPhysX->lockParticles(); +} + +void PhysicsBackend::UnlockClothParticles(void* cloth) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + clothPhysX->unlockParticles(); +} + +Span PhysicsBackend::GetClothCurrentParticles(void* cloth) +{ + auto clothPhysX = (const nv::cloth::Cloth*)cloth; + const nv::cloth::MappedRange range = clothPhysX->getCurrentParticles(); + return Span((const Float4*)range.begin(), (int32)range.size()); +} + +void PhysicsBackend::AddCloth(void* scene, void* cloth) +{ + auto scenePhysX = (ScenePhysX*)scene; + auto clothPhysX = (nv::cloth::Cloth*)cloth; + if (scenePhysX->ClothSolver == nullptr) + { + scenePhysX->ClothSolver = ClothFactory->createSolver(); + ASSERT(scenePhysX->ClothSolver); + } + scenePhysX->ClothSolver->addCloth(clothPhysX); +} + +void PhysicsBackend::RemoveCloth(void* scene, void* cloth) +{ + auto scenePhysX = (ScenePhysX*)scene; + auto clothPhysX = (nv::cloth::Cloth*)cloth; + scenePhysX->ClothSolver->removeCloth(clothPhysX); +} + +#endif + void* PhysicsBackend::CreateConvexMesh(byte* data, int32 dataSize, BoundingBox& localBounds) { PxDefaultMemoryInputData input(data, dataSize); diff --git a/Source/Engine/Physics/Physics.Build.cs b/Source/Engine/Physics/Physics.Build.cs index d31e0d271..2a5911245 100644 --- a/Source/Engine/Physics/Physics.Build.cs +++ b/Source/Engine/Physics/Physics.Build.cs @@ -14,6 +14,16 @@ public class Physics : EngineModule /// public static bool WithCooking = true; + /// + /// Enables using vehicles simulation. + /// + public static bool WithVehicle = true; + + /// + /// Enables using cloth simulation. + /// + public static bool WithCloth = true; + /// /// Enables using PhysX library. Can be overriden by SetupPhysicsBackend. /// @@ -42,6 +52,8 @@ public class Physics : EngineModule if (WithPhysX) { options.PrivateDependencies.Add("PhysX"); + if (WithCloth && options.Platform.Target != TargetPlatform.PS4) // TODO: build nvcloth for ps4 with vs2017 + options.PrivateDependencies.Add("NvCloth"); } else { diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index f72c7f679..5e8743d69 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -22,8 +22,9 @@ enum class D6JointDriveType; class IPhysicsActor; class PhysicalMaterial; class JsonAsset; +class JsonAsset; -struct FLAXENGINE_API PhysicsJointDesc +struct PhysicsJointDesc { Joint* Joint; void* Actor0; @@ -34,6 +35,17 @@ struct FLAXENGINE_API PhysicsJointDesc Vector3 Pos1; }; +struct PhysicsClothDesc +{ + class Cloth* Actor; + void* VerticesData; + void* IndicesData; + uint32 VerticesCount; + uint32 VerticesStride; + uint32 IndicesCount; + uint32 IndicesStride; +}; + /// /// Interface for the physical simulation backend implementation. /// @@ -256,6 +268,23 @@ public: static void AddVehicle(void* scene, WheeledVehicle* actor); static void RemoveVehicle(void* scene, WheeledVehicle* actor); #endif + +#if WITH_CLOTH + // Cloth + static void* CreateCloth(const PhysicsClothDesc& desc); + static void DestroyCloth(void* cloth); + static void SetClothForceSettings(void* cloth, const void* settingsPtr); + static void SetClothCollisionSettings(void* cloth, const void* settingsPtr); + static void SetClothSimulationSettings(void* cloth, const void* settingsPtr); + static void SetClothFabricSettings(void* cloth, const void* settingsPtr); + static void SetClothTransform(void* cloth, const Transform& transform, bool teleport); + static void ClearClothInertia(void* cloth); + static void LockClothParticles(void* cloth); + static void UnlockClothParticles(void* cloth); + static Span GetClothCurrentParticles(void* cloth); + static void AddCloth(void* scene, void* cloth); + static void RemoveCloth(void* scene, void* cloth); +#endif // Resources static void* CreateConvexMesh(byte* data, int32 dataSize, BoundingBox& localBounds); diff --git a/Source/ThirdParty/PhysX/PhysX.Build.cs b/Source/ThirdParty/PhysX/PhysX.Build.cs index 7b598f8b1..31d6ec2db 100644 --- a/Source/ThirdParty/PhysX/PhysX.Build.cs +++ b/Source/ThirdParty/PhysX/PhysX.Build.cs @@ -38,7 +38,7 @@ public class PhysX : DepsModule bool useDynamicLinking = false; bool usePVD = false; - bool useVehicle = true; + bool useVehicle = Physics.WithVehicle; bool usePhysicsCooking = Physics.WithCooking; var depsRoot = options.DepsFolder; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs new file mode 100644 index 000000000..7288add63 --- /dev/null +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs @@ -0,0 +1,193 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Flax.Build; +using Flax.Build.Platforms; + +namespace Flax.Deps.Dependencies +{ + /// + /// NVIDIA NvCloth + /// + /// + class NvCloth : Dependency + { + private string root, nvCloth; + + /// + public override TargetPlatform[] Platforms + { + get + { + switch (BuildPlatform) + { + case TargetPlatform.Windows: + return new[] + { + TargetPlatform.Windows, + TargetPlatform.XboxOne, + TargetPlatform.XboxScarlett, + TargetPlatform.PS4, + TargetPlatform.PS5, + TargetPlatform.Switch, + TargetPlatform.Android, + }; + case TargetPlatform.Linux: + return new[] + { + TargetPlatform.Linux, + }; + case TargetPlatform.Mac: + return new[] + { + TargetPlatform.Mac, + TargetPlatform.iOS, + }; + default: return new TargetPlatform[0]; + } + } + } + + /// + public override void Build(BuildOptions options) + { + root = options.IntermediateFolder; + nvCloth = Path.Combine(root, "NvCloth"); + + // Get the source + CloneGitRepoSingleBranch(root, "https://github.com/FlaxEngine/NvCloth.git", "master"); + + foreach (var platform in options.Platforms) + { + switch (platform) + { + case TargetPlatform.Windows: + Build(options, platform, TargetArchitecture.x64); + break; + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: + Build(options, platform, TargetArchitecture.x64); + break; + case TargetPlatform.PS4: + case TargetPlatform.PS5: + Utilities.DirectoryCopy(Path.Combine(GetBinariesFolder(options, platform), "Data", "NvCloth"), root, true, true); + Build(options, platform, TargetArchitecture.x64); + break; + case TargetPlatform.Switch: + Utilities.DirectoryCopy(Path.Combine(GetBinariesFolder(options, platform), "Data", "NvCloth"), root, true, true); + Build(options, platform, TargetArchitecture.ARM64); + break; + case TargetPlatform.Android: + Build(options, platform, TargetArchitecture.ARM64); + break; + } + } + + // Copy header files + var dstIncludePath = Path.Combine(options.ThirdPartyFolder, "NvCloth"); + Directory.GetFiles(dstIncludePath, "*.h", SearchOption.AllDirectories).ToList().ForEach(File.Delete); + Utilities.FileCopy(Path.Combine(nvCloth, "license.txt"), Path.Combine(dstIncludePath, "License.txt")); + Utilities.DirectoryCopy(Path.Combine(nvCloth, "include", "NvCloth"), dstIncludePath, true, true, "*.h"); + Utilities.DirectoryCopy(Path.Combine(nvCloth, "extensions", "include"), dstIncludePath, true, true, "*.h"); + } + + private void Build(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture) + { + // Peek options + var binariesPrefix = string.Empty; + var binariesPostfix = string.Empty; + var cmakeArgs = "-DNV_CLOTH_ENABLE_DX11=0 -DNV_CLOTH_ENABLE_CUDA=0 -DPX_GENERATE_GPU_PROJECTS=0"; + var cmakeName = string.Empty; + var buildFolder = Path.Combine(nvCloth, "compiler", platform.ToString() + '_' + architecture.ToString()); + var envVars = new Dictionary(); + envVars["GW_DEPS_ROOT"] = root; + switch (platform) + { + case TargetPlatform.Windows: + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=windows -DSTATIC_WINCRT=0"; + cmakeName = "windows"; + binariesPostfix = "_x64"; + break; + case TargetPlatform.PS4: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=ps4"; + cmakeArgs += $" -DCMAKE_TOOLCHAIN_FILE=\"{Path.Combine(nvCloth, "Externals/CMakeModules/ps4/PS4Toolchain.txt").Replace('\\', '/')}\""; + cmakeName = "ps4"; + binariesPrefix = "lib"; + break; + case TargetPlatform.PS5: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=ps5"; + cmakeArgs += $" -DCMAKE_TOOLCHAIN_FILE=\"{Path.Combine(nvCloth, "Externals/CMakeModules/ps5/PS5Toolchain.txt").Replace('\\', '/')}\""; + cmakeName = "ps5"; + binariesPrefix = "lib"; + break; + case TargetPlatform.Switch: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=NX64"; + cmakeName = "switch"; + binariesPrefix = "lib"; + envVars.Add("NintendoSdkRoot", Sdk.Get("SwitchSdk").RootPath + '\\'); + break; + case TargetPlatform.Android: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=android"; + cmakeName = "android"; + binariesPrefix = "lib"; + if (AndroidNdk.Instance.IsValid) + { + envVars.Add("ANDROID_NDK_HOME", AndroidNdk.Instance.RootPath); + envVars.Add("PM_ANDROIDNDK_PATH", AndroidNdk.Instance.RootPath); + } + break; + default: throw new InvalidPlatformException(platform); + } + var cmakeFolder = Path.Combine(nvCloth, "compiler", "cmake", cmakeName); + + // Setup build environment variables for the build system + switch (BuildPlatform) + { + case TargetPlatform.Windows: + { + GetMsBuildForPlatform(platform, out var vsVersion, out var msBuild); + if (File.Exists(msBuild)) + { + envVars.Add("PATH", Path.GetDirectoryName(msBuild)); + } + break; + } + } + + // Print the NvCloth version + Log.Info($"Building {File.ReadAllLines(Path.Combine(root, "README.md"))[0].Trim()} to {platform} {architecture}"); + + // Generate project files + SetupDirectory(buildFolder, false); + Utilities.FileDelete(Path.Combine(cmakeFolder, "CMakeCache.txt")); + cmakeArgs += $" -DPX_STATIC_LIBRARIES=1 -DPX_OUTPUT_DLL_DIR=\"{Path.Combine(buildFolder, "bin")}\" -DPX_OUTPUT_LIB_DIR=\"{Path.Combine(buildFolder, "lib")}\" -DPX_OUTPUT_EXE_DIR=\"{Path.Combine(buildFolder, "bin")}\""; + RunCmake(cmakeFolder, platform, architecture, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + cmakeArgs, envVars); + + // Run build + Utilities.Run("cmake", "--build . --config Release", null, cmakeFolder, Utilities.RunOptions.ThrowExceptionOnError, envVars); + + // Deploy binaries + var libs = new[] + { + "NvCloth", + }; + var dstBinaries = GetThirdPartyFolder(options, platform, architecture); + var srcBinaries = Path.Combine(buildFolder, "lib"); + var platformObj = Platform.GetPlatform(platform); + var binariesExtension = platformObj.StaticLibraryFileExtension; + foreach (var lib in libs) + { + var filename = binariesPrefix + lib + binariesPostfix + binariesExtension; + Utilities.FileCopy(Path.Combine(srcBinaries, filename), Path.Combine(dstBinaries, filename)); + + var filenamePdb = Path.ChangeExtension(filename, "pdb"); + if (File.Exists(Path.Combine(srcBinaries, filenamePdb))) + Utilities.FileCopy(Path.Combine(srcBinaries, filenamePdb), Path.Combine(dstBinaries, filenamePdb)); + } + } + } +} From 53ee65a5a62b2e29a69f1cc05c7fe471123d6e68 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Jul 2023 14:13:35 +0200 Subject: [PATCH 023/294] Add thickness to cloth to prevent intersecting with colliders --- Source/Engine/Physics/Actors/Cloth.h | 5 +++++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 22 +++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 588a4f4fd..dcc6544a8 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -100,6 +100,11 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph /// API_FIELD() bool ContinuousCollisionDetection = false; + /// + /// Additional thickness of the simulated cloth to prevent intersections with nearby colliders. + /// + API_FIELD(Attributes="Limit(0)") float CollisionThickness = 1.0f; + /// /// The minimum distance that the colliding cloth particles must maintain from each other in meters. 0: self collision disabled. /// diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index e33c66efe..52f6e58e4 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -152,6 +152,7 @@ struct ClothSettings { bool SceneCollisions = false; float GravityScale = 1.0f; + float CollisionThickness = 0.0f; Cloth* Actor; void UpdateBounds(const nv::cloth::Cloth* clothPhysX) const @@ -1400,6 +1401,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) DynamicHitBuffer buffer; if (scenePhysX->Scene->overlap(overlapGeo, overlapPose, buffer, filterData, &QueryFilter)) { + const float collisionThickness = clothSettings.CollisionThickness; for (uint32 j = 0; j < buffer.getNbTouches(); j++) { const auto& hit = buffer.getTouch(j); @@ -1413,7 +1415,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) case PxGeometryType::eSPHERE: { const PxSphereGeometry& geoSphere = (const PxSphereGeometry&)geo; - const PxVec4 packedSphere(shapeToCloth.p, geoSphere.radius); + const PxVec4 packedSphere(shapeToCloth.p, geoSphere.radius + collisionThickness); const nv::cloth::Range sphereRange(&packedSphere, &packedSphere + 1); const uint32_t spheresCount = clothPhysX->getNumSpheres(); if (spheresCount + 1 > MAX_CLOTH_SPHERE_COUNT) @@ -1425,8 +1427,8 @@ void PhysicsBackend::EndSimulateScene(void* scene) { const PxCapsuleGeometry& geomCapsule = (const PxCapsuleGeometry&)geo; const PxVec4 packedSpheres[2] = { - PxVec4(shapeToCloth.transform(PxVec3(+geomCapsule.halfHeight, 0, 0)), geomCapsule.radius), - PxVec4(shapeToCloth.transform(PxVec3(-geomCapsule.halfHeight, 0, 0)), geomCapsule.radius) + PxVec4(shapeToCloth.transform(PxVec3(+geomCapsule.halfHeight, 0, 0)), geomCapsule.radius + collisionThickness), + PxVec4(shapeToCloth.transform(PxVec3(-geomCapsule.halfHeight, 0, 0)), geomCapsule.radius + collisionThickness) }; const nv::cloth::Range sphereRange(packedSpheres, packedSpheres + 2); const uint32_t spheresCount = clothPhysX->getNumSpheres(); @@ -1445,12 +1447,12 @@ void PhysicsBackend::EndSimulateScene(void* scene) if (planesCount + 6 > MAX_CLOTH_PLANE_COUNT) break; const PxPlane packedPlanes[6] = { - PxPlane(PxVec3(1, 0, 0), -geomBox.halfExtents.x).transform(shapeToCloth), - PxPlane(PxVec3(-1, 0, 0), -geomBox.halfExtents.x).transform(shapeToCloth), - PxPlane(PxVec3(0, 1, 0), -geomBox.halfExtents.y).transform(shapeToCloth), - PxPlane(PxVec3(0, -1, 0), -geomBox.halfExtents.y).transform(shapeToCloth), - PxPlane(PxVec3(0, 0, 1), -geomBox.halfExtents.z).transform(shapeToCloth), - PxPlane(PxVec3(0, 0, -1), -geomBox.halfExtents.z).transform(shapeToCloth) + PxPlane(PxVec3(1, 0, 0), -geomBox.halfExtents.x - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(-1, 0, 0), -geomBox.halfExtents.x - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, 1, 0), -geomBox.halfExtents.y - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, -1, 0), -geomBox.halfExtents.y - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, 0, 1), -geomBox.halfExtents.z - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, 0, -1), -geomBox.halfExtents.z - collisionThickness).transform(shapeToCloth) }; clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)packedPlanes, (const PxVec4*)packedPlanes + 6), planesCount, planesCount); const PxU32 convexMask = PxU32(0x3f << planesCount); @@ -1472,6 +1474,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PxHullPolygon polygon; geomConvexMesh.convexMesh->getPolygonData(k, polygon); + polygon.mPlane[3] -= collisionThickness; planes[k] = transform(reinterpret_cast(polygon.mPlane), convexToShapeInv).transform(shapeToCloth); } clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)planes, (const PxVec4*)planes + convexPlanesCount), planesCount, planesCount); @@ -3371,6 +3374,7 @@ void PhysicsBackend::SetClothCollisionSettings(void* cloth, const void* settings clothPhysX->setTriangles(nv::cloth::Range(), 0, clothPhysX->getNumTriangles()); } clothSettings.SceneCollisions = settings.SceneCollisions; + clothSettings.CollisionThickness = settings.CollisionThickness; } void PhysicsBackend::SetClothSimulationSettings(void* cloth, const void* settingsPtr) From 8a07c486bc30f4e6abe8d4da834f8072f2afe363 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 12 Jul 2023 12:24:13 +0200 Subject: [PATCH 024/294] Refactor Editor gizmo modes ownership to support using them in prefab window --- Source/Editor/Gizmo/GizmosCollection.cs | 100 ++++++++++- .../Tools/Foliage/EditFoliageGizmoMode.cs | 13 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 6 +- .../Tools/Foliage/PaintFoliageGizmoMode.cs | 9 +- Source/Editor/Tools/Terrain/CarveTab.cs | 6 +- .../Tools/Terrain/EditTerrainGizmoMode.cs | 9 +- .../Tools/Terrain/PaintTerrainGizmoMode.cs | 9 +- .../Tools/Terrain/SculptTerrainGizmoMode.cs | 9 +- Source/Editor/Tools/VertexPainting.cs | 8 +- Source/Editor/Viewport/EditorGizmoViewport.cs | 14 +- .../Viewport/MainEditorGizmoViewport.Modes.cs | 158 ------------------ .../Viewport/MainEditorGizmoViewport.cs | 44 ++++- .../Editor/Viewport/Modes/EditorGizmoMode.cs | 15 +- Source/Editor/Viewport/Modes/NoGizmoMode.cs | 2 +- .../Viewport/Modes/TransformGizmoMode.cs | 2 +- .../Editor/Viewport/PrefabWindowViewport.cs | 3 +- Source/Editor/Windows/ToolboxWindow.cs | 2 +- 17 files changed, 203 insertions(+), 206 deletions(-) delete mode 100644 Source/Editor/Viewport/MainEditorGizmoViewport.Modes.cs diff --git a/Source/Editor/Gizmo/GizmosCollection.cs b/Source/Editor/Gizmo/GizmosCollection.cs index 30593d47e..beb653826 100644 --- a/Source/Editor/Gizmo/GizmosCollection.cs +++ b/Source/Editor/Gizmo/GizmosCollection.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using FlaxEditor.Viewport.Modes; using FlaxEngine; namespace FlaxEditor.Gizmo @@ -13,7 +14,10 @@ namespace FlaxEditor.Gizmo [HideInEditor] public class GizmosCollection : List { + private IGizmoOwner _owner; private GizmoBase _active; + private EditorGizmoMode _activeMode; + private readonly List _modes = new List(); /// /// Occurs when active gizmo tool gets changed. @@ -31,7 +35,7 @@ namespace FlaxEditor.Gizmo if (_active == value) return; if (value != null && !Contains(value)) - throw new InvalidOperationException("Invalid Gizmo."); + throw new ArgumentException("Not added."); _active?.OnDeactivated(); _active = value; @@ -40,6 +44,42 @@ namespace FlaxEditor.Gizmo } } + /// + /// Gets the active gizmo mode. + /// + public EditorGizmoMode ActiveMode + { + get => _activeMode; + set + { + if (_activeMode == value) + return; + if (value != null) + { + if (!_modes.Contains(value)) + throw new ArgumentException("Not added."); + if (value.Owner != _owner) + throw new InvalidOperationException(); + } + + _activeMode?.OnDeactivated(); + Active = null; + _activeMode = value; + _activeMode?.OnActivated(); + ActiveModeChanged?.Invoke(value); + } + } + + /// + /// Occurs when active mode gets changed. + /// + public event Action ActiveModeChanged; + + public GizmosCollection(IGizmoOwner owner) + { + _owner = owner; + } + /// /// Removes the specified item. /// @@ -57,7 +97,65 @@ namespace FlaxEditor.Gizmo public new void Clear() { Active = null; + ActiveMode = null; + foreach (var mode in _modes) + mode.Dispose(); + _modes.Clear(); + base.Clear(); } + + /// + /// Adds the mode to the viewport. + /// + /// The mode. + public void AddMode(EditorGizmoMode mode) + { + if (mode == null) + throw new ArgumentNullException(nameof(mode)); + if (_modes.Contains(mode)) + throw new ArgumentException("Already added."); + if (mode.Owner != null) + throw new ArgumentException("Already added to other viewport."); + + _modes.Add(mode); + mode.Init(_owner); + } + + /// + /// Removes the mode from the viewport. + /// + /// The mode. + public void RemoveMode(EditorGizmoMode mode) + { + if (mode == null) + throw new ArgumentNullException(nameof(mode)); + if (!_modes.Contains(mode)) + throw new ArgumentException("Not added."); + if (mode.Owner != _owner) + throw new ArgumentException("Not added to this viewport."); + + if (_activeMode == mode) + ActiveMode = null; + _modes.Remove(mode); + } + + /// + /// Sets the active mode. + /// + /// The mode type. + /// The activated mode. + public T SetActiveMode() where T : EditorGizmoMode + { + for (int i = 0; i < _modes.Count; i++) + { + if (_modes[i] is T mode) + { + ActiveMode = mode; + return mode; + } + } + throw new ArgumentException("Not added mode to activate."); + } } } diff --git a/Source/Editor/Tools/Foliage/EditFoliageGizmoMode.cs b/Source/Editor/Tools/Foliage/EditFoliageGizmoMode.cs index 56356c0b4..64b789fcb 100644 --- a/Source/Editor/Tools/Foliage/EditFoliageGizmoMode.cs +++ b/Source/Editor/Tools/Foliage/EditFoliageGizmoMode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Viewport; using FlaxEditor.Viewport.Modes; @@ -60,11 +61,11 @@ namespace FlaxEditor.Tools.Foliage public event Action SelectedInstanceIndexChanged; /// - public override void Init(MainEditorGizmoViewport viewport) + public override void Init(IGizmoOwner owner) { - base.Init(viewport); + base.Init(owner); - Gizmo = new EditFoliageGizmo(viewport, this); + Gizmo = new EditFoliageGizmo(owner, this); SelectionOutline = FlaxEngine.Object.New(); SelectionOutline.GizmoMode = this; } @@ -82,15 +83,15 @@ namespace FlaxEditor.Tools.Foliage { base.OnActivated(); - Viewport.Gizmos.Active = Gizmo; - Viewport.OverrideSelectionOutline(SelectionOutline); + Owner.Gizmos.Active = Gizmo; + ((MainEditorGizmoViewport)Owner).OverrideSelectionOutline(SelectionOutline); SelectedInstanceIndex = -1; } /// public override void OnDeactivated() { - Viewport.OverrideSelectionOutline(null); + ((MainEditorGizmoViewport)Owner).OverrideSelectionOutline(null); base.OnDeactivated(); } diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 814376c75..fe5412118 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -242,13 +242,13 @@ namespace FlaxEditor.Tools.Foliage switch (_modes.SelectedTabIndex) { case 0: - Editor.Windows.EditWin.Viewport.SetActiveMode(); + Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); break; case 1: - Editor.Windows.EditWin.Viewport.SetActiveMode(); + Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); break; case 2: - Editor.Windows.EditWin.Viewport.SetActiveMode(); + Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); break; default: throw new IndexOutOfRangeException("Invalid foliage tab mode."); } diff --git a/Source/Editor/Tools/Foliage/PaintFoliageGizmoMode.cs b/Source/Editor/Tools/Foliage/PaintFoliageGizmoMode.cs index 16fe5c9d3..c66c9752d 100644 --- a/Source/Editor/Tools/Foliage/PaintFoliageGizmoMode.cs +++ b/Source/Editor/Tools/Foliage/PaintFoliageGizmoMode.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Viewport; using FlaxEditor.Viewport.Modes; @@ -69,11 +70,11 @@ namespace FlaxEditor.Tools.Foliage } /// - public override void Init(MainEditorGizmoViewport viewport) + public override void Init(IGizmoOwner owner) { - base.Init(viewport); + base.Init(owner); - Gizmo = new PaintFoliageGizmo(viewport, this); + Gizmo = new PaintFoliageGizmo(owner, this); } /// @@ -81,7 +82,7 @@ namespace FlaxEditor.Tools.Foliage { base.OnActivated(); - Viewport.Gizmos.Active = Gizmo; + Owner.Gizmos.Active = Gizmo; ClearCursor(); } diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 6bee0bd92..6e0a9c6d3 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -191,13 +191,13 @@ namespace FlaxEditor.Tools.Terrain switch (_modes.SelectedTabIndex) { case 0: - Editor.Windows.EditWin.Viewport.SetActiveMode(); + Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); break; case 1: - Editor.Windows.EditWin.Viewport.SetActiveMode(); + Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); break; case 2: - Editor.Windows.EditWin.Viewport.SetActiveMode(); + Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); break; default: throw new IndexOutOfRangeException("Invalid carve tab mode."); } diff --git a/Source/Editor/Tools/Terrain/EditTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/EditTerrainGizmoMode.cs index 538de582c..fdb0a2464 100644 --- a/Source/Editor/Tools/Terrain/EditTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/EditTerrainGizmoMode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.Gizmo; using FlaxEditor.Viewport; using FlaxEditor.Viewport.Modes; using FlaxEngine; @@ -84,12 +85,12 @@ namespace FlaxEditor.Tools.Terrain public event Action SelectedChunkCoordChanged; /// - public override void Init(MainEditorGizmoViewport viewport) + public override void Init(IGizmoOwner owner) { - base.Init(viewport); + base.Init(owner); EditMode = Modes.Edit; - Gizmo = new EditTerrainGizmo(viewport, this); + Gizmo = new EditTerrainGizmo(owner, this); } /// @@ -97,7 +98,7 @@ namespace FlaxEditor.Tools.Terrain { base.OnActivated(); - Viewport.Gizmos.Active = Gizmo; + Owner.Gizmos.Active = Gizmo; } /// diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 40145c0c4..1d1bf87ca 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Tools.Terrain.Brushes; using FlaxEditor.Tools.Terrain.Paint; @@ -251,11 +252,11 @@ namespace FlaxEditor.Tools.Terrain internal EditTerrainMapAction CurrentEditUndoAction => _activeAction; /// - public override void Init(MainEditorGizmoViewport viewport) + public override void Init(IGizmoOwner owner) { - base.Init(viewport); + base.Init(owner); - Gizmo = new PaintTerrainGizmo(viewport, this); + Gizmo = new PaintTerrainGizmo(owner, this); Gizmo.PaintStarted += OnPaintStarted; Gizmo.PaintEnded += OnPaintEnded; } @@ -265,7 +266,7 @@ namespace FlaxEditor.Tools.Terrain { base.OnActivated(); - Viewport.Gizmos.Active = Gizmo; + Owner.Gizmos.Active = Gizmo; ClearCursor(); } diff --git a/Source/Editor/Tools/Terrain/SculptTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/SculptTerrainGizmoMode.cs index be89f605d..4b19cfbde 100644 --- a/Source/Editor/Tools/Terrain/SculptTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/SculptTerrainGizmoMode.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Tools.Terrain.Brushes; using FlaxEditor.Tools.Terrain.Sculpt; @@ -270,11 +271,11 @@ namespace FlaxEditor.Tools.Terrain internal EditTerrainMapAction CurrentEditUndoAction => _activeAction; /// - public override void Init(MainEditorGizmoViewport viewport) + public override void Init(IGizmoOwner owner) { - base.Init(viewport); + base.Init(owner); - Gizmo = new SculptTerrainGizmo(viewport, this); + Gizmo = new SculptTerrainGizmo(owner, this); Gizmo.PaintStarted += OnPaintStarted; Gizmo.PaintEnded += OnPaintEnded; } @@ -284,7 +285,7 @@ namespace FlaxEditor.Tools.Terrain { base.OnActivated(); - Viewport.Gizmos.Active = Gizmo; + Owner.Gizmos.Active = Gizmo; ClearCursor(); } diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 0863569c2..bb6b2c716 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -244,9 +244,9 @@ namespace FlaxEditor.Tools { Tab = this, }; - _editor.Windows.EditWin.Viewport.AddMode(_gizmoMode); + _editor.Windows.EditWin.Viewport.Gizmos.AddMode(_gizmoMode); } - _editor.Windows.EditWin.Viewport.SetActiveMode(_gizmoMode); + _editor.Windows.EditWin.Viewport.Gizmos.ActiveMode = _gizmoMode; } /// @@ -317,7 +317,7 @@ namespace FlaxEditor.Tools public Color PaintColor = Color.White; public VertexColorsMask PaintMask = VertexColorsMask.RGB; - public override void Init(MainEditorGizmoViewport viewport) + public override void Init(IGizmoOwner viewport) { base.Init(viewport); @@ -328,7 +328,7 @@ namespace FlaxEditor.Tools { base.OnActivated(); - Viewport.Gizmos.Active = Gizmo; + Owner.Gizmos.Active = Gizmo; } } diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index c58bb5805..2a99080ed 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -27,6 +27,7 @@ namespace FlaxEditor.Viewport { Undo = undo; SceneGraphRoot = sceneGraphRoot; + Gizmos = new GizmosCollection(this); SetUpdate(ref _update, OnUpdate); } @@ -40,7 +41,7 @@ namespace FlaxEditor.Viewport } /// - public GizmosCollection Gizmos { get; } = new GizmosCollection(); + public GizmosCollection Gizmos { get; } /// public SceneRenderTask RenderTask => Task; @@ -96,5 +97,16 @@ namespace FlaxEditor.Viewport root.UpdateCallbacksToRemove.Add(_update); } + + /// + public override void OnDestroy() + { + if (IsDisposing) + return; + + Gizmos.Clear(); + + base.OnDestroy(); + } } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.Modes.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.Modes.cs deleted file mode 100644 index 27400c1e7..000000000 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.Modes.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -using System; -using System.Collections.Generic; -using FlaxEditor.Viewport.Modes; - -namespace FlaxEditor.Viewport -{ - public partial class MainEditorGizmoViewport - { - private EditorGizmoMode _activeMode; - private readonly List _modes = new List(); - - /// - /// Gets the active gizmo mode. - /// - public EditorGizmoMode ActiveMode => _activeMode; - - /// - /// Occurs when active mode gets changed. - /// - public event Action ActiveModeChanged; - - /// - /// The sculpt terrain gizmo. - /// - public Tools.Terrain.SculptTerrainGizmoMode SculptTerrainGizmo; - - /// - /// The paint terrain gizmo. - /// - public Tools.Terrain.PaintTerrainGizmoMode PaintTerrainGizmo; - - /// - /// The edit terrain gizmo. - /// - public Tools.Terrain.EditTerrainGizmoMode EditTerrainGizmo; - - /// - /// The paint foliage gizmo. - /// - public Tools.Foliage.PaintFoliageGizmoMode PaintFoliageGizmo; - - /// - /// The edit foliage gizmo. - /// - public Tools.Foliage.EditFoliageGizmoMode EditFoliageGizmo; - - private void InitModes() - { - // Add default modes used by the editor - _modes.Add(new TransformGizmoMode()); - _modes.Add(new NoGizmoMode()); - _modes.Add(SculptTerrainGizmo = new Tools.Terrain.SculptTerrainGizmoMode()); - _modes.Add(PaintTerrainGizmo = new Tools.Terrain.PaintTerrainGizmoMode()); - _modes.Add(EditTerrainGizmo = new Tools.Terrain.EditTerrainGizmoMode()); - _modes.Add(PaintFoliageGizmo = new Tools.Foliage.PaintFoliageGizmoMode()); - _modes.Add(EditFoliageGizmo = new Tools.Foliage.EditFoliageGizmoMode()); - for (int i = 0; i < _modes.Count; i++) - { - _modes[i].Init(this); - } - - // Activate transform mode first - _activeMode = _modes[0]; - } - - private void DisposeModes() - { - // Cleanup - _activeMode = null; - for (int i = 0; i < _modes.Count; i++) - _modes[i].Dispose(); - _modes.Clear(); - } - - /// - /// Adds the mode to the viewport. - /// - /// The mode. - public void AddMode(EditorGizmoMode mode) - { - if (mode == null) - throw new ArgumentNullException(nameof(mode)); - if (_modes.Contains(mode)) - throw new ArgumentException("Already added."); - if (mode.Viewport != null) - throw new ArgumentException("Already added to other viewport."); - - _modes.Add(mode); - mode.Init(this); - } - - /// - /// Removes the mode from the viewport. - /// - /// The mode. - public void RemoveMode(EditorGizmoMode mode) - { - if (mode == null) - throw new ArgumentNullException(nameof(mode)); - if (!_modes.Contains(mode)) - throw new ArgumentException("Not added."); - if (mode.Viewport != this) - throw new ArgumentException("Not added to this viewport."); - - if (_activeMode == mode) - SetActiveMode(null); - - _modes.Remove(mode); - } - - /// - /// Sets the active mode. - /// - /// The mode. - public void SetActiveMode(EditorGizmoMode mode) - { - if (mode == _activeMode) - return; - if (mode != null) - { - if (!_modes.Contains(mode)) - throw new ArgumentException("Not added."); - if (mode.Viewport != this) - throw new ArgumentException("Not added to this viewport."); - } - - _activeMode?.OnDeactivated(); - - Gizmos.Active = null; - _activeMode = mode; - - _activeMode?.OnActivated(); - - ActiveModeChanged?.Invoke(mode); - } - - /// - /// Sets the active mode. - /// - /// The mode type. - /// The activated mode. - public T SetActiveMode() where T : EditorGizmoMode - { - for (int i = 0; i < _modes.Count; i++) - { - if (_modes[i] is T mode) - { - SetActiveMode(mode); - return mode; - } - } - - throw new ArgumentException("Not added mode to activate."); - } - } -} diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 48cd4fd98..77032ff80 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -10,6 +10,7 @@ using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; +using FlaxEditor.Viewport.Modes; using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows; using FlaxEngine; @@ -22,7 +23,7 @@ namespace FlaxEditor.Viewport /// Main editor gizmo viewport used by the . /// /// - public partial class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner, IGizmoOwner + public class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner { private readonly Editor _editor; @@ -183,6 +184,31 @@ namespace FlaxEditor.Viewport set => _showNavigationButton.Checked = value; } + /// + /// The sculpt terrain gizmo. + /// + public Tools.Terrain.SculptTerrainGizmoMode SculptTerrainGizmo; + + /// + /// The paint terrain gizmo. + /// + public Tools.Terrain.PaintTerrainGizmoMode PaintTerrainGizmo; + + /// + /// The edit terrain gizmo. + /// + public Tools.Terrain.EditTerrainGizmoMode EditTerrainGizmo; + + /// + /// The paint foliage gizmo. + /// + public Tools.Foliage.PaintFoliageGizmoMode PaintFoliageGizmo; + + /// + /// The edit foliage gizmo. + /// + public Tools.Foliage.EditFoliageGizmoMode EditFoliageGizmo; + /// /// Initializes a new instance of the class. /// @@ -380,7 +406,20 @@ namespace FlaxEditor.Viewport DragHandlers.Add(_dragActorType); DragHandlers.Add(_dragAssets); - InitModes(); + // Init gizmo modes + { + // Add default modes used by the editor + Gizmos.AddMode(new TransformGizmoMode()); + Gizmos.AddMode(new NoGizmoMode()); + Gizmos.AddMode(SculptTerrainGizmo = new Tools.Terrain.SculptTerrainGizmoMode()); + Gizmos.AddMode(PaintTerrainGizmo = new Tools.Terrain.PaintTerrainGizmoMode()); + Gizmos.AddMode(EditTerrainGizmo = new Tools.Terrain.EditTerrainGizmoMode()); + Gizmos.AddMode(PaintFoliageGizmo = new Tools.Foliage.PaintFoliageGizmoMode()); + Gizmos.AddMode(EditFoliageGizmo = new Tools.Foliage.EditFoliageGizmoMode()); + + // Activate transform mode first + Gizmos.SetActiveMode(); + } // Setup input actions InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); @@ -1117,7 +1156,6 @@ namespace FlaxEditor.Viewport if (IsDisposing) return; - DisposeModes(); _debugDrawData.Dispose(); if (_task != null) { diff --git a/Source/Editor/Viewport/Modes/EditorGizmoMode.cs b/Source/Editor/Viewport/Modes/EditorGizmoMode.cs index fdf4bee98..cb61d35ce 100644 --- a/Source/Editor/Viewport/Modes/EditorGizmoMode.cs +++ b/Source/Editor/Viewport/Modes/EditorGizmoMode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.Gizmo; using FlaxEngine; namespace FlaxEditor.Viewport.Modes @@ -14,12 +15,12 @@ namespace FlaxEditor.Viewport.Modes [HideInEditor] public abstract class EditorGizmoMode { - private MainEditorGizmoViewport _viewport; + private IGizmoOwner _owner; /// - /// Gets the viewport. + /// Gets the gizmos owner viewport. /// - public MainEditorGizmoViewport Viewport => _viewport; + public IGizmoOwner Owner => _owner; /// /// Occurs when mode gets activated. @@ -34,10 +35,10 @@ namespace FlaxEditor.Viewport.Modes /// /// Initializes the specified mode and links it to the viewport. /// - /// The viewport. - public virtual void Init(MainEditorGizmoViewport viewport) + /// The gizmos owner. + public virtual void Init(IGizmoOwner owner) { - _viewport = viewport; + _owner = owner; } /// @@ -45,7 +46,7 @@ namespace FlaxEditor.Viewport.Modes /// public virtual void Dispose() { - _viewport = null; + _owner = null; } /// diff --git a/Source/Editor/Viewport/Modes/NoGizmoMode.cs b/Source/Editor/Viewport/Modes/NoGizmoMode.cs index adf2ed192..0d3dd5ef1 100644 --- a/Source/Editor/Viewport/Modes/NoGizmoMode.cs +++ b/Source/Editor/Viewport/Modes/NoGizmoMode.cs @@ -13,7 +13,7 @@ namespace FlaxEditor.Viewport.Modes { base.OnActivated(); - Viewport.Gizmos.Active = null; + Owner.Gizmos.Active = null; } } } diff --git a/Source/Editor/Viewport/Modes/TransformGizmoMode.cs b/Source/Editor/Viewport/Modes/TransformGizmoMode.cs index 8a4de5c00..0b72d5678 100644 --- a/Source/Editor/Viewport/Modes/TransformGizmoMode.cs +++ b/Source/Editor/Viewport/Modes/TransformGizmoMode.cs @@ -13,7 +13,7 @@ namespace FlaxEditor.Viewport.Modes { base.OnActivated(); - Viewport.Gizmos.Active = Viewport.TransformGizmo; + Owner.Gizmos.Active = ((MainEditorGizmoViewport)Owner).TransformGizmo; } } } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index ec6a983ce..e8289d214 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -84,6 +84,7 @@ namespace FlaxEditor.Viewport _dragAssets = new DragAssets(ValidateDragItem); ShowDebugDraw = true; ShowEditorPrimitives = true; + Gizmos = new GizmosCollection(this); // Prepare rendering task Task.ActorsSource = ActorsSources.CustomActors; @@ -305,7 +306,7 @@ namespace FlaxEditor.Viewport } /// - public GizmosCollection Gizmos { get; } = new GizmosCollection(); + public GizmosCollection Gizmos { get; } /// public SceneRenderTask RenderTask => Task; diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 3df4d3527..26aa7a2bf 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -116,7 +116,7 @@ namespace FlaxEditor.Windows : base(string.Empty, icon) { Editor = editor; - Selected += tab => Editor.Windows.EditWin.Viewport.SetActiveMode(); + Selected += tab => Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode(); ScriptsBuilder.ScriptsReload += OnScriptsReload; ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; From 15b2d4e0415a65359cec5ad255c902c80d2fce88 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 12 Jul 2023 12:36:30 +0200 Subject: [PATCH 025/294] Add `IPresenterOwner` to editor Custom Editor for more context and advanced interactions --- .../CustomEditors/CustomEditorPresenter.cs | 21 +++++++++++++++++-- .../CustomEditors/Dedicated/RagdollEditor.cs | 7 ++----- Source/Editor/Windows/Assets/PrefabWindow.cs | 5 ++++- Source/Editor/Windows/PropertiesWindow.cs | 13 +++++++++++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 73e2ede0f..927f1d7be 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -37,6 +37,23 @@ namespace FlaxEditor.CustomEditors UseDefault = 1 << 2, } + /// + /// The interface for Editor context that owns the presenter. Can be or or other window/panel - custom editor scan use it for more specific features. + /// + public interface IPresenterOwner + { + /// + /// Gets the viewport linked with properties presenter (optional, null if unused). + /// + public Viewport.EditorViewport PresenterViewport { get; } + + /// + /// Selects the scene objects. + /// + /// The nodes to select + public void Select(List nodes); + } + /// /// Main class for Custom Editors used to present selected objects properties and allow to modify them. /// @@ -254,7 +271,7 @@ namespace FlaxEditor.CustomEditors /// /// The Editor context that owns this presenter. Can be or or other window/panel - custom editor scan use it for more specific features. /// - public object Owner; + public IPresenterOwner Owner; /// /// Gets or sets the text to show when no object is selected. @@ -278,7 +295,7 @@ namespace FlaxEditor.CustomEditors /// The undo. It's optional. /// The custom text to display when no object is selected. Default is No selection. /// The owner of the presenter. - public CustomEditorPresenter(Undo undo, string noSelectionText = null, object owner = null) + public CustomEditorPresenter(Undo undo, string noSelectionText = null, IPresenterOwner owner = null) { Undo = undo; Owner = owner; diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index 5a10b9a52..c4b334b3a 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -50,7 +50,7 @@ namespace FlaxEditor.CustomEditors.Dedicated grid.Button("Remove bone").Button.ButtonClicked += OnRemoveBone; } - if (Presenter.Owner is Windows.PropertiesWindow || Presenter.Owner is Windows.Assets.PrefabWindow) + if (Presenter.Owner != null) { // Selection var grid = editorGroup.CustomContainer(); @@ -309,10 +309,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (node != null) selection.Add(node); } - if (Presenter.Owner is Windows.PropertiesWindow propertiesWindow) - propertiesWindow.Editor.SceneEditing.Select(selection); - else if (Presenter.Owner is Windows.Assets.PrefabWindow prefabWindow) - prefabWindow.Select(selection); + Presenter.Owner.Select(selection); } } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 659bab249..755bf4b1d 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -19,7 +19,7 @@ namespace FlaxEditor.Windows.Assets /// /// /// - public sealed partial class PrefabWindow : AssetEditorWindowBase + public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner { private readonly SplitPanel _split1; private readonly SplitPanel _split2; @@ -520,5 +520,8 @@ namespace FlaxEditor.Windows.Assets base.OnDestroy(); } + + /// + public EditorViewport PresenterViewport => _viewport; } } diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index baa10f8b9..a7e6f6fe9 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using System.Xml; using FlaxEditor.CustomEditors; +using FlaxEditor.SceneGraph; +using FlaxEditor.Viewport; using FlaxEngine.GUI; namespace FlaxEditor.Windows @@ -13,7 +15,7 @@ namespace FlaxEditor.Windows /// /// /// - public class PropertiesWindow : SceneEditorWindow + public class PropertiesWindow : SceneEditorWindow, IPresenterOwner { private IEnumerable undoRecordObjects; @@ -82,5 +84,14 @@ namespace FlaxEditor.Windows if (bool.TryParse(node.GetAttribute("UIPivotRelative"), out value1)) UIPivotRelative = value1; } + + /// + public EditorViewport PresenterViewport => Editor.Windows.EditWin.Viewport; + + /// + public void Select(List nodes) + { + Editor.SceneEditing.Select(nodes); + } } } From c6a82b8c360eac5ce5d160ffbfcea03cc8dcd0c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Jul 2023 13:08:53 +0200 Subject: [PATCH 026/294] Add `DebugDraw::DrawLine` with separate start/end colors --- Source/Engine/Debug/DebugDraw.cpp | 20 ++++++++++++++++++++ Source/Engine/Debug/DebugDraw.h | 11 +++++++++++ 2 files changed, 31 insertions(+) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index e4d53fcb5..1552da6be 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -938,6 +938,26 @@ void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& } } +void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration, bool depthTest) +{ + const Float3 startF = start - Context->Origin, endF = end - Context->Origin; + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) + { + // TODO: separate start/end colors for persistent lines + DebugLine l = { startF, endF, Color32(startColor), duration }; + debugDrawData.DefaultLines.Add(l); + } + else + { + Vertex l = { startF, Color32(startColor) }; + debugDrawData.OneFrameLines.Add(l); + l.Position = endF; + l.Color = Color32(endColor); + debugDrawData.OneFrameLines.Add(l); + } +} + void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration, bool depthTest) { if (lines.Length() == 0) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 8cc5452ed..090752781 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -79,6 +79,17 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the line. + /// + /// The start point. + /// The end point. + /// The color of the start point. + /// The color of the end point. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration = 0.0f, bool depthTest = true); + /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). /// From 3b90e75307ac510831ac1bd0943e490bfa02e377 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Jul 2023 13:10:34 +0200 Subject: [PATCH 027/294] Various improvements to serialization of data encoded via Base64 to Json --- Source/Engine/Serialization/JsonWriter.cpp | 7 +------ Source/Engine/Serialization/Serialization.h | 18 ++++------------ Source/Engine/Utilities/Encryption.cpp | 23 ++++++++------------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/Source/Engine/Serialization/JsonWriter.cpp b/Source/Engine/Serialization/JsonWriter.cpp index 2cbf423fd..223baf77e 100644 --- a/Source/Engine/Serialization/JsonWriter.cpp +++ b/Source/Engine/Serialization/JsonWriter.cpp @@ -306,13 +306,8 @@ void JsonWriter::CommonValue(const ::CommonValue& value) Matrix(value.AsMatrix); break; case CommonType::Blob: - { - ::Array base64; - base64.Resize(Encryption::Base64EncodeLength(value.AsBlob.Length)); - Encryption::Base64Encode(value.AsBlob.Data, value.AsBlob.Length, base64.Get()); - String(base64.Get(), base64.Count()); + Blob(value.AsBlob.Data, value.AsBlob.Length); break; - } case CommonType::Object: Guid(value.GetObjectId()); break; diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 182ac71cf..94acd1d79 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -588,16 +588,6 @@ namespace Serialization } template inline void Deserialize(ISerializable::DeserializeStream& stream, Array& v, ISerializeModifier* modifier) - { - if (!stream.IsArray()) - return; - const auto& streamArray = stream.GetArray(); - v.Resize(streamArray.Size()); - for (int32 i = 0; i < v.Count(); i++) - Deserialize(streamArray[i], (T&)v[i], modifier); - } - template - inline void Deserialize(ISerializable::DeserializeStream& stream, Array& v, ISerializeModifier* modifier) { if (stream.IsArray()) { @@ -606,12 +596,12 @@ namespace Serialization for (int32 i = 0; i < v.Count(); i++) Deserialize(streamArray[i], v[i], modifier); } - else if (stream.IsString()) + else if (TIsPODType::Value && stream.IsString()) { - // byte[] encoded as Base64 + // T[] encoded as Base64 const StringAnsiView streamView(stream.GetStringAnsiView()); - v.Resize(Encryption::Base64DecodeLength(*streamView, streamView.Length())); - Encryption::Base64Decode(*streamView, streamView.Length(), v.Get()); + v.Resize(Encryption::Base64DecodeLength(*streamView, streamView.Length()) / sizeof(T)); + Encryption::Base64Decode(*streamView, streamView.Length(), (byte*)v.Get()); } } diff --git a/Source/Engine/Utilities/Encryption.cpp b/Source/Engine/Utilities/Encryption.cpp index 60b6e18f4..0693c99b0 100644 --- a/Source/Engine/Utilities/Encryption.cpp +++ b/Source/Engine/Utilities/Encryption.cpp @@ -5,16 +5,11 @@ namespace { - const char* base64_chars = + const char* Base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; - bool is_base64(byte c) - { - return StringUtils::IsAlnum((char)c) || c == '+' || c == '/'; - } - // @formatter:off const int32 B64index[256] = { @@ -76,23 +71,23 @@ void Encryption::Base64Encode(const byte* bytes, int32 size, char* encoded) int32 i; for (i = 0; i < size - 2; i += 3) { - *encoded++ = base64_chars[bytes[i] >> 2 & 0x3F]; - *encoded++ = base64_chars[(bytes[i] & 0x3) << 4 | (int32)(bytes[i + 1] & 0xF0) >> 4]; - *encoded++ = base64_chars[(bytes[i + 1] & 0xF) << 2 | (int32)(bytes[i + 2] & 0xC0) >> 6]; - *encoded++ = base64_chars[bytes[i + 2] & 0x3F]; + *encoded++ = Base64Chars[bytes[i] >> 2 & 0x3F]; + *encoded++ = Base64Chars[(bytes[i] & 0x3) << 4 | (int32)(bytes[i + 1] & 0xF0) >> 4]; + *encoded++ = Base64Chars[(bytes[i + 1] & 0xF) << 2 | (int32)(bytes[i + 2] & 0xC0) >> 6]; + *encoded++ = Base64Chars[bytes[i + 2] & 0x3F]; } if (i < size) { - *encoded++ = base64_chars[bytes[i] >> 2 & 0x3F]; + *encoded++ = Base64Chars[bytes[i] >> 2 & 0x3F]; if (i == size - 1) { - *encoded++ = base64_chars[((bytes[i] & 0x3) << 4)]; + *encoded++ = Base64Chars[((bytes[i] & 0x3) << 4)]; *encoded++ = '='; } else { - *encoded++ = base64_chars[(bytes[i] & 0x3) << 4 | (int32)(bytes[i + 1] & 0xF0) >> 4]; - *encoded++ = base64_chars[((bytes[i + 1] & 0xF) << 2)]; + *encoded++ = Base64Chars[(bytes[i] & 0x3) << 4 | (int32)(bytes[i + 1] & 0xF0) >> 4]; + *encoded++ = Base64Chars[((bytes[i + 1] & 0xF) << 2)]; } *encoded = '='; } From 24c03c0e4b8bbbf4fb640d7b53b84bc2eaebcd57 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Jul 2023 23:30:37 +0200 Subject: [PATCH 028/294] Add cloth painting tools to Editor --- .../CustomEditors/Dedicated/ClothEditor.cs | 77 ++++ Source/Editor/Gizmo/GizmosCollection.cs | 4 + Source/Editor/Gizmo/IGizmoOwner.cs | 7 + Source/Editor/Tools/ClothPainting.cs | 359 ++++++++++++++++++ Source/Editor/Tools/VertexPainting.cs | 11 +- Source/Editor/Viewport/EditorGizmoViewport.cs | 7 +- .../Viewport/MainEditorGizmoViewport.cs | 6 + .../Editor/Viewport/PrefabWindowViewport.cs | 7 +- Source/Engine/Content/Assets/VisualScript.cpp | 4 +- Source/Engine/Core/Math/Vector3.cpp | 4 +- Source/Engine/Core/Types/Span.h | 6 + Source/Engine/Physics/Actors/Cloth.cpp | 305 ++++++++++++++- Source/Engine/Physics/Actors/Cloth.h | 28 ++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 83 +++- Source/Engine/Physics/PhysicsBackend.h | 8 +- 15 files changed, 873 insertions(+), 43 deletions(-) create mode 100644 Source/Editor/CustomEditors/Dedicated/ClothEditor.cs create mode 100644 Source/Editor/Tools/ClothPainting.cs diff --git a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs new file mode 100644 index 000000000..999631991 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using FlaxEditor.Gizmo; +using FlaxEditor.Scripting; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Tools; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(Cloth)), DefaultEditor] + class ClothEditor : ActorEditor + { + private ClothPaintingGizmoMode _gizmoMode; + private Viewport.Modes.EditorGizmoMode _prevMode; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + if (Values.Count != 1) + return; + + // Add gizmo painting mode to the viewport + var owner = Presenter.Owner; + if (owner == null) + return; + var gizmoOwner = owner as IGizmoOwner ?? owner.PresenterViewport as IGizmoOwner; + if (gizmoOwner == null) + return; + var gizmos = gizmoOwner.Gizmos; + _gizmoMode = new ClothPaintingGizmoMode(); + gizmos.AddMode(_gizmoMode); + _prevMode = gizmos.ActiveMode; + gizmos.ActiveMode = _gizmoMode; + _gizmoMode.Gizmo.SetPaintCloth((Cloth)Values[0]); + + // Insert gizmo mode options to properties editing + var paintGroup = layout.Group("Cloth Painting"); + var paintValue = new ReadOnlyValueContainer(new ScriptType(typeof(ClothPaintingGizmoMode)), _gizmoMode); + paintGroup.Object(paintValue); + { + var grid = paintGroup.CustomContainer(); + var gridControl = grid.CustomControl; + gridControl.ClipChildren = false; + gridControl.Height = Button.DefaultHeight; + gridControl.SlotsHorizontally = 2; + gridControl.SlotsVertically = 1; + grid.Button("Fill", "Fills the cloth particles with given paint value.").Button.Clicked += _gizmoMode.Gizmo.Fill; + grid.Button("Reset", "Clears the cloth particles paint.").Button.Clicked += _gizmoMode.Gizmo.Reset; + } + } + + /// + protected override void Deinitialize() + { + // Cleanup gizmos + if (_gizmoMode != null) + { + var gizmos = _gizmoMode.Owner.Gizmos; + if (gizmos.ActiveMode == _gizmoMode) + gizmos.ActiveMode = _prevMode; + gizmos.RemoveMode(_gizmoMode); + _gizmoMode.Dispose(); + _gizmoMode = null; + } + _prevMode = null; + + base.Deinitialize(); + } + } +} diff --git a/Source/Editor/Gizmo/GizmosCollection.cs b/Source/Editor/Gizmo/GizmosCollection.cs index beb653826..0df516d32 100644 --- a/Source/Editor/Gizmo/GizmosCollection.cs +++ b/Source/Editor/Gizmo/GizmosCollection.cs @@ -75,6 +75,10 @@ namespace FlaxEditor.Gizmo /// public event Action ActiveModeChanged; + /// + /// Init. + /// + /// The gizmos owner interface. public GizmosCollection(IGizmoOwner owner) { _owner = owner; diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs index 893e9a44a..a28810179 100644 --- a/Source/Editor/Gizmo/IGizmoOwner.cs +++ b/Source/Editor/Gizmo/IGizmoOwner.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using FlaxEngine; namespace FlaxEditor.Gizmo @@ -94,5 +95,11 @@ namespace FlaxEditor.Gizmo /// Gets the root tree node for the scene graph. /// SceneGraph.RootNode SceneGraphRoot { get; } + + /// + /// Selects the scene objects. + /// + /// The nodes to select + void Select(List nodes); } } diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs new file mode 100644 index 000000000..2ad3b94a1 --- /dev/null +++ b/Source/Editor/Tools/ClothPainting.cs @@ -0,0 +1,359 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using FlaxEditor; +using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; +using FlaxEditor.Viewport.Modes; + +namespace FlaxEngine.Tools +{ + sealed class ClothPaintingGizmoMode : EditorGizmoMode + { + [HideInEditor] + public ClothPaintingGizmo Gizmo; + +#pragma warning disable CS0649 + /// + /// Brush radius (world-space). + /// + public float BrushSize = 50.0f; + + /// + /// Brush paint intensity. + /// + public float BrushStrength = 2.0f; + + /// + /// Brush paint falloff. Hardens or softens painting. + /// + public float BrushFalloff = 1.5f; + + /// + /// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value). + /// + public float PaintValue = 0.0f; + + /// + /// Enables continuous painting, otherwise single paint on click. + /// + public bool ContinuousPaint; +#pragma warning restore CS0649 + + public override void Init(IGizmoOwner owner) + { + base.Init(owner); + + Gizmo = new ClothPaintingGizmo(owner, this); + } + + public override void OnActivated() + { + base.OnActivated(); + + Owner.Gizmos.Active = Gizmo; + } + } + + sealed class ClothPaintingGizmo : GizmoBase + { + private Model _brushModel; + private MaterialInstance _brushMaterial; + private ClothPaintingGizmoMode _gizmoMode; + private bool _isPainting; + private int _paintUpdateCount; + private bool _hasHit; + private Vector3 _hitLocation; + private Vector3 _hitNormal; + private Cloth _cloth; + private Float3[] _clothParticles; + private float[] _clothPaint; + private EditClothPaintAction _undoAction; + + public bool IsPainting => _isPainting; + + public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode) + : base(owner) + { + _gizmoMode = mode; + } + + public void SetPaintCloth(Cloth cloth) + { + if (_cloth == cloth) + return; + PaintEnd(); + _cloth = cloth; + _clothParticles = cloth?.GetParticles(); + _clothPaint = null; + _hasHit = false; + } + + public void Fill() + { + PaintEnd(); + PaintStart(); + var clothPaint = _clothPaint; + var paintValue = Mathf.Saturate(_gizmoMode.PaintValue); + for (int i = 0; i < clothPaint.Length; i++) + clothPaint[i] = paintValue; + _cloth.SetPaint(clothPaint); + PaintEnd(); + } + + public void Reset() + { + PaintEnd(); + PaintStart(); + var clothPaint = _clothPaint; + for (int i = 0; i < clothPaint.Length; i++) + clothPaint[i] = 1.0f; + _cloth.SetPaint(clothPaint); + PaintEnd(); + } + + private void PaintStart() + { + if (IsPainting) + return; + + if (Editor.Instance.Undo.Enabled) + _undoAction = new EditClothPaintAction(_cloth); + _isPainting = true; + _paintUpdateCount = 0; + + // Get initial cloth paint state + var clothParticles = _clothParticles; + var clothPaint = _cloth.GetPaint(); + if (clothPaint == null || clothPaint.Length != clothParticles.Length) + { + _clothPaint = clothPaint = new float[clothParticles.Length]; + for (int i = 0; i < clothPaint.Length; i++) + clothPaint[i] = 1.0f; + } + _clothPaint = clothPaint; + } + + private void PaintUpdate() + { + if (!_gizmoMode.ContinuousPaint && _paintUpdateCount > 0) + return; + Profiler.BeginEvent("Cloth Paint"); + + // Edit the cloth paint + var clothParticles = _clothParticles; + var clothPaint = _clothPaint; + if (clothParticles == null || clothPaint == null) + throw new Exception(); + var instanceTransform = _cloth.Transform; + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var paintValue = Mathf.Saturate(_gizmoMode.PaintValue); + if (Owner.IsControlDown) + paintValue = 1.0f - paintValue; + var modified = false; + for (int i = 0; i < clothParticles.Length; i++) + { + var pos = instanceTransform.LocalToWorld(clothParticles[i]); + var dst = Vector3.Distance(ref pos, ref brushSphere.Center); + if (dst > brushSphere.Radius) + continue; + float strength = _gizmoMode.BrushStrength * Mathf.Lerp(1.0f, 1.0f - (float)dst / (float)brushSphere.Radius, _gizmoMode.BrushFalloff); + if (strength > Mathf.Epsilon) + { + // Paint the particle + ref var paint = ref clothPaint[i]; + paint = Mathf.Saturate(Mathf.Lerp(paint, paintValue, Mathf.Saturate(strength))); + modified = true; + } + } + _paintUpdateCount++; + if (modified) + { + // Update cloth particles state + _cloth.SetPaint(clothPaint); + } + + Profiler.EndEvent(); + } + + private void PaintEnd() + { + if (!IsPainting) + return; + + if (_undoAction != null) + { + _undoAction.RecordEnd(); + Editor.Instance.Undo.AddAction(_undoAction); + _undoAction = null; + } + _isPainting = false; + _paintUpdateCount = 0; + _clothPaint = null; + } + + public override bool IsControllingMouse => IsPainting; + + public override BoundingSphere FocusBounds => _cloth?.Sphere ?? base.FocusBounds; + + public override void Update(float dt) + { + _hasHit = false; + if (!IsActive) + { + SetPaintCloth(null); + return; + } + var cloth = _cloth; + if (cloth == null) + return; + + // Perform detailed tracing to find cursor location for the brush + var ray = Owner.MouseRay; + if (cloth.IntersectsItself(ray, out var closest, out var hitNormal)) + { + // Cursor hit cloth + _hasHit = true; + _hitLocation = ray.GetPoint(closest); + _hitNormal = hitNormal; + } + else + { + // Cursor hit other object or nothing + PaintEnd(); + return; + } + + // Handle painting + if (Owner.IsLeftMouseButtonDown) + PaintStart(); + else + PaintEnd(); + if (IsPainting) + PaintUpdate(); + } + + public override void Pick() + { + var ray = Owner.MouseRay; + var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); + var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; + var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out _, rayCastFlags); + if (hit != null && hit is ActorNode) + Owner.Select(new List { hit }); + } + + public override void Draw(ref RenderContext renderContext) + { + if (!IsActive || !_cloth) + return; + + base.Draw(ref renderContext); + + // TODO: impl this + if (_hasHit) + { + var viewOrigin = renderContext.View.Origin; + + // Draw paint brush + if (!_brushModel) + _brushModel = Content.LoadAsyncInternal("Editor/Primitives/Sphere"); + if (!_brushMaterial) + _brushMaterial = Content.LoadAsyncInternal(EditorAssets.FoliageBrushMaterial)?.CreateVirtualInstance(); + if (_brushModel && _brushMaterial) + { + _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options + _brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer); + Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal); + Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin); + _brushModel.Draw(ref renderContext, _brushMaterial, ref transform); + } + } + } + + public override void OnActivated() + { + base.OnActivated(); + + _hasHit = false; + } + + public override void OnDeactivated() + { + base.OnDeactivated(); + + PaintEnd(); + SetPaintCloth(null); + Object.Destroy(ref _brushMaterial); + _brushModel = null; + } + } + + sealed class EditClothPaintAction : IUndoAction + { + private Guid _actorId; + private string _before, _after; + + public EditClothPaintAction(Cloth cloth) + { + _actorId = cloth.ID; + _before = GetState(cloth); + } + + public static bool IsValidState(string state) + { + return state != null && state.Contains("\"Paint\":"); + } + + public static string GetState(Cloth cloth) + { + var json = cloth.ToJson(); + var start = json.IndexOf("\"Paint\":"); + if (start == -1) + return null; + var end = json.IndexOf('\"', json.IndexOf('\"', start + 8) + 1); + json = "{" + json.Substring(start, end - start) + "\"}"; + return json; + } + + public static void SetState(Cloth cloth, string state) + { + if (state == null) + cloth.SetPaint(null); + else + Editor.Internal_DeserializeSceneObject(Object.GetUnmanagedPtr(cloth), state); + } + + public void RecordEnd() + { + var cloth = Object.Find(ref _actorId); + _after = GetState(cloth); + Editor.Instance.Scene.MarkSceneEdited(cloth.Scene); + } + + private void Set(string state) + { + var cloth = Object.Find(ref _actorId); + SetState(cloth, state); + Editor.Instance.Scene.MarkSceneEdited(cloth.Scene); + } + + public string ActionString => "Edit Cloth Paint"; + + public void Do() + { + Set(_after); + } + + public void Undo() + { + Set(_before); + } + + public void Dispose() + { + _before = _after = null; + } + } +} diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index bb6b2c716..30bb0c84f 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using FlaxEditor.CustomEditors; @@ -596,18 +597,12 @@ namespace FlaxEditor.Tools /// public override void Pick() { - // Get mouse ray and try to hit any object var ray = Owner.MouseRay; var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; - var hit = Editor.Instance.Scene.Root.RayCast(ref ray, ref view, out _, rayCastFlags); - - // Update selection - var sceneEditing = Editor.Instance.SceneEditing; + var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out _, rayCastFlags); if (hit != null && hit is ActorNode actorNode && actorNode.Actor is StaticModel model) - { - sceneEditing.Select(hit); - } + Owner.Select(new List { hit }); } /// diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 2a99080ed..9a4fd34a2 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; using FlaxEditor.Viewport.Cameras; using FlaxEngine; using FlaxEngine.GUI; @@ -12,7 +14,7 @@ namespace FlaxEditor.Viewport /// /// /// - public class EditorGizmoViewport : EditorViewport, IGizmoOwner + public abstract class EditorGizmoViewport : EditorViewport, IGizmoOwner { private UpdateDelegate _update; @@ -79,6 +81,9 @@ namespace FlaxEditor.Viewport /// public SceneGraph.RootNode SceneGraphRoot { get; } + /// + public abstract void Select(List nodes); + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 77032ff80..9591915ab 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -1150,6 +1150,12 @@ namespace FlaxEditor.Viewport return result; } + /// + public override void Select(List nodes) + { + _editor.SceneEditing.Select(nodes); + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index e8289d214..99d7cb8e6 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -301,7 +301,7 @@ namespace FlaxEditor.Viewport /// public void ShowSelectedActors() { - var orient = Viewport.ViewOrientation; + var orient = ViewOrientation; ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); } @@ -345,7 +345,10 @@ namespace FlaxEditor.Viewport public RootNode SceneGraphRoot => _window.Graph.Root; /// - public EditorViewport Viewport => this; + public void Select(List nodes) + { + _window.Select(nodes); + } /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index e292a0133..0f1c73e2c 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -2209,14 +2209,14 @@ void VisualScript::GetMethodSignature(int32 index, String& name, byte& flags, St Span VisualScript::GetMetaData(int32 typeID) { auto meta = Graph.Meta.GetEntry(typeID); - return meta ? ToSpan(meta->Data.Get(), meta->Data.Count()) : Span(nullptr, 0); + return meta ? ToSpan(meta->Data) : Span(nullptr, 0); } Span VisualScript::GetMethodMetaData(int32 index, int32 typeID) { auto& method = _methods[index]; auto meta = method.Node->Meta.GetEntry(typeID); - return meta ? ToSpan(meta->Data.Get(), meta->Data.Count()) : Span(nullptr, 0); + return meta ? ToSpan(meta->Data) : Span(nullptr, 0); } #endif diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index d73cbd6d5..1bf03c2ef 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -312,7 +312,7 @@ void Float3::FindBestAxisVectors(Float3& firstAxis, Float3& secondAxis) const template<> float Float3::TriangleArea(const Float3& v0, const Float3& v1, const Float3& v2) { - return (v2 - v0 ^ v1 - v0).Length() * 0.5f; + return ((v2 - v0) ^ (v1 - v0)).Length() * 0.5f; } template<> @@ -626,7 +626,7 @@ void Double3::FindBestAxisVectors(Double3& firstAxis, Double3& secondAxis) const template<> double Double3::TriangleArea(const Double3& v0, const Double3& v1, const Double3& v2) { - return (v2 - v0 ^ v1 - v0).Length() * 0.5; + return ((v2 - v0) ^ (v1 - v0)).Length() * 0.5; } template<> diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h index e7bea5fbd..e0518ec77 100644 --- a/Source/Engine/Core/Types/Span.h +++ b/Source/Engine/Core/Types/Span.h @@ -115,6 +115,12 @@ inline Span ToSpan(const T* ptr, int32 length) return Span(ptr, length); } +template +inline Span ToSpan(const Array& data) +{ + return Span((U*)data.Get(), data.Count()); +} + template inline bool SpanContains(const Span span, const T& value) { diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index a16f5b8af..5bf8d264e 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -2,6 +2,7 @@ #include "Cloth.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Ray.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Physics/PhysicsBackend.h" @@ -109,6 +110,160 @@ void Cloth::ClearInteria() #endif } +Array Cloth::GetParticles() const +{ + Array result; +#if WITH_CLOTH + if (_cloth) + { + PROFILE_CPU(); + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); + result.Resize(particles.Length()); + const Float4* src = particles.Get(); + Float3* dst = result.Get(); + for (int32 i = 0; i < particles.Length(); i++) + dst[i] = Float3(src[i]); + PhysicsBackend::UnlockClothParticles(_cloth); + } +#endif + return result; +} + +void Cloth::SetParticles(Span value) +{ + PROFILE_CPU(); +#if !BUILD_RELEASE + { + // Sanity check + const Float3* src = value.Get(); + bool allValid = true; + for (int32 i = 0; i < value.Length(); i++) + allValid &= !src[i].IsNanOrInfinity(); + ASSERT(allValid); + } +#endif +#if WITH_CLOTH + if (_cloth) + { + // Update cloth particles + PhysicsBackend::LockClothParticles(_cloth); + PhysicsBackend::SetClothParticles(_cloth, Span(), value, Span()); + PhysicsBackend::UnlockClothParticles(_cloth); + } +#endif +} + +Span Cloth::GetPaint() const +{ + return ToSpan(_paint); +} + +void Cloth::SetPaint(Span value) +{ + PROFILE_CPU(); + if (value.IsInvalid()) + { + // Remove paint when set to empty + _paint.SetCapacity(0); +#if WITH_CLOTH + if (_cloth) + { + PhysicsBackend::SetClothPaint(_cloth, value); + } +#endif + return; + } +#if !BUILD_RELEASE + { + // Sanity check + const float* src = value.Get(); + bool allValid = true; + for (int32 i = 0; i < value.Length(); i++) + allValid &= !isnan(src[i]) && !isinf(src[i]); + ASSERT(allValid); + } +#endif + _paint.Set(value.Get(), value.Length()); +#if WITH_CLOTH + if (_cloth) + { + // Update cloth particles + Array invMasses; + CalculateInvMasses(invMasses); + PhysicsBackend::LockClothParticles(_cloth); + PhysicsBackend::SetClothParticles(_cloth, Span(), Span(), ToSpan(invMasses)); + PhysicsBackend::UnlockClothParticles(_cloth); + PhysicsBackend::SetClothPaint(_cloth, value); + } +#endif +} + +bool Cloth::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) +{ +#if USE_PRECISE_MESH_INTERSECTS + if (!Actor::IntersectsItself(ray, distance, normal)) + return false; +#if WITH_CLOTH + if (_cloth) + { + // Precise per-triangle intersection + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return false; + BytesContainer indicesData; + int32 indicesCount; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) + return false; + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); + const Transform transform = GetTransform(); + const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); + const int32 trianglesCount = indicesCount / 3; + bool result = false; + distance = MAX_Real; + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + int32 i0, i1, i2; + if (indices16bit) + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + else + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); + const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); + const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); + Real d; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, d) && d < distance) + { + result = true; + normal = Vector3::Normalize((v1 - v0) ^ (v2 - v0)); + distance = d; + + // Flip normal if needed as cloth is two-sided + const Vector3 hitPos = ray.GetPoint(d); + if (Vector3::DistanceSquared(hitPos + normal, ray.Position) > Math::Square(d)) + normal = -normal; + } + } + PhysicsBackend::UnlockClothParticles(_cloth); + return result; + } +#endif + return false; +#else + return Actor::IntersectsItself(ray, distance, normal); +#endif +} + void Cloth::Serialize(SerializeStream& stream, const void* otherObj) { Actor::Serialize(stream, otherObj); @@ -120,6 +275,12 @@ void Cloth::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(Collision, _collisionSettings); SERIALIZE_MEMBER(Simulation, _simulationSettings); SERIALIZE_MEMBER(Fabric, _fabricSettings); + if (Serialization::ShouldSerialize(_paint, other ? &other->_paint : nullptr)) + { + // Serialize as Base64 + stream.JKEY("Paint"); + stream.Blob(_paint.Get(), _paint.Count() * sizeof(float)); + } } void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -132,24 +293,29 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE_MEMBER(Collision, _collisionSettings); DESERIALIZE_MEMBER(Simulation, _simulationSettings); DESERIALIZE_MEMBER(Fabric, _fabricSettings); + DESERIALIZE_MEMBER(Paint, _paint); + + // Refresh cloth when settings were changed + if (IsDuringPlay()) + Rebuild(); } #if USE_EDITOR void Cloth::DrawPhysicsDebug(RenderView& view) { -#if WITH_CLOTH +#if WITH_CLOTH && COMPILE_WITH_DEBUG_DRAW if (_cloth) { const ModelInstanceActor::MeshReference mesh = GetMesh(); if (mesh.Actor == nullptr) return; BytesContainer indicesData; - int32 indicesCount = 0; + int32 indicesCount; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) return; PhysicsBackend::LockClothParticles(_cloth); - const Span particles = PhysicsBackend::GetClothCurrentParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); const Transform transform = GetTransform(); const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const int32 trianglesCount = indicesCount / 3; @@ -172,7 +338,6 @@ void Cloth::DrawPhysicsDebug(RenderView& view) const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); - // TODO: highlight immovable cloth particles with a different color DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Pink, 0, true); } PhysicsBackend::UnlockClothParticles(_cloth); @@ -182,7 +347,7 @@ void Cloth::DrawPhysicsDebug(RenderView& view) void Cloth::OnDebugDrawSelected() { -#if WITH_CLOTH +#if WITH_CLOTH && COMPILE_WITH_DEBUG_DRAW if (_cloth) { DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true); @@ -190,11 +355,11 @@ void Cloth::OnDebugDrawSelected() if (mesh.Actor == nullptr) return; BytesContainer indicesData; - int32 indicesCount = 0; + int32 indicesCount; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) return; PhysicsBackend::LockClothParticles(_cloth); - const Span particles = PhysicsBackend::GetClothCurrentParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); const Transform transform = GetTransform(); const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const int32 trianglesCount = indicesCount / 3; @@ -217,10 +382,16 @@ void Cloth::OnDebugDrawSelected() const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); - // TODO: highlight immovable cloth particles with a different color - DEBUG_DRAW_LINE(v0, v1, Color::White, 0, false); - DEBUG_DRAW_LINE(v1, v2, Color::White, 0, false); - DEBUG_DRAW_LINE(v2, v0, Color::White, 0, false); + Color c0 = Color::White, c1 = Color::White, c2 = Color::White; + if (_paint.Count() == particles.Length()) + { + c0 = Color::Lerp(Color::Red, Color::White, _paint[i0]); + c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]); + c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]); + } + DebugDraw::DrawLine(v0, v1, c0, c1, 0, false); + DebugDraw::DrawLine(v1, v2, c1, c2, 0, false); + DebugDraw::DrawLine(v2, v0, c2, c0, 0, false); } PhysicsBackend::UnlockClothParticles(_cloth); } @@ -233,10 +404,11 @@ void Cloth::OnDebugDrawSelected() void Cloth::BeginPlay(SceneBeginData* data) { +#if WITH_CLOTH if (CreateCloth()) - { LOG(Error, "Failed to create cloth '{0}'", GetNamePath()); - } + +#endif Actor::BeginPlay(data); } @@ -245,10 +417,10 @@ void Cloth::EndPlay() { Actor::EndPlay(); +#if WITH_CLOTH if (_cloth) - { DestroyCloth(); - } +#endif } void Cloth::OnEnable() @@ -258,9 +430,7 @@ void Cloth::OnEnable() #endif #if WITH_CLOTH if (_cloth) - { PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); - } #endif Actor::OnEnable(); @@ -272,9 +442,7 @@ void Cloth::OnDisable() #if WITH_CLOTH if (_cloth) - { PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); - } #endif #if USE_EDITOR GetSceneRendering()->RemovePhysicsDebug(this); @@ -347,6 +515,12 @@ bool Cloth::CreateCloth() desc.IndicesData = data.Get(); desc.IndicesCount = count; desc.IndicesStride = data.Length() / count; + Array invMasses; + CalculateInvMasses(invMasses); + desc.InvMassesData = invMasses.Count() == desc.VerticesCount ? invMasses.Get() : nullptr; + desc.InvMassesStride = sizeof(float); + desc.MaxDistancesData = _paint.Count() == desc.VerticesCount ? _paint.Get() : nullptr; + desc.MaxDistancesStride = sizeof(float); // Create cloth ASSERT(_cloth == nullptr); @@ -389,6 +563,95 @@ void Cloth::DestroyCloth() #endif } +void Cloth::CalculateInvMasses(Array& invMasses) +{ + // Use per-particle max distance to evaluate which particles are immovable +#if WITH_CLOTH + if (_paint.IsEmpty()) + return; + + // Get mesh data + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return; + BytesContainer verticesData; + int32 verticesCount; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) + return; + BytesContainer indicesData; + int32 indicesCount; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) + return; + const int32 verticesStride = verticesData.Length() / verticesCount; + const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); + const int32 trianglesCount = indicesCount / 3; + + // Sum triangle area for each influenced particle + invMasses.Resize(verticesCount); + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + int32 i0, i1, i2; + if (indices16bit) + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } + else + { + i0 = indicesData.Get()[index]; + i1 = indicesData.Get()[index + 1]; + i2 = indicesData.Get()[index + 2]; + } +#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) + const Float3 v0(GET_POS(i0)); + const Float3 v1(GET_POS(i1)); + const Float3 v2(GET_POS(i2)); +#undef GET_POS + const float area = Float3::TriangleArea(v0, v1, v2); + invMasses.Get()[i0] += area; + invMasses.Get()[i1] += area; + invMasses.Get()[i2] += area; + } + + // Count fixed vertices which max movement distance is zero + int32 fixedCount = 0; + float massSum = 0; + for (int32 i = 0; i < verticesCount; i++) + { + float& mass = invMasses[i]; + const float maxDistance = _paint[i]; + if (maxDistance < 0.01f) + { + // Fixed + fixedCount++; + mass = 0.0f; + } + else + { + // Kinetic so include it's mass contribution + massSum += mass; + } + } + + if (massSum > ZeroTolerance) + { + // Normalize and inverse particles mass + const float massScale = (float)(verticesCount - fixedCount) / massSum; + for (int32 i = 0; i < verticesCount; i++) + { + float& mass = invMasses[i]; + if (mass > 0.0f) + { + mass *= massScale; + mass = 1.0f / mass; + } + } + } +#endif +} + void Cloth::OnUpdated() { if (_meshDeformation) @@ -410,7 +673,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat #if WITH_CLOTH PROFILE_CPU_NAMED("Cloth"); PhysicsBackend::LockClothParticles(_cloth); - const Span particles = PhysicsBackend::GetClothCurrentParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); // Update mesh vertices based on the cloth particles positions auto vbData = deformation.VertexBuffer.Data.Get(); @@ -478,7 +741,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat { for (uint32 i = 0; i < vbCount; i++) { - *((Float3*)vbData) = *(Float3*)&particles.Get()[i]; + *(Float3*)vbData = *(Float3*)&particles.Get()[i]; vbData += vbStride; } } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index dcc6544a8..ac87bf5ee 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -129,6 +129,11 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph /// API_FIELD() float SolverFrequency = 300.0f; + /// + /// The maximum distance cloth particles can move from the original location (within local-space of the actor). Scaled by painted per-particle value (0-1) to restrict movement of certain particles. + /// + API_FIELD() float MaxDistance = 1000.0f; + /// /// Wind velocity vector (direction and magnitude) in world coordinates. A greater magnitude applies a stronger wind force. Ensure that Air Drag and Air Lift coefficients are non-zero in order to apply wind force. /// @@ -202,6 +207,7 @@ private: Vector3 _cachedPosition = Vector3::Zero; ModelInstanceActor::MeshReference _mesh; MeshDeformation* _meshDeformation = nullptr; + Array _paint; public: /// @@ -283,8 +289,29 @@ public: /// API_FUNCTION() void ClearInteria(); + /// + /// Gets the cloth particles data with per-particle XYZ position (in local cloth-space). + /// + API_FUNCTION() Array GetParticles() const; + + /// + /// Sets the cloth particles data with per-particle XYZ position (in local cloth-space). The size of the input data had to match the cloth size. + /// + API_FUNCTION() void SetParticles(Span value); + + /// + /// Gets the cloth particles paint data with per-particle max distance (normalized 0-1, 0 makes particle immovable). Returned value is empty if cloth was not initialized or doesn't use paint feature. + /// + API_FUNCTION() Span GetPaint() const; + + /// + /// Sets the cloth particles paint data with per-particle max distance (normalized 0-1, 0 makes particle immovable). The size of the input data had to match the cloth size. Set to empty to remove paint. + /// + API_FUNCTION() void SetPaint(Span value); + public: // [Actor] + bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; @@ -307,6 +334,7 @@ private: #endif bool CreateCloth(); void DestroyCloth(); + void CalculateInvMasses(Array& invMasses); void OnUpdated(); void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); }; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 57ef4ce17..da393f529 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -129,7 +129,7 @@ public: } }; -class ProfilerPhysX : public physx::PxProfilerCallback +class ProfilerPhysX : public PxProfilerCallback { public: void* zoneStart(const char* eventName, bool detached, uint64_t contextId) override @@ -162,6 +162,7 @@ struct ClothSettings const PxVec3& clothBoundsSize = clothPhysX->getBoundingBoxScale(); BoundingBox localBounds; BoundingBox::FromPoints(P2C(clothBoundsPos - clothBoundsSize), P2C(clothBoundsPos + clothBoundsSize), localBounds); + CHECK(!localBounds.Minimum.IsNanOrInfinity() && !localBounds.Maximum.IsNanOrInfinity()); // Transform local-space bounds into world-space const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation()); @@ -3340,12 +3341,14 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) meshDesc.points.data = desc.VerticesData; meshDesc.points.stride = desc.VerticesStride; meshDesc.points.count = desc.VerticesCount; + meshDesc.invMasses.data = desc.InvMassesData; + meshDesc.invMasses.stride = desc.InvMassesStride; + meshDesc.invMasses.count = desc.InvMassesData ? desc.VerticesCount : 0; meshDesc.triangles.data = desc.IndicesData; meshDesc.triangles.stride = desc.IndicesStride * 3; meshDesc.triangles.count = desc.IndicesCount / 3; if (desc.IndicesStride == sizeof(uint16)) meshDesc.flags |= nv::cloth::MeshFlag::e16_BIT_INDICES; - // TODO: provide invMasses data const Float3 gravity(PhysicsSettings::Get()->DefaultGravity); nv::cloth::Vector::Type phaseTypeInfo; // TODO: automatically reuse fabric from existing cloths (simply check for input data used for computations to improve perf when duplicating cloths or with prefab) @@ -3359,10 +3362,17 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) // Create cloth object static_assert(sizeof(Float4) == sizeof(PxVec4), "Size mismatch"); Array initialState; - // TODO: provide initial state for cloth from the owner (eg. current skinned mesh position) initialState.Resize((int32)desc.VerticesCount); - for (uint32 i = 0; i < desc.VerticesCount; i++) - initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), 1.0f); // TODO: set .w to invMass of that vertex + if (desc.InvMassesData) + { + for (uint32 i = 0; i < desc.VerticesCount; i++) + initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), *(float*)((byte*)desc.InvMassesData + i * desc.InvMassesStride)); + } + else + { + for (uint32 i = 0; i < desc.VerticesCount; i++) + initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), 1.0f); + } const nv::cloth::Range initialParticlesRange((PxVec4*)initialState.Get(), (PxVec4*)initialState.Get() + initialState.Count()); nv::cloth::Cloth* clothPhysX = ClothFactory->createCloth(initialParticlesRange, *fabric); fabric->decRefCount(); @@ -3371,6 +3381,14 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) LOG(Error, "createCloth failed"); return nullptr; } + if (desc.MaxDistancesData) + { + nv::cloth::Range motionConstraints = clothPhysX->getMotionConstraints(); + ASSERT(motionConstraints.size() == desc.VerticesCount); + for (uint32 i = 0; i < desc.VerticesCount; i++) + motionConstraints.begin()[i] = PxVec4(*(PxVec3*)((byte*)desc.VerticesData + i * desc.VerticesStride), *(float*)((byte*)desc.MaxDistancesData + i * desc.MaxDistancesStride)); + } + clothPhysX->setUserData(desc.Actor); // Setup settings FabricSettings fabricSettings; @@ -3438,6 +3456,7 @@ void PhysicsBackend::SetClothSimulationSettings(void* cloth, const void* setting auto clothPhysX = (nv::cloth::Cloth*)cloth; const auto& settings = *(const Cloth::SimulationSettings*)settingsPtr; clothPhysX->setSolverFrequency(settings.SolverFrequency); + clothPhysX->setMotionConstraintScaleBias(settings.MaxDistance, 0.0f); clothPhysX->setWindVelocity(C2P(settings.WindVelocity)); } @@ -3510,13 +3529,65 @@ void PhysicsBackend::UnlockClothParticles(void* cloth) clothPhysX->unlockParticles(); } -Span PhysicsBackend::GetClothCurrentParticles(void* cloth) +Span PhysicsBackend::GetClothParticles(void* cloth) { auto clothPhysX = (const nv::cloth::Cloth*)cloth; const nv::cloth::MappedRange range = clothPhysX->getCurrentParticles(); return Span((const Float4*)range.begin(), (int32)range.size()); } +void PhysicsBackend::SetClothParticles(void* cloth, Span value, Span positions, Span invMasses) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + nv::cloth::MappedRange range = clothPhysX->getCurrentParticles(); + const uint32_t size = range.size(); + PxVec4* dst = range.begin(); + if (value.IsValid()) + { + // Set XYZW + CHECK((uint32_t)value.Length() >= size); + Platform::MemoryCopy(dst, value.Get(), size * sizeof(Float4)); + } + if (positions.IsValid()) + { + // Set XYZ + CHECK((uint32_t)positions.Length() >= size); + const Float3* src = positions.Get(); + for (uint32 i = 0; i < size; i++) + dst[i] = PxVec4(C2P(src[i]), dst[i].w); + } + if (invMasses.IsValid()) + { + // Set W + CHECK((uint32_t)invMasses.Length() >= size); + const float* src = invMasses.Get(); + for (uint32 i = 0; i < size; i++) + dst[i].w = src[i]; + + // Apply previous particles too + nv::cloth::MappedRange range2 = clothPhysX->getPreviousParticles(); + for (uint32 i = 0; i < size; i++) + range2.begin()[i].w = src[i]; + } +} + +void PhysicsBackend::SetClothPaint(void* cloth, Span value) +{ + auto clothPhysX = (nv::cloth::Cloth*)cloth; + if (value.IsValid()) + { + const nv::cloth::MappedRange range = ((const nv::cloth::Cloth*)clothPhysX)->getCurrentParticles(); + nv::cloth::Range motionConstraints = clothPhysX->getMotionConstraints(); + ASSERT(motionConstraints.size() <= (uint32)value.Length()); + for (int32 i = 0; i < value.Length(); i++) + motionConstraints.begin()[i] = PxVec4(range[i].getXYZ(), value[i]); + } + else + { + clothPhysX->clearMotionConstraints(); + } +} + void PhysicsBackend::AddCloth(void* scene, void* cloth) { auto scenePhysX = (ScenePhysX*)scene; diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index 5e8743d69..2de719183 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -40,10 +40,14 @@ struct PhysicsClothDesc class Cloth* Actor; void* VerticesData; void* IndicesData; + float* InvMassesData; + float* MaxDistancesData; uint32 VerticesCount; uint32 VerticesStride; uint32 IndicesCount; uint32 IndicesStride; + uint32 InvMassesStride; + uint32 MaxDistancesStride; }; /// @@ -281,7 +285,9 @@ public: static void ClearClothInertia(void* cloth); static void LockClothParticles(void* cloth); static void UnlockClothParticles(void* cloth); - static Span GetClothCurrentParticles(void* cloth); + static Span GetClothParticles(void* cloth); + static void SetClothParticles(void* cloth, Span value, Span positions, Span invMasses); + static void SetClothPaint(void* cloth, Span value); static void AddCloth(void* scene, void* cloth); static void RemoveCloth(void* scene, void* cloth); #endif From b1056e160ab2c3f0ad0f04c2223415d52ae5d151 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Jul 2023 13:36:44 +0200 Subject: [PATCH 029/294] Add debug sanity checks to cloth to solve issues --- Source/Engine/Physics/Actors/Cloth.cpp | 49 ++++++++++++++----- Source/Engine/Physics/Actors/Cloth.h | 3 ++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 26 ++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 5bf8d264e..3b9c1cf1c 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -133,7 +133,7 @@ Array Cloth::GetParticles() const void Cloth::SetParticles(Span value) { PROFILE_CPU(); -#if !BUILD_RELEASE +#if USE_CLOTH_SANITY_CHECKS { // Sanity check const Float3* src = value.Get(); @@ -162,6 +162,16 @@ Span Cloth::GetPaint() const void Cloth::SetPaint(Span value) { PROFILE_CPU(); +#if USE_CLOTH_SANITY_CHECKS + { + // Sanity check + const float* src = value.Get(); + bool allValid = true; + for (int32 i = 0; i < value.Length(); i++) + allValid &= !isnan(src[i]) && !isinf(src[i]); + ASSERT(allValid); + } +#endif if (value.IsInvalid()) { // Remove paint when set to empty @@ -174,16 +184,6 @@ void Cloth::SetPaint(Span value) #endif return; } -#if !BUILD_RELEASE - { - // Sanity check - const float* src = value.Get(); - bool allValid = true; - for (int32 i = 0; i < value.Length(); i++) - allValid &= !isnan(src[i]) && !isinf(src[i]); - ASSERT(allValid); - } -#endif _paint.Set(value.Get(), value.Length()); #if WITH_CLOTH if (_cloth) @@ -295,6 +295,17 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE_MEMBER(Fabric, _fabricSettings); DESERIALIZE_MEMBER(Paint, _paint); +#if USE_CLOTH_SANITY_CHECKS + { + // Sanity check + const float* data = _paint.Get(); + bool allValid = true; + for (int32 i = 0; i < _paint.Count(); i++) + allValid &= !isnan(data[i]) && !isinf(data[i]); + ASSERT(allValid); + } +#endif + // Refresh cloth when settings were changed if (IsDuringPlay()) Rebuild(); @@ -588,6 +599,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) // Sum triangle area for each influenced particle invMasses.Resize(verticesCount); + invMasses.SetAll(0.0f); for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) { const int32 index = triangleIndex * 3; @@ -621,6 +633,10 @@ void Cloth::CalculateInvMasses(Array& invMasses) for (int32 i = 0; i < verticesCount; i++) { float& mass = invMasses[i]; +#if USE_CLOTH_SANITY_CHECKS + // Sanity check + ASSERT(!isnan(mass) && !isinf(mass) && mass >= 0.0f); +#endif const float maxDistance = _paint[i]; if (maxDistance < 0.01f) { @@ -649,6 +665,17 @@ void Cloth::CalculateInvMasses(Array& invMasses) } } } + +#if USE_CLOTH_SANITY_CHECKS + { + // Sanity check + const float* data = invMasses.Get(); + bool allValid = true; + for (int32 i = 0; i < invMasses.Count(); i++) + allValid &= !isnan(data[i]) && !isinf(data[i]); + ASSERT(allValid); + } +#endif #endif } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index ac87bf5ee..b31c12984 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -5,6 +5,9 @@ #include "Engine/Level/Actor.h" #include "Engine/Level/Actors/ModelInstanceActor.h" +// Used internally to validate cloth data against invalid nan/inf values +#define USE_CLOTH_SANITY_CHECKS (BUILD_DEBUG) + /// /// Physical simulation actor for cloth objects made of vertices that are simulated as cloth particles with physical properties, forces, and constraints to affect cloth behavior. /// diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index da393f529..a1072f540 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3328,6 +3328,32 @@ void PhysicsBackend::RemoveVehicle(void* scene, WheeledVehicle* actor) void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) { +#if USE_CLOTH_SANITY_CHECKS + { + // Sanity check + bool allValid = true; + for (int32 i = 0; i < desc.VerticesCount; i++) + allValid &= !(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride)).IsNanOrInfinity(); + if (desc.InvMassesData) + { + for (int32 i = 0; i < desc.VerticesCount; i++) + { + float v = *(float*)((byte*)desc.InvMassesData + i * desc.InvMassesStride); + allValid &= !isnan(v) && !isinf(v); + } + } + if (desc.MaxDistancesData) + { + for (int32 i = 0; i < desc.VerticesCount; i++) + { + float v = *(float*)((byte*)desc.MaxDistancesData + i * desc.MaxDistancesStride); + allValid &= !isnan(v) && !isinf(v); + } + } + ASSERT(allValid); + } +#endif + // Lazy-init NvCloth if (ClothFactory == nullptr) { From 3f9e2862792a745ec224149847b470270a3a7079 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Jul 2023 14:12:30 +0200 Subject: [PATCH 030/294] Fix crash when creating cloth with all vertices fixed --- Source/Engine/Physics/Actors/Cloth.cpp | 10 ++++++++++ Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 3b9c1cf1c..23f7633e1 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -507,6 +507,16 @@ bool Cloth::CreateCloth() #if WITH_CLOTH PROFILE_CPU(); + // Skip if all vertices are fixed so cloth sim doesn't make sense + if (_paint.HasItems()) + { + bool allZero = true; + for (int32 i = 0; i < _paint.Count() && allZero; i++) + allZero = _paint[i] <= ZeroTolerance; + if (allZero) + return false; + } + // Get mesh data // TODO: consider making it via async task so physics can wait on the cloth setup from mesh data just before next fixed update which gives more time when loading scene const ModelInstanceActor::MeshReference mesh = GetMesh(); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index a1072f540..88a123be9 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3332,21 +3332,21 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) { // Sanity check bool allValid = true; - for (int32 i = 0; i < desc.VerticesCount; i++) + for (uint32 i = 0; i < desc.VerticesCount; i++) allValid &= !(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride)).IsNanOrInfinity(); if (desc.InvMassesData) { - for (int32 i = 0; i < desc.VerticesCount; i++) + for (uint32 i = 0; i < desc.VerticesCount; i++) { - float v = *(float*)((byte*)desc.InvMassesData + i * desc.InvMassesStride); + const float v = *(float*)((byte*)desc.InvMassesData + i * desc.InvMassesStride); allValid &= !isnan(v) && !isinf(v); } } if (desc.MaxDistancesData) { - for (int32 i = 0; i < desc.VerticesCount; i++) + for (uint32 i = 0; i < desc.VerticesCount; i++) { - float v = *(float*)((byte*)desc.MaxDistancesData + i * desc.MaxDistancesStride); + const float v = *(float*)((byte*)desc.MaxDistancesData + i * desc.MaxDistancesStride); allValid &= !isnan(v) && !isinf(v); } } From 68d97fcc485f7295095a4d7b8942320bca858985 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Jul 2023 14:17:19 +0200 Subject: [PATCH 031/294] Improve cloth paint picking logic --- Source/Editor/Tools/ClothPainting.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index 2ad3b94a1..c831eb35d 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -222,6 +222,16 @@ namespace FlaxEngine.Tools { // Cursor hit other object or nothing PaintEnd(); + + if (Owner.IsLeftMouseButtonDown) + { + // Select something else + var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); + var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; + var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out _, rayCastFlags); + if (hit != null && hit is ActorNode) + Owner.Select(new List { hit }); + } return; } @@ -234,16 +244,6 @@ namespace FlaxEngine.Tools PaintUpdate(); } - public override void Pick() - { - var ray = Owner.MouseRay; - var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); - var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; - var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out _, rayCastFlags); - if (hit != null && hit is ActorNode) - Owner.Select(new List { hit }); - } - public override void Draw(ref RenderContext renderContext) { if (!IsActive || !_cloth) From aef3ae14d4ba6c80c804bda9bdc50e7cd5d5c587 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Jul 2023 17:10:09 +0200 Subject: [PATCH 032/294] Add animated model pose handling as a input to cloth for proper character clothing sim --- Source/Engine/Physics/Actors/Cloth.cpp | 107 +++++++++++++++++- Source/Engine/Physics/Actors/Cloth.h | 3 +- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 14 ++- 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 23f7633e1..40c058455 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -193,8 +193,8 @@ void Cloth::SetPaint(Span value) CalculateInvMasses(invMasses); PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::SetClothParticles(_cloth, Span(), Span(), ToSpan(invMasses)); - PhysicsBackend::UnlockClothParticles(_cloth); PhysicsBackend::SetClothPaint(_cloth, value); + PhysicsBackend::UnlockClothParticles(_cloth); } #endif } @@ -346,6 +346,11 @@ void Cloth::DrawPhysicsDebug(RenderView& view) i1 = indicesData.Get()[index + 1]; i2 = indicesData.Get()[index + 2]; } + if (_paint.Count() == particles.Length()) + { + if (Math::Max(_paint[i0], _paint[i1], _paint[i2]) < ZeroTolerance) + continue; + } const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); @@ -609,7 +614,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) // Sum triangle area for each influenced particle invMasses.Resize(verticesCount); - invMasses.SetAll(0.0f); + Platform::MemoryClear(invMasses.Get(), verticesCount * sizeof(float)); for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) { const int32 index = triangleIndex * 3; @@ -689,7 +694,92 @@ void Cloth::CalculateInvMasses(Array& invMasses) #endif } -void Cloth::OnUpdated() +void Cloth::OnPreUpdate() +{ + // Get current skinned mesh pose for the simulation of the non-kinematic vertices + if (auto* animatedModel = Cast(GetParent())) + { + if (animatedModel->GraphInstance.NodesPose.IsEmpty() || _paint.IsEmpty()) + return; + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return; + BytesContainer verticesData; + int32 verticesCount; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) + return; + auto vbStride = (uint32)verticesData.Length() / verticesCount; + ASSERT(vbStride == sizeof(VB0SkinnedElementType)); + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); + // TODO: optimize memory allocs (eg. write directly to nvCloth mapped range or use shared allocator) + Array particlesSkinned; + particlesSkinned.Set(particles.Get(), particles.Length()); + + // TODO: optimize memory allocs (eg. get pose as Span for readonly) + Array pose; + animatedModel->GetCurrentPose(pose); + const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; + + // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones + const float* paint = _paint.Get(); + bool anyFixed = false; + for (int32 i = 0; i < verticesCount; i++) + { + if (paint[i] > ZeroTolerance) + continue; + VB0SkinnedElementType& vb0 = verticesData.Get()[i]; + + // Calculate skinned vertex matrix from bones blending + const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); + // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly + Matrix matrix; + const SkeletonBone& bone0 = skeleton.Bones[vb0.BlendIndices.R]; + Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); + Matrix boneMatrix = matrix * blendWeights.X; + if (blendWeights.Y > 0.0f) + { + const SkeletonBone& bone1 = skeleton.Bones[vb0.BlendIndices.G]; + Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.Y; + } + if (blendWeights.Z > 0.0f) + { + const SkeletonBone& bone2 = skeleton.Bones[vb0.BlendIndices.B]; + Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.Z; + } + if (blendWeights.W > 0.0f) + { + const SkeletonBone& bone3 = skeleton.Bones[vb0.BlendIndices.A]; + Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.W; + } + + // Skin vertex position (similar to GPU vertex shader) + Float3 pos = Float3::Transform(vb0.Position, boneMatrix); + + // Transform back to the cloth space + // TODO: skip when using identity? + pos = _localTransform.WorldToLocal(pos); + + // Override fixed particle position + particlesSkinned[i] = Float4(pos, 0.0f); + anyFixed = true; + } + + if (anyFixed) + { + // Update particles + PhysicsBackend::SetClothParticles(_cloth, ToSpan(particlesSkinned), Span(), Span()); + PhysicsBackend::SetClothPaint(_cloth, ToSpan(_paint)); + } + + PhysicsBackend::UnlockClothParticles(_cloth); + } +} + +void Cloth::OnPostUpdate() { if (_meshDeformation) { @@ -722,7 +812,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat { if (animatedModel->GraphInstance.NodesPose.IsEmpty()) { - // Delay unit skinning data is ready + // Delay until skinning data is ready PhysicsBackend::UnlockClothParticles(_cloth); _meshDeformation->Dirty(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0); return; @@ -735,9 +825,15 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones ASSERT(vbStride == sizeof(VB0SkinnedElementType)); + const float* paint = _paint.Count() >= particles.Length() ? _paint.Get() : nullptr; for (uint32 i = 0; i < vbCount; i++) { VB0SkinnedElementType& vb0 = *(VB0SkinnedElementType*)vbData; + vbData += vbStride; + + // Skip fixed vertices + if (paint && paint[i] < ZeroTolerance) + continue; // Calculate skinned vertex matrix from bones blending const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); @@ -770,14 +866,13 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat Matrix::Invert(boneMatrix, boneMatrixInv); Float3 pos = *(Float3*)&particles.Get()[i]; vb0.Position = Float3::Transform(pos, boneMatrixInv); - - vbData += vbStride; } } else { for (uint32 i = 0; i < vbCount; i++) { + // Copy particle positions to the mesh data *(Float3*)vbData = *(Float3*)&particles.Get()[i]; vbData += vbStride; } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index b31c12984..fc5eaea63 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -338,6 +338,7 @@ private: bool CreateCloth(); void DestroyCloth(); void CalculateInvMasses(Array& invMasses); - void OnUpdated(); + void OnPreUpdate(); + void OnPostUpdate(); void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); }; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 88a123be9..a93ab0292 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1436,6 +1436,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) { auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; const auto& clothSettings = Cloths[clothPhysX]; + clothSettings.Actor->OnPreUpdate(); // Setup automatic scene collisions with colliders around the cloth if (clothSettings.SceneCollisions) @@ -1609,7 +1610,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; const auto& clothSettings = Cloths[clothPhysX]; clothSettings.UpdateBounds(clothPhysX); - clothSettings.Actor->OnUpdated(); + clothSettings.Actor->OnPostUpdate(); } } } @@ -3572,7 +3573,16 @@ void PhysicsBackend::SetClothParticles(void* cloth, Span value, Sp { // Set XYZW CHECK((uint32_t)value.Length() >= size); - Platform::MemoryCopy(dst, value.Get(), size * sizeof(Float4)); + const Float4* src = value.Get(); + Platform::MemoryCopy(dst, src, size * sizeof(Float4)); + + // Apply previous particles too for immovable particles + nv::cloth::MappedRange range2 = clothPhysX->getPreviousParticles(); + for (uint32 i = 0; i < size; i++) + { + if (src[i].W <= ZeroTolerance) + range2.begin()[i] = (PxVec4&)src[i]; + } } if (positions.IsValid()) { From 2645b69ec0da1f3522d784aea606f1f634226da0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Jul 2023 17:10:28 +0200 Subject: [PATCH 033/294] Tweak air lif/drag coeffs to properly simulate wind on cloth --- Source/Engine/Physics/Actors/Cloth.h | 4 ++-- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index fc5eaea63..9ce500147 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -62,12 +62,12 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph /// /// Defines how much drag air applies to the cloth particles. Set to 0 to disable wind. /// - API_FIELD(Attributes="Limit(0, 1)") float AirDragCoefficient = 0.0f; + API_FIELD(Attributes="Limit(0, 1)") float AirDragCoefficient = 0.02f; /// /// Defines how much lift air applies to the cloth particles. Set to 0 to disable wind. /// - API_FIELD(Attributes="Limit(0, 1)") float AirLiftCoefficient = 0.0f; + API_FIELD(Attributes="Limit(0, 1)") float AirLiftCoefficient = 0.02f; /// /// Defines fluid density of air used for drag and lift calculations. diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index a93ab0292..e47fad0b3 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3450,8 +3450,8 @@ void PhysicsBackend::SetClothForceSettings(void* cloth, const void* settingsPtr) clothPhysX->setLinearInertia(PxVec3(settings.LinearInertia)); clothPhysX->setAngularInertia(PxVec3(settings.AngularInertia)); clothPhysX->setCentrifugalInertia(PxVec3(settings.CentrifugalInertia)); - clothPhysX->setDragCoefficient(Math::Saturate(settings.AirDragCoefficient)); - clothPhysX->setLiftCoefficient(Math::Saturate(settings.AirLiftCoefficient)); + clothPhysX->setDragCoefficient(Math::Saturate(settings.AirDragCoefficient) * 0.01f); + clothPhysX->setLiftCoefficient(Math::Saturate(settings.AirLiftCoefficient) * 0.01f); clothPhysX->setFluidDensity(Math::Max(settings.AirDensity, ZeroTolerance)); auto& clothSettings = Cloths[clothPhysX]; clothSettings.GravityScale = settings.GravityScale; From 6664972fbf9bcfbf32ce11b409cc929aa70a2695 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Jul 2023 11:22:00 +0200 Subject: [PATCH 034/294] Add NvCloth for macOS --- .../Platforms/Mac/Binaries/ThirdParty/ARM64/libNvCloth.a | 3 +++ .../Platforms/Mac/Binaries/ThirdParty/x64/libNvCloth.a | 3 +++ Source/ThirdParty/NvCloth/NvCloth.Build.cs | 2 ++ Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs | 9 +++++++++ 4 files changed, 17 insertions(+) create mode 100644 Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libNvCloth.a create mode 100644 Source/Platforms/Mac/Binaries/ThirdParty/x64/libNvCloth.a diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libNvCloth.a b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libNvCloth.a new file mode 100644 index 000000000..569db20de --- /dev/null +++ b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libNvCloth.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac9b222da70871221c00cb4a143ae9a6400244e92f321f2cc1313aa999f9bc9 +size 301400 diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/x64/libNvCloth.a b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libNvCloth.a new file mode 100644 index 000000000..69e23cabf --- /dev/null +++ b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libNvCloth.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ca814f6aae7ee644da11440dfa5dacc0c70cb8ca25f6de70f6e6983f36bb907 +size 367504 diff --git a/Source/ThirdParty/NvCloth/NvCloth.Build.cs b/Source/ThirdParty/NvCloth/NvCloth.Build.cs index ea2bc02c8..3be1e903e 100644 --- a/Source/ThirdParty/NvCloth/NvCloth.Build.cs +++ b/Source/ThirdParty/NvCloth/NvCloth.Build.cs @@ -35,6 +35,8 @@ public class NvCloth : DepsModule case TargetPlatform.PS4: case TargetPlatform.PS5: case TargetPlatform.Android: + case TargetPlatform.Mac: + case TargetPlatform.iOS: libName = "NvCloth"; break; case TargetPlatform.Switch: diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs index 7288add63..23f4f6a27 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs @@ -82,6 +82,10 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Android: Build(options, platform, TargetArchitecture.ARM64); break; + case TargetPlatform.Mac: + Build(options, platform, TargetArchitecture.x64); + Build(options, platform, TargetArchitecture.ARM64); + break; } } @@ -140,6 +144,11 @@ namespace Flax.Deps.Dependencies envVars.Add("PM_ANDROIDNDK_PATH", AndroidNdk.Instance.RootPath); } break; + case TargetPlatform.Mac: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=mac"; + cmakeName = "mac"; + binariesPrefix = "lib"; + break; default: throw new InvalidPlatformException(platform); } var cmakeFolder = Path.Combine(nvCloth, "compiler", "cmake", cmakeName); From 18fb7fb849eaa52254e3f7e192b9cfb6213115df Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Jul 2023 11:27:26 +0200 Subject: [PATCH 035/294] Add NvCloth for iOS --- Source/Engine/Graphics/Models/SkinnedModelLOD.cpp | 1 + .../Platforms/iOS/Binaries/ThirdParty/ARM64/libNvCloth.a | 3 +++ Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 Source/Platforms/iOS/Binaries/ThirdParty/ARM64/libNvCloth.a diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp index 0e2b60aea..ada7d89f2 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp @@ -3,6 +3,7 @@ #include "SkinnedModelLOD.h" #include "MeshDeformation.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Serialization/MemoryReadStream.h" diff --git a/Source/Platforms/iOS/Binaries/ThirdParty/ARM64/libNvCloth.a b/Source/Platforms/iOS/Binaries/ThirdParty/ARM64/libNvCloth.a new file mode 100644 index 000000000..63187c05f --- /dev/null +++ b/Source/Platforms/iOS/Binaries/ThirdParty/ARM64/libNvCloth.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5257892706e3ec0360fc4a323f09c0b30fa3991f4b9d4bce56a77f2b5fe0eb6 +size 300696 diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs index 23f4f6a27..b096eef24 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs @@ -86,6 +86,9 @@ namespace Flax.Deps.Dependencies Build(options, platform, TargetArchitecture.x64); Build(options, platform, TargetArchitecture.ARM64); break; + case TargetPlatform.iOS: + Build(options, platform, TargetArchitecture.ARM64); + break; } } @@ -149,6 +152,11 @@ namespace Flax.Deps.Dependencies cmakeName = "mac"; binariesPrefix = "lib"; break; + case TargetPlatform.iOS: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=ios"; + cmakeName = "ios"; + binariesPrefix = "lib"; + break; default: throw new InvalidPlatformException(platform); } var cmakeFolder = Path.Combine(nvCloth, "compiler", "cmake", cmakeName); From a7e436412c8c937a88d35446ed9e1ef2846dfefe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Jul 2023 14:01:56 +0200 Subject: [PATCH 036/294] Fix mouse cursor setting on macOS to properly handle screen scale --- Source/Engine/Platform/Mac/MacPlatform.cpp | 7 ++++--- Source/Engine/Platform/Mac/MacWindow.cpp | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 04f928bba..15b526f02 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -301,15 +301,16 @@ Float2 MacPlatform::GetMousePosition() CGEventRef event = CGEventCreate(nullptr); CGPoint cursor = CGEventGetLocation(event); CFRelease(event); - return Float2((float)cursor.x, (float)cursor.y); + return Float2((float)cursor.x, (float)cursor.y) * MacPlatform::ScreenScale; } void MacPlatform::SetMousePosition(const Float2& pos) { CGPoint cursor; - cursor.x = (CGFloat)pos.X; - cursor.y = (CGFloat)pos.Y; + cursor.x = (CGFloat)(pos.X / MacPlatform::ScreenScale); + cursor.y = (CGFloat)(pos.Y / MacPlatform::ScreenScale); CGWarpMouseCursorPosition(cursor); + CGAssociateMouseAndMouseCursorPosition(true); } Float2 MacPlatform::GetDesktopSize() diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 90bb07279..c55f36c7c 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -643,7 +643,6 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) // TODO: impl StartPosition for MacWindow // TODO: impl Fullscreen for MacWindow // TODO: impl ShowInTaskbar for MacWindow - // TODO: impl AllowInput for MacWindow // TODO: impl IsTopmost for MacWindow } From 8f4ada25555c0dbc181e38b15178a1cb36a6d1df Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Jul 2023 14:42:54 +0200 Subject: [PATCH 037/294] Add macOS message box with buttons --- Source/Engine/Platform/Mac/MacPlatform.cpp | 80 +++++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 15b526f02..46b978e1d 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -56,36 +56,94 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri { if (CommandLine::Options.Headless) return DialogResult::None; - CFStringRef textRef = AppleUtils::ToString(text); - CFStringRef captionRef = AppleUtils::ToString(caption); - CFOptionFlags flags = 0; + NSAlert* alert = [[NSAlert alloc] init]; + ASSERT(alert); switch (buttons) { case MessageBoxButtons::AbortRetryIgnore: + [alert addButtonWithTitle:@"Abort"]; + [alert addButtonWithTitle:@"Retry"]; + [alert addButtonWithTitle:@"Ignore"]; + break; + case MessageBoxButtons::OK: + [alert addButtonWithTitle:@"OK"]; + break; case MessageBoxButtons::OKCancel: + [alert addButtonWithTitle:@"OK"]; + [alert addButtonWithTitle:@"Cancel"]; + break; case MessageBoxButtons::RetryCancel: + [alert addButtonWithTitle:@"Retry"]; + [alert addButtonWithTitle:@"Cancel"]; + break; case MessageBoxButtons::YesNo: + [alert addButtonWithTitle:@"Yes"]; + [alert addButtonWithTitle:@"No"]; + break; case MessageBoxButtons::YesNoCancel: - flags |= kCFUserNotificationCancelResponse; + [alert addButtonWithTitle:@"Yes"]; + [alert addButtonWithTitle:@"No"]; + [alert addButtonWithTitle:@"Cancel"]; break; } switch (icon) { case MessageBoxIcon::Information: - flags |= kCFUserNotificationNoteAlertLevel; + [alert setAlertStyle:NSAlertStyleCritical]; break; case MessageBoxIcon::Error: case MessageBoxIcon::Stop: - flags |= kCFUserNotificationStopAlertLevel; + [alert setAlertStyle:NSAlertStyleInformational]; break; case MessageBoxIcon::Warning: - flags |= kCFUserNotificationCautionAlertLevel; + [alert setAlertStyle:NSAlertStyleWarning]; break; } - SInt32 result = CFUserNotificationDisplayNotice(0, flags, nullptr, nullptr, nullptr, captionRef, textRef, nullptr); - CFRelease(captionRef); - CFRelease(textRef); - return DialogResult::OK; + [alert setMessageText:(NSString*)AppleUtils::ToString(caption)]; + [alert setInformativeText:(NSString*)AppleUtils::ToString(text)]; + NSInteger button = [alert runModal]; + DialogResult result = DialogResult::OK; + switch (buttons) + { + case MessageBoxButtons::AbortRetryIgnore: + if (button == NSAlertFirstButtonReturn) + result = DialogResult::Abort; + else if (button == NSAlertSecondButtonReturn) + result = DialogResult::Retry; + else + result = DialogResult::Ignore; + break; + case MessageBoxButtons::OK: + result = DialogResult::OK; + break; + case MessageBoxButtons::OKCancel: + if (button == NSAlertFirstButtonReturn) + result = DialogResult::OK; + else + result = DialogResult::Cancel; + break; + case MessageBoxButtons::RetryCancel: + if (button == NSAlertFirstButtonReturn) + result = DialogResult::Retry; + else + result = DialogResult::Cancel; + break; + case MessageBoxButtons::YesNo: + if (button == NSAlertFirstButtonReturn) + result = DialogResult::Yes; + else + result = DialogResult::No; + break; + case MessageBoxButtons::YesNoCancel: + if (button == NSAlertFirstButtonReturn) + result = DialogResult::Yes; + else if (button == NSAlertSecondButtonReturn) + result = DialogResult::No; + else + result = DialogResult::Cancel; + break; + } + return result; } Float2 AppleUtils::PosToCoca(const Float2& pos) From 4a0235785b140f5c034e52aa6f66628a4a3ac881 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Jul 2023 15:00:55 +0200 Subject: [PATCH 038/294] Add control/command/option keys handling on macOS --- Source/Engine/Platform/Mac/MacWindow.cpp | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index c55f36c7c..a52c11324 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -75,8 +75,8 @@ KeyboardKeys GetKey(NSEvent* event) case 0x33: return KeyboardKeys::Delete; //case 0x34: case 0x35: return KeyboardKeys::Escape; - //case 0x36: - //case 0x37: Command + case 0x36: return KeyboardKeys::Control; // Command (right) + case 0x37: return KeyboardKeys::Control; // Command (left) case 0x38: return KeyboardKeys::Shift; case 0x39: return KeyboardKeys::Capital; case 0x3A: return KeyboardKeys::Alt; @@ -411,6 +411,31 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) Input::Keyboard->OnKeyUp(key); } +- (void)flagsChanged:(NSEvent*)event +{ + int32 modMask; + int32 keyCode = [event keyCode]; + if (keyCode == 0x36 || keyCode == 0x37) + modMask = NSEventModifierFlagCommand; + else if (keyCode == 0x38 || keyCode == 0x3c) + modMask = NSEventModifierFlagShift; + else if (keyCode == 0x3a || keyCode == 0x3d) + modMask = NSEventModifierFlagOption; + else if (keyCode == 0x3b || keyCode == 0x3e) + modMask = NSEventModifierFlagControl; + else + return; + KeyboardKeys key = GetKey(event); + if (key != KeyboardKeys::None) + { + int32 modifierFlags = [event modifierFlags]; + if ((modifierFlags & modMask) == modMask) + Input::Keyboard->OnKeyDown(key); + else + Input::Keyboard->OnKeyUp(key); + } +} + - (void)scrollWheel:(NSEvent*)event { Float2 mousePos = GetMousePosition(Window, event); From 1ae89c75452cb82ae207b94cd75127bb9e3b5fad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Jul 2023 15:07:52 +0200 Subject: [PATCH 039/294] Fix various keyboard handling on macOS --- Source/Engine/Platform/Mac/MacWindow.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index a52c11324..aeee93e37 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -72,7 +72,7 @@ KeyboardKeys GetKey(NSEvent* event) case 0x30: return KeyboardKeys::Tab; case 0x31: return KeyboardKeys::Spacebar; case 0x32: return KeyboardKeys::BackQuote; - case 0x33: return KeyboardKeys::Delete; + case 0x33: return KeyboardKeys::Backspace; //case 0x34: case 0x35: return KeyboardKeys::Escape; case 0x36: return KeyboardKeys::Control; // Command (right) @@ -378,7 +378,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) { KeyboardKeys key = GetKey(event); if (key != KeyboardKeys::None) - Input::Keyboard->OnKeyDown(key); + Input::Keyboard->OnKeyDown(key, Window); // Send a text input event switch (key) @@ -400,7 +400,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) if (length >= 16) length = 15; [text getCharacters:buffer range:NSMakeRange(0, length)]; - Input::Keyboard->OnCharInput((Char)buffer[0]); + Input::Keyboard->OnCharInput((Char)buffer[0], Window); } } @@ -408,7 +408,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) { KeyboardKeys key = GetKey(event); if (key != KeyboardKeys::None) - Input::Keyboard->OnKeyUp(key); + Input::Keyboard->OnKeyUp(key, Window); } - (void)flagsChanged:(NSEvent*)event @@ -430,9 +430,9 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) { int32 modifierFlags = [event modifierFlags]; if ((modifierFlags & modMask) == modMask) - Input::Keyboard->OnKeyDown(key); + Input::Keyboard->OnKeyDown(key, Window); else - Input::Keyboard->OnKeyUp(key); + Input::Keyboard->OnKeyUp(key, Window); } } From f0496d53e808245eb11fb579c3c75fd22f15e26e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 11:57:21 +0200 Subject: [PATCH 040/294] Add `RenderTools::CalculateTangentFrame` utility --- Source/Engine/Graphics/Models/Mesh.cpp | 30 +++------------- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 36 ++++--------------- Source/Engine/Graphics/RenderTools.cpp | 28 +++++++++++++++ Source/Engine/Graphics/RenderTools.h | 4 +++ 4 files changed, 43 insertions(+), 55 deletions(-) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index a2d962b64..71541eb66 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -10,6 +10,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/ManagedCLR/MCore.h" @@ -41,14 +42,8 @@ namespace { const Float3 normal = normals[i]; const Float3 tangent = tangents[i]; - - // Calculate bitangent sign - Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); - byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); - - // Set tangent frame - vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); - vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); + auto& v = vb1.Get()[i]; + RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent); } } else @@ -56,23 +51,8 @@ namespace for (uint32 i = 0; i < vertexCount; i++) { const Float3 normal = normals[i]; - - // Calculate tangent - Float3 c1 = Float3::Cross(normal, Float3::UnitZ); - Float3 c2 = Float3::Cross(normal, Float3::UnitY); - Float3 tangent; - if (c1.LengthSquared() > c2.LengthSquared()) - tangent = c1; - else - tangent = c2; - - // Calculate bitangent sign - Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); - byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); - - // Set tangent frame - vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); - vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); + auto& v = vb1.Get()[i]; + RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal); } } } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 8bc938684..cc65e59d6 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -9,6 +9,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -389,9 +390,7 @@ bool UpdateMesh(SkinnedMesh* mesh, MArray* verticesObj, MArray* trianglesObj, MA Array vb; vb.Resize(vertexCount); for (uint32 i = 0; i < vertexCount; i++) - { - vb[i].Position = vertices[i]; - } + vb.Get()[i].Position = vertices[i]; if (normalsObj) { const auto normals = MCore::Array::GetAddress(normalsObj); @@ -400,42 +399,19 @@ bool UpdateMesh(SkinnedMesh* mesh, MArray* verticesObj, MArray* trianglesObj, MA const auto tangents = MCore::Array::GetAddress(tangentsObj); for (uint32 i = 0; i < vertexCount; i++) { - // Peek normal and tangent const Float3 normal = normals[i]; const Float3 tangent = tangents[i]; - - // Calculate bitangent sign - Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); - byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); - - // Set tangent frame - vb[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); - vb[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); + auto& v = vb.Get()[i]; + RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent); } } else { for (uint32 i = 0; i < vertexCount; i++) { - // Peek normal const Float3 normal = normals[i]; - - // Calculate tangent - Float3 c1 = Float3::Cross(normal, Float3::UnitZ); - Float3 c2 = Float3::Cross(normal, Float3::UnitY); - Float3 tangent; - if (c1.LengthSquared() > c2.LengthSquared()) - tangent = c1; - else - tangent = c2; - - // Calculate bitangent sign - Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); - byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); - - // Set tangent frame - vb[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); - vb[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); + auto& v = vb.Get()[i]; + RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal); } } } diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index a40bde11b..55f4df7d2 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -10,6 +10,7 @@ #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Packed.h" #include "Engine/Engine/Time.h" const Char* ToString(RendererType value) @@ -538,6 +539,33 @@ void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascad } } +void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal) +{ + // Calculate tangent + const Float3 c1 = Float3::Cross(normal, Float3::UnitZ); + const Float3 c2 = Float3::Cross(normal, Float3::UnitY); + const Float3 tangent = c1.LengthSquared() > c2.LengthSquared() ? c1 : c2; + + // Calculate bitangent sign + const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); + const byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); + + // Set tangent frame + resultNormal = Float1010102(normal * 0.5f + 0.5f, 0); + resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign); +} + +void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent) +{ + // Calculate bitangent sign + const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); + const byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); + + // Set tangent frame + resultNormal = Float1010102(normal * 0.5f + 0.5f, 0); + resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign); +} + int32 MipLevelsCount(int32 width, bool useMipLevels) { if (!useMipLevels) diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 7e399ec4a..6ded26c8e 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -9,6 +9,7 @@ class Model; class SkinnedModel; struct RenderContext; +struct FloatR10G10B10A2; PACK_STRUCT(struct QuadShaderData { @@ -119,6 +120,9 @@ public: ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame); return (frameIndex % updateFrequency == updatePhrase) || updateForce; } + + static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal); + static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent); }; // Calculate mip levels count for a texture 1D From 31943ca9bd4896f2247732d97086a1da743435c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 12:02:33 +0200 Subject: [PATCH 041/294] Add normals computing to cloth mesh --- .../Graphics/Models/MeshDeformation.cpp | 2 +- .../Engine/Graphics/Models/MeshDeformation.h | 4 +- Source/Engine/Physics/Actors/Cloth.cpp | 105 ++++++++++++++++-- Source/Engine/Physics/Actors/Cloth.h | 5 + .../Physics/PhysX/PhysicsBackendPhysX.cpp | 3 + 5 files changed, 107 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Graphics/Models/MeshDeformation.cpp b/Source/Engine/Graphics/Models/MeshDeformation.cpp index 5a654459b..e1b2e2ddf 100644 --- a/Source/Engine/Graphics/Models/MeshDeformation.cpp +++ b/Source/Engine/Graphics/Models/MeshDeformation.cpp @@ -127,7 +127,7 @@ void MeshDeformation::RunDeformers(const MeshBase* mesh, MeshBufferType type, GP } if (!deformation) { - deformation = New(key, vertexStride); + deformation = New(key, type, vertexStride); deformation->VertexBuffer.Data.Resize(vertexBuffer->GetSize()); deformation->Bounds = mesh->GetBox(); _deformations.Add(deformation); diff --git a/Source/Engine/Graphics/Models/MeshDeformation.h b/Source/Engine/Graphics/Models/MeshDeformation.h index c33025926..9a51cad30 100644 --- a/Source/Engine/Graphics/Models/MeshDeformation.h +++ b/Source/Engine/Graphics/Models/MeshDeformation.h @@ -13,14 +13,16 @@ struct MeshDeformationData { uint64 Key; + MeshBufferType Type; uint32 DirtyMinIndex = 0; uint32 DirtyMaxIndex = MAX_uint32 - 1; bool Dirty = true; BoundingBox Bounds; DynamicVertexBuffer VertexBuffer; - MeshDeformationData(uint64 key, uint32 stride) + MeshDeformationData(uint64 key, MeshBufferType type, uint32 stride) : Key(key) + , Type(type) , VertexBuffer(0, stride, TEXT("MeshDeformation")) { } diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 40c058455..45e4df067 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -3,6 +3,7 @@ #include "Cloth.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Physics/PhysicsBackend.h" @@ -567,6 +568,8 @@ bool Cloth::CreateCloth() Function deformer; deformer.Bind(this); deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex0, deformer); + if (_simulationSettings.ComputeNormals) + deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex1, deformer); _meshDeformation = deformation; } #endif @@ -582,6 +585,7 @@ void Cloth::DestroyCloth() Function deformer; deformer.Bind(this); _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex1, deformer); _meshDeformation = nullptr; } PhysicsBackend::DestroyCloth(_cloth); @@ -788,6 +792,8 @@ void Cloth::OnPostUpdate() BoundingBox localBounds; BoundingBox::Transform(_box, invWorld, localBounds); _meshDeformation->Dirty(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, localBounds); + if (_simulationSettings.ComputeNormals) + _meshDeformation->Dirty(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex1, localBounds); // Update bounds (for mesh culling) auto* actor = (ModelInstanceActor*)GetParent(); @@ -797,17 +803,69 @@ void Cloth::OnPostUpdate() void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformation) { + if (!_simulationSettings.ComputeNormals && deformation.Type != MeshBufferType::Vertex0) + return; #if WITH_CLOTH PROFILE_CPU_NAMED("Cloth"); PhysicsBackend::LockClothParticles(_cloth); const Span particles = PhysicsBackend::GetClothParticles(_cloth); - // Update mesh vertices based on the cloth particles positions auto vbData = deformation.VertexBuffer.Data.Get(); auto vbCount = (uint32)mesh->GetVertexCount(); auto vbStride = (uint32)deformation.VertexBuffer.Data.Count() / vbCount; // TODO: add support for mesh vertex data layout descriptor instead hardcoded position data at the beginning of VB0 ASSERT((uint32)particles.Length() >= vbCount); + + // Calculate normals + Array normals; + const ModelInstanceActor::MeshReference meshRef = GetMesh(); + BytesContainer indicesData; + int32 indicesCount; + if ((_simulationSettings.ComputeNormals || deformation.Type == MeshBufferType::Vertex1) && + meshRef.Actor && !meshRef.Actor->GetMeshData(meshRef, MeshBufferType::Index, indicesData, indicesCount)) + { + // TODO: optimize memory allocs (eg. use shared allocator) + normals.Resize(vbCount); + Platform::MemoryClear(normals.Get(), vbCount * sizeof(Float3)); + const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); + const int32 trianglesCount = indicesCount / 3; + if (indices16bit) + { + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + const int32 i0 = indicesData.Get()[index]; + const int32 i1 = indicesData.Get()[index + 1]; + const int32 i2 = indicesData.Get()[index + 2]; + const Float3 v0(particles.Get()[i0]); + const Float3 v1(particles.Get()[i1]); + const Float3 v2(particles.Get()[i2]); + const Float3 normal = Float3::Cross(v1 - v0, v2 - v0); + normals.Get()[i0] += normal; + normals.Get()[i1] += normal; + normals.Get()[i2] += normal; + } + } + else + { + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + const int32 i0 = indicesData.Get()[index]; + const int32 i1 = indicesData.Get()[index + 1]; + const int32 i2 = indicesData.Get()[index + 2]; + const Float3 v0(particles.Get()[i0]); + const Float3 v1(particles.Get()[i1]); + const Float3 v2(particles.Get()[i2]); + const Float3 normal = Float3::Cross(v1 - v0, v2 - v0); + normals.Get()[i0] += normal; + normals.Get()[i1] += normal; + normals.Get()[i2] += normal; + } + } + } + + // Update mesh vertices based on the cloth particles positions if (auto* animatedModel = Cast(GetParent())) { if (animatedModel->GraphInstance.NodesPose.IsEmpty()) @@ -828,7 +886,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat const float* paint = _paint.Count() >= particles.Length() ? _paint.Get() : nullptr; for (uint32 i = 0; i < vbCount; i++) { - VB0SkinnedElementType& vb0 = *(VB0SkinnedElementType*)vbData; + VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData; vbData += vbStride; // Skip fixed vertices @@ -836,27 +894,27 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat continue; // Calculate skinned vertex matrix from bones blending - const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); + const Float4 blendWeights = vb.BlendWeights.ToFloat4(); // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly Matrix matrix; - const SkeletonBone& bone0 = skeleton.Bones[vb0.BlendIndices.R]; + const SkeletonBone& bone0 = skeleton.Bones[vb.BlendIndices.R]; Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); Matrix boneMatrix = matrix * blendWeights.X; if (blendWeights.Y > 0.0f) { - const SkeletonBone& bone1 = skeleton.Bones[vb0.BlendIndices.G]; + const SkeletonBone& bone1 = skeleton.Bones[vb.BlendIndices.G]; Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Y; } if (blendWeights.Z > 0.0f) { - const SkeletonBone& bone2 = skeleton.Bones[vb0.BlendIndices.B]; + const SkeletonBone& bone2 = skeleton.Bones[vb.BlendIndices.B]; Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Z; } if (blendWeights.W > 0.0f) { - const SkeletonBone& bone3 = skeleton.Bones[vb0.BlendIndices.A]; + const SkeletonBone& bone3 = skeleton.Bones[vb.BlendIndices.A]; Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); boneMatrix += matrix * blendWeights.W; } @@ -865,16 +923,43 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat Matrix boneMatrixInv; Matrix::Invert(boneMatrix, boneMatrixInv); Float3 pos = *(Float3*)&particles.Get()[i]; - vb0.Position = Float3::Transform(pos, boneMatrixInv); + vb.Position = Float3::Transform(pos, boneMatrixInv); + } + + if (_simulationSettings.ComputeNormals) + { + // Write normals + for (uint32 i = 0; i < vbCount; i++) + { + Float3 normal = normals.Get()[i]; + normal.Normalize(); + VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData; + vbData += vbStride; + RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal); + } + } + } + else if (deformation.Type == MeshBufferType::Vertex0) + { + // Copy particle positions to the mesh data + ASSERT(vbStride == sizeof(VB0ElementType)); + for (uint32 i = 0; i < vbCount; i++) + { + *(Float3*)vbData = *(Float3*)&particles.Get()[i]; + vbData += vbStride; } } else { + // Write normals for the modified vertices by the cloth + ASSERT(vbStride == sizeof(VB1ElementType)); for (uint32 i = 0; i < vbCount; i++) { - // Copy particle positions to the mesh data - *(Float3*)vbData = *(Float3*)&particles.Get()[i]; + Float3 normal = normals.Get()[i]; + normal.Normalize(); + VB1ElementType& vb = *(VB1ElementType*)vbData; vbData += vbStride; + RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal); } } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 9ce500147..9a9f69577 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -137,6 +137,11 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph /// API_FIELD() float MaxDistance = 1000.0f; + /// + /// Enables automatic normal vectors computing for the cloth mesh, otherwise original mesh normals will be used. + /// + API_FIELD() bool ComputeNormals = true; + /// /// Wind velocity vector (direction and magnitude) in world coordinates. A greater magnitude applies a stronger wind force. Ensure that Air Drag and Air Lift coefficients are non-zero in order to apply wind force. /// diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index e47fad0b3..b5aeb1175 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3329,6 +3329,7 @@ void PhysicsBackend::RemoveVehicle(void* scene, WheeledVehicle* actor) void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) { + PROFILE_CPU(); #if USE_CLOTH_SANITY_CHECKS { // Sanity check @@ -3565,6 +3566,7 @@ Span PhysicsBackend::GetClothParticles(void* cloth) void PhysicsBackend::SetClothParticles(void* cloth, Span value, Span positions, Span invMasses) { + PROFILE_CPU(); auto clothPhysX = (nv::cloth::Cloth*)cloth; nv::cloth::MappedRange range = clothPhysX->getCurrentParticles(); const uint32_t size = range.size(); @@ -3609,6 +3611,7 @@ void PhysicsBackend::SetClothParticles(void* cloth, Span value, Sp void PhysicsBackend::SetClothPaint(void* cloth, Span value) { + PROFILE_CPU(); auto clothPhysX = (nv::cloth::Cloth*)cloth; if (value.IsValid()) { From 9179586f14d34fe6a29781fc843eee7771354390 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 12:36:38 +0200 Subject: [PATCH 042/294] Fix cloth painting in prefab window --- Source/Editor/Tools/ClothPainting.cs | 19 ++++++++- Source/Engine/Physics/Actors/Cloth.cpp | 53 ++++++++++++++++---------- Source/Engine/Physics/Actors/Cloth.h | 3 +- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index c831eb35d..226c7b49c 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -54,6 +54,13 @@ namespace FlaxEngine.Tools Owner.Gizmos.Active = Gizmo; } + + public override void Dispose() + { + Owner.Gizmos.Remove(Gizmo); + + base.Dispose(); + } } sealed class ClothPaintingGizmo : GizmoBase @@ -85,9 +92,19 @@ namespace FlaxEngine.Tools return; PaintEnd(); _cloth = cloth; - _clothParticles = cloth?.GetParticles(); + _clothParticles = null; _clothPaint = null; _hasHit = false; + if (cloth != null) + { + _clothParticles = cloth.GetParticles(); + if (_clothParticles == null || _clothParticles.Length == 0) + { + // Setup cloth to get proper particles (eg. if cloth is not on a scene like in Prefab Window) + cloth.Rebuild(); + _clothParticles = cloth.GetParticles(); + } + } } public void Fill() diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 45e4df067..492dedc51 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -46,7 +46,10 @@ void Cloth::SetMesh(const ModelInstanceActor::MeshReference& value) _mesh = value; _mesh.Actor = nullptr; // Don't store this reference - Rebuild(); +#if WITH_CLOTH + if (_cloth) + Rebuild(); +#endif } void Cloth::SetForce(const ForceSettings& value) @@ -88,18 +91,15 @@ void Cloth::SetFabric(const FabricSettings& value) void Cloth::Rebuild() { #if WITH_CLOTH - if (_cloth) - { - // Remove old - if (IsDuringPlay()) - PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); - DestroyCloth(); + // Remove old + if (IsDuringPlay()) + PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); + DestroyCloth(); - // Create new - CreateCloth(); - if (IsDuringPlay()) - PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); - } + // Create new + CreateCloth(); + if (IsDuringPlay()) + PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); #endif } @@ -308,8 +308,10 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) #endif // Refresh cloth when settings were changed - if (IsDuringPlay()) +#if WITH_CLOTH + if (_cloth) Rebuild(); +#endif } #if USE_EDITOR @@ -424,7 +426,6 @@ void Cloth::BeginPlay(SceneBeginData* data) #if WITH_CLOTH if (CreateCloth()) LOG(Error, "Failed to create cloth '{0}'", GetNamePath()); - #endif Actor::BeginPlay(data); @@ -434,10 +435,7 @@ void Cloth::EndPlay() { Actor::EndPlay(); -#if WITH_CLOTH - if (_cloth) - DestroyCloth(); -#endif + DestroyCloth(); } void Cloth::OnEnable() @@ -466,11 +464,21 @@ void Cloth::OnDisable() #endif } +void Cloth::OnDeleteObject() +{ + DestroyCloth(); + + Actor::OnDeleteObject(); +} + void Cloth::OnParentChanged() { Actor::OnParentChanged(); - Rebuild(); +#if WITH_CLOTH + if (_cloth) + Rebuild(); +#endif } void Cloth::OnTransformChanged() @@ -588,8 +596,11 @@ void Cloth::DestroyCloth() _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex1, deformer); _meshDeformation = nullptr; } - PhysicsBackend::DestroyCloth(_cloth); - _cloth = nullptr; + if (_cloth) + { + PhysicsBackend::DestroyCloth(_cloth); + _cloth = nullptr; + } #endif } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 9a9f69577..2f39f8015 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -288,7 +288,7 @@ public: public: /// - /// Recreates the cloth by removing current instance data and creating a new physical cloth object. Does nothing if cloth was not created (eg. no parent mesh). + /// Recreates the cloth by removing current instance data and creating a new physical cloth object. /// API_FUNCTION() void Rebuild(); @@ -332,6 +332,7 @@ protected: void EndPlay() override; void OnEnable() override; void OnDisable() override; + void OnDeleteObject() override; void OnParentChanged() override; void OnTransformChanged() override; void OnPhysicsSceneChanged(PhysicsScene* previous) override; From adfaf8c96160df49cb85983ec36d33d50844f004 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 12:53:59 +0200 Subject: [PATCH 043/294] Don't update cloth colliders every frame --- Source/Engine/Physics/Actors/Cloth.cpp | 2 ++ Source/Engine/Physics/Actors/Cloth.h | 2 +- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 20 ++++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 492dedc51..df419eeb9 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -723,6 +723,7 @@ void Cloth::OnPreUpdate() int32 verticesCount; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) return; + PROFILE_CPU_NAMED("Skinned Pose"); auto vbStride = (uint32)verticesData.Length() / verticesCount; ASSERT(vbStride == sizeof(VB0SkinnedElementType)); PhysicsBackend::LockClothParticles(_cloth); @@ -835,6 +836,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat if ((_simulationSettings.ComputeNormals || deformation.Type == MeshBufferType::Vertex1) && meshRef.Actor && !meshRef.Actor->GetMeshData(meshRef, MeshBufferType::Index, indicesData, indicesCount)) { + PROFILE_CPU_NAMED("Normals"); // TODO: optimize memory allocs (eg. use shared allocator) normals.Resize(vbCount); Platform::MemoryClear(normals.Get(), vbCount * sizeof(Float3)); diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 2f39f8015..e870b3bef 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -6,7 +6,7 @@ #include "Engine/Level/Actors/ModelInstanceActor.h" // Used internally to validate cloth data against invalid nan/inf values -#define USE_CLOTH_SANITY_CHECKS (BUILD_DEBUG) +#define USE_CLOTH_SANITY_CHECKS 0 /// /// Physical simulation actor for cloth objects made of vertices that are simulated as cloth particles with physical properties, forces, and constraints to affect cloth behavior. diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index b5aeb1175..f6cddb82b 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -48,6 +48,7 @@ #include #define MAX_CLOTH_SPHERE_COUNT 32 #define MAX_CLOTH_PLANE_COUNT 32 +#define CLOTH_COLLISIONS_UPDATE_RATE 10 // Frames between cloth collisions updates #endif #if WITH_PVD #include @@ -151,6 +152,8 @@ struct FabricSettings struct ClothSettings { bool SceneCollisions = false; + byte CollisionsUpdateFramesLeft = 0; + bool CollisionsUpdateFramesRandomize = true; float GravityScale = 1.0f; float CollisionThickness = 0.0f; Cloth* Actor; @@ -1425,7 +1428,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) // TODO: jobify to run in async (eg. with vehicles update and events setup) { - PROFILE_CPU_NAMED("Collisions"); + PROFILE_CPU_NAMED("Pre"); const bool hitTriggers = false; const bool blockSingle = false; PxQueryFilterData filterData; @@ -1435,12 +1438,20 @@ void PhysicsBackend::EndSimulateScene(void* scene) for (int32 i = 0; i < clothsCount; i++) { auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; - const auto& clothSettings = Cloths[clothPhysX]; + auto& clothSettings = Cloths[clothPhysX]; clothSettings.Actor->OnPreUpdate(); // Setup automatic scene collisions with colliders around the cloth - if (clothSettings.SceneCollisions) + if (clothSettings.SceneCollisions && clothSettings.CollisionsUpdateFramesLeft == 0) { + PROFILE_CPU_NAMED("Collisions"); + clothSettings.CollisionsUpdateFramesLeft = CLOTH_COLLISIONS_UPDATE_RATE; + if (clothSettings.CollisionsUpdateFramesRandomize) + { + clothSettings.CollisionsUpdateFramesRandomize = false; + clothSettings.CollisionsUpdateFramesLeft = i % CLOTH_COLLISIONS_UPDATE_RATE; + } + // Reset existing colliders clothPhysX->setSpheres(nv::cloth::Range(), 0, clothPhysX->getNumSpheres()); clothPhysX->setPlanes(nv::cloth::Range(), 0, clothPhysX->getNumPlanes()); @@ -1587,6 +1598,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) } } } + clothSettings.CollisionsUpdateFramesLeft--; } } @@ -3475,6 +3487,7 @@ void PhysicsBackend::SetClothCollisionSettings(void* cloth, const void* settings clothPhysX->setPlanes(nv::cloth::Range(), 0, clothPhysX->getNumPlanes()); clothPhysX->setTriangles(nv::cloth::Range(), 0, clothPhysX->getNumTriangles()); } + clothSettings.CollisionsUpdateFramesLeft = 0; clothSettings.SceneCollisions = settings.SceneCollisions; clothSettings.CollisionThickness = settings.CollisionThickness; } @@ -3536,6 +3549,7 @@ void PhysicsBackend::SetClothTransform(void* cloth, const Transform& transform, clothPhysX->setRotation(C2P(transform.Orientation)); } const auto& clothSettings = Cloths[clothPhysX]; + clothSettings.CollisionsUpdateFramesLeft = 0; clothSettings.UpdateBounds(clothPhysX); } From e075a1c6aa99977eebdb9e83c78a5003ae5ed230 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 12:59:18 +0200 Subject: [PATCH 044/294] Run cloth simulation in async via job system --- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index f6cddb82b..10d362e3a 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -40,6 +40,7 @@ #endif #if WITH_CLOTH #include "Engine/Physics/Actors/Cloth.h" +#include "Engine/Threading/JobSystem.h" #include #include #include @@ -94,6 +95,14 @@ struct ScenePhysX #if WITH_CLOTH nv::cloth::Solver* ClothSolver = nullptr; #endif + +#if WITH_CLOTH + void SimulateCloth(int32 i) + { + PROFILE_CPU(); + ClothSolver->simulateChunk(i); + } +#endif }; class AllocatorPhysX : public PxAllocatorCallback @@ -1606,11 +1615,9 @@ void PhysicsBackend::EndSimulateScene(void* scene) PROFILE_CPU_NAMED("Simulation"); if (clothSolver->beginSimulation(scenePhysX->LastDeltaTime)) { - const int32 count = clothSolver->getSimulationChunkCount(); - for (int32 i = 0; i < count; i++) - { - clothSolver->simulateChunk(i); - } + Function job; + job.Bind(scenePhysX); + JobSystem::Execute(job, clothSolver->getSimulationChunkCount()); clothSolver->endSimulation(); } } @@ -3548,7 +3555,7 @@ void PhysicsBackend::SetClothTransform(void* cloth, const Transform& transform, clothPhysX->setTranslation(C2P(transform.Translation)); clothPhysX->setRotation(C2P(transform.Orientation)); } - const auto& clothSettings = Cloths[clothPhysX]; + auto& clothSettings = Cloths[clothPhysX]; clothSettings.CollisionsUpdateFramesLeft = 0; clothSettings.UpdateBounds(clothPhysX); } From 2046eca45a557a0a17858150afed71a177fd777f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 14:40:11 +0200 Subject: [PATCH 045/294] Add NvCloth fabric reusing for multiple instances of the same cloth mesh --- Source/Engine/Physics/Actors/Cloth.cpp | 55 ++++++----- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 91 ++++++++++++++----- 2 files changed, 101 insertions(+), 45 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index df419eeb9..a7ab5fe0e 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -610,6 +610,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) #if WITH_CLOTH if (_paint.IsEmpty()) return; + PROFILE_CPU(); // Get mesh data const ModelInstanceActor::MeshReference mesh = GetMesh(); @@ -630,31 +631,43 @@ void Cloth::CalculateInvMasses(Array& invMasses) // Sum triangle area for each influenced particle invMasses.Resize(verticesCount); Platform::MemoryClear(invMasses.Get(), verticesCount * sizeof(float)); - for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + if (indices16bit) { - const int32 index = triangleIndex * 3; - int32 i0, i1, i2; - if (indices16bit) + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) { - i0 = indicesData.Get()[index]; - i1 = indicesData.Get()[index + 1]; - i2 = indicesData.Get()[index + 2]; - } - else - { - i0 = indicesData.Get()[index]; - i1 = indicesData.Get()[index + 1]; - i2 = indicesData.Get()[index + 2]; - } + const int32 index = triangleIndex * 3; + const int32 i0 = indicesData.Get()[index]; + const int32 i1 = indicesData.Get()[index + 1]; + const int32 i2 = indicesData.Get()[index + 2]; #define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) - const Float3 v0(GET_POS(i0)); - const Float3 v1(GET_POS(i1)); - const Float3 v2(GET_POS(i2)); + const Float3 v0(GET_POS(i0)); + const Float3 v1(GET_POS(i1)); + const Float3 v2(GET_POS(i2)); #undef GET_POS - const float area = Float3::TriangleArea(v0, v1, v2); - invMasses.Get()[i0] += area; - invMasses.Get()[i1] += area; - invMasses.Get()[i2] += area; + const float area = Float3::TriangleArea(v0, v1, v2); + invMasses.Get()[i0] += area; + invMasses.Get()[i1] += area; + invMasses.Get()[i2] += area; + } + } + else + { + for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) + { + const int32 index = triangleIndex * 3; + const int32 i0 = indicesData.Get()[index]; + const int32 i1 = indicesData.Get()[index + 1]; + const int32 i2 = indicesData.Get()[index + 2]; +#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) + const Float3 v0(GET_POS(i0)); + const Float3 v1(GET_POS(i1)); + const Float3 v2(GET_POS(i2)); +#undef GET_POS + const float area = Float3::TriangleArea(v0, v1, v2); + invMasses.Get()[i0] += area; + invMasses.Get()[i1] += area; + invMasses.Get()[i2] += area; + } } // Count fixed vertices which max movement distance is zero diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 10d362e3a..801fd07bf 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -156,6 +156,29 @@ struct FabricSettings { int32 Refs; nv::cloth::Vector::Type PhraseTypes; + PhysicsClothDesc Desc; + Array InvMasses; + + bool MatchesDesc(const PhysicsClothDesc& r) const + { + if (Desc.VerticesData == r.VerticesData && + Desc.VerticesStride == r.VerticesStride && + Desc.VerticesCount == r.VerticesCount && + Desc.IndicesData == r.IndicesData && + Desc.IndicesStride == r.IndicesStride && + Desc.IndicesCount == r.IndicesCount && + Desc.InvMassesStride == r.InvMassesStride) + { + if (Desc.InvMassesData && r.InvMassesData) + { + bool matches = Platform::MemoryCompare(InvMasses.Get(), r.InvMassesData, r.VerticesCount * r.InvMassesStride) == 0; + return matches; + } + bool matches = !Desc.InvMassesData && !r.InvMassesData; + return matches; + } + return false; + } }; struct ClothSettings @@ -1434,7 +1457,6 @@ void PhysicsBackend::EndSimulateScene(void* scene) PROFILE_CPU_NAMED("Physics.Cloth"); const int32 clothsCount = scenePhysX->ClothSolver->getNumCloths(); nv::cloth::Cloth* const* cloths = scenePhysX->ClothSolver->getClothList(); - // TODO: jobify to run in async (eg. with vehicles update and events setup) { PROFILE_CPU_NAMED("Pre"); @@ -3384,26 +3406,51 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) } // Cook fabric from the mesh data - nv::cloth::ClothMeshDesc meshDesc; - meshDesc.points.data = desc.VerticesData; - meshDesc.points.stride = desc.VerticesStride; - meshDesc.points.count = desc.VerticesCount; - meshDesc.invMasses.data = desc.InvMassesData; - meshDesc.invMasses.stride = desc.InvMassesStride; - meshDesc.invMasses.count = desc.InvMassesData ? desc.VerticesCount : 0; - meshDesc.triangles.data = desc.IndicesData; - meshDesc.triangles.stride = desc.IndicesStride * 3; - meshDesc.triangles.count = desc.IndicesCount / 3; - if (desc.IndicesStride == sizeof(uint16)) - meshDesc.flags |= nv::cloth::MeshFlag::e16_BIT_INDICES; - const Float3 gravity(PhysicsSettings::Get()->DefaultGravity); - nv::cloth::Vector::Type phaseTypeInfo; - // TODO: automatically reuse fabric from existing cloths (simply check for input data used for computations to improve perf when duplicating cloths or with prefab) - nv::cloth::Fabric* fabric = NvClothCookFabricFromMesh(ClothFactory, meshDesc, gravity.Raw, &phaseTypeInfo); - if (!fabric) + nv::cloth::Fabric* fabric = nullptr; { - LOG(Error, "NvClothCookFabricFromMesh failed"); - return nullptr; + for (auto& e : Fabrics) + { + if (e.Value.MatchesDesc(desc)) + { + fabric = e.Key; + break; + } + } + if (fabric) + { + Fabrics[fabric].Refs++; + } + else + { + PROFILE_CPU_NAMED("CookFabric"); + nv::cloth::ClothMeshDesc meshDesc; + meshDesc.points.data = desc.VerticesData; + meshDesc.points.stride = desc.VerticesStride; + meshDesc.points.count = desc.VerticesCount; + meshDesc.invMasses.data = desc.InvMassesData; + meshDesc.invMasses.stride = desc.InvMassesStride; + meshDesc.invMasses.count = desc.InvMassesData ? desc.VerticesCount : 0; + meshDesc.triangles.data = desc.IndicesData; + meshDesc.triangles.stride = desc.IndicesStride * 3; + meshDesc.triangles.count = desc.IndicesCount / 3; + if (desc.IndicesStride == sizeof(uint16)) + meshDesc.flags |= nv::cloth::MeshFlag::e16_BIT_INDICES; + const Float3 gravity(PhysicsSettings::Get()->DefaultGravity); + nv::cloth::Vector::Type phaseTypeInfo; + fabric = NvClothCookFabricFromMesh(ClothFactory, meshDesc, gravity.Raw, &phaseTypeInfo); + if (!fabric) + { + LOG(Error, "NvClothCookFabricFromMesh failed"); + return nullptr; + } + FabricSettings fabricSettings; + fabricSettings.Refs = 1; + fabricSettings.PhraseTypes.swap(phaseTypeInfo); + fabricSettings.Desc = desc; + if (desc.InvMassesData) + fabricSettings.InvMasses.Set((byte*)desc.InvMassesData, desc.VerticesCount * desc.InvMassesStride); + Fabrics.Add(fabric, MoveTemp(fabricSettings)); + } } // Create cloth object @@ -3438,10 +3485,6 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) clothPhysX->setUserData(desc.Actor); // Setup settings - FabricSettings fabricSettings; - fabricSettings.Refs = 1; - fabricSettings.PhraseTypes.swap(phaseTypeInfo); - Fabrics.Add(fabric, fabricSettings); ClothSettings clothSettings; clothSettings.Actor = desc.Actor; clothSettings.UpdateBounds(clothPhysX); From 10d9942e8f8d3cfe501fc43cdf897624020c2b42 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Jul 2023 22:39:56 +0200 Subject: [PATCH 046/294] Add async pre-sim update for cloth via job system --- Source/Engine/Physics/Actors/Cloth.h | 6 +- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 354 +++++++++--------- 2 files changed, 186 insertions(+), 174 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index e870b3bef..dbf073e18 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -13,7 +13,6 @@ /// API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor { - friend class PhysicsBackend; DECLARE_SCENE_OBJECT(Cloth); /// @@ -317,6 +316,9 @@ public: /// API_FUNCTION() void SetPaint(Span value); + void OnPreUpdate(); + void OnPostUpdate(); + public: // [Actor] bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; @@ -344,7 +346,5 @@ private: bool CreateCloth(); void DestroyCloth(); void CalculateInvMasses(Array& invMasses); - void OnPreUpdate(); - void OnPostUpdate(); void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); }; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 801fd07bf..8e55f5001 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -97,6 +97,7 @@ struct ScenePhysX #endif #if WITH_CLOTH + void PreSimulateCloth(int32 i); void SimulateCloth(int32 i) { PROFILE_CPU(); @@ -702,6 +703,185 @@ void InitVehicleSDK() #endif +#if WITH_CLOTH + +void ScenePhysX::PreSimulateCloth(int32 i) +{ + PROFILE_CPU(); + auto clothPhysX = (nv::cloth::Cloth*)ClothSolver->getClothList()[i]; + auto& clothSettings = Cloths[clothPhysX]; + + clothSettings.Actor->OnPreUpdate(); + + // Setup automatic scene collisions with colliders around the cloth + if (clothSettings.SceneCollisions && clothSettings.CollisionsUpdateFramesLeft == 0) + { + PROFILE_CPU_NAMED("Collisions"); + clothSettings.CollisionsUpdateFramesLeft = CLOTH_COLLISIONS_UPDATE_RATE; + if (clothSettings.CollisionsUpdateFramesRandomize) + { + clothSettings.CollisionsUpdateFramesRandomize = false; + clothSettings.CollisionsUpdateFramesLeft = i % CLOTH_COLLISIONS_UPDATE_RATE; + } + + // Reset existing colliders + clothPhysX->setSpheres(nv::cloth::Range(), 0, clothPhysX->getNumSpheres()); + clothPhysX->setPlanes(nv::cloth::Range(), 0, clothPhysX->getNumPlanes()); + clothPhysX->setTriangles(nv::cloth::Range(), 0, clothPhysX->getNumTriangles()); + + // Setup environment query + const bool hitTriggers = false; + const bool blockSingle = false; + PxQueryFilterData filterData; + filterData.flags |= PxQueryFlag::ePREFILTER; + filterData.data.word1 = blockSingle ? 1 : 0; + filterData.data.word2 = hitTriggers ? 1 : 0; + filterData.data.word0 = Physics::LayerMasks[clothSettings.Actor->GetLayer()]; + const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation()); + const PxVec3 clothBoundsPos = clothPhysX->getBoundingBoxCenter(); + const PxVec3 clothBoundsSize = clothPhysX->getBoundingBoxScale(); + const PxTransform overlapPose(clothPose.transform(clothBoundsPos), clothPose.q); + const float boundsMargin = 1.6f; // Pick nearby objects + const PxSphereGeometry overlapGeo(clothBoundsSize.magnitude() * boundsMargin); + + // Find any colliders around the cloth + DynamicHitBuffer buffer; + if (Scene->overlap(overlapGeo, overlapPose, buffer, filterData, &QueryFilter)) + { + const float collisionThickness = clothSettings.CollisionThickness; + for (uint32 j = 0; j < buffer.getNbTouches(); j++) + { + const auto& hit = buffer.getTouch(j); + if (hit.shape) + { + const PxGeometry& geo = hit.shape->getGeometry(); + const PxTransform shapeToCloth = clothPose.transformInv(hit.actor->getGlobalPose().transform(hit.shape->getLocalPose())); + // TODO: maybe use shared spheres/planes buffers for batched assigning? + switch (geo.getType()) + { + case PxGeometryType::eSPHERE: + { + const PxSphereGeometry& geoSphere = (const PxSphereGeometry&)geo; + const PxVec4 packedSphere(shapeToCloth.p, geoSphere.radius + collisionThickness); + const nv::cloth::Range sphereRange(&packedSphere, &packedSphere + 1); + const uint32_t spheresCount = clothPhysX->getNumSpheres(); + if (spheresCount + 1 > MAX_CLOTH_SPHERE_COUNT) + break; + clothPhysX->setSpheres(sphereRange, spheresCount, spheresCount); + break; + } + case PxGeometryType::eCAPSULE: + { + const PxCapsuleGeometry& geomCapsule = (const PxCapsuleGeometry&)geo; + const PxVec4 packedSpheres[2] = { + PxVec4(shapeToCloth.transform(PxVec3(+geomCapsule.halfHeight, 0, 0)), geomCapsule.radius + collisionThickness), + PxVec4(shapeToCloth.transform(PxVec3(-geomCapsule.halfHeight, 0, 0)), geomCapsule.radius + collisionThickness) + }; + const nv::cloth::Range sphereRange(packedSpheres, packedSpheres + 2); + const uint32_t spheresCount = clothPhysX->getNumSpheres(); + if (spheresCount + 2 > MAX_CLOTH_SPHERE_COUNT) + break; + clothPhysX->setSpheres(sphereRange, spheresCount, spheresCount); + const uint32_t packedCapsules[2] = { spheresCount, spheresCount + 1 }; + const int32 capsulesCount = clothPhysX->getNumCapsules(); + clothPhysX->setCapsules(nv::cloth::Range(packedCapsules, packedCapsules + 2), capsulesCount, capsulesCount); + break; + } + case PxGeometryType::eBOX: + { + const PxBoxGeometry& geomBox = (const PxBoxGeometry&)geo; + const uint32_t planesCount = clothPhysX->getNumPlanes(); + if (planesCount + 6 > MAX_CLOTH_PLANE_COUNT) + break; + const PxPlane packedPlanes[6] = { + PxPlane(PxVec3(1, 0, 0), -geomBox.halfExtents.x - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(-1, 0, 0), -geomBox.halfExtents.x - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, 1, 0), -geomBox.halfExtents.y - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, -1, 0), -geomBox.halfExtents.y - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, 0, 1), -geomBox.halfExtents.z - collisionThickness).transform(shapeToCloth), + PxPlane(PxVec3(0, 0, -1), -geomBox.halfExtents.z - collisionThickness).transform(shapeToCloth) + }; + clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)packedPlanes, (const PxVec4*)packedPlanes + 6), planesCount, planesCount); + const PxU32 convexMask = PxU32(0x3f << planesCount); + const uint32_t convexesCount = clothPhysX->getNumConvexes(); + clothPhysX->setConvexes(nv::cloth::Range(&convexMask, &convexMask + 1), convexesCount, convexesCount); + break; + } + case PxGeometryType::eCONVEXMESH: + { + const PxConvexMeshGeometry& geomConvexMesh = (const PxConvexMeshGeometry&)geo; + const PxU32 convexPlanesCount = geomConvexMesh.convexMesh->getNbPolygons(); + const uint32_t planesCount = clothPhysX->getNumPlanes(); + if (planesCount + convexPlanesCount > MAX_CLOTH_PLANE_COUNT) + break; + const PxMat33 convexToShapeInv = geomConvexMesh.scale.toMat33().getInverse(); + // TODO: merge convexToShapeInv with shapeToCloth to have a single matrix multiplication + PxPlane planes[MAX_CLOTH_PLANE_COUNT]; + for (PxU32 k = 0; k < convexPlanesCount; k++) + { + PxHullPolygon polygon; + geomConvexMesh.convexMesh->getPolygonData(k, polygon); + polygon.mPlane[3] -= collisionThickness; + planes[k] = transform(reinterpret_cast(polygon.mPlane), convexToShapeInv).transform(shapeToCloth); + } + clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)planes, (const PxVec4*)planes + convexPlanesCount), planesCount, planesCount); + const PxU32 convexMask = PxU32(((1 << convexPlanesCount) - 1) << planesCount); + const uint32_t convexesCount = clothPhysX->getNumConvexes(); + clothPhysX->setConvexes(nv::cloth::Range(&convexMask, &convexMask + 1), convexesCount, convexesCount); + break; + } + // Cloth vs Triangle collisions are too slow for real-time use + #if 0 + case PxGeometryType::eTRIANGLEMESH: + { + const PxTriangleMeshGeometry& geomTriangleMesh = (const PxTriangleMeshGeometry&)geo; + if (geomTriangleMesh.triangleMesh->getNbTriangles() >= 1024) + break; // Ignore too-tessellated meshes due to poor solver performance + // TODO: use shared memory allocators maybe? maybe per-frame stack allocator? + Array vertices; + vertices.Add(geomTriangleMesh.triangleMesh->getVertices(), geomTriangleMesh.triangleMesh->getNbVertices()); + const PxMat33 triangleMeshToShape = geomTriangleMesh.scale.toMat33(); + // TODO: merge triangleMeshToShape with shapeToCloth to have a single matrix multiplication + for (int32 k = 0; k < vertices.Count(); k++) + { + PxVec3& v = vertices.Get()[k]; + v = shapeToCloth.transform(triangleMeshToShape.transform(v)); + } + Array triangles; + triangles.Resize(geomTriangleMesh.triangleMesh->getNbTriangles() * 3); + if (geomTriangleMesh.triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::e16_BIT_INDICES) + { + auto indices = (const uint16*)geomTriangleMesh.triangleMesh->getTriangles(); + for (int32 k = 0; k < triangles.Count(); k++) + triangles.Get()[k] = vertices.Get()[indices[k]]; + } + else + { + auto indices = (const uint32*)geomTriangleMesh.triangleMesh->getTriangles(); + for (int32 k = 0; k < triangles.Count(); k++) + triangles.Get()[k] = vertices.Get()[indices[k]]; + } + const uint32_t trianglesCount = clothPhysX->getNumTriangles(); + clothPhysX->setTriangles(nv::cloth::Range(triangles.begin(), triangles.end()), trianglesCount, trianglesCount); + break; + } + case PxGeometryType::eHEIGHTFIELD: + { + const PxHeightFieldGeometry& geomHeightField = (const PxHeightFieldGeometry&)geo; + // TODO: heightfield collisions (gather triangles only nearby cloth to not blow sim perf) + break; + } +#endif + } + } + } + } + } + clothSettings.CollisionsUpdateFramesLeft--; +} + +#endif + void* PhysicalMaterial::GetPhysicsMaterial() { if (_material == nullptr && PhysX) @@ -1460,177 +1640,9 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_CPU_NAMED("Pre"); - const bool hitTriggers = false; - const bool blockSingle = false; - PxQueryFilterData filterData; - filterData.flags |= PxQueryFlag::ePREFILTER; - filterData.data.word1 = blockSingle ? 1 : 0; - filterData.data.word2 = hitTriggers ? 1 : 0; - for (int32 i = 0; i < clothsCount; i++) - { - auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; - auto& clothSettings = Cloths[clothPhysX]; - clothSettings.Actor->OnPreUpdate(); - - // Setup automatic scene collisions with colliders around the cloth - if (clothSettings.SceneCollisions && clothSettings.CollisionsUpdateFramesLeft == 0) - { - PROFILE_CPU_NAMED("Collisions"); - clothSettings.CollisionsUpdateFramesLeft = CLOTH_COLLISIONS_UPDATE_RATE; - if (clothSettings.CollisionsUpdateFramesRandomize) - { - clothSettings.CollisionsUpdateFramesRandomize = false; - clothSettings.CollisionsUpdateFramesLeft = i % CLOTH_COLLISIONS_UPDATE_RATE; - } - - // Reset existing colliders - clothPhysX->setSpheres(nv::cloth::Range(), 0, clothPhysX->getNumSpheres()); - clothPhysX->setPlanes(nv::cloth::Range(), 0, clothPhysX->getNumPlanes()); - clothPhysX->setTriangles(nv::cloth::Range(), 0, clothPhysX->getNumTriangles()); - - filterData.data.word0 = Physics::LayerMasks[clothSettings.Actor->GetLayer()]; - const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation()); - const PxVec3 clothBoundsPos = clothPhysX->getBoundingBoxCenter(); - const PxVec3 clothBoundsSize = clothPhysX->getBoundingBoxScale(); - const PxTransform overlapPose(clothPose.transform(clothBoundsPos), clothPose.q); - const float boundsMargin = 1.6f; - const PxSphereGeometry overlapGeo(clothBoundsSize.magnitude() * boundsMargin); - - // Find any colliders around the cloth - DynamicHitBuffer buffer; - if (scenePhysX->Scene->overlap(overlapGeo, overlapPose, buffer, filterData, &QueryFilter)) - { - const float collisionThickness = clothSettings.CollisionThickness; - for (uint32 j = 0; j < buffer.getNbTouches(); j++) - { - const auto& hit = buffer.getTouch(j); - if (hit.shape) - { - const PxGeometry& geo = hit.shape->getGeometry(); - const PxTransform shapeToCloth = clothPose.transformInv(hit.actor->getGlobalPose().transform(hit.shape->getLocalPose())); - // TODO: maybe use shared spheres/planes buffers for batched assigning? - switch (geo.getType()) - { - case PxGeometryType::eSPHERE: - { - const PxSphereGeometry& geoSphere = (const PxSphereGeometry&)geo; - const PxVec4 packedSphere(shapeToCloth.p, geoSphere.radius + collisionThickness); - const nv::cloth::Range sphereRange(&packedSphere, &packedSphere + 1); - const uint32_t spheresCount = clothPhysX->getNumSpheres(); - if (spheresCount + 1 > MAX_CLOTH_SPHERE_COUNT) - break; - clothPhysX->setSpheres(sphereRange, spheresCount, spheresCount); - break; - } - case PxGeometryType::eCAPSULE: - { - const PxCapsuleGeometry& geomCapsule = (const PxCapsuleGeometry&)geo; - const PxVec4 packedSpheres[2] = { - PxVec4(shapeToCloth.transform(PxVec3(+geomCapsule.halfHeight, 0, 0)), geomCapsule.radius + collisionThickness), - PxVec4(shapeToCloth.transform(PxVec3(-geomCapsule.halfHeight, 0, 0)), geomCapsule.radius + collisionThickness) - }; - const nv::cloth::Range sphereRange(packedSpheres, packedSpheres + 2); - const uint32_t spheresCount = clothPhysX->getNumSpheres(); - if (spheresCount + 2 > MAX_CLOTH_SPHERE_COUNT) - break; - clothPhysX->setSpheres(sphereRange, spheresCount, spheresCount); - const uint32_t packedCapsules[2] = { spheresCount, spheresCount + 1 }; - const int32 capsulesCount = clothPhysX->getNumCapsules(); - clothPhysX->setCapsules(nv::cloth::Range(packedCapsules, packedCapsules + 2), capsulesCount, capsulesCount); - break; - } - case PxGeometryType::eBOX: - { - const PxBoxGeometry& geomBox = (const PxBoxGeometry&)geo; - const uint32_t planesCount = clothPhysX->getNumPlanes(); - if (planesCount + 6 > MAX_CLOTH_PLANE_COUNT) - break; - const PxPlane packedPlanes[6] = { - PxPlane(PxVec3(1, 0, 0), -geomBox.halfExtents.x - collisionThickness).transform(shapeToCloth), - PxPlane(PxVec3(-1, 0, 0), -geomBox.halfExtents.x - collisionThickness).transform(shapeToCloth), - PxPlane(PxVec3(0, 1, 0), -geomBox.halfExtents.y - collisionThickness).transform(shapeToCloth), - PxPlane(PxVec3(0, -1, 0), -geomBox.halfExtents.y - collisionThickness).transform(shapeToCloth), - PxPlane(PxVec3(0, 0, 1), -geomBox.halfExtents.z - collisionThickness).transform(shapeToCloth), - PxPlane(PxVec3(0, 0, -1), -geomBox.halfExtents.z - collisionThickness).transform(shapeToCloth) - }; - clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)packedPlanes, (const PxVec4*)packedPlanes + 6), planesCount, planesCount); - const PxU32 convexMask = PxU32(0x3f << planesCount); - const uint32_t convexesCount = clothPhysX->getNumConvexes(); - clothPhysX->setConvexes(nv::cloth::Range(&convexMask, &convexMask + 1), convexesCount, convexesCount); - break; - } - case PxGeometryType::eCONVEXMESH: - { - const PxConvexMeshGeometry& geomConvexMesh = (const PxConvexMeshGeometry&)geo; - const PxU32 convexPlanesCount = geomConvexMesh.convexMesh->getNbPolygons(); - const uint32_t planesCount = clothPhysX->getNumPlanes(); - if (planesCount + convexPlanesCount > MAX_CLOTH_PLANE_COUNT) - break; - const PxMat33 convexToShapeInv = geomConvexMesh.scale.toMat33().getInverse(); - // TODO: merge convexToShapeInv with shapeToCloth to have a single matrix multiplication - PxPlane planes[MAX_CLOTH_PLANE_COUNT]; - for (PxU32 k = 0; k < convexPlanesCount; k++) - { - PxHullPolygon polygon; - geomConvexMesh.convexMesh->getPolygonData(k, polygon); - polygon.mPlane[3] -= collisionThickness; - planes[k] = transform(reinterpret_cast(polygon.mPlane), convexToShapeInv).transform(shapeToCloth); - } - clothPhysX->setPlanes(nv::cloth::Range((const PxVec4*)planes, (const PxVec4*)planes + convexPlanesCount), planesCount, planesCount); - const PxU32 convexMask = PxU32(((1 << convexPlanesCount) - 1) << planesCount); - const uint32_t convexesCount = clothPhysX->getNumConvexes(); - clothPhysX->setConvexes(nv::cloth::Range(&convexMask, &convexMask + 1), convexesCount, convexesCount); - break; - } - // Cloth vs Triangle collisions are too slow for real-time use -#if 0 - case PxGeometryType::eTRIANGLEMESH: - { - const PxTriangleMeshGeometry& geomTriangleMesh = (const PxTriangleMeshGeometry&)geo; - if (geomTriangleMesh.triangleMesh->getNbTriangles() >= 1024) - break; // Ignore too-tessellated meshes due to poor solver performance - // TODO: use shared memory allocators maybe? maybe per-frame stack allocator? - Array vertices; - vertices.Add(geomTriangleMesh.triangleMesh->getVertices(), geomTriangleMesh.triangleMesh->getNbVertices()); - const PxMat33 triangleMeshToShape = geomTriangleMesh.scale.toMat33(); - // TODO: merge triangleMeshToShape with shapeToCloth to have a single matrix multiplication - for (int32 k = 0; k < vertices.Count(); k++) - { - PxVec3& v = vertices.Get()[k]; - v = shapeToCloth.transform(triangleMeshToShape.transform(v)); - } - Array triangles; - triangles.Resize(geomTriangleMesh.triangleMesh->getNbTriangles() * 3); - if (geomTriangleMesh.triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::e16_BIT_INDICES) - { - auto indices = (const uint16*)geomTriangleMesh.triangleMesh->getTriangles(); - for (int32 k = 0; k < triangles.Count(); k++) - triangles.Get()[k] = vertices.Get()[indices[k]]; - } - else - { - auto indices = (const uint32*)geomTriangleMesh.triangleMesh->getTriangles(); - for (int32 k = 0; k < triangles.Count(); k++) - triangles.Get()[k] = vertices.Get()[indices[k]]; - } - const uint32_t trianglesCount = clothPhysX->getNumTriangles(); - clothPhysX->setTriangles(nv::cloth::Range(triangles.begin(), triangles.end()), trianglesCount, trianglesCount); - break; - } - case PxGeometryType::eHEIGHTFIELD: - { - const PxHeightFieldGeometry& geomHeightField = (const PxHeightFieldGeometry&)geo; - // TODO: heightfield collisions (gather triangles only nearby cloth to not blow sim perf) - break; - } -#endif - } - } - } - } - } - clothSettings.CollisionsUpdateFramesLeft--; - } + Function job; + job.Bind(scenePhysX); + JobSystem::Execute(job, clothsCount); } { From 1af076f1808a78fc43d71e8cce2db5fcac51101d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Jul 2023 11:16:01 +0200 Subject: [PATCH 047/294] Add distance-based and frustum-based culling to cloth --- Source/Engine/Level/Actors/AnimatedModel.cpp | 4 +- Source/Engine/Physics/Actors/Cloth.cpp | 63 +++++++++++++-- Source/Engine/Physics/Actors/Cloth.h | 23 +++++- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 78 +++++++++++++------ 4 files changed, 130 insertions(+), 38 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index b3904f653..5a7150bac 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -881,7 +881,7 @@ void AnimatedModel::Draw(RenderContext& renderContext) Matrix::Transformation(_transform.Scale, _transform.Orientation, translation, world); GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); - _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.Position + renderContext.View.Origin)); + _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); if (_skinningData.IsReady()) { // Flush skinning data with GPU @@ -924,7 +924,7 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch) Matrix::Transformation(_transform.Scale, _transform.Orientation, translation, world); GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); - _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.Position + renderContext.View.Origin)); + _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); if (_skinningData.IsReady()) { // Flush skinning data with GPU diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index a7ab5fe0e..d0e8cbe1d 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -3,6 +3,7 @@ #include "Cloth.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" @@ -21,6 +22,9 @@ Cloth::Cloth(const SpawnParams& params) { // Use the first mesh by default _mesh.LODIndex = _mesh.MeshIndex = 0; + + // Register for drawing to handle culling and distance LOD + _drawCategory = SceneRendering::SceneDrawAsync; } ModelInstanceActor::MeshReference Cloth::GetMesh() const @@ -441,6 +445,7 @@ void Cloth::EndPlay() void Cloth::OnEnable() { #if USE_EDITOR + GetSceneRendering()->AddActor(this, _sceneRenderingKey); GetSceneRendering()->AddPhysicsDebug(this); #endif #if WITH_CLOTH @@ -461,6 +466,7 @@ void Cloth::OnDisable() #endif #if USE_EDITOR GetSceneRendering()->RemovePhysicsDebug(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #endif } @@ -580,6 +586,8 @@ bool Cloth::CreateCloth() deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex1, deformer); _meshDeformation = deformation; } + + _lastMinDstSqr = MAX_Real; #endif return false; @@ -588,6 +596,7 @@ bool Cloth::CreateCloth() void Cloth::DestroyCloth() { #if WITH_CLOTH + _lastMinDstSqr = MAX_Real; if (_meshDeformation) { Function deformer; @@ -722,20 +731,38 @@ void Cloth::CalculateInvMasses(Array& invMasses) #endif } -void Cloth::OnPreUpdate() +bool Cloth::OnPreUpdate() { + if (!IsActiveInHierarchy()) + return true; + if (!_simulationSettings.UpdateWhenOffscreen && _simulationSettings.CullDistance > 0) + { + // Cull based on distance + bool cull = false; + if (_lastMinDstSqr >= Math::Square(_simulationSettings.CullDistance)) + cull = true; // Cull + else if (_lastMinDstSqr >= Math::Square(_simulationSettings.CullDistance * 0.8f)) + cull = _frameCounter % 4 == 0; // Update once every 4 frames + else if (_lastMinDstSqr >= Math::Square(_simulationSettings.CullDistance * 0.5f)) + cull = _frameCounter % 2 == 0; // Update once every 2 frames + _lastMinDstSqr = MAX_Real; + _frameCounter++; + if (cull) + return true; + } + // Get current skinned mesh pose for the simulation of the non-kinematic vertices if (auto* animatedModel = Cast(GetParent())) { if (animatedModel->GraphInstance.NodesPose.IsEmpty() || _paint.IsEmpty()) - return; + return false; const ModelInstanceActor::MeshReference mesh = GetMesh(); if (mesh.Actor == nullptr) - return; + return false; BytesContainer verticesData; int32 verticesCount; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) - return; + return false; PROFILE_CPU_NAMED("Skinned Pose"); auto vbStride = (uint32)verticesData.Length() / verticesCount; ASSERT(vbStride == sizeof(VB0SkinnedElementType)); @@ -749,6 +776,7 @@ void Cloth::OnPreUpdate() Array pose; animatedModel->GetCurrentPose(pose); const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; + const SkeletonBone* bones = skeleton.Bones.Get(); // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones const float* paint = _paint.Get(); @@ -763,24 +791,24 @@ void Cloth::OnPreUpdate() const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly Matrix matrix; - const SkeletonBone& bone0 = skeleton.Bones[vb0.BlendIndices.R]; + const SkeletonBone& bone0 = bones[vb0.BlendIndices.R]; Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); Matrix boneMatrix = matrix * blendWeights.X; if (blendWeights.Y > 0.0f) { - const SkeletonBone& bone1 = skeleton.Bones[vb0.BlendIndices.G]; + const SkeletonBone& bone1 = bones[vb0.BlendIndices.G]; Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Y; } if (blendWeights.Z > 0.0f) { - const SkeletonBone& bone2 = skeleton.Bones[vb0.BlendIndices.B]; + const SkeletonBone& bone2 = bones[vb0.BlendIndices.B]; Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Z; } if (blendWeights.W > 0.0f) { - const SkeletonBone& bone3 = skeleton.Bones[vb0.BlendIndices.A]; + const SkeletonBone& bone3 = bones[vb0.BlendIndices.A]; Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); boneMatrix += matrix * blendWeights.W; } @@ -806,6 +834,8 @@ void Cloth::OnPreUpdate() PhysicsBackend::UnlockClothParticles(_cloth); } + + return false; } void Cloth::OnPostUpdate() @@ -823,11 +853,28 @@ void Cloth::OnPostUpdate() // Update bounds (for mesh culling) auto* actor = (ModelInstanceActor*)GetParent(); actor->UpdateBounds(); + if (_sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } } +void Cloth::Draw(RenderContext& renderContext) +{ + // Update min draw distance for the next simulation tick + _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); +} + +void Cloth::Draw(RenderContextBatch& renderContextBatch) +{ + // Update min draw distance for the next simulation tick + const RenderContext& renderContext = renderContextBatch.GetMainContext(); + _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); +} + void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformation) { + if (!IsActiveInHierarchy()) + return; if (!_simulationSettings.ComputeNormals && deformation.Type != MeshBufferType::Vertex0) return; #if WITH_CLOTH diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index dbf073e18..4dc81ce3f 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -13,6 +13,7 @@ /// API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor { + friend class PhysicsBackend; DECLARE_SCENE_OBJECT(Cloth); /// @@ -129,12 +130,22 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph /// /// Target cloth solver iterations per second. The executed number of iterations per second may vary dependent on many performance factors. However, at least one iteration per frame is solved regardless of the value set. /// - API_FIELD() float SolverFrequency = 300.0f; + API_FIELD() float SolverFrequency = 200.0f; + + /// + /// The maximum distance from the camera at which to run cloth simulation. Used to improve performance and skip updating too far clothes. The physics system might reduce the update rate for clothes far enough (eg. half this distance). 0 to disable any culling. + /// + API_FIELD() float CullDistance = 5000.0f; + + /// + /// If true, the cloth will be updated even when an actor cannot be seen by any camera. Otherwise, the cloth simulation will stop running when the actor is off-screen. + /// + API_FIELD() bool UpdateWhenOffscreen = false; /// /// The maximum distance cloth particles can move from the original location (within local-space of the actor). Scaled by painted per-particle value (0-1) to restrict movement of certain particles. /// - API_FIELD() float MaxDistance = 1000.0f; + API_FIELD() float MaxParticleDistance = 1000.0f; /// /// Enables automatic normal vectors computing for the cloth mesh, otherwise original mesh normals will be used. @@ -207,6 +218,9 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph private: void* _cloth = nullptr; + Real _lastMinDstSqr = MAX_Real; + uint32 _frameCounter = 0; + int32 _sceneRenderingKey = -1; ForceSettings _forceSettings; CollisionSettings _collisionSettings; SimulationSettings _simulationSettings; @@ -220,7 +234,6 @@ public: /// /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). /// - /// API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")") ModelInstanceActor::MeshReference GetMesh() const; @@ -316,11 +329,13 @@ public: /// API_FUNCTION() void SetPaint(Span value); - void OnPreUpdate(); + bool OnPreUpdate(); void OnPostUpdate(); public: // [Actor] + void Draw(RenderContext& renderContext) override; + void Draw(RenderContextBatch& renderContextBatch) override; bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 8e55f5001..8194d92e4 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -41,6 +41,7 @@ #if WITH_CLOTH #include "Engine/Physics/Actors/Cloth.h" #include "Engine/Threading/JobSystem.h" +#include "Engine/Threading/Threading.h" #include #include #include @@ -94,15 +95,12 @@ struct ScenePhysX #endif #if WITH_CLOTH nv::cloth::Solver* ClothSolver = nullptr; + Array ClothsList; #endif #if WITH_CLOTH void PreSimulateCloth(int32 i); - void SimulateCloth(int32 i) - { - PROFILE_CPU(); - ClothSolver->simulateChunk(i); - } + void SimulateCloth(int32 i); #endif }; @@ -171,12 +169,8 @@ struct FabricSettings Desc.InvMassesStride == r.InvMassesStride) { if (Desc.InvMassesData && r.InvMassesData) - { - bool matches = Platform::MemoryCompare(InvMasses.Get(), r.InvMassesData, r.VerticesCount * r.InvMassesStride) == 0; - return matches; - } - bool matches = !Desc.InvMassesData && !r.InvMassesData; - return matches; + return Platform::MemoryCompare(InvMasses.Get(), r.InvMassesData, r.VerticesCount * r.InvMassesStride) == 0; + return !Desc.InvMassesData && !r.InvMassesData; } return false; } @@ -184,9 +178,10 @@ struct FabricSettings struct ClothSettings { + bool Culled = false; bool SceneCollisions = false; - byte CollisionsUpdateFramesLeft = 0; bool CollisionsUpdateFramesRandomize = true; + byte CollisionsUpdateFramesLeft = 0; float GravityScale = 1.0f; float CollisionThickness = 0.0f; Cloth* Actor; @@ -576,6 +571,7 @@ namespace #endif #if WITH_CLOTH + CriticalSection ClothLocker; nv::cloth::Factory* ClothFactory = nullptr; Dictionary Fabrics; Dictionary Cloths; @@ -708,10 +704,28 @@ void InitVehicleSDK() void ScenePhysX::PreSimulateCloth(int32 i) { PROFILE_CPU(); - auto clothPhysX = (nv::cloth::Cloth*)ClothSolver->getClothList()[i]; + auto clothPhysX = ClothsList[i]; auto& clothSettings = Cloths[clothPhysX]; - - clothSettings.Actor->OnPreUpdate(); + + if (clothSettings.Actor->OnPreUpdate()) + { + // Cull simulation based on distance + if (!clothSettings.Culled) + { + clothSettings.Culled = true; + ClothLocker.Lock(); + ClothSolver->removeCloth(clothPhysX); + ClothLocker.Unlock(); + } + return; + } + if (clothSettings.Culled) + { + clothSettings.Culled = false; + ClothLocker.Lock(); + ClothSolver->addCloth(clothPhysX); + ClothLocker.Unlock(); + } // Setup automatic scene collisions with colliders around the cloth if (clothSettings.SceneCollisions && clothSettings.CollisionsUpdateFramesLeft == 0) @@ -831,7 +845,7 @@ void ScenePhysX::PreSimulateCloth(int32 i) break; } // Cloth vs Triangle collisions are too slow for real-time use - #if 0 +#if 0 case PxGeometryType::eTRIANGLEMESH: { const PxTriangleMeshGeometry& geomTriangleMesh = (const PxTriangleMeshGeometry&)geo; @@ -880,6 +894,12 @@ void ScenePhysX::PreSimulateCloth(int32 i) clothSettings.CollisionsUpdateFramesLeft--; } +void ScenePhysX::SimulateCloth(int32 i) +{ + PROFILE_CPU(); + ClothSolver->simulateChunk(i); +} + #endif void* PhysicalMaterial::GetPhysicsMaterial() @@ -1632,17 +1652,15 @@ void PhysicsBackend::EndSimulateScene(void* scene) #if WITH_CLOTH nv::cloth::Solver* clothSolver = scenePhysX->ClothSolver; - if (clothSolver && clothSolver->getNumCloths() != 0) + if (clothSolver && scenePhysX->ClothsList.Count() != 0) { PROFILE_CPU_NAMED("Physics.Cloth"); - const int32 clothsCount = scenePhysX->ClothSolver->getNumCloths(); - nv::cloth::Cloth* const* cloths = scenePhysX->ClothSolver->getClothList(); { PROFILE_CPU_NAMED("Pre"); Function job; job.Bind(scenePhysX); - JobSystem::Execute(job, clothsCount); + JobSystem::Execute(job, scenePhysX->ClothsList.Count()); } { @@ -1658,10 +1676,12 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_CPU_NAMED("Post"); - for (int32 i = 0; i < clothsCount; i++) + ScopeLock lock(ClothLocker); + for (auto clothPhysX : scenePhysX->ClothsList) { - auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; const auto& clothSettings = Cloths[clothPhysX]; + if (clothSettings.Culled) + continue; clothSettings.UpdateBounds(clothPhysX); clothSettings.Actor->OnPostUpdate(); } @@ -3410,6 +3430,7 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) #endif // Lazy-init NvCloth + ScopeLock lock(ClothLocker); if (ClothFactory == nullptr) { nv::cloth::InitializeNvCloth(&AllocatorCallback, &ErrorCallback, &AssertCallback, &ProfilerCallback); @@ -3507,6 +3528,7 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) void PhysicsBackend::DestroyCloth(void* cloth) { + ScopeLock lock(ClothLocker); auto clothPhysX = (nv::cloth::Cloth*)cloth; if (--Fabrics[&clothPhysX->getFabric()].Refs == 0) Fabrics.Remove(&clothPhysX->getFabric()); @@ -3559,7 +3581,7 @@ void PhysicsBackend::SetClothSimulationSettings(void* cloth, const void* setting auto clothPhysX = (nv::cloth::Cloth*)cloth; const auto& settings = *(const Cloth::SimulationSettings*)settingsPtr; clothPhysX->setSolverFrequency(settings.SolverFrequency); - clothPhysX->setMotionConstraintScaleBias(settings.MaxDistance, 0.0f); + clothPhysX->setMotionConstraintScaleBias(settings.MaxParticleDistance, 0.0f); clothPhysX->setWindVelocity(C2P(settings.WindVelocity)); } @@ -3705,6 +3727,7 @@ void PhysicsBackend::SetClothPaint(void* cloth, Span value) void PhysicsBackend::AddCloth(void* scene, void* cloth) { + ScopeLock lock(ClothLocker); auto scenePhysX = (ScenePhysX*)scene; auto clothPhysX = (nv::cloth::Cloth*)cloth; if (scenePhysX->ClothSolver == nullptr) @@ -3713,13 +3736,20 @@ void PhysicsBackend::AddCloth(void* scene, void* cloth) ASSERT(scenePhysX->ClothSolver); } scenePhysX->ClothSolver->addCloth(clothPhysX); + scenePhysX->ClothsList.Add(clothPhysX); } void PhysicsBackend::RemoveCloth(void* scene, void* cloth) { + ScopeLock lock(ClothLocker); auto scenePhysX = (ScenePhysX*)scene; auto clothPhysX = (nv::cloth::Cloth*)cloth; - scenePhysX->ClothSolver->removeCloth(clothPhysX); + auto& clothSettings = Cloths[clothPhysX]; + if (clothSettings.Culled) + clothSettings.Culled = false; + else + scenePhysX->ClothSolver->removeCloth(clothPhysX); + scenePhysX->ClothsList.Remove(clothPhysX); } #endif From 868084286c26e2aad1d20c739f0faaad486dfc2e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Jul 2023 15:17:22 +0200 Subject: [PATCH 048/294] Add automatic cloth rebuild when simulation fails with NaN --- .../Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 8194d92e4..7815ffd1e 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -186,14 +186,17 @@ struct ClothSettings float CollisionThickness = 0.0f; Cloth* Actor; - void UpdateBounds(const nv::cloth::Cloth* clothPhysX) const + bool UpdateBounds(const nv::cloth::Cloth* clothPhysX) const { // Get cloth particles bounds (in local-space) const PxVec3& clothBoundsPos = clothPhysX->getBoundingBoxCenter(); const PxVec3& clothBoundsSize = clothPhysX->getBoundingBoxScale(); BoundingBox localBounds; BoundingBox::FromPoints(P2C(clothBoundsPos - clothBoundsSize), P2C(clothBoundsPos + clothBoundsSize), localBounds); - CHECK(!localBounds.Minimum.IsNanOrInfinity() && !localBounds.Maximum.IsNanOrInfinity()); + + // Automatic cloth reset when simulation fails (eg. ends with NaN) + if (localBounds.Minimum.IsNanOrInfinity() || localBounds.Maximum.IsNanOrInfinity()) + return true; // Transform local-space bounds into world-space const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation()); @@ -206,6 +209,7 @@ struct ClothSettings // Setup bounds BoundingBox::FromPoints(boundsCorners, 8, const_cast(Actor->GetBox())); BoundingSphere::FromBox(Actor->GetBox(), const_cast(Actor->GetSphere())); + return false; } }; @@ -1677,14 +1681,18 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_CPU_NAMED("Post"); ScopeLock lock(ClothLocker); + Array brokenCloths; for (auto clothPhysX : scenePhysX->ClothsList) { const auto& clothSettings = Cloths[clothPhysX]; if (clothSettings.Culled) continue; - clothSettings.UpdateBounds(clothPhysX); + if (clothSettings.UpdateBounds(clothPhysX)) + brokenCloths.Add(clothSettings.Actor); clothSettings.Actor->OnPostUpdate(); } + for (auto cloth : brokenCloths) + cloth->Rebuild(); } } From 3d5d7ad968750fcac5f6471bfd6047dc045faf94 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Jul 2023 15:34:38 +0200 Subject: [PATCH 049/294] Don't recook cloth mesh when rebuilding it --- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 7815ffd1e..2fb6cc5d6 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1692,7 +1692,16 @@ void PhysicsBackend::EndSimulateScene(void* scene) clothSettings.Actor->OnPostUpdate(); } for (auto cloth : brokenCloths) + { + // Rebuild cloth object but keep fabric ref to prevent fabric recook + auto fabric = &((nv::cloth::Cloth*)cloth->_cloth)->getFabric(); + Fabrics[fabric].Refs++; + fabric->incRefCount(); cloth->Rebuild(); + fabric->decRefCount(); + if (--Fabrics[fabric].Refs == 0) + Fabrics.Remove(fabric); + } } } @@ -3460,6 +3469,7 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) if (fabric) { Fabrics[fabric].Refs++; + fabric->incRefCount(); } else { From 5012d4432e762ba22ce8b250b560cef131c73076 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Jul 2023 18:22:40 +0200 Subject: [PATCH 050/294] Add NvCloth for Linux --- Source/Engine/Physics/Actors/Cloth.cpp | 2 ++ .../Platforms/Linux/Binaries/ThirdParty/x64/libNvCloth.a | 3 +++ Source/ThirdParty/NvCloth/NvCloth.Build.cs | 1 + Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs | 8 ++++++++ 4 files changed, 14 insertions(+) create mode 100644 Source/Platforms/Linux/Binaries/ThirdParty/x64/libNvCloth.a diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index a7ab5fe0e..5dc53af8b 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -724,6 +724,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) void Cloth::OnPreUpdate() { +#if WITH_CLOTH // Get current skinned mesh pose for the simulation of the non-kinematic vertices if (auto* animatedModel = Cast(GetParent())) { @@ -806,6 +807,7 @@ void Cloth::OnPreUpdate() PhysicsBackend::UnlockClothParticles(_cloth); } +#endif } void Cloth::OnPostUpdate() diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libNvCloth.a b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libNvCloth.a new file mode 100644 index 000000000..573dfb245 --- /dev/null +++ b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libNvCloth.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6234ee8e0cea08be467f8d13994fee8b94dfa2ed2147460a3268dbe6cfeb87a6 +size 553518 diff --git a/Source/ThirdParty/NvCloth/NvCloth.Build.cs b/Source/ThirdParty/NvCloth/NvCloth.Build.cs index 3be1e903e..a28d619af 100644 --- a/Source/ThirdParty/NvCloth/NvCloth.Build.cs +++ b/Source/ThirdParty/NvCloth/NvCloth.Build.cs @@ -37,6 +37,7 @@ public class NvCloth : DepsModule case TargetPlatform.Android: case TargetPlatform.Mac: case TargetPlatform.iOS: + case TargetPlatform.Linux: libName = "NvCloth"; break; case TargetPlatform.Switch: diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs index b096eef24..6e565b94c 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs @@ -89,6 +89,9 @@ namespace Flax.Deps.Dependencies case TargetPlatform.iOS: Build(options, platform, TargetArchitecture.ARM64); break; + case TargetPlatform.Linux: + Build(options, platform, TargetArchitecture.x64); + break; } } @@ -157,6 +160,11 @@ namespace Flax.Deps.Dependencies cmakeName = "ios"; binariesPrefix = "lib"; break; + case TargetPlatform.Linux: + cmakeArgs += " -DTARGET_BUILD_PLATFORM=linux"; + cmakeName = "linux"; + binariesPrefix = "lib"; + break; default: throw new InvalidPlatformException(platform); } var cmakeFolder = Path.Combine(nvCloth, "compiler", "cmake", cmakeName); From b26b9302e0cae626929b23b18c8222757745d64a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Jul 2023 18:43:10 +0200 Subject: [PATCH 051/294] Remove unused `TIsArithmetic` --- Source/Engine/Core/Templates.h | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Core/Templates.h b/Source/Engine/Core/Templates.h index 49da8b84a..d2e7e4359 100644 --- a/Source/Engine/Core/Templates.h +++ b/Source/Engine/Core/Templates.h @@ -12,7 +12,7 @@ namespace THelpers { - template + template struct TIsTriviallyDestructibleImpl { enum { Value = true }; @@ -96,7 +96,7 @@ struct TOr<> template struct TNot { - enum { Value = !Type::Value }; + enum { Value = !Type::Value }; }; //////////////////////////////////////////////////////////////////////////////////// @@ -147,30 +147,6 @@ struct TIsEnum //////////////////////////////////////////////////////////////////////////////////// -// Checks if a type is arithmetic. - -template struct TIsArithmetic { enum { Value = false }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; -template<> struct TIsArithmetic { enum { Value = true }; }; - -template struct TIsArithmetic { enum { Value = TIsArithmetic::Value }; }; -template struct TIsArithmetic< volatile T> { enum { Value = TIsArithmetic::Value }; }; -template struct TIsArithmetic { enum { Value = TIsArithmetic::Value }; }; - -//////////////////////////////////////////////////////////////////////////////////// - // Checks if a type is POD (plain old data type). template From 32d9067710ff2ec7283bfedbca781e44e3b7d531 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 19 Jul 2023 09:08:17 +0200 Subject: [PATCH 052/294] Fix build --- .../Vulkan/GPUSwapChainVulkan.cpp | 2 ++ .../Vulkan/RenderToolsVulkan.cpp | 17 ++++++++++++++++ .../GraphicsDevice/Vulkan/RenderToolsVulkan.h | 20 +------------------ Source/Engine/Physics/Actors/Cloth.cpp | 2 +- Source/ThirdParty/NvCloth/NvCloth.Build.cs | 1 + 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index aee1f4239..bb89399a6 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -460,10 +460,12 @@ GPUSwapChainVulkan::Status GPUSwapChainVulkan::Present(QueueVulkan* presentQueue { return Status::LostSurface; } +#if GPU_ENABLE_ASSERTION if (presentResult != VK_SUCCESS && presentResult != VK_SUBOPTIMAL_KHR) { VALIDATE_VULKAN_RESULT(presentResult); } +#endif return Status::Ok; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index 334a71370..7bf254f6a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -205,6 +205,23 @@ void RenderToolsVulkan::SetObjectName(VkDevice device, uint64 objectHandle, VkOb #endif } +void RenderToolsVulkan::SetObjectName(VkDevice device, uint64 objectHandle, VkObjectType objectType, const char* name) +{ +#if VK_EXT_debug_utils + // Check for valid function pointer (may not be present if not running in a debugging application) + if (vkSetDebugUtilsObjectNameEXT != nullptr && name != nullptr && *name != 0) + { + VkDebugUtilsObjectNameInfoEXT objectNameInfo; + ZeroStruct(objectNameInfo, VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT); + objectNameInfo.objectType = objectType; + objectNameInfo.objectHandle = objectHandle; + objectNameInfo.pObjectName = name; + const VkResult result = vkSetDebugUtilsObjectNameEXT(device, &objectNameInfo); + LOG_VULKAN_RESULT(result); + } +#endif +} + #endif String RenderToolsVulkan::GetVkErrorString(VkResult result) diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index 083f1225a..835ae883c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -43,26 +43,8 @@ private: public: #if GPU_ENABLE_RESOURCE_NAMING - static void SetObjectName(VkDevice device, uint64 objectHandle, VkObjectType objectType, const String& name); - - static void SetObjectName(VkDevice device, uint64 objectHandle, VkObjectType objectType, const char* name) - { -#if VK_EXT_debug_utils - // Check for valid function pointer (may not be present if not running in a debugging application) - if (vkSetDebugUtilsObjectNameEXT != nullptr && name != nullptr && *name != 0) - { - VkDebugUtilsObjectNameInfoEXT objectNameInfo; - ZeroStruct(objectNameInfo, VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT); - objectNameInfo.objectType = objectType; - objectNameInfo.objectHandle = objectHandle; - objectNameInfo.pObjectName = name; - const VkResult result = vkSetDebugUtilsObjectNameEXT(device, &objectNameInfo); - LOG_VULKAN_RESULT(result); - } -#endif - } - + static void SetObjectName(VkDevice device, uint64 objectHandle, VkObjectType objectType, const char* name); #endif static String GetVkErrorString(VkResult result); diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 33f2b02f4..ec6dd4ec4 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -12,8 +12,8 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Actors/AnimatedModel.h" -#if USE_EDITOR #include "Engine/Level/Scene/SceneRendering.h" +#if USE_EDITOR #include "Engine/Debug/DebugDraw.h" #endif diff --git a/Source/ThirdParty/NvCloth/NvCloth.Build.cs b/Source/ThirdParty/NvCloth/NvCloth.Build.cs index a28d619af..58d4b9b84 100644 --- a/Source/ThirdParty/NvCloth/NvCloth.Build.cs +++ b/Source/ThirdParty/NvCloth/NvCloth.Build.cs @@ -44,6 +44,7 @@ public class NvCloth : DepsModule libName = "NvCloth"; options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/Platforms/Switch/Binaries/Data/PhysX/physx/include")); options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/Platforms/Switch/Binaries/Data/PhysX/physx/include/foundation")); + options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/Platforms/Switch/Binaries/Data/NvCloth/NvCloth/include/NvCloth/ps")); break; } AddLib(options, options.DepsFolder, libName); From 94386d24ab9b695a62c348c1f56e779d4efa2726 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 1 Aug 2023 10:27:31 +0200 Subject: [PATCH 053/294] Minor tweaks --- Source/Editor/Windows/Assets/AnimationGraphWindow.cs | 4 ++-- Source/Editor/Windows/Assets/MaterialWindow.cs | 2 +- Source/Editor/Windows/Assets/ParticleEmitterWindow.cs | 2 +- Source/Editor/Windows/Assets/VisualScriptWindow.cs | 2 +- Source/Engine/Content/Assets/VisualScript.cpp | 2 +- Source/Engine/Content/Assets/VisualScript.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index 98304fd8f..622dfe066 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -322,13 +322,13 @@ namespace FlaxEditor.Windows.Assets { if (value == null) { - Editor.LogError("Failed to save animation graph surface"); + Editor.LogError("Failed to save surface data"); return; } if (_asset.SaveSurface(value)) { _surface.MarkAsEdited(); - Editor.LogError("Failed to save animation graph surface data"); + Editor.LogError("Failed to save surface data"); return; } _asset.Reload(); diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index 4ade1eb70..bb5765f50 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -349,7 +349,7 @@ namespace FlaxEditor.Windows.Assets if (_asset.SaveSurface(value, info)) { _surface.MarkAsEdited(); - Editor.LogError("Failed to save material surface data"); + Editor.LogError("Failed to save surface data"); } _asset.Reload(); _asset.WaitForLoaded(); diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 5a401242b..b7c3ce786 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -223,7 +223,7 @@ namespace FlaxEditor.Windows.Assets if (_asset.SaveSurface(value)) { _surface.MarkAsEdited(); - Editor.LogError("Failed to save Particle Emitter surface data"); + Editor.LogError("Failed to save surface data"); } _asset.Reload(); _asset.WaitForLoaded(); diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 89a74c9e0..41293a4fe 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -1133,7 +1133,7 @@ namespace FlaxEditor.Windows.Assets { // Error _surface.MarkAsEdited(); - Editor.LogError("Failed to save Visual Script surface data"); + Editor.LogError("Failed to save surface data"); } _asset.Reload(); SaveBreakpoints(); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 0f1c73e2c..66e393c98 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -2143,7 +2143,7 @@ BytesContainer VisualScript::LoadSurface() #if USE_EDITOR -bool VisualScript::SaveSurface(BytesContainer& data, const Metadata& meta) +bool VisualScript::SaveSurface(const BytesContainer& data, const Metadata& meta) { // Wait for asset to be loaded or don't if last load failed if (LastLoadFailed()) diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 71caaf37a..4b55370d9 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -255,7 +255,7 @@ public: /// Stream with graph data. /// Script metadata. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(BytesContainer& data, API_PARAM(Ref) const Metadata& meta); + API_FUNCTION() bool SaveSurface(const BytesContainer& data, API_PARAM(Ref) const Metadata& meta); // Returns the amount of methods in the script. API_FUNCTION() int32 GetMethodsCount() const From 8dc98174cfb34c0e109ed65287daefb470f37174 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 1 Aug 2023 14:14:27 +0200 Subject: [PATCH 054/294] Minor tweaks --- Source/Editor/Content/Thumbnails/ThumbnailsModule.cs | 2 -- Source/Editor/Editor.cs | 1 - Source/Editor/Modules/ContentDatabaseModule.cs | 12 ------------ Source/Editor/Windows/Assets/AnimationGraphWindow.cs | 1 - Source/Editor/Windows/Assets/MaterialWindow.cs | 1 - .../Editor/Windows/Assets/ParticleEmitterWindow.cs | 1 - Source/Editor/Windows/Assets/VisualScriptWindow.cs | 2 -- Source/Editor/Windows/ContentWindow.cs | 1 - Source/Engine/Content/Assets/VisualScript.cpp | 6 ++---- 9 files changed, 2 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs index a6996310e..fa289bb3d 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs @@ -368,7 +368,6 @@ namespace FlaxEditor.Content.Thumbnails // Create atlas if (PreviewsCache.Create(path)) { - // Error Editor.LogError("Failed to create thumbnails atlas."); return null; } @@ -377,7 +376,6 @@ namespace FlaxEditor.Content.Thumbnails var atlas = FlaxEngine.Content.LoadAsync(path); if (atlas == null) { - // Error Editor.LogError("Failed to load thumbnails atlas."); return null; } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 1a1fd3b2d..d46bf48ed 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -795,7 +795,6 @@ namespace FlaxEditor { if (projectFilePath == null || !File.Exists(projectFilePath)) { - // Error MessageBox.Show("Missing project"); return; } diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index ecafbf5d4..45428b9df 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -373,7 +373,6 @@ namespace FlaxEditor.Modules // Note: we use content backend because file may be in use or sth, it's safe if (FlaxEngine.Content.RenameAsset(oldPath, newPath)) { - // Error Editor.LogError(string.Format("Cannot rename asset \'{0}\' to \'{1}\'", oldPath, newPath)); return true; } @@ -387,7 +386,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogError(string.Format("Cannot rename asset \'{0}\' to \'{1}\'", oldPath, newPath)); return true; @@ -418,7 +416,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogError(string.Format("Cannot move folder \'{0}\' to \'{1}\'", oldPath, newPath)); return; @@ -479,7 +476,6 @@ namespace FlaxEditor.Modules if (item.IsFolder && Directory.Exists(newPath)) { - // Error MessageBox.Show("Cannot move folder. Target location already exists."); return; } @@ -489,7 +485,6 @@ namespace FlaxEditor.Modules var newParent = Find(newDirPath) as ContentFolder; if (newParent == null) { - // Error MessageBox.Show("Cannot move item. Missing target location."); return; } @@ -511,7 +506,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogError(string.Format("Cannot move folder \'{0}\' to \'{1}\'", oldPath, newPath)); return; @@ -531,7 +525,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogWarning(string.Format("Cannot remove folder \'{0}\'", oldPath)); return; @@ -566,7 +559,6 @@ namespace FlaxEditor.Modules { if (item == null || !item.Exists) { - // Error MessageBox.Show("Cannot move item. It's missing."); return; } @@ -590,7 +582,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogError(string.Format("Cannot copy folder \'{0}\' to \'{1}\'", sourcePath, targetPath)); return; @@ -615,7 +606,6 @@ namespace FlaxEditor.Modules // Note: we use content backend because file may be in use or sth, it's safe if (Editor.ContentEditing.CloneAssetFile(sourcePath, targetPath, Guid.NewGuid())) { - // Error Editor.LogError(string.Format("Cannot copy asset \'{0}\' to \'{1}\'", sourcePath, targetPath)); return; } @@ -629,7 +619,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogError(string.Format("Cannot copy asset \'{0}\' to \'{1}\'", sourcePath, targetPath)); return; @@ -680,7 +669,6 @@ namespace FlaxEditor.Modules } catch (Exception ex) { - // Error Editor.LogWarning(ex); Editor.LogWarning(string.Format("Cannot remove folder \'{0}\'", path)); return; diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index 622dfe066..12eac22b5 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -344,7 +344,6 @@ namespace FlaxEditor.Windows.Assets // Load surface graph if (_surface.Load()) { - // Error Editor.LogError("Failed to load animation graph surface."); return true; } diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index bb5765f50..6fbe32d7e 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -284,7 +284,6 @@ namespace FlaxEditor.Windows.Assets var mainNode = _surface.FindNode(1, 1) as Surface.Archetypes.Material.SurfaceNodeMaterial; if (mainNode == null) { - // Error Editor.LogError("Failed to find main material node."); } return mainNode; diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index b7c3ce786..f4fae5bdb 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -238,7 +238,6 @@ namespace FlaxEditor.Windows.Assets // Load surface graph if (_surface.Load()) { - // Error Editor.LogError("Failed to load Particle Emitter surface."); return true; } diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 41293a4fe..63bde3c8a 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -1131,7 +1131,6 @@ namespace FlaxEditor.Windows.Assets // Save data to the asset if (_asset.SaveSurface(value, ref meta)) { - // Error _surface.MarkAsEdited(); Editor.LogError("Failed to save surface data"); } @@ -1192,7 +1191,6 @@ namespace FlaxEditor.Windows.Assets // Load surface graph if (_surface.Load()) { - // Error Editor.LogError("Failed to load Visual Script surface."); return true; } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index f993b79f9..594692624 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -426,7 +426,6 @@ namespace FlaxEditor.Windows // Ensure has parent if (item.ParentFolder == null) { - // Error Editor.LogWarning("Cannot rename root items. " + item.Path); return; } diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 66e393c98..6f0916e97 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -2163,18 +2163,16 @@ bool VisualScript::SaveSurface(const BytesContainer& data, const Metadata& meta) ReleaseChunk(i); // Set Visject Surface data - auto visjectSurfaceChunk = GetOrCreateChunk(0); - visjectSurfaceChunk->Data.Copy(data); + GetOrCreateChunk(0)->Data.Copy(data); // Set metadata - auto metadataChunk = GetOrCreateChunk(1); MemoryWriteStream metaStream(512); { metaStream.WriteInt32(1); metaStream.WriteString(meta.BaseTypename, 31); metaStream.WriteInt32((int32)meta.Flags); } - metadataChunk->Data.Copy(metaStream.GetHandle(), metaStream.GetPosition()); + GetOrCreateChunk(1)->Data.Copy(metaStream.GetHandle(), metaStream.GetPosition()); // Save AssetInitData assetData; From bb96f2cd6d71b7875041bca3c7f1a919bded64ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Aug 2023 14:53:02 +0200 Subject: [PATCH 055/294] Add `GroupArchetype` to Visject nodes spawn query check --- Source/Editor/Surface/AnimGraphSurface.cs | 6 +++--- Source/Editor/Surface/AnimationGraphFunctionSurface.cs | 4 ++-- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 5 +++-- Source/Editor/Surface/MaterialFunctionSurface.cs | 4 ++-- Source/Editor/Surface/MaterialSurface.cs | 4 ++-- Source/Editor/Surface/ParticleEmitterFunctionSurface.cs | 4 ++-- Source/Editor/Surface/ParticleEmitterSurface.cs | 4 ++-- Source/Editor/Surface/VisjectSurface.CopyPaste.cs | 2 +- Source/Editor/Surface/VisjectSurface.cs | 3 ++- Source/Editor/Surface/VisjectSurfaceContext.cs | 2 +- Source/Editor/Surface/VisualScriptSurface.cs | 4 ++-- 11 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 162b82270..3cc07813c 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.Surface _cmStateMachineMenu = new VisjectCM(new VisjectCM.InitInfo { Groups = StateMachineGroupArchetypes, - CanSpawnNode = arch => true, + CanSpawnNode = (_, _) => true, }); _cmStateMachineMenu.ShowExpanded = true; } @@ -406,9 +406,9 @@ namespace FlaxEditor.Surface } /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { - return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(nodeArchetype); + return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype); } /// diff --git a/Source/Editor/Surface/AnimationGraphFunctionSurface.cs b/Source/Editor/Surface/AnimationGraphFunctionSurface.cs index fa6619e1d..d5d4ae132 100644 --- a/Source/Editor/Surface/AnimationGraphFunctionSurface.cs +++ b/Source/Editor/Surface/AnimationGraphFunctionSurface.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.Surface } /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { if (nodeArchetype.Title == "Function Input") return true; @@ -44,7 +44,7 @@ namespace FlaxEditor.Surface if (Context == RootContext && nodeArchetype.Title == "Function Output") return true; - return base.CanUseNodeType(nodeArchetype); + return base.CanUseNodeType(groupArchetype, nodeArchetype); } /// diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index bae62556e..d293d82f3 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -29,9 +29,10 @@ namespace FlaxEditor.Surface.ContextMenu /// /// Visject Surface node archetype spawn ability checking delegate. /// + /// The nodes group archetype to check. /// The node archetype to check. /// True if can use this node to spawn it on a surface, otherwise false.. - public delegate bool NodeSpawnCheckDelegate(NodeArchetype arch); + public delegate bool NodeSpawnCheckDelegate(GroupArchetype groupArch, NodeArchetype arch); /// /// Visject Surface parameters getter delegate. @@ -184,7 +185,7 @@ namespace FlaxEditor.Surface.ContextMenu nodes.Clear(); foreach (var nodeArchetype in groupArchetype.Archetypes) { - if ((nodeArchetype.Flags & NodeFlags.NoSpawnViaGUI) == 0 && info.CanSpawnNode(nodeArchetype)) + if ((nodeArchetype.Flags & NodeFlags.NoSpawnViaGUI) == 0 && info.CanSpawnNode(groupArchetype, nodeArchetype)) { nodes.Add(nodeArchetype); } diff --git a/Source/Editor/Surface/MaterialFunctionSurface.cs b/Source/Editor/Surface/MaterialFunctionSurface.cs index 1f820d3a7..000ff16fd 100644 --- a/Source/Editor/Surface/MaterialFunctionSurface.cs +++ b/Source/Editor/Surface/MaterialFunctionSurface.cs @@ -36,13 +36,13 @@ namespace FlaxEditor.Surface } /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { if (nodeArchetype.Title == "Function Input" || nodeArchetype.Title == "Function Output") return true; - return base.CanUseNodeType(nodeArchetype); + return base.CanUseNodeType(groupArchetype, nodeArchetype); } /// diff --git a/Source/Editor/Surface/MaterialSurface.cs b/Source/Editor/Surface/MaterialSurface.cs index 2fa44f6c6..08e1161d9 100644 --- a/Source/Editor/Surface/MaterialSurface.cs +++ b/Source/Editor/Surface/MaterialSurface.cs @@ -33,9 +33,9 @@ namespace FlaxEditor.Surface } /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { - return (nodeArchetype.Flags & NodeFlags.MaterialGraph) != 0 && base.CanUseNodeType(nodeArchetype); + return (nodeArchetype.Flags & NodeFlags.MaterialGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype); } /// diff --git a/Source/Editor/Surface/ParticleEmitterFunctionSurface.cs b/Source/Editor/Surface/ParticleEmitterFunctionSurface.cs index a1880c3a8..452905409 100644 --- a/Source/Editor/Surface/ParticleEmitterFunctionSurface.cs +++ b/Source/Editor/Surface/ParticleEmitterFunctionSurface.cs @@ -35,13 +35,13 @@ namespace FlaxEditor.Surface } /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { if (nodeArchetype.Title == "Function Input" || nodeArchetype.Title == "Function Output") return true; - return base.CanUseNodeType(nodeArchetype); + return base.CanUseNodeType(groupArchetype, nodeArchetype); } /// diff --git a/Source/Editor/Surface/ParticleEmitterSurface.cs b/Source/Editor/Surface/ParticleEmitterSurface.cs index 99ed61f8e..76f96f06c 100644 --- a/Source/Editor/Surface/ParticleEmitterSurface.cs +++ b/Source/Editor/Surface/ParticleEmitterSurface.cs @@ -87,9 +87,9 @@ namespace FlaxEditor.Surface } /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { - return (nodeArchetype.Flags & NodeFlags.ParticleEmitterGraph) != 0 && base.CanUseNodeType(nodeArchetype); + return (nodeArchetype.Flags & NodeFlags.ParticleEmitterGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype); } /// diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index 3a5be4857..f9e37dc20 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -254,7 +254,7 @@ namespace FlaxEditor.Surface throw new InvalidOperationException("Unknown node type."); // Validate given node type - if (!CanUseNodeType(nodeArchetype)) + if (!CanUseNodeType(groupArchetype, nodeArchetype)) continue; // Create diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 22aba4855..03e32cb29 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -520,9 +520,10 @@ namespace FlaxEditor.Surface /// /// Determines whether the specified node archetype can be used in the surface. /// + /// The nodes group archetype. /// The node archetype. /// True if can use this node archetype, otherwise false. - public virtual bool CanUseNodeType(NodeArchetype nodeArchetype) + public virtual bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { return (nodeArchetype.Flags & NodeFlags.NoSpawnViaPaste) == 0; } diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 8393d6162..0a4b59412 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -361,7 +361,7 @@ namespace FlaxEditor.Surface var flags = nodeArchetype.Flags; nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaGUI; nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaPaste; - if (_surface != null && !_surface.CanUseNodeType(nodeArchetype)) + if (_surface != null && !_surface.CanUseNodeType(groupArchetype, nodeArchetype)) { nodeArchetype.Flags = flags; Editor.LogWarning("Cannot spawn given node type. Title: " + nodeArchetype.Title); diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index e961f686d..2cb82b351 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -532,9 +532,9 @@ namespace FlaxEditor.Surface public override bool CanSetParameters => true; /// - public override bool CanUseNodeType(NodeArchetype nodeArchetype) + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) { - return (nodeArchetype.Flags & NodeFlags.VisualScriptGraph) != 0 && base.CanUseNodeType(nodeArchetype); + return (nodeArchetype.Flags & NodeFlags.VisualScriptGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype); } /// From c0d32a99b0e7b52fa8c02168c4166277b22fa242 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Aug 2023 15:44:02 +0200 Subject: [PATCH 056/294] Refactor Visject surface nodes cache to reuse between graphs --- Source/Editor/Editor.cs | 4 +- Source/Editor/Surface/AnimGraphSurface.cs | 272 +++---- .../Surface/VisjectSurface.ContextMenu.cs | 155 ++++ Source/Editor/Surface/VisualScriptSurface.cs | 677 +++++++----------- 4 files changed, 515 insertions(+), 593 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index d46bf48ed..6f2111e9c 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -724,8 +724,8 @@ namespace FlaxEditor // Cleanup Undo.Dispose(); - Surface.VisualScriptSurface.NodesCache.Clear(); - Surface.AnimGraphSurface.NodesCache.Clear(); + foreach (var cache in Surface.VisjectSurface.NodesCache.Caches.ToArray()) + cache.Clear(); Instance = null; // Invoke new instance if need to open a project diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 3cc07813c..d7746662f 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using FlaxEditor.Content; using FlaxEditor.Scripting; using FlaxEditor.Surface.ContextMenu; @@ -89,188 +87,7 @@ namespace FlaxEditor.Surface } }; - internal static class NodesCache - { - private static readonly object _locker = new object(); - private static int _version; - private static Task _task; - private static VisjectCM _taskContextMenu; - private static Dictionary, GroupArchetype> _cache; - - public static void Wait() - { - _task?.Wait(); - } - - public static void Clear() - { - Wait(); - - if (_cache != null && _cache.Count != 0) - { - OnCodeEditingTypesCleared(); - } - } - - public static void Get(VisjectCM contextMenu) - { - Wait(); - - lock (_locker) - { - if (_cache == null) - _cache = new Dictionary, GroupArchetype>(); - contextMenu.LockChildrenRecursive(); - - // Check if has cached groups - if (_cache.Count != 0) - { - // Check if context menu doesn't have the recent cached groups - if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version)) - { - var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray(); - foreach (var g in groups) - contextMenu.RemoveGroup(g); - foreach (var g in _cache.Values) - contextMenu.AddGroup(g); - } - } - else - { - // Remove any old groups from context menu - var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray(); - foreach (var g in groups) - contextMenu.RemoveGroup(g); - - // Register for scripting types reload - Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared; - - // Run caching on an async - _task = Task.Run(OnActiveContextMenuShowAsync); - _taskContextMenu = contextMenu; - } - - contextMenu.UnlockChildrenRecursive(); - } - } - - private static void OnActiveContextMenuShowAsync() - { - Profiler.BeginEvent("Setup Anim Graph Context Menu (async)"); - - foreach (var scriptType in Editor.Instance.CodeEditing.All.Get()) - { - if (!SurfaceUtils.IsValidVisualScriptType(scriptType)) - continue; - - // Skip Newtonsoft.Json stuff - var scriptTypeTypeName = scriptType.TypeName; - if (scriptTypeTypeName.StartsWith("Newtonsoft.Json.")) - continue; - var scriptTypeName = scriptType.Name; - - // Enum - if (scriptType.IsEnum) - { - // Create node archetype - var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone(); - node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type); - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = scriptTypeName; - node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType); - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 2); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(243, 156, 18), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); - continue; - } - - // Structure - if (scriptType.IsValueType) - { - if (scriptType.IsVoid) - continue; - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 4); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(155, 89, 182), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType); - - // Create Pack node archetype - var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = "Pack " + scriptTypeName; - node.Description = tooltip; - ((IList)group.Archetypes).Add(node); - - // Create Unpack node archetype - node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = "Unpack " + scriptTypeName; - node.Description = tooltip; - ((IList)group.Archetypes).Add(node); - } - } - - // Add group to context menu (on a main thread) - FlaxEngine.Scripting.InvokeOnUpdate(() => - { - lock (_locker) - { - _taskContextMenu.AddGroups(_cache.Values); - _taskContextMenu = null; - } - }); - - Profiler.EndEvent(); - - lock (_locker) - { - _task = null; - } - } - - private static void OnCodeEditingTypesCleared() - { - Wait(); - - lock (_locker) - { - _cache.Clear(); - _version++; - } - - Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared; - } - } + private static NodesCache _nodesCache = new NodesCache(IterateNodesCache); /// /// The state machine editing context menu. @@ -378,9 +195,7 @@ namespace FlaxEditor.Surface // Check if show additional nodes in the current surface context if (activeCM != _cmStateMachineMenu) { - Profiler.BeginEvent("Setup Anim Graph Context Menu"); - NodesCache.Get(activeCM); - Profiler.EndEvent(); + _nodesCache.Get(activeCM); base.OnShowPrimaryMenu(activeCM, location, startBox); @@ -394,7 +209,86 @@ namespace FlaxEditor.Surface private void OnActiveContextMenuVisibleChanged(Control activeCM) { - NodesCache.Wait(); + _nodesCache.Wait(); + } + + private static void IterateNodesCache(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version) + { + // Skip Newtonsoft.Json stuff + var scriptTypeTypeName = scriptType.TypeName; + if (scriptTypeTypeName.StartsWith("Newtonsoft.Json.")) + return; + var scriptTypeName = scriptType.Name; + + // Enum + if (scriptType.IsEnum) + { + // Create node archetype + var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone(); + node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type); + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = scriptTypeName; + node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType); + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 2); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(243, 156, 18), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); + return; + } + + // Structure + if (scriptType.IsValueType) + { + if (scriptType.IsVoid) + return; + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 4); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(155, 89, 182), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType); + + // Create Pack node archetype + var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = "Pack " + scriptTypeName; + node.Description = tooltip; + ((IList)group.Archetypes).Add(node); + + // Create Unpack node archetype + node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = "Unpack " + scriptTypeName; + node.Description = tooltip; + ((IList)group.Archetypes).Add(node); + } } /// @@ -488,7 +382,7 @@ namespace FlaxEditor.Surface _cmStateMachineTransitionMenu = null; } ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; - NodesCache.Wait(); + _nodesCache.Wait(); base.OnDestroy(); } diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 659131e8b..3ca7ddfc7 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -1,8 +1,12 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +//#define DEBUG_SEARCH_TIME + using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Scripting; using FlaxEditor.Surface.ContextMenu; using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Undo; @@ -13,6 +17,157 @@ namespace FlaxEditor.Surface { public partial class VisjectSurface { + /// + /// Utility for easy nodes archetypes generation for Visject Surface based on scripting types. + /// + internal class NodesCache + { + public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version); + + internal static readonly List Caches = new List(8); + + private readonly object _locker = new object(); + private readonly IterateType _iterator; + private int _version; + private Task _task; + private VisjectCM _taskContextMenu; + private Dictionary, GroupArchetype> _cache; + + public NodesCache(IterateType iterator) + { + _iterator = iterator; + } + + public void Wait() + { + if (_task != null) + { + Profiler.BeginEvent("Setup Context Menu"); + _task.Wait(); + Profiler.EndEvent(); + } + } + + public void Clear() + { + Wait(); + + if (_cache != null && _cache.Count != 0) + { + OnCodeEditingTypesCleared(); + } + lock (_locker) + { + Caches.Remove(this); + } + } + + public void Get(VisjectCM contextMenu) + { + Profiler.BeginEvent("Setup Context Menu"); + + Wait(); + + lock (_locker) + { + if (_cache == null) + { + if (!Caches.Contains(this)) + Caches.Add(this); + _cache = new Dictionary, GroupArchetype>(); + } + contextMenu.LockChildrenRecursive(); + + // Check if has cached groups + if (_cache.Count != 0) + { + // Check if context menu doesn't have the recent cached groups + if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version)) + { + var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray(); + foreach (var g in groups) + contextMenu.RemoveGroup(g); + foreach (var g in _cache.Values) + contextMenu.AddGroup(g); + } + } + else + { + // Remove any old groups from context menu + var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray(); + foreach (var g in groups) + contextMenu.RemoveGroup(g); + + // Register for scripting types reload + Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared; + + // Run caching on an async + _task = Task.Run(OnActiveContextMenuShowAsync); + _taskContextMenu = contextMenu; + } + + contextMenu.UnlockChildrenRecursive(); + } + + Profiler.EndEvent(); + } + + private void OnActiveContextMenuShowAsync() + { + Profiler.BeginEvent("Setup Context Menu (async)"); +#if DEBUG_SEARCH_TIME + var searchStartTime = DateTime.Now; +#endif + + foreach (var scriptType in Editor.Instance.CodeEditing.All.Get()) + { + if (!SurfaceUtils.IsValidVisualScriptType(scriptType)) + continue; + + _iterator(scriptType, _cache, _version); + } + + // Add group to context menu (on a main thread) + FlaxEngine.Scripting.InvokeOnUpdate(() => + { +#if DEBUG_SEARCH_TIME + var addStartTime = DateTime.Now; +#endif + lock (_locker) + { + _taskContextMenu.AddGroups(_cache.Values); + _taskContextMenu = null; + } +#if DEBUG_SEARCH_TIME + Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms"); +#endif + }); + +#if DEBUG_SEARCH_TIME + Editor.LogError($"Collected items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms"); +#endif + Profiler.EndEvent(); + + lock (_locker) + { + _task = null; + } + } + + private void OnCodeEditingTypesCleared() + { + Wait(); + + lock (_locker) + { + _cache.Clear(); + _version++; + } + + Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared; + } + } + private ContextMenuButton _cmCopyButton; private ContextMenuButton _cmDuplicateButton; private ContextMenuButton _cmFormatNodesConnectionButton; diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 2cb82b351..902582311 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -4,15 +4,10 @@ //#define DEBUG_FIELDS_SEARCHING //#define DEBUG_EVENTS_SEARCHING -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING || DEBUG_EVENTS_SEARCHING -#define DEBUG_SEARCH_TIME -#endif - using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading.Tasks; using FlaxEditor.Content; using FlaxEditor.GUI.Drag; using FlaxEditor.SceneGraph; @@ -41,396 +36,7 @@ namespace FlaxEditor.Surface Archetypes = new List(), }; - internal static class NodesCache - { - private static readonly object _locker = new object(); - private static int _version; - private static Task _task; - private static VisjectCM _taskContextMenu; - private static Dictionary, GroupArchetype> _cache; - - public static void Wait() - { - _task?.Wait(); - } - - public static void Clear() - { - Wait(); - - if (_cache != null && _cache.Count != 0) - { - OnCodeEditingTypesCleared(); - } - } - - public static void Get(VisjectCM contextMenu) - { - Wait(); - - lock (_locker) - { - if (_cache == null) - _cache = new Dictionary, GroupArchetype>(); - contextMenu.LockChildrenRecursive(); - - // Check if has cached groups - if (_cache.Count != 0) - { - // Check if context menu doesn't have the recent cached groups - if (!contextMenu.Groups.Any(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int asInt && asInt == _version)) - { - var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray(); - foreach (var g in groups) - contextMenu.RemoveGroup(g); - foreach (var g in _cache.Values) - contextMenu.AddGroup(g); - } - } - else - { - // Remove any old groups from context menu - var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray(); - foreach (var g in groups) - contextMenu.RemoveGroup(g); - - // Register for scripting types reload - Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared; - - // Run caching on an async - _task = Task.Run(OnActiveContextMenuShowAsync); - _taskContextMenu = contextMenu; - } - - contextMenu.UnlockChildrenRecursive(); - } - } - - private static void OnActiveContextMenuShowAsync() - { - Profiler.BeginEvent("Setup Visual Script Context Menu (async)"); -#if DEBUG_SEARCH_TIME - var searchStartTime = DateTime.Now; - var searchHitsCount = 0; -#endif - - foreach (var scriptType in Editor.Instance.CodeEditing.All.Get()) - { - if (!SurfaceUtils.IsValidVisualScriptType(scriptType)) - continue; - - // Skip Newtonsoft.Json stuff - var scriptTypeTypeName = scriptType.TypeName; - if (scriptTypeTypeName.StartsWith("Newtonsoft.Json.")) - continue; - var scriptTypeName = scriptType.Name; - - // Enum - if (scriptType.IsEnum) - { - // Create node archetype - var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone(); - node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type); - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = scriptTypeName; - node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType); - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 2); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(243, 156, 18), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); - continue; - } - - // Structure - if (scriptType.IsValueType) - { - if (scriptType.IsVoid) - continue; - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 4); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(155, 89, 182), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType); - - // Create Pack node archetype - var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = "Pack " + scriptTypeName; - node.Description = tooltip; - ((IList)group.Archetypes).Add(node); - - // Create Unpack node archetype - node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = "Unpack " + scriptTypeName; - node.Description = tooltip; - ((IList)group.Archetypes).Add(node); - } - - foreach (var member in scriptType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) - { - if (member.IsGeneric) - continue; - - if (member.IsMethod) - { - // Skip methods not declared in this type - if (member.Type is MethodInfo m && m.GetBaseDefinition().DeclaringType != m.DeclaringType) - continue; - var name = member.Name; - if (name == "ToString") - continue; - - // Skip if searching by name doesn't return a match - var members = scriptType.GetMembers(name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); - if (!members.Contains(member)) - continue; - - // Check if method is valid for Visual Script usage - if (SurfaceUtils.IsValidVisualScriptInvokeMethod(member, out var parameters)) - { - // Create node archetype - var node = (NodeArchetype)Archetypes.Function.Nodes[3].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.DefaultValues[1] = name; - node.DefaultValues[2] = parameters.Length; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = SurfaceUtils.GetMethodDisplayName((string)node.DefaultValues[1]); - node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); - node.SubTitle = string.Format(" (in {0})", scriptTypeName); - node.Tag = member; - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 16); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(109, 160, 24), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); -#if DEBUG_INVOKE_METHODS_SEARCHING - Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); - searchHitsCount++; -#endif - } - } - else if (member.IsField) - { - var name = member.Name; - - // Skip if searching by name doesn't return a match - var members = scriptType.GetMembers(name, MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); - if (!members.Contains(member)) - continue; - - // Check if field is valid for Visual Script usage - if (SurfaceUtils.IsValidVisualScriptField(member)) - { - if (member.HasGet) - { - // Create node archetype - var node = (NodeArchetype)Archetypes.Function.Nodes[6].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.DefaultValues[1] = name; - node.DefaultValues[2] = member.ValueType.TypeName; - node.DefaultValues[3] = member.IsStatic; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = "Get " + name; - node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); - node.SubTitle = string.Format(" (in {0})", scriptTypeName); - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 16); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(109, 160, 24), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); -#if DEBUG_FIELDS_SEARCHING - Editor.LogWarning(scriptTypeTypeName + " -> Get " + member.GetSignature()); - searchHitsCount++; -#endif - } - if (member.HasSet) - { - // Create node archetype - var node = (NodeArchetype)Archetypes.Function.Nodes[7].Clone(); - node.DefaultValues[0] = scriptTypeTypeName; - node.DefaultValues[1] = name; - node.DefaultValues[2] = member.ValueType.TypeName; - node.DefaultValues[3] = member.IsStatic; - node.Flags &= ~NodeFlags.NoSpawnViaGUI; - node.Title = "Set " + name; - node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); - node.SubTitle = string.Format(" (in {0})", scriptTypeName); - - // Create group archetype - var groupKey = new KeyValuePair(scriptTypeName, 16); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(109, 160, 24), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); -#if DEBUG_FIELDS_SEARCHING - Editor.LogWarning(scriptTypeTypeName + " -> Set " + member.GetSignature()); - searchHitsCount++; -#endif - } - } - } - else if (member.IsEvent) - { - var name = member.Name; - - // Skip if searching by name doesn't return a match - var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); - if (!members.Contains(member)) - continue; - - // Check if field is valid for Visual Script usage - if (SurfaceUtils.IsValidVisualScriptEvent(member)) - { - var groupKey = new KeyValuePair(scriptTypeName, 16); - if (!_cache.TryGetValue(groupKey, out var group)) - { - group = new GroupArchetype - { - GroupID = groupKey.Value, - Name = groupKey.Key, - Color = new Color(109, 160, 24), - Tag = _version, - Archetypes = new List(), - }; - _cache.Add(groupKey, group); - } - - // Add Bind event node - var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone(); - bindNode.DefaultValues[0] = scriptTypeTypeName; - bindNode.DefaultValues[1] = name; - bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; - bindNode.Title = "Bind " + name; - bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); - bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName); - ((IList)group.Archetypes).Add(bindNode); - - // Add Unbind event node - var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone(); - unbindNode.DefaultValues[0] = scriptTypeTypeName; - unbindNode.DefaultValues[1] = name; - unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; - unbindNode.Title = "Unbind " + name; - unbindNode.Description = bindNode.Description; - unbindNode.SubTitle = bindNode.SubTitle; - ((IList)group.Archetypes).Add(unbindNode); - -#if DEBUG_EVENTS_SEARCHING - Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); - searchHitsCount++; -#endif - } - } - } - } - - // Add group to context menu (on a main thread) - FlaxEngine.Scripting.InvokeOnUpdate(() => - { -#if DEBUG_SEARCH_TIME - var addStartTime = DateTime.Now; -#endif - lock (_locker) - { - _taskContextMenu.AddGroups(_cache.Values); - _taskContextMenu = null; - } -#if DEBUG_SEARCH_TIME - Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms"); -#endif - }); - -#if DEBUG_SEARCH_TIME - Editor.LogError($"Collected {searchHitsCount} items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms"); -#endif - Profiler.EndEvent(); - - lock (_locker) - { - _task = null; - } - } - - private static void OnCodeEditingTypesCleared() - { - Wait(); - - lock (_locker) - { - _cache.Clear(); - _version++; - } - - Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared; - } - } - + private static NodesCache _nodesCache = new NodesCache(IterateNodesCache); private DragActors _dragActors; /// @@ -547,9 +153,8 @@ namespace FlaxEditor.Surface /// protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox) { - Profiler.BeginEvent("Setup Visual Script Context Menu"); - // Update nodes for method overrides + Profiler.BeginEvent("Overrides"); activeCM.RemoveGroup(_methodOverridesGroupArchetype); if (Script && !Script.WaitForLoaded(100)) { @@ -595,11 +200,10 @@ namespace FlaxEditor.Surface activeCM.AddGroup(_methodOverridesGroupArchetype, false); } + Profiler.EndEvent(); // Update nodes for invoke methods (async) - NodesCache.Get(activeCM); - - Profiler.EndEvent(); + _nodesCache.Get(activeCM); base.OnShowPrimaryMenu(activeCM, location, startBox); @@ -608,7 +212,276 @@ namespace FlaxEditor.Surface private void OnActiveContextMenuVisibleChanged(Control activeCM) { - NodesCache.Wait(); + _nodesCache.Wait(); + } + + private static void IterateNodesCache(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version) + { + // Skip Newtonsoft.Json stuff + var scriptTypeTypeName = scriptType.TypeName; + if (scriptTypeTypeName.StartsWith("Newtonsoft.Json.")) + return; + var scriptTypeName = scriptType.Name; + + // Enum + if (scriptType.IsEnum) + { + // Create node archetype + var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone(); + node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type); + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = scriptTypeName; + node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType); + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 2); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(243, 156, 18), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); + return; + } + + // Structure + if (scriptType.IsValueType) + { + if (scriptType.IsVoid) + return; + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 4); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(155, 89, 182), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType); + + // Create Pack node archetype + var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = "Pack " + scriptTypeName; + node.Description = tooltip; + ((IList)group.Archetypes).Add(node); + + // Create Unpack node archetype + node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = "Unpack " + scriptTypeName; + node.Description = tooltip; + ((IList)group.Archetypes).Add(node); + } + + foreach (var member in scriptType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + if (member.IsGeneric) + continue; + + if (member.IsMethod) + { + // Skip methods not declared in this type + if (member.Type is MethodInfo m && m.GetBaseDefinition().DeclaringType != m.DeclaringType) + continue; + var name = member.Name; + if (name == "ToString") + continue; + + // Skip if searching by name doesn't return a match + var members = scriptType.GetMembers(name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (!members.Contains(member)) + continue; + + // Check if method is valid for Visual Script usage + if (SurfaceUtils.IsValidVisualScriptInvokeMethod(member, out var parameters)) + { + // Create node archetype + var node = (NodeArchetype)Archetypes.Function.Nodes[3].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.DefaultValues[1] = name; + node.DefaultValues[2] = parameters.Length; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = SurfaceUtils.GetMethodDisplayName((string)node.DefaultValues[1]); + node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + node.SubTitle = string.Format(" (in {0})", scriptTypeName); + node.Tag = member; + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); +#if DEBUG_INVOKE_METHODS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); +#endif + } + } + else if (member.IsField) + { + var name = member.Name; + + // Skip if searching by name doesn't return a match + var members = scriptType.GetMembers(name, MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (!members.Contains(member)) + continue; + + // Check if field is valid for Visual Script usage + if (SurfaceUtils.IsValidVisualScriptField(member)) + { + if (member.HasGet) + { + // Create node archetype + var node = (NodeArchetype)Archetypes.Function.Nodes[6].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.DefaultValues[1] = name; + node.DefaultValues[2] = member.ValueType.TypeName; + node.DefaultValues[3] = member.IsStatic; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = "Get " + name; + node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + node.SubTitle = string.Format(" (in {0})", scriptTypeName); + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); +#if DEBUG_FIELDS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> Get " + member.GetSignature()); +#endif + } + if (member.HasSet) + { + // Create node archetype + var node = (NodeArchetype)Archetypes.Function.Nodes[7].Clone(); + node.DefaultValues[0] = scriptTypeTypeName; + node.DefaultValues[1] = name; + node.DefaultValues[2] = member.ValueType.TypeName; + node.DefaultValues[3] = member.IsStatic; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = "Set " + name; + node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + node.SubTitle = string.Format(" (in {0})", scriptTypeName); + + // Create group archetype + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); +#if DEBUG_FIELDS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> Set " + member.GetSignature()); +#endif + } + } + } + else if (member.IsEvent) + { + var name = member.Name; + + // Skip if searching by name doesn't return a match + var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (!members.Contains(member)) + continue; + + // Check if field is valid for Visual Script usage + if (SurfaceUtils.IsValidVisualScriptEvent(member)) + { + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Add Bind event node + var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone(); + bindNode.DefaultValues[0] = scriptTypeTypeName; + bindNode.DefaultValues[1] = name; + bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + bindNode.Title = "Bind " + name; + bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName); + ((IList)group.Archetypes).Add(bindNode); + + // Add Unbind event node + var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone(); + unbindNode.DefaultValues[0] = scriptTypeTypeName; + unbindNode.DefaultValues[1] = name; + unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + unbindNode.Title = "Unbind " + name; + unbindNode.Description = bindNode.Description; + unbindNode.SubTitle = bindNode.SubTitle; + ((IList)group.Archetypes).Add(unbindNode); + +#if DEBUG_EVENTS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); +#endif + } + } + } } /// @@ -686,7 +559,7 @@ namespace FlaxEditor.Surface { if (IsDisposing) return; - NodesCache.Wait(); + _nodesCache.Wait(); base.OnDestroy(); } From cab1d8cac4d7804bf3a71bc74e96654de66fe16b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Aug 2023 16:29:07 +0200 Subject: [PATCH 057/294] Fix missing Visject CM groups auto-expanding if enabled --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index d293d82f3..e7b71ddb0 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -283,6 +283,8 @@ namespace FlaxEditor.Surface.ContextMenu { group.UnlockChildrenRecursive(); SortGroups(); + if (ShowExpanded) + group.Open(false); group.PerformLayout(); if (_searchBox.TextLength != 0) { @@ -323,6 +325,8 @@ namespace FlaxEditor.Surface.ContextMenu }; } group.SortChildren(); + if (ShowExpanded) + group.Open(false); group.Parent = _groupsPanel; _groups.Add(group); From b5fa5fa68e6082d45883e9a82b84566f86fee1ab Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Aug 2023 10:11:58 +0200 Subject: [PATCH 058/294] Add `SerializableScriptingObject` for easier serialization of scripting objects in gameplay or content --- Source/Engine/Animations/AnimEvent.cpp | 70 ------------------- Source/Engine/Animations/AnimEvent.h | 9 +-- Source/Engine/Animations/Animations.cpp | 11 +++ Source/Engine/Scripting/ScriptingObject.cpp | 61 ++++++++++++++++ .../Scripting/SerializableScriptingObject.h | 18 +++++ 5 files changed, 92 insertions(+), 77 deletions(-) delete mode 100644 Source/Engine/Animations/AnimEvent.cpp create mode 100644 Source/Engine/Scripting/SerializableScriptingObject.h diff --git a/Source/Engine/Animations/AnimEvent.cpp b/Source/Engine/Animations/AnimEvent.cpp deleted file mode 100644 index e46a99c62..000000000 --- a/Source/Engine/Animations/AnimEvent.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -#include "AnimEvent.h" -#include "Engine/Scripting/BinaryModule.h" -#include "Engine/Scripting/Internal/ManagedSerialization.h" -#include "Engine/Serialization/SerializationFwd.h" -#include "Engine/Serialization/Serialization.h" - -AnimEvent::AnimEvent(const SpawnParams& params) - : ScriptingObject(params) -{ -} - -void AnimEvent::Serialize(SerializeStream& stream, const void* otherObj) -{ - SERIALIZE_GET_OTHER_OBJ(AnimEvent); - -#if !COMPILE_WITHOUT_CSHARP - // Handle C# objects data serialization - if (EnumHasAnyFlags(Flags, ObjectFlags::IsManagedType)) - { - stream.JKEY("V"); - if (other) - { - ManagedSerialization::SerializeDiff(stream, GetOrCreateManagedInstance(), other->GetOrCreateManagedInstance()); - } - else - { - ManagedSerialization::Serialize(stream, GetOrCreateManagedInstance()); - } - } -#endif - - // Handle custom scripting objects data serialization - if (EnumHasAnyFlags(Flags, ObjectFlags::IsCustomScriptingType)) - { - stream.JKEY("D"); - _type.Module->SerializeObject(stream, this, other); - } -} - -void AnimEvent::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ -#if !COMPILE_WITHOUT_CSHARP - // Handle C# objects data serialization - if (EnumHasAnyFlags(Flags, ObjectFlags::IsManagedType)) - { - auto* const v = SERIALIZE_FIND_MEMBER(stream, "V"); - if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) - { - ManagedSerialization::Deserialize(v->value, GetOrCreateManagedInstance()); - } - } -#endif - - // Handle custom scripting objects data serialization - if (EnumHasAnyFlags(Flags, ObjectFlags::IsCustomScriptingType)) - { - auto* const v = SERIALIZE_FIND_MEMBER(stream, "D"); - if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) - { - _type.Module->DeserializeObject(v->value, this, modifier); - } - } -} - -AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params) - : AnimEvent(params) -{ -} diff --git a/Source/Engine/Animations/AnimEvent.h b/Source/Engine/Animations/AnimEvent.h index 2028501ea..4ed7eab48 100644 --- a/Source/Engine/Animations/AnimEvent.h +++ b/Source/Engine/Animations/AnimEvent.h @@ -2,8 +2,7 @@ #pragma once -#include "Engine/Scripting/ScriptingObject.h" -#include "Engine/Core/ISerializable.h" +#include "Engine/Scripting/SerializableScriptingObject.h" #if USE_EDITOR #include "Engine/Core/Math/Color.h" #endif @@ -14,7 +13,7 @@ class Animation; /// /// The animation notification event triggered during animation playback. /// -API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public ScriptingObject, public ISerializable +API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public SerializableScriptingObject { DECLARE_SCRIPTING_TYPE(AnimEvent); @@ -35,10 +34,6 @@ API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public ScriptingObject, pub API_FUNCTION() virtual void OnEvent(AnimatedModel* actor, Animation* anim, float time, float deltaTime) { } - - // [ISerializable] - void Serialize(SerializeStream& stream, const void* otherObj) override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; /// diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index 3c80cadbe..7dd2834bf 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Animations.h" +#include "AnimEvent.h" #include "Engine/Engine/Engine.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Level/Actors/AnimatedModel.h" @@ -52,6 +53,16 @@ TaskGraphSystem* Animations::System = nullptr; Delegate Animations::DebugFlow; #endif +AnimEvent::AnimEvent(const SpawnParams& params) + : SerializableScriptingObject(params) +{ +} + +AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params) + : AnimEvent(params) +{ +} + bool AnimationsService::Init() { Animations::System = New(); diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 9ece58f2a..c22616cd5 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "ScriptingObject.h" +#include "SerializableScriptingObject.h" #include "Scripting.h" #include "BinaryModule.h" #include "Engine/Level/Actor.h" @@ -10,12 +11,14 @@ #include "Engine/Content/Content.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/ThreadLocal.h" +#include "Engine/Serialization/SerializationFwd.h" #include "ManagedCLR/MAssembly.h" #include "ManagedCLR/MClass.h" #include "ManagedCLR/MUtils.h" #include "ManagedCLR/MField.h" #include "ManagedCLR/MCore.h" #include "Internal/InternalCalls.h" +#include "Internal/ManagedSerialization.h" #include "FlaxEngine.Gen.h" #define ScriptingObject_unmanagedPtr "__unmanagedPtr" @@ -24,6 +27,64 @@ // TODO: don't leak memory (use some kind of late manual GC for those wrapper objects) Dictionary ScriptingObjectsInterfaceWrappers; +SerializableScriptingObject::SerializableScriptingObject(const SpawnParams& params) + : ScriptingObject(params) +{ +} + +void SerializableScriptingObject::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(SerializableScriptingObject); + +#if !COMPILE_WITHOUT_CSHARP + // Handle C# objects data serialization + if (EnumHasAnyFlags(Flags, ObjectFlags::IsManagedType)) + { + stream.JKEY("V"); + if (other) + { + ManagedSerialization::SerializeDiff(stream, GetOrCreateManagedInstance(), other->GetOrCreateManagedInstance()); + } + else + { + ManagedSerialization::Serialize(stream, GetOrCreateManagedInstance()); + } + } +#endif + + // Handle custom scripting objects data serialization + if (EnumHasAnyFlags(Flags, ObjectFlags::IsCustomScriptingType)) + { + stream.JKEY("D"); + _type.Module->SerializeObject(stream, this, other); + } +} + +void SerializableScriptingObject::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ +#if !COMPILE_WITHOUT_CSHARP + // Handle C# objects data serialization + if (EnumHasAnyFlags(Flags, ObjectFlags::IsManagedType)) + { + auto* const v = SERIALIZE_FIND_MEMBER(stream, "V"); + if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) + { + ManagedSerialization::Deserialize(v->value, GetOrCreateManagedInstance()); + } + } +#endif + + // Handle custom scripting objects data serialization + if (EnumHasAnyFlags(Flags, ObjectFlags::IsCustomScriptingType)) + { + auto* const v = SERIALIZE_FIND_MEMBER(stream, "D"); + if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) + { + _type.Module->DeserializeObject(v->value, this, modifier); + } + } +} + ScriptingObject::ScriptingObject(const SpawnParams& params) : _gcHandle(0) , _type(params.Type) diff --git a/Source/Engine/Scripting/SerializableScriptingObject.h b/Source/Engine/Scripting/SerializableScriptingObject.h new file mode 100644 index 000000000..8cd20af0b --- /dev/null +++ b/Source/Engine/Scripting/SerializableScriptingObject.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Core/ISerializable.h" + +/// +/// Base class for scripting objects that contain in-built serialization via ISerializable interface. +/// +API_CLASS() class FLAXENGINE_API SerializableScriptingObject : public ScriptingObject, public ISerializable +{ + DECLARE_SCRIPTING_TYPE(SerializableScriptingObject); + + // [ISerializable] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +}; From 05d477d6c8552434fa4538e162a7549899740b38 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Aug 2023 12:52:54 +0200 Subject: [PATCH 059/294] Add `SelectionChanged` public event for `VisjectSurface` and properly invoke it only when selection actually changes --- Source/Editor/Surface/VisjectSurface.cs | 47 ++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 03e32cb29..a0dc4a955 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -112,7 +112,7 @@ namespace FlaxEditor.Surface /// /// Occurs when selection gets changed. /// - protected event Action SelectionChanged; + public event Action SelectionChanged; /// /// The surface owner. @@ -252,6 +252,9 @@ namespace FlaxEditor.Surface /// /// Gets the list of the selected nodes. /// + /// + /// Don't call it too often. It does memory allocation and iterates over the surface controls to find selected nodes in the graph. + /// public List SelectedNodes { get @@ -269,6 +272,9 @@ namespace FlaxEditor.Surface /// /// Gets the list of the selected controls (comments and nodes). /// + /// + /// Don't call it too often. It does memory allocation and iterates over the surface controls to find selected nodes in the graph. + /// public List SelectedControls { get @@ -573,12 +579,17 @@ namespace FlaxEditor.Surface /// public void SelectAll() { + bool selectionChanged = false; for (int i = 0; i < _rootControl.Children.Count; i++) { - if (_rootControl.Children[i] is SurfaceControl control) + if (_rootControl.Children[i] is SurfaceControl control && !control.IsSelected) + { control.IsSelected = true; + selectionChanged = true; + } } - SelectionChanged?.Invoke(); + if (selectionChanged) + SelectionChanged?.Invoke(); } /// @@ -586,12 +597,17 @@ namespace FlaxEditor.Surface /// public void ClearSelection() { + bool selectionChanged = false; for (int i = 0; i < _rootControl.Children.Count; i++) { if (_rootControl.Children[i] is SurfaceControl control) + { control.IsSelected = false; + selectionChanged = true; + } } - SelectionChanged?.Invoke(); + if (selectionChanged) + SelectionChanged?.Invoke(); } /// @@ -600,6 +616,8 @@ namespace FlaxEditor.Surface /// The control. public void AddToSelection(SurfaceControl control) { + if (control.IsSelected) + return; control.IsSelected = true; SelectionChanged?.Invoke(); } @@ -610,9 +628,22 @@ namespace FlaxEditor.Surface /// The control. public void Select(SurfaceControl control) { - ClearSelection(); - control.IsSelected = true; - SelectionChanged?.Invoke(); + bool selectionChanged = false; + for (int i = 0; i < _rootControl.Children.Count; i++) + { + if (_rootControl.Children[i] is SurfaceControl c && c != control && c.IsSelected) + { + c.IsSelected = false; + selectionChanged = true; + } + } + if (!control.IsSelected) + { + control.IsSelected = true; + selectionChanged = true; + } + if (selectionChanged) + SelectionChanged?.Invoke(); } /// @@ -903,7 +934,7 @@ namespace FlaxEditor.Surface { return _context.FindNode(id); } - + /// /// Adds the undo action to be batched (eg. if multiple undo actions is performed in a sequence during single update). /// From 73bbe63c1ebca6663c9dc49e3aebfec95a2e353f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Aug 2023 13:10:36 +0200 Subject: [PATCH 060/294] Missing change for 05d477d6c8552434fa4538e162a7549899740b38 --- Source/Editor/Surface/VisjectSurface.Input.cs | 10 +++++++++- Source/Editor/Surface/VisjectSurface.cs | 16 +++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 7264321c3..ecc29d74f 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -115,15 +115,23 @@ namespace FlaxEditor.Surface var p1 = _rootControl.PointFromParent(ref _leftMouseDownPos); var p2 = _rootControl.PointFromParent(ref _mousePos); var selectionRect = Rectangle.FromPoints(p1, p2); + var selectionChanged = false; // Find controls to select for (int i = 0; i < _rootControl.Children.Count; i++) { if (_rootControl.Children[i] is SurfaceControl control) { - control.IsSelected = control.IsSelectionIntersecting(ref selectionRect); + var select = control.IsSelectionIntersecting(ref selectionRect); + if (select != control.IsSelected) + { + control.IsSelected = select; + selectionChanged = true; + } } } + if (selectionChanged) + SelectionChanged?.Invoke(); } private void OnSurfaceControlSpawned(SurfaceControl control) diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index a0dc4a955..675598b16 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; using FlaxEditor.Options; @@ -600,7 +601,7 @@ namespace FlaxEditor.Surface bool selectionChanged = false; for (int i = 0; i < _rootControl.Children.Count; i++) { - if (_rootControl.Children[i] is SurfaceControl control) + if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected) { control.IsSelected = false; selectionChanged = true; @@ -652,11 +653,13 @@ namespace FlaxEditor.Surface /// The controls. public void Select(IEnumerable controls) { + var newSelection = controls.ToList(); + var prevSelection = SelectedControls; + if (Utils.ArraysEqual(newSelection, prevSelection)) + return; ClearSelection(); - foreach (var control in controls) - { + foreach (var control in newSelection) control.IsSelected = true; - } SelectionChanged?.Invoke(); } @@ -666,6 +669,8 @@ namespace FlaxEditor.Surface /// The control. public void Deselect(SurfaceControl control) { + if (!control.IsSelected) + return; control.IsSelected = false; SelectionChanged?.Invoke(); } @@ -724,11 +729,8 @@ namespace FlaxEditor.Surface Context.OnControlDeleted(control); } } - if (selectionChanged) - { SelectionChanged?.Invoke(); - } if (nodes != null) { From 1958adb126a155431f979bfa3883afbd8dda2803 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Aug 2023 13:19:03 +0200 Subject: [PATCH 061/294] Add `AI` module to engine --- Source/Engine/AI/AI.Build.cs | 18 ++++++++++++++++++ Source/Engine/Engine/Engine.Build.cs | 1 + 2 files changed, 19 insertions(+) create mode 100644 Source/Engine/AI/AI.Build.cs diff --git a/Source/Engine/AI/AI.Build.cs b/Source/Engine/AI/AI.Build.cs new file mode 100644 index 000000000..db975dbfd --- /dev/null +++ b/Source/Engine/AI/AI.Build.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// Artificial Intelligence module. +/// +public class AI : EngineModule +{ + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PrivateDependencies.Add("Navigation"); + } +} diff --git a/Source/Engine/Engine/Engine.Build.cs b/Source/Engine/Engine/Engine.Build.cs index 56997a721..6a317f112 100644 --- a/Source/Engine/Engine/Engine.Build.cs +++ b/Source/Engine/Engine/Engine.Build.cs @@ -14,6 +14,7 @@ public class Engine : EngineModule { base.Setup(options); + options.PublicDependencies.Add("AI"); options.PublicDependencies.Add("Animations"); options.PublicDependencies.Add("Audio"); options.PublicDependencies.Add("Content"); From af3d9d0eb3acb07dcb078ff23cf08e96ecec1caf Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:28:47 -0400 Subject: [PATCH 062/294] Re-implement functionality for importing materials as instances. --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 10 +++++++--- Source/Engine/Tools/ModelTool/ModelTool.h | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 13fc753af..dfc249c6a 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -385,6 +385,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(LODCount); SERIALIZE(TriangleReduction); SERIALIZE(ImportMaterials); + SERIALIZE(ImportMaterialsAsInstances); + SERIALIZE(InstanceToImportAs); SERIALIZE(ImportTextures); SERIALIZE(RestoreMaterialsOnReimport); SERIALIZE(GenerateSDF); @@ -426,6 +428,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(LODCount); DESERIALIZE(TriangleReduction); DESERIALIZE(ImportMaterials); + DESERIALIZE(ImportMaterialsAsInstances); + DESERIALIZE(InstanceToImportAs); DESERIALIZE(ImportTextures); DESERIALIZE(RestoreMaterialsOnReimport); DESERIALIZE(GenerateSDF); @@ -977,17 +981,17 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op if (options.ImportMaterialsAsInstances) { AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID); - MaterialInstance* materialInstance = (MaterialInstance*) LoadAsset(assetPath, MaterialInstance::TypeInitializer); + MaterialInstance* materialInstance = (MaterialInstance*)LoadAsset(assetPath, MaterialInstance::TypeInitializer); if (materialInstance->WaitForLoaded()) { LOG(Error, "Failed to load material instance after creation. ({0})", assetPath); return true; } - MaterialBase* materialInstanceOf = (MaterialBase*) LoadAsset(options.InstanceToImportAs, MaterialBase::TypeInitializer); + MaterialBase* materialInstanceOf = options.InstanceToImportAs; if (materialInstanceOf->WaitForLoaded()) { - LOG(Error, "Failed to load material to create an instance of. ({0})", options.InstanceToImportAs); + LOG(Error, "Failed to load material to create an instance of. ({0})", options.InstanceToImportAs->GetID()); return true; } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 992a929ca..f6aaefa2b 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -12,6 +12,7 @@ #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Animations/AnimationData.h" #include +#include class JsonWriter; @@ -329,6 +330,12 @@ public: // If checked, the importer will create materials for model meshes as specified in the file. API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportMaterials = true; + // If checked, the importer will create the model's materials as instances of a base material. + API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials))") + bool ImportMaterialsAsInstances = false; + // The material to import the model's materials as an instance of. + API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))") + AssetReference InstanceToImportAs = (Material*) GPUDevice::Instance->GetDefaultMaterial(); // If checked, the importer will import texture files used by the model and any embedded texture resources. API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportTextures = true; From 52e546b3ba936e66928ac46b2fab0d6bf0942df3 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:29:14 -0400 Subject: [PATCH 063/294] fix unneeded include. --- Source/ThirdParty/OpenFBX/ofbx.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/ThirdParty/OpenFBX/ofbx.h b/Source/ThirdParty/OpenFBX/ofbx.h index 87dd5999f..7066659e3 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.h +++ b/Source/ThirdParty/OpenFBX/ofbx.h @@ -1,5 +1,4 @@ #pragma once -#include namespace ofbx From ae4bce7a683a33f3721f5cf0f3ec3fea60a6ffaa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 13 Aug 2023 19:14:23 +0200 Subject: [PATCH 064/294] Add Visject surface boxes and connections drawing customization via style --- .../Archetypes/Animation.StateMachine.cs | 11 +++-- Source/Editor/Surface/Archetypes/Tools.cs | 2 +- Source/Editor/Surface/Elements/Box.cs | 45 +++---------------- Source/Editor/Surface/Elements/InputBox.cs | 2 +- Source/Editor/Surface/Elements/OutputBox.cs | 18 +++++--- Source/Editor/Surface/SurfaceNode.cs | 15 +++---- Source/Editor/Surface/SurfaceStyle.cs | 45 +++++++++++++++++++ Source/Editor/Surface/VisjectSurface.Draw.cs | 2 +- 8 files changed, 78 insertions(+), 62 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 7e547c326..f044cd6ff 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -484,7 +484,7 @@ namespace FlaxEditor.Surface.Archetypes var startPos = PointToParent(ref center); targetState.GetConnectionEndPoint(ref startPos, out var endPos); var color = style.Foreground; - StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color); + StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); } } @@ -514,7 +514,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color); + StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); } /// @@ -680,11 +680,10 @@ namespace FlaxEditor.Surface.Archetypes /// /// Draws the connection between two state machine nodes. /// - /// The surface. /// The start position. /// The end position. /// The line color. - public static void DrawConnection(VisjectSurface surface, ref Float2 startPos, ref Float2 endPos, ref Color color) + public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color) { var sub = endPos - startPos; var length = sub.Length; @@ -1293,7 +1292,7 @@ namespace FlaxEditor.Surface.Archetypes isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f; } var color = isMouseOver ? Color.Wheat : t.LineColor; - DrawConnection(Surface, ref t.StartPos, ref t.EndPos, ref color); + DrawConnection(ref t.StartPos, ref t.EndPos, ref color); } } @@ -1322,7 +1321,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - DrawConnection(Surface, ref startPos, ref endPos, ref color); + DrawConnection(ref startPos, ref endPos, ref color); } /// diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 9f6db7ea4..d6c5d8c30 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -1237,7 +1237,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - OutputBox.DrawConnection(ref startPos, ref endPos, ref color, 2); + OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2); } /// diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index e771fa71c..8b0caab84 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -133,6 +133,11 @@ namespace FlaxEditor.Surface.Elements } } + /// + /// Cached color for . + /// + public Color CurrentTypeColor => _currentTypeColor; + /// /// The collection of the attributes used by the box. Assigned externally. Can be used to control the default value editing for the or to provide more metadata for the surface UI. /// @@ -544,44 +549,6 @@ namespace FlaxEditor.Surface.Elements } } - /// - /// Draws the box GUI using . - /// - protected void DrawBox() - { - var rect = new Rectangle(Float2.Zero, Size); - - // Size culling - const float minBoxSize = 5.0f; - if (rect.Size.LengthSquared < minBoxSize * minBoxSize) - return; - - // Debugging boxes size - //Render2D.DrawRectangle(rect, Color.Orange); return; - - // Draw icon - bool hasConnections = HasAnyConnection; - float alpha = Enabled ? 1.0f : 0.6f; - Color color = _currentTypeColor * alpha; - var style = Surface.Style; - SpriteHandle icon; - if (_currentType.Type == typeof(void)) - icon = hasConnections ? style.Icons.ArrowClose : style.Icons.ArrowOpen; - else - icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen; - color *= ConnectionsHighlightIntensity + 1; - Render2D.DrawSprite(icon, rect, color); - - // Draw selection hint - if (_isSelected) - { - float outlineAlpha = Mathf.Sin(Time.TimeSinceStartup * 4.0f) * 0.5f + 0.5f; - float outlineWidth = Mathf.Lerp(1.5f, 4.0f, outlineAlpha); - var outlineRect = new Rectangle(rect.X - outlineWidth, rect.Y - outlineWidth, rect.Width + outlineWidth * 2, rect.Height + outlineWidth * 2); - Render2D.DrawSprite(icon, outlineRect, FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f)); - } - } - /// public override void OnMouseEnter(Float2 location) { @@ -813,7 +780,7 @@ namespace FlaxEditor.Surface.Elements /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - OutputBox.DrawConnection(ref startPos, ref endPos, ref color, 2); + OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2); } /// diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 4eac03105..2047bcd1a 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1438,7 +1438,7 @@ namespace FlaxEditor.Surface.Elements base.Draw(); // Box - DrawBox(); + Surface.Style.DrawBox(this); // Draw text var style = Style.Current; diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 67b6e8409..cf527402d 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -27,12 +27,19 @@ namespace FlaxEditor.Surface.Elements /// /// Draws the connection between two boxes. /// + /// The Visject surface style. /// The start location. /// The end location. /// The connection color. /// The connection thickness. - public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1) + public static void DrawConnection(SurfaceStyle style, ref Float2 start, ref Float2 end, ref Color color, float thickness = 1) { + if (style.DrawConnection != null) + { + style.DrawConnection(start, end, color, thickness); + return; + } + // Calculate control points var dst = (end - start) * new Float2(0.5f, 0.05f); var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); @@ -122,8 +129,9 @@ namespace FlaxEditor.Surface.Elements /// public void DrawConnections(ref Float2 mousePosition) { - float mouseOverDistance = MouseOverConnectionDistance; // Draw all the connections + var style = Surface.Style; + var mouseOverDistance = MouseOverConnectionDistance; var startPos = Parent.PointToParent(Center); var startHighlight = ConnectionsHighlightIntensity; for (int i = 0; i < Connections.Count; i++) @@ -139,7 +147,7 @@ namespace FlaxEditor.Surface.Elements highlight += 0.5f; } - DrawConnection(ref startPos, ref endPos, ref color, highlight); + DrawConnection(style, ref startPos, ref endPos, ref color, highlight); } } @@ -151,7 +159,7 @@ namespace FlaxEditor.Surface.Elements // Draw all the connections var startPos = Parent.PointToParent(Center); var endPos = targetBox.Parent.PointToParent(targetBox.Center); - DrawConnection(ref startPos, ref endPos, ref _currentTypeColor, 2.5f); + DrawConnection(Surface.Style, ref startPos, ref endPos, ref _currentTypeColor, 2.5f); } /// @@ -163,7 +171,7 @@ namespace FlaxEditor.Surface.Elements base.Draw(); // Box - DrawBox(); + Surface.Style.DrawBox(this); // Draw text var style = Style.Current; diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index c24c47fbd..a15f5e42d 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -165,25 +165,22 @@ namespace FlaxEditor.Surface { if (Surface == null) return; - Size = CalculateNodeSize(width, height); - // Update boxes on width change - //if (!Mathf.NearEqual(prevSize.X, Size.X)) + for (int i = 0; i < Elements.Count; i++) { - for (int i = 0; i < Elements.Count; i++) + if (Elements[i] is OutputBox box) { - if (Elements[i] is OutputBox box) - { - box.Location = box.Archetype.Position + new Float2(width, 0); - } + box.Location = box.Archetype.Position + new Float2(width, 0); } } + + Size = CalculateNodeSize(width, height); } /// /// Automatically resizes the node to match the title size and all the elements for best fit of the node dimensions. /// - public void ResizeAuto() + public virtual void ResizeAuto() { if (Surface == null) return; diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 18fa70e4e..2b8e97f62 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -140,6 +140,16 @@ namespace FlaxEditor.Surface /// public Texture Background; + /// + /// Boxes drawing callback. + /// + public Action DrawBox = DefaultDrawBox; + + /// + /// Custom box connection drawing callback (null by default). + /// + public Action DrawConnection = null; + /// /// Gets the color for the connection. /// @@ -204,6 +214,41 @@ namespace FlaxEditor.Surface color = Colors.Default; } + private static void DefaultDrawBox(Elements.Box box) + { + var rect = new Rectangle(Float2.Zero, box.Size); + + // Size culling + const float minBoxSize = 5.0f; + if (rect.Size.LengthSquared < minBoxSize * minBoxSize) + return; + + // Debugging boxes size + //Render2D.DrawRectangle(rect, Color.Orange); return; + + // Draw icon + bool hasConnections = box.HasAnyConnection; + float alpha = box.Enabled ? 1.0f : 0.6f; + Color color = box.CurrentTypeColor * alpha; + var style = box.Surface.Style; + SpriteHandle icon; + if (box.CurrentType.Type == typeof(void)) + icon = hasConnections ? style.Icons.ArrowClose : style.Icons.ArrowOpen; + else + icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen; + color *= box.ConnectionsHighlightIntensity + 1; + Render2D.DrawSprite(icon, rect, color); + + // Draw selection hint + if (box.IsSelected) + { + float outlineAlpha = Mathf.Sin(Time.TimeSinceStartup * 4.0f) * 0.5f + 0.5f; + float outlineWidth = Mathf.Lerp(1.5f, 4.0f, outlineAlpha); + var outlineRect = new Rectangle(rect.X - outlineWidth, rect.Y - outlineWidth, rect.Width + outlineWidth * 2, rect.Height + outlineWidth * 2); + Render2D.DrawSprite(icon, outlineRect, FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f)); + } + } + /// /// Function used to create style for the given surface type. Can be overriden to provide some customization via user plugin. /// diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs index 8db64b476..edfa15214 100644 --- a/Source/Editor/Surface/VisjectSurface.Draw.cs +++ b/Source/Editor/Surface/VisjectSurface.Draw.cs @@ -176,7 +176,7 @@ namespace FlaxEditor.Surface var bezierStartPoint = new Float2(upperRight.X + offsetX * 0.75f, (upperRight.Y + bottomRight.Y) * 0.5f); var bezierEndPoint = inputBracket.Box.ParentNode.PointToParent(_rootControl.Parent, inputBracket.Box.Center); - Elements.OutputBox.DrawConnection(ref bezierStartPoint, ref bezierEndPoint, ref fadedColor); + Elements.OutputBox.DrawConnection(Style, ref bezierStartPoint, ref bezierEndPoint, ref fadedColor); // Debug Area //Rectangle drawRect = Rectangle.FromPoints(upperLeft, bottomRight); From bb8f098714fb4a0817b90bf7187777f794513b0a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 13 Aug 2023 19:14:57 +0200 Subject: [PATCH 065/294] Add reroute node usage to Visject only if surface type allows it --- Source/Editor/Surface/VisjectSurface.Input.cs | 8 +++---- Source/Editor/Surface/VisjectSurface.cs | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index ecc29d74f..062a0d603 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -299,7 +299,7 @@ namespace FlaxEditor.Surface if (IsMouseOver && !_leftMouseDown && !IsPrimaryMenuOpened) { var nextViewScale = ViewScale + delta * 0.1f; - + if (delta > 0 && !_rightMouseDown) { // Scale towards mouse when zooming in @@ -314,7 +314,7 @@ namespace FlaxEditor.Surface ViewScale = nextViewScale; ViewCenterPosition = viewCenter; } - + return true; } @@ -329,12 +329,12 @@ namespace FlaxEditor.Surface if (!handled) CustomMouseDoubleClick?.Invoke(ref location, button, ref handled); - if (!handled && CanEdit) + // Insert reroute node + if (!handled && CanEdit && CanUseNodeType(7, 29)) { var mousePos = _rootControl.PointFromParent(ref _mousePos); if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null) { - // Insert reroute node if (Undo != null) { bool undoEnabled = Undo.Enabled; diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 675598b16..0275586a4 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -524,6 +524,27 @@ namespace FlaxEditor.Surface /// public virtual bool CanSetParameters => false; + /// + /// Determines whether the specified node archetype can be used in the surface. + /// + /// The nodes group archetype identifier. + /// The node archetype identifier. + /// True if can use this node archetype, otherwise false. + public bool CanUseNodeType(ushort groupID, ushort typeID) + { + var result = false; + var nodeArchetypes = NodeArchetypes ?? NodeFactory.DefaultGroups; + if (NodeFactory.GetArchetype(nodeArchetypes, groupID, typeID, out var groupArchetype, out var nodeArchetype)) + { + var flags = nodeArchetype.Flags; + nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaGUI; + nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaPaste; + result = CanUseNodeType(groupArchetype, nodeArchetype); + nodeArchetype.Flags = flags; + } + return result; + } + /// /// Determines whether the specified node archetype can be used in the surface. /// From da44babb03869ee269162420488b01cce74519a6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 13:11:00 +0200 Subject: [PATCH 066/294] Add ignoring types with `CompilerGeneratedAttribute` in Editor --- Source/Editor/GUI/Popups/TypeSearchPopup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs index 73c5baab7..47ec94154 100644 --- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs +++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.GUI if (_isValid(type)) { var attributes = type.GetAttributes(true); - if (attributes.FirstOrDefault(x => x is HideInEditorAttribute) == null) + if (attributes.FirstOrDefault(x => x is HideInEditorAttribute || x is System.Runtime.CompilerServices.CompilerGeneratedAttribute) == null) { AddItem(new TypeItemView(type, attributes)); } From 19f14919c043da2de40506bb17bdd834f928ffc8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 13:11:42 +0200 Subject: [PATCH 067/294] Allow modifying surface node archetype reference --- Source/Editor/Surface/SurfaceNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index a15f5e42d..eee753655 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Surface /// /// The node archetype. /// - public readonly NodeArchetype Archetype; + public NodeArchetype Archetype; /// /// The group archetype. From 81622c92aea0f3e9dd93ecd9d63e5da7cf7ed305 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 13:09:50 +0200 Subject: [PATCH 068/294] Add `ReadOnly` to `CustomEditorPresenter` --- .../CustomEditors/CustomEditorPresenter.cs | 29 +++++++++++++++++++ .../Windows/Assets/VisualScriptWindow.cs | 6 +--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 927f1d7be..93896107c 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -287,7 +287,24 @@ namespace FlaxEditor.CustomEditors } } + /// + /// Gets or sets the value indicating whether properties are read-only. + /// + public bool ReadOnly + { + get => _readOnly; + set + { + if (_readOnly != value) + { + _readOnly = value; + UpdateReadOnly(); + } + } + } + private bool _buildOnUpdate; + private bool _readOnly; /// /// Initializes a new instance of the class. @@ -381,6 +398,8 @@ namespace FlaxEditor.CustomEditors // Restore scroll value if (parentScrollV > -1) panel.VScrollBar.Value = parentScrollV; + if (_readOnly) + UpdateReadOnly(); } /// @@ -391,6 +410,16 @@ namespace FlaxEditor.CustomEditors _buildOnUpdate = true; } + private void UpdateReadOnly() + { + // Only scrollbars are enabled + foreach (var child in Panel.Children) + { + if (!(child is ScrollBar)) + child.Enabled = !_readOnly; + } + } + private void ExpandGroups(LayoutElementsContainer c, bool open) { if (c is Elements.GroupElement group) diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 63bde3c8a..584f1c307 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -1215,11 +1215,7 @@ namespace FlaxEditor.Windows.Assets _canEdit = canEdit; _undo.Enabled = canEdit; _surface.CanEdit = canEdit; - foreach (var child in _propertiesEditor.Panel.Children) - { - if (!(child is ScrollBar)) - child.Enabled = canEdit; - } + _propertiesEditor.ReadOnly = !canEdit; UpdateToolstrip(); } From f8dc59d6703a2a75ed66c99108decf99e6eb3e58 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 13:10:58 +0200 Subject: [PATCH 069/294] Change `Debug` to be static --- Source/Engine/Engine/Debug.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Engine/Debug.cs b/Source/Engine/Engine/Debug.cs index 30407cb9a..7539e8bba 100644 --- a/Source/Engine/Engine/Debug.cs +++ b/Source/Engine/Engine/Debug.cs @@ -8,23 +8,15 @@ namespace FlaxEngine /// /// Class containing methods to ease debugging while developing a game. /// - public sealed class Debug + public static class Debug { - internal static Logger _logger; + internal static Logger _logger = new Logger(new DebugLogHandler()); /// /// Get default debug logger. /// [HideInEditor] - public static ILogger Logger - { - get { return _logger; } - } - - static Debug() - { - _logger = new Logger(new DebugLogHandler()); - } + public static ILogger Logger => _logger; /// /// Assert a condition and logs a formatted error message to the Flax console on failure. From 18b47257fdbbbd77e74bff8fc86df498419c94a7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 13:26:33 +0200 Subject: [PATCH 070/294] Add **Behavior Tree** asset type and editing --- .../Editor/Content/Proxy/BehaviorTreeProxy.cs | 66 +++ Source/Editor/Editor.cs | 5 + .../Editor/Managed/ManagedEditor.Internal.cpp | 4 + .../Editor/Modules/ContentDatabaseModule.cs | 1 + .../SourceCodeEditing/CodeEditingModule.cs | 6 + Source/Editor/Scripting/TypeUtils.cs | 7 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 243 ++++++++++ Source/Editor/Surface/BehaviorTreeSurface.cs | 158 +++++++ Source/Editor/Surface/NodeFactory.cs | 7 + Source/Editor/Surface/NodeFlags.cs | 7 +- .../Windows/Assets/BehaviorTreeWindow.cs | 433 ++++++++++++++++++ Source/Engine/AI/BehaviorKnowledge.h | 18 + Source/Engine/AI/BehaviorTree.cpp | 161 +++++++ Source/Engine/AI/BehaviorTree.cs | 42 ++ Source/Engine/AI/BehaviorTree.h | 91 ++++ Source/Engine/AI/BehaviorTreeNode.h | 29 ++ Source/Engine/AI/BehaviorTreeNodes.cpp | 20 + Source/Engine/AI/BehaviorTreeNodes.h | 41 ++ Source/Engine/AI/BehaviorTypes.h | 35 ++ .../AssetsImportingManager.cpp | 3 + .../ContentImporters/AssetsImportingManager.h | 5 + .../ContentImporters/CreateBehaviorTree.h | 36 ++ Source/Engine/Visject/VisjectGraph.h | 2 +- 23 files changed, 1417 insertions(+), 3 deletions(-) create mode 100644 Source/Editor/Content/Proxy/BehaviorTreeProxy.cs create mode 100644 Source/Editor/Surface/Archetypes/BehaviorTree.cs create mode 100644 Source/Editor/Surface/BehaviorTreeSurface.cs create mode 100644 Source/Editor/Windows/Assets/BehaviorTreeWindow.cs create mode 100644 Source/Engine/AI/BehaviorKnowledge.h create mode 100644 Source/Engine/AI/BehaviorTree.cpp create mode 100644 Source/Engine/AI/BehaviorTree.cs create mode 100644 Source/Engine/AI/BehaviorTree.h create mode 100644 Source/Engine/AI/BehaviorTreeNode.h create mode 100644 Source/Engine/AI/BehaviorTreeNodes.cpp create mode 100644 Source/Engine/AI/BehaviorTreeNodes.h create mode 100644 Source/Engine/AI/BehaviorTypes.h create mode 100644 Source/Engine/ContentImporters/CreateBehaviorTree.h diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs new file mode 100644 index 000000000..33ad0862f --- /dev/null +++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using FlaxEditor.Content.Thumbnails; +using FlaxEditor.Windows; +using FlaxEditor.Windows.Assets; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.Content +{ + /// + /// A asset proxy object. + /// + /// + [ContentContextMenu("New/AI/Behavior Tree")] + public class BehaviorTreeProxy : BinaryAssetProxy + { + /// + public override string Name => "Behavior Tree"; + + /// + public override bool CanReimport(ContentItem item) + { + return true; + } + + /// + public override EditorWindow Open(Editor editor, ContentItem item) + { + return new BehaviorTreeWindow(editor, item as BinaryAssetItem); + } + + /// + public override Color AccentColor => Color.FromRGB(0x3256A8); + + /// + public override Type AssetType => typeof(BehaviorTree); + + /// + public override bool CanCreate(ContentFolder targetLocation) + { + return targetLocation.CanHaveAssets; + } + + /// + public override void Create(string outputPath, object arg) + { + if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath)) + throw new Exception("Failed to create new asset."); + } + + /// + public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context) + { + guiRoot.AddChild(new Label + { + Text = Path.GetFileNameWithoutExtension(request.Asset.Path), + Offsets = Margin.Zero, + AnchorPreset = AnchorPresets.StretchAll, + Wrapping = TextWrapping.WrapWords + }); + } + } +} diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 6f2111e9c..994d94280 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -930,6 +930,11 @@ namespace FlaxEditor /// The . /// Animation = 11, + + /// + /// The . + /// + BehaviorTree = 12, } /// diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index bed9cfb5c..d1abdf8af 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -221,6 +221,7 @@ enum class NewAssetType ParticleEmitterFunction = 9, AnimationGraphFunction = 10, Animation = 11, + BehaviorTree = 12, }; DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj) @@ -264,6 +265,9 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString case NewAssetType::Animation: tag = AssetsImportingManager::CreateAnimationTag; break; + case NewAssetType::BehaviorTree: + tag = AssetsImportingManager::CreateBehaviorTreeTag; + break; default: return true; } diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 45428b9df..d699883c8 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -1032,6 +1032,7 @@ namespace FlaxEditor.Modules Proxy.Add(new SkeletonMaskProxy()); Proxy.Add(new GameplayGlobalsProxy()); Proxy.Add(new VisualScriptProxy()); + Proxy.Add(new BehaviorTreeProxy()); Proxy.Add(new LocalizedStringTableProxy()); Proxy.Add(new FileProxy()); Proxy.Add(new SpawnableJsonAssetProxy()); diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs index c61fad713..d3c2f6bca 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs @@ -178,6 +178,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing /// public readonly CachedCustomAnimGraphNodesCollection AnimGraphNodes = new CachedCustomAnimGraphNodesCollection(32, new ScriptType(typeof(AnimationGraph.CustomNodeArchetypeFactoryAttribute)), IsTypeValidScriptingType, HasAssemblyValidScriptingTypes); + /// + /// The Behavior Tree custom nodes collection. + /// + public readonly CachedTypesCollection BehaviorTreeNodes = new CachedTypesCollection(64, new ScriptType(typeof(BehaviorTreeNode)), IsTypeValidScriptingType, HasAssemblyValidScriptingTypes); + internal CodeEditingModule(Editor editor) : base(editor) { @@ -361,6 +366,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing Scripts.ClearTypes(); Controls.ClearTypes(); AnimGraphNodes.ClearTypes(); + BehaviorTreeNodes.ClearTypes(); TypesCleared?.Invoke(); } diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 4f66d6320..570449839 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -385,7 +385,12 @@ namespace FlaxEngine.Utilities return type.IsValueType && !type.IsEnum && !type.IsPrimitive; } - internal static bool IsDelegate(Type type) + /// + /// Checks if the input type represents a delegate. + /// + /// The input type of the object to check. + /// Returns true if the input type represents a delegate. + public static bool IsDelegate(this Type type) { return typeof(MulticastDelegate).IsAssignableFrom(type.BaseType); } diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs new file mode 100644 index 000000000..f86bfaecf --- /dev/null +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -0,0 +1,243 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Scripting; +using FlaxEditor.Surface.Elements; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.Surface.Archetypes +{ + /// + /// Contains archetypes for nodes from the Behavior Tree group. + /// + [HideInEditor] + public static class BehaviorTree + { + /// + /// Customized for the Behavior Tree node. + /// + internal class Node : SurfaceNode + { + private const float ConnectionAreaMargin = 12.0f; + private const float ConnectionAreaHeight = 12.0f; + + private ScriptType _type; + private InputBox _input; + private OutputBox _output; + + public BehaviorTreeNode Instance; + + internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + { + return new Node(id, context, nodeArch, groupArch); + } + + internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + public static string GetTitle(ScriptType scriptType) + { + var title = scriptType.Name; + if (title.StartsWith("BehaviorTree")) + title = title.Substring(12); + if (title.EndsWith("Node")) + title = title.Substring(0, title.Length - 4); + title = Utilities.Utils.GetPropertyNameUI(title); + return title; + } + + private void UpdateTitle() + { + string title = null; + if (Instance != null) + { + title = Instance.Name; + if (string.IsNullOrEmpty(title)) + title = GetTitle(_type); + } + else + { + var typeName = (string)Values[0]; + title = "Missing Type " + typeName; + } + if (title != Title) + { + Title = title; + ResizeAuto(); + } + } + + /// + public override void OnLoaded() + { + base.OnLoaded(); + + // Setup boxes + _input = (InputBox)GetBox(0); + _output = (OutputBox)GetBox(1); + + // Setup node type and data + var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste; + var flags = Archetype.Flags & ~flagsRoot; + var typeName = (string)Values[0]; + _type = TypeUtils.GetType(typeName); + if (_type != null) + { + bool isRoot = _type.Type == typeof(BehaviorTreeRootNode); + _input.Enabled = _input.Visible = !isRoot; + _output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type); + if (isRoot) + flags |= flagsRoot; + TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type); + try + { + // Load node instance from data + Instance = (BehaviorTreeNode)_type.CreateInstance(); + var instanceData = (byte[])Values[1]; + FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber); + } + catch (Exception ex) + { + Editor.LogError("Failed to load Behavior Tree node of type " + typeName); + Editor.LogWarning(ex); + } + } + else + { + Instance = null; + } + if (Archetype.Flags != flags) + { + // Apply custom flags + Archetype = (NodeArchetype)Archetype.Clone(); + Archetype.Flags = flags; + } + + UpdateTitle(); + } + + /// + public override void ResizeAuto() + { + if (Surface == null) + return; + var width = 0.0f; + var height = 0.0f; + var titleLabelFont = Style.Current.FontLarge; + width = Mathf.Max(width, 100.0f); + width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30); + if (_input != null && _input.Visible) + height += ConnectionAreaHeight; + if (_output != null && _output.Visible) + height += ConnectionAreaHeight; + Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize); + if (_input != null && _input.Visible) + _input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight); + if (_output != null && _output.Visible) + _output.Bounds = new Rectangle(ConnectionAreaMargin, Height - ConnectionAreaHeight, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight); + } + + /// + protected override void UpdateRectangles() + { + base.UpdateRectangles(); + + // Update boxes placement + const float footerSize = FlaxEditor.Surface.Constants.NodeFooterSize; + const float headerSize = FlaxEditor.Surface.Constants.NodeHeaderSize; + const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin; + const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize; + _headerRect = new Rectangle(0, 0, Width, headerSize); + if (_input != null && _input.Visible) + _headerRect.Y += ConnectionAreaHeight; + _footerRect = new Rectangle(0, _headerRect.Bottom, Width, footerSize); + _closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize); + } + + /// + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + try + { + if (Instance != null) + { + // Reload node instance from data + var instanceData = (byte[])Values[1]; + if (instanceData == null || instanceData.Length == 0) + { + // Recreate instance data to default state if previous state was empty + var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType + instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance); + } + FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber); + UpdateTitle(); + } + } + catch (Exception ex) + { + Editor.LogError("Failed to load Behavior Tree node of type " + _type); + Editor.LogWarning(ex); + } + } + + /// + public override void OnDestroy() + { + if (IsDisposing) + return; + _type = ScriptType.Null; + Object.Destroy(ref Instance); + + base.OnDestroy(); + } + } + + /// + /// The nodes for that group. + /// + public static NodeArchetype[] Nodes = + { + new NodeArchetype + { + TypeID = 1, + Create = Node.Create, + Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI, + DefaultValues = new object[] + { + string.Empty, // Type Name + Utils.GetEmptyArray(), // Instance Data + }, + Size = new Float2(100, 0), + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1), + } + }, + new NodeArchetype + { + TypeID = 2, + Create = Node.Create, + Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI, + DefaultValues = new object[] + { + typeof(BehaviorTreeRootNode).FullName, // Root node + Utils.GetEmptyArray(), // Instance Data + }, + Size = new Float2(100, 0), + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1), + } + }, + }; + } +} diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs new file mode 100644 index 000000000..30b548a5d --- /dev/null +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -0,0 +1,158 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using FlaxEditor.Scripting; +using FlaxEditor.Surface.ContextMenu; +using FlaxEditor.Surface.Elements; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.Surface +{ + /// + /// The Visject Surface implementation for the Behavior Tree graphs. + /// + /// + [HideInEditor] + public class BehaviorTreeSurface : VisjectSurface + { + private static NodesCache _nodesCache = new NodesCache(IterateNodesCache); + + /// + public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) + : base(owner, onSave, undo, CreateStyle()) + { + } + + private static SurfaceStyle CreateStyle() + { + var editor = Editor.Instance; + var style = SurfaceStyle.CreateStyleHandler(editor); + style.DrawBox = DrawBox; + style.DrawConnection = DrawConnection; + return style; + } + + private static void DrawBox(Box box) + { + var rect = new Rectangle(Float2.Zero, box.Size); + const float minBoxSize = 5.0f; + if (rect.Size.LengthSquared < minBoxSize * minBoxSize) + return; + + var style = FlaxEngine.GUI.Style.Current; + var color = style.LightBackground; + if (box.IsMouseOver) + color *= 1.2f; + Render2D.FillRectangle(rect, color); + } + + private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness) + { + Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color); + } + + private void OnActiveContextMenuVisibleChanged(Control activeCM) + { + _nodesCache.Wait(); + } + + private static void IterateNodesCache(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version) + { + // Filter by BT node types only + if (!new ScriptType(typeof(BehaviorTreeNode)).IsAssignableFrom(scriptType)) + return; + + // Skip in-built types + if (scriptType == typeof(BehaviorTreeNode) || + scriptType == typeof(BehaviorTreeCompoundNode) || + scriptType == typeof(BehaviorTreeRootNode)) + return; + + // Create group archetype + var groupKey = new KeyValuePair("Behavior Tree", 19); + if (!cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(70, 220, 181), + Tag = version, + Archetypes = new List(), + }; + cache.Add(groupKey, group); + } + + // Create node archetype + var node = (NodeArchetype)Archetypes.BehaviorTree.Nodes[0].Clone(); + node.DefaultValues[0] = scriptType.TypeName; + node.Flags &= ~NodeFlags.NoSpawnViaGUI; + node.Title = Archetypes.BehaviorTree.Node.GetTitle(scriptType); + node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType); + ((IList)group.Archetypes).Add(node); + } + + /// + protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox) + { + activeCM.ShowExpanded = true; + _nodesCache.Get(activeCM); + + base.OnShowPrimaryMenu(activeCM, location, startBox); + + activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged; + } + + /// + public override string GetTypeName(ScriptType type) + { + if (type == ScriptType.Void) + return string.Empty; // Remove `Void` tooltip from connection areas + return base.GetTypeName(type); + } + + /// + public override bool Load() + { + if (base.Load()) + return true; + + // Ensure to have Root node created (UI blocks spawning of it) + if (RootContext.FindNode(19, 2) == null) + { + RootContext.SpawnNode(19, 2, Float2.Zero); + } + + return false; + } + + /// + public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype) + { + // Comments + if (groupArchetype.GroupID == 7 && nodeArchetype.TypeID == 11) + return true; + + // Single root node + if (groupArchetype.GroupID == 19 && nodeArchetype.TypeID == 2 && RootContext.FindNode(19, 2) != null) + return false; + + // Behavior Tree nodes only + return (nodeArchetype.Flags & NodeFlags.BehaviorTreeGraph) != 0 && + groupArchetype.GroupID == 19 && + base.CanUseNodeType(groupArchetype, nodeArchetype); + } + + /// + public override void OnDestroy() + { + if (IsDisposing) + return; + _nodesCache.Wait(); + + base.OnDestroy(); + } + } +} diff --git a/Source/Editor/Surface/NodeFactory.cs b/Source/Editor/Surface/NodeFactory.cs index 27c850258..0c956f640 100644 --- a/Source/Editor/Surface/NodeFactory.cs +++ b/Source/Editor/Surface/NodeFactory.cs @@ -175,6 +175,13 @@ namespace FlaxEditor.Surface Color = new Color(110, 180, 81), Archetypes = Archetypes.Collections.Nodes }, + new GroupArchetype + { + GroupID = 19, + Name = "Behavior Tree", + Color = new Color(70, 220, 181), + Archetypes = Archetypes.BehaviorTree.Nodes + }, }; /// diff --git a/Source/Editor/Surface/NodeFlags.cs b/Source/Editor/Surface/NodeFlags.cs index f90154806..52590bf2e 100644 --- a/Source/Editor/Surface/NodeFlags.cs +++ b/Source/Editor/Surface/NodeFlags.cs @@ -68,9 +68,14 @@ namespace FlaxEditor.Surface /// NoSpawnViaPaste = 512, + /// + /// Node can be used in the Behavior Tree graphs. + /// + BehaviorTreeGraph = 1024, + /// /// Node can be used in the all visual graphs. /// - AllGraphs = MaterialGraph | ParticleEmitterGraph | AnimGraph | VisualScriptGraph, + AllGraphs = MaterialGraph | ParticleEmitterGraph | AnimGraph | VisualScriptGraph | BehaviorTreeGraph, } } diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs new file mode 100644 index 000000000..f2dde24a8 --- /dev/null +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -0,0 +1,433 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Xml; +using FlaxEditor.Content; +using FlaxEditor.CustomEditors; +using FlaxEditor.GUI; +using FlaxEditor.Scripting; +using FlaxEditor.Surface; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; + +namespace FlaxEditor.Windows.Assets +{ + /// + /// Behavior Tree window allows to view and edit asset. + /// + /// + /// + public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow + { + private readonly SplitPanel _split1; + private readonly SplitPanel _split2; + private CustomEditorPresenter _nodePropertiesEditor; + private CustomEditorPresenter _knowledgePropertiesEditor; + private BehaviorTreeSurface _surface; + private Undo _undo; + private readonly ToolStripButton _saveButton; + private readonly ToolStripButton _undoButton; + private readonly ToolStripButton _redoButton; + private bool _showWholeGraphOnLoad = true; + private bool _isWaitingForSurfaceLoad; + private bool _canEdit = true; + + /// + /// Gets the Visject Surface. + /// + public BehaviorTreeSurface Surface => _surface; + + /// + /// Gets the undo history context for this window. + /// + public Undo Undo => _undo; + + /// + public BehaviorTreeWindow(Editor editor, BinaryAssetItem item) + : base(editor, item) + { + var isPlayMode = Editor.IsPlayMode; + + // Undo + _undo = new Undo(); + _undo.UndoDone += OnUndoRedo; + _undo.RedoDone += OnUndoRedo; + _undo.ActionDone += OnUndoRedo; + + // Split Panels + _split1 = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.None) + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = new Margin(0, 0, _toolstrip.Bottom, 0), + SplitterValue = 0.7f, + Parent = this + }; + _split2 = new SplitPanel(Orientation.Vertical, ScrollBars.Vertical, ScrollBars.Vertical) + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = Margin.Zero, + SplitterValue = 0.5f, + Parent = _split1.Panel2 + }; + + // Surface + _surface = new BehaviorTreeSurface(this, Save, _undo) + { + Parent = _split1.Panel1, + Enabled = false + }; + _surface.SelectionChanged += OnNodeSelectionChanged; + + // Properties editors + _nodePropertiesEditor = new CustomEditorPresenter(null); // Surface handles undo for nodes editing + _nodePropertiesEditor.Features = FeatureFlags.UseDefault; + _nodePropertiesEditor.Panel.Parent = _split2.Panel1; + _nodePropertiesEditor.Modified += OnNodePropertyEdited; + _knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned"); // No undo for knowledge editing + _knowledgePropertiesEditor.Features = FeatureFlags.None; + _knowledgePropertiesEditor.Panel.Parent = _split2.Panel2; + + // Toolstrip + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); + _toolstrip.AddSeparator(); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _toolstrip.AddSeparator(); + _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)"); + _toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph"); + + // Setup input actions + InputActions.Add(options => options.Undo, _undo.PerformUndo); + InputActions.Add(options => options.Redo, _undo.PerformRedo); + InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch); + + SetCanEdit(!isPlayMode); + } + + private void OnUndoRedo(IUndoAction action) + { + MarkAsEdited(); + UpdateToolstrip(); + _nodePropertiesEditor.BuildLayoutOnUpdate(); + } + + private void OnNodeSelectionChanged() + { + // Select node instances to view/edit + var selection = new List(); + var nodes = _surface.Nodes; + if (nodes != null) + { + for (var i = 0; i < nodes.Count; i++) + { + if (nodes[i] is Surface.Archetypes.BehaviorTree.Node node && node.IsSelected && node.Instance) + selection.Add(node.Instance); + } + } + _nodePropertiesEditor.Select(selection); + } + + private void OnNodePropertyEdited() + { + _surface.MarkAsEdited(); + var nodes = _surface.Nodes; + for (var i = 0; i < _nodePropertiesEditor.Selection.Count; i++) + { + if (_nodePropertiesEditor.Selection[i] is BehaviorTreeNode instance) + { + // Sync instance data with surface node value storage + for (var j = 0; j < nodes.Count; j++) + { + if (nodes[j] is Surface.Archetypes.BehaviorTree.Node node && node.Instance == instance) + { + node.SetValue(1, FlaxEngine.Json.JsonSerializer.SaveToBytes(instance)); + break; + } + } + } + } + } + + private void UpdateKnowledge() + { + var rootNode = _surface.FindNode(19, 2) as Surface.Archetypes.BehaviorTree.Node; + if (rootNode != null) + rootNode.ValuesChanged += UpdateKnowledge; + var rootInstance = rootNode?.Instance as BehaviorTreeRootNode; + var blackboardType = TypeUtils.GetType(rootInstance?.BlackboardType); + if (blackboardType) + { + var blackboardInstance = blackboardType.CreateInstance(); + _knowledgePropertiesEditor.Select(blackboardInstance); + } + else + { + _knowledgePropertiesEditor.Deselect(); + } + } + + /// + public override void Save() + { + // Early check + if (!IsEdited || _asset == null || _isWaitingForSurfaceLoad) + return; + + // Check if surface has been edited + if (_surface.IsEdited) + { + if (SaveSurface()) + return; + } + + ClearEditedFlag(); + OnSurfaceEditedChanged(); + _item.RefreshThumbnail(); + } + + /// + protected override void UpdateToolstrip() + { + _saveButton.Enabled = _canEdit && IsEdited; + _undoButton.Enabled = _canEdit && _undo.CanUndo; + _redoButton.Enabled = _canEdit && _undo.CanRedo; + + base.UpdateToolstrip(); + } + + /// + protected override void OnAssetLinked() + { + _isWaitingForSurfaceLoad = true; + + base.OnAssetLinked(); + } + + /// + /// Focuses the node. + /// + /// The node. + public void ShowNode(SurfaceNode node) + { + SelectTab(); + RootWindow.Focus(); + Surface.Focus(); + Surface.FocusNode(node); + } + + /// + public Asset SurfaceAsset => Asset; + + /// + public string SurfaceName => "Behavior Tree"; + + /// + public byte[] SurfaceData + { + get => _asset.LoadSurface(); + set + { + // Save data to the asset + if (_asset.SaveSurface(value)) + { + _surface.MarkAsEdited(); + Editor.LogError("Failed to save surface data"); + } + _asset.Reload(); + } + } + + /// + public VisjectSurfaceContext ParentContext => null; + + /// + public void OnContextCreated(VisjectSurfaceContext context) + { + } + + /// + public void OnSurfaceEditedChanged() + { + if (_surface.IsEdited) + MarkAsEdited(); + } + + /// + public void OnSurfaceGraphEdited() + { + } + + /// + public void OnSurfaceClose() + { + Close(); + } + + /// + protected override void UnlinkItem() + { + _isWaitingForSurfaceLoad = false; + + base.UnlinkItem(); + } + + private bool LoadSurface() + { + if (_surface.Load()) + { + Editor.LogError("Failed to load Behavior Tree surface."); + return true; + } + return false; + } + + private bool SaveSurface() + { + _surface.Save(); + return false; + } + + private void SetCanEdit(bool canEdit) + { + if (_canEdit == canEdit) + return; + _canEdit = canEdit; + _undo.Enabled = canEdit; + _surface.CanEdit = canEdit; + _nodePropertiesEditor.ReadOnly = !_canEdit; + _knowledgePropertiesEditor.ReadOnly = true; + UpdateToolstrip(); + } + + /// + public override void OnPlayBegin() + { + base.OnPlayBegin(); + + SetCanEdit(false); + } + + /// + public override void OnPlayEnd() + { + SetCanEdit(true); + + base.OnPlayEnd(); + } + + /// + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (_isWaitingForSurfaceLoad && _asset.IsLoaded) + { + _isWaitingForSurfaceLoad = false; + + if (LoadSurface()) + { + Close(); + return; + } + + // Setup + _undo.Clear(); + _surface.Enabled = true; + _nodePropertiesEditor.BuildLayout(); + _knowledgePropertiesEditor.BuildLayout(); + ClearEditedFlag(); + if (_showWholeGraphOnLoad) + { + _showWholeGraphOnLoad = false; + _surface.ShowWholeGraph(); + } + SurfaceLoaded?.Invoke(); + _knowledgePropertiesEditor.ReadOnly = true; + UpdateKnowledge(); + } + } + + /// + public override bool UseLayoutData => true; + + /// + public override void OnLayoutSerialize(XmlWriter writer) + { + LayoutSerializeSplitter(writer, "Split1", _split1); + LayoutSerializeSplitter(writer, "Split2", _split1); + } + + /// + public override void OnLayoutDeserialize(XmlElement node) + { + LayoutDeserializeSplitter(node, "Split1", _split1); + LayoutDeserializeSplitter(node, "Split2", _split2); + } + + /// + public override void OnLayoutDeserialize() + { + _split1.SplitterValue = 0.7f; + _split2.SplitterValue = 0.5f; + } + + /// + public override void OnDestroy() + { + if (IsDisposing) + return; + _undo.Enabled = false; + _nodePropertiesEditor.Deselect(); + _knowledgePropertiesEditor.Deselect(); + _undo.Clear(); + + base.OnDestroy(); + } + + /// + public IEnumerable NewParameterTypes => Editor.CodeEditing.VisualScriptPropertyTypes.Get(); + + /// + public event Action SurfaceLoaded; + + /// + public void OnParamRenameUndo() + { + } + + /// + public void OnParamEditAttributesUndo() + { + } + + /// + public void OnParamAddUndo() + { + } + + /// + public void OnParamRemoveUndo() + { + } + + /// + public object GetParameter(int index) + { + throw new NotSupportedException(); + } + + /// + public void SetParameter(int index, object value) + { + throw new NotSupportedException(); + } + + /// + public Asset VisjectAsset => Asset; + + /// + public VisjectSurface VisjectSurface => _surface; + } +} diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h new file mode 100644 index 000000000..95917fb45 --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" + +/// +/// Behavior logic component knowledge data container. Contains blackboard values, sensors data and goals storage for Behavior Tree execution. +/// +API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorKnowledge, ScriptingObject); + + // TODO: blackboard + // TODO: sensors data + // TODO: goals + // TODO: GetGoal/HasGoal +}; diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp new file mode 100644 index 000000000..aea8105a9 --- /dev/null +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "BehaviorTree.h" +#include "BehaviorTreeNode.h" +#include "BehaviorTreeNodes.h" +#include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Serialization/JsonSerializer.h" +#include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Threading/Threading.h" +#include "FlaxEngine.Gen.h" + +REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false); + +BehaviorTreeGraphNode::~BehaviorTreeGraphNode() +{ + SAFE_DELETE(Instance); +} + +bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta) +{ + if (VisjectGraph::Load(stream, loadMeta)) + return true; + + // Build node instances hierarchy + for (Node& node : Nodes) + { + if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) + { + for (const GraphBox* childBox : node.Boxes[1].Connections) + { + const Node* child = childBox ? (Node*)childBox->Parent : nullptr; + if (child && child->Instance) + { + nodeCompound->Children.Add(child->Instance); + } + } + } + } + + return false; +} + +void BehaviorTreeGraph::Clear() +{ + VisjectGraph::Clear(); + + Root = nullptr; +} + +bool BehaviorTreeGraph::onNodeLoaded(Node* n) +{ + if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2)) + { + // Create node instance object + ScriptingTypeHandle type = Scripting::FindScriptingType((StringAnsiView)n->Values[0]); + if (!type) + type = Scripting::FindScriptingType(StringAnsi((StringView)n->Values[0])); + if (type) + { + n->Instance = (BehaviorTreeNode*)Scripting::NewObject(type); + const Variant& data = n->Values[1]; + if (data.Type == VariantType::Blob) + JsonSerializer::LoadFromBytes(n->Instance, Span((byte*)data.AsBlob.Data, data.AsBlob.Length), FLAXENGINE_VERSION_BUILD); + + // Find root node + if (!Root && n->Instance && BehaviorTreeRootNode::TypeInitializer == type) + Root = (BehaviorTreeRootNode*)n->Instance; + } + else + { + const String name = n->Values[0].ToString(); + if (name.HasChars()) + LOG(Error, "Missing type '{0}'", name); + } + } + + return VisjectGraph::onNodeLoaded(n); +} + +BehaviorTree::BehaviorTree(const SpawnParams& params, const AssetInfo* info) + : BinaryAsset(params, info) +{ +} + +BytesContainer BehaviorTree::LoadSurface() +{ + if (WaitForLoaded()) + return BytesContainer(); + ScopeLock lock(Locker); + if (!LoadChunks(GET_CHUNK_FLAG(0))) + { + const auto data = GetChunk(0); + BytesContainer result; + result.Copy(data->Data); + return result; + } + LOG(Warning, "\'{0}\' surface data is missing.", ToString()); + return BytesContainer(); +} + +#if USE_EDITOR + +bool BehaviorTree::SaveSurface(const BytesContainer& data) +{ + // Wait for asset to be loaded or don't if last load failed + if (LastLoadFailed()) + { + LOG(Warning, "Saving asset that failed to load."); + } + else if (WaitForLoaded()) + { + LOG(Error, "Asset loading failed. Cannot save it."); + return true; + } + + ScopeLock lock(Locker); + + // Set Visject Surface data + GetOrCreateChunk(0)->Data.Copy(data); + + // Save + AssetInitData assetData; + assetData.SerializedVersion = 1; + if (SaveAsset(assetData)) + { + LOG(Error, "Cannot save \'{0}\'", ToString()); + return true; + } + + return false; +} + +#endif + +Asset::LoadResult BehaviorTree::load() +{ + // Load graph + const auto surfaceChunk = GetChunk(0); + if (surfaceChunk == nullptr) + return LoadResult::MissingDataChunk; + MemoryReadStream surfaceStream(surfaceChunk->Get(), surfaceChunk->Size()); + if (Graph.Load(&surfaceStream, true)) + { + LOG(Warning, "Failed to load graph \'{0}\'", ToString()); + return LoadResult::Failed; + } + + return LoadResult::Ok; +} + +void BehaviorTree::unload(bool isReloading) +{ + // Clear resources + Graph.Clear(); +} + +AssetChunksFlag BehaviorTree::getChunksToPreload() const +{ + return GET_CHUNK_FLAG(0); +} diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs new file mode 100644 index 000000000..fcaf22846 --- /dev/null +++ b/Source/Engine/AI/BehaviorTree.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if FLAX_EDITOR +using System; +using FlaxEngine.Utilities; +using FlaxEditor.Scripting; +using FlaxEngine.GUI; +#endif + +namespace FlaxEngine +{ + partial class BehaviorTreeRootNode + { +#if FLAX_EDITOR + private static bool IsValidBlackboardType(ScriptType type) + { + if (ScriptType.FlaxObject.IsAssignableFrom(type)) + return false; + if (type.Type != null) + { + if (type.Type.IsDelegate()) + return false; + if (typeof(Control).IsAssignableFrom(type.Type)) + return false; + if (typeof(Attribute).IsAssignableFrom(type.Type)) + return false; + if (type.Type.FullName.StartsWith("FlaxEditor.", StringComparison.Ordinal)) + return false; + } + return !type.IsGenericType && + !type.IsInterface && + !type.IsStatic && + !type.IsAbstract && + !type.IsArray && + !type.IsVoid && + (type.IsClass || type.IsStructure) && + type.IsPublic && + type.CanCreateInstance; + } +#endif + } +} diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h new file mode 100644 index 000000000..d3af415e4 --- /dev/null +++ b/Source/Engine/AI/BehaviorTree.h @@ -0,0 +1,91 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Content/BinaryAsset.h" +#include "Engine/Visject/VisjectGraph.h" + +class BehaviorTreeNode; +class BehaviorTreeRootNode; + +/// +/// Behavior Tree graph node. +/// +class BehaviorTreeGraphNode : public VisjectGraphNode<> +{ +public: + // Instance of the graph node. + BehaviorTreeNode* Instance = nullptr; + + ~BehaviorTreeGraphNode(); +}; + +/// +/// Behavior Tree graph. +/// +class BehaviorTreeGraph : public VisjectGraph +{ +public: + // Instance of the graph root node. + BehaviorTreeRootNode* Root = nullptr; + + // [VisjectGraph] + bool Load(ReadStream* stream, bool loadMeta) override; + void Clear() override; + bool onNodeLoaded(Node* n) override; +}; + +/// +/// Behavior Tree graph executor runtime. +/// +class BehaviorTreeExecutor : public VisjectExecutor +{ +}; + +/// +/// Behavior Tree asset with AI logic graph. +/// +/// +API_CLASS(NoSpawn, Sealed) class FLAXENGINE_API BehaviorTree : public BinaryAsset +{ + DECLARE_BINARY_ASSET_HEADER(BehaviorTree, 1); + +public: + /// + /// The Behavior Tree graph. + /// + BehaviorTreeGraph Graph; + + /// + /// Tries to load surface graph from the asset. + /// + /// The surface data or empty if failed to load it. + API_FUNCTION() BytesContainer LoadSurface(); + +#if USE_EDITOR + /// + /// Updates the graph surface (save new one, discard cached data, reload asset). + /// + /// Stream with graph data. + /// True if cannot save it, otherwise false. + API_FUNCTION() bool SaveSurface(const BytesContainer& data); +#endif + +public: + // [BinaryAsset] +#if USE_EDITOR + void GetReferences(Array& output) const override + { + // Base + BinaryAsset::GetReferences(output); + + Graph.GetReferences(output); + } +#endif + +protected: + // [BinaryAsset] + LoadResult load() override; + void unload(bool isReloading) override; + AssetChunksFlag getChunksToPreload() const override; +}; diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h new file mode 100644 index 000000000..6e3e29495 --- /dev/null +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/SerializableScriptingObject.h" +#include "BehaviorTypes.h" + +/// +/// Base class for Behavior Tree nodes. +/// +API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableScriptingObject +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); + +public: + /// + /// Node user name (eg. Follow Enemy, or Pick up Weapon). + /// + API_FIELD() String Name; + + // TODO: decorators/conditionals + // TODO: init methods + // TODO: instance data ctor/dtor + // TODO: start/stop methods + // TODO: update method + + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +}; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp new file mode 100644 index 000000000..8cfcb4899 --- /dev/null +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -0,0 +1,20 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "BehaviorTreeNodes.h" +#include "Engine/Serialization/Serialization.h" + +void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj) +{ + SerializableScriptingObject::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(BehaviorTreeNode); + SERIALIZE(Name); +} + +void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + SerializableScriptingObject::Deserialize(stream, modifier); + + Name.Clear(); // Missing Name is assumes as unnamed node + DESERIALIZE(Name); +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h new file mode 100644 index 000000000..7a8561a45 --- /dev/null +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "BehaviorTreeNode.h" +#include "Engine/Core/Collections/Array.h" + +/// +/// Base class for compound Behavior Tree nodes that composite child nodes. +/// +API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeCompoundNode : public BehaviorTreeNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeCompoundNode, BehaviorTreeNode); + +public: + /// + /// List with all child nodes. + /// + API_FIELD(Readonly) Array> Children; +}; + +/// +/// Sequence node updates all its children as long as they return success. If any child fails, the sequence is failed. +/// +API_CLASS() class FLAXENGINE_API BehaviorTreeSequenceNode : public BehaviorTreeCompoundNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSequenceNode, BehaviorTreeCompoundNode); +}; + +/// +/// Root node of the behavior tree. Contains logic properties and definitions for the runtime. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTreeCompoundNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeRootNode, BehaviorTreeCompoundNode); + API_AUTO_SERIALIZATION(); + + // Full typename of the blackboard data type (structure or class). Spawned for each instance of the behavior. + API_FIELD(Attributes="EditorOrder(0), TypeReference(\"\", \"IsValidBlackboardType\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.TypeNameEditor\")") + StringAnsi BlackboardType; +}; diff --git a/Source/Engine/AI/BehaviorTypes.h b/Source/Engine/AI/BehaviorTypes.h new file mode 100644 index 000000000..5887460e4 --- /dev/null +++ b/Source/Engine/AI/BehaviorTypes.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingType.h" + +class BehaviorTree; +class BehaviorTreeNode; +class BehaviorKnowledge; + +/// +/// Behavior update context state. +/// +API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext); + + /// + /// Simulation time delta (in seconds) since the last update. + /// + float DeltaTime; +}; + +/// +/// Behavior update result. +/// +API_ENUM() enum class BehaviorUpdateResult +{ + // Action completed successfully. + Success, + // Action is still running and active. + Running, + // Action failed. + Failed, +}; diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index 52fa8105f..cb366ffb3 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -33,6 +33,7 @@ #include "CreateAnimationGraphFunction.h" #include "CreateVisualScript.h" #include "CreateAnimation.h" +#include "CreateBehaviorTree.h" #include "CreateJson.h" // Tags used to detect asset creation mode @@ -54,6 +55,7 @@ const String AssetsImportingManager::CreateMaterialFunctionTag(TEXT("MaterialFun const String AssetsImportingManager::CreateParticleEmitterFunctionTag(TEXT("ParticleEmitterFunction")); const String AssetsImportingManager::CreateAnimationGraphFunctionTag(TEXT("AnimationGraphFunction")); const String AssetsImportingManager::CreateAnimationTag(TEXT("Animation")); +const String AssetsImportingManager::CreateBehaviorTreeTag(TEXT("BehaviorTree")); const String AssetsImportingManager::CreateVisualScriptTag(TEXT("VisualScript")); class AssetsImportingManagerService : public EngineService @@ -485,6 +487,7 @@ bool AssetsImportingManagerService::Init() { AssetsImportingManager::CreateParticleEmitterFunctionTag, CreateParticleEmitterFunction::Create }, { AssetsImportingManager::CreateAnimationGraphFunctionTag, CreateAnimationGraphFunction::Create }, { AssetsImportingManager::CreateAnimationTag, CreateAnimation::Create }, + { AssetsImportingManager::CreateBehaviorTreeTag, CreateBehaviorTree::Create }, { AssetsImportingManager::CreateVisualScriptTag, CreateVisualScript::Create }, }; AssetsImportingManager::Creators.Add(InBuildCreators, ARRAY_COUNT(InBuildCreators)); diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.h b/Source/Engine/ContentImporters/AssetsImportingManager.h index 2930f8979..eb5058650 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.h +++ b/Source/Engine/ContentImporters/AssetsImportingManager.h @@ -118,6 +118,11 @@ public: /// static const String CreateAnimationTag; + /// + /// The create Behavior Tree asset tag. + /// + static const String CreateBehaviorTreeTag; + /// /// The create visual script asset tag. /// diff --git a/Source/Engine/ContentImporters/CreateBehaviorTree.h b/Source/Engine/ContentImporters/CreateBehaviorTree.h new file mode 100644 index 000000000..feea85d34 --- /dev/null +++ b/Source/Engine/ContentImporters/CreateBehaviorTree.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Types.h" + +#if COMPILE_WITH_ASSETS_IMPORTER + +#include "Engine/AI/BehaviorTree.h" + +/// +/// Creating animation utility +/// +class CreateBehaviorTree +{ +public: + static CreateAssetResult Create(CreateAssetContext& context) + { + // Base + IMPORT_SETUP(BehaviorTree, 1); + + // Chunk 0 - Visject Surface + if (context.AllocateChunk(0)) + return CreateAssetResult::CannotAllocateChunk; + { + const BehaviorTreeGraph graph; + MemoryWriteStream stream(64); + graph.Save(&stream, true); + context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + } + + return CreateAssetResult::Ok; + } +}; + +#endif diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 0d841f3d4..5836afb2f 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -216,7 +216,7 @@ public: typedef void (VisjectExecutor::*ProcessBoxHandler)(Box*, Node*, Value&); protected: - ProcessBoxHandler _perGroupProcessCall[19]; + ProcessBoxHandler _perGroupProcessCall[20]; public: /// From 017b45d674d41ce6c893949f3e31d4762280313c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 15 Aug 2023 21:55:16 +0300 Subject: [PATCH 071/294] Use mutex over atomic operations in `Delegate` --- Source/Engine/Core/Delegate.h | 100 ++++++++++++++++------------------ 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index df1b4bb8d..4a483a2e1 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Memory/Allocation.h" +#include "Engine/Threading/Threading.h" /// /// The function object that supports binding static, member and lambda functions. @@ -264,8 +265,9 @@ public: protected: // Single allocation for list of binded functions. Thread-safe access via atomic operations. Removing binded function simply clears the entry to handle function unregister during invocation. - intptr volatile _ptr = 0; - intptr volatile _size = 0; + intptr _ptr = 0; + intptr _size = 0; + CriticalSection _locker; typedef void (*StubSignature)(void*, Params ...); public: @@ -388,15 +390,18 @@ public: /// The function to bind. void Bind(const FunctionType& f) { - const intptr size = Platform::AtomicRead(&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); + ScopeLock lock(_locker); + + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; if (bindings) { // Find a first free slot for (intptr i = 0; i < size; i++) { - if (Platform::InterlockedCompareExchange((intptr volatile*)&bindings[i]._function, (intptr)f._function, 0) == 0) + if (bindings[i]._function == 0) { + bindings[i]._function = f._function; bindings[i]._callee = f._callee; bindings[i]._lambda = f._lambda; if (f._lambda) @@ -416,20 +421,9 @@ public: newBindings[size] = f; // Set the new list - auto oldBindings = (FunctionType*)Platform::InterlockedCompareExchange(&_ptr, (intptr)newBindings, (intptr)bindings); - if (oldBindings != bindings) - { - // Other thread already set the new list so free this list and try again - Allocator::Free(newBindings); - Bind(f); - } - else - { - // Free previous bindings and update list size - Platform::AtomicStore(&_size, newSize); - // TODO: what is someone read this value before and is using the old table? - Allocator::Free(bindings); - } + _ptr = (intptr)newBindings; + _size = newSize; + Allocator::Free(bindings); } /// @@ -471,14 +465,15 @@ public: /// The function to bind. void BindUnique(const FunctionType& f) { - const intptr size = Platform::AtomicRead(&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); + ScopeLock lock(_locker); + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; if (bindings) { // Skip if already binded for (intptr i = 0; i < size; i++) { - if (Platform::AtomicRead((intptr volatile*)&bindings[i]._callee) == (intptr)f._callee && Platform::AtomicRead((intptr volatile*)&bindings[i]._function) == (intptr)f._function) + if (bindings[i]._callee == f._callee && bindings[i]._function == f._function) return; } } @@ -524,28 +519,24 @@ public: /// The function to unbind. void Unbind(const FunctionType& f) { + ScopeLock lock(_locker); // Find slot with that function - const intptr size = Platform::AtomicRead(&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; for (intptr i = 0; i < size; i++) { - if (Platform::AtomicRead((intptr volatile*)&bindings[i]._callee) == (intptr)f._callee && Platform::AtomicRead((intptr volatile*)&bindings[i]._function) == (intptr)f._function) + if (bindings[i]._callee == f._callee && bindings[i]._function == f._function) { if (bindings[i]._lambda) { bindings[i].LambdaDtor(); bindings[i]._lambda = nullptr; } - Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0); - Platform::AtomicStore((intptr volatile*)&bindings[i]._function, 0); + bindings[i]._callee = 0; + bindings[i]._function = 0; break; } } - if ((FunctionType*)Platform::AtomicRead(&_ptr) != bindings) - { - // Someone changed the bindings list so retry unbind from the new one - Unbind(f); - } } /// @@ -553,8 +544,9 @@ public: /// void UnbindAll() { - const intptr size = Platform::AtomicRead(&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); + ScopeLock lock(_locker); + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; for (intptr i = 0; i < size; i++) { if (bindings[i]._lambda) @@ -562,8 +554,8 @@ public: bindings[i].LambdaDtor(); bindings[i]._lambda = nullptr; } - Platform::AtomicStore((intptr volatile*)&bindings[i]._function, 0); - Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0); + bindings[i]._callee = 0; + bindings[i]._function = 0; } } @@ -573,12 +565,13 @@ public: /// The binded functions count. int32 Count() const { + ScopeLock lock(_locker); int32 count = 0; - const intptr size = Platform::AtomicRead((intptr volatile*)&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; for (intptr i = 0; i < size; i++) { - if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0) + if (bindings[i]._function != 0) count++; } return count; @@ -598,11 +591,12 @@ public: /// true if any function is binded; otherwise, false. bool IsBinded() const { - const intptr size = Platform::AtomicRead((intptr volatile*)&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); + ScopeLock lock(_locker); + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; for (intptr i = 0; i < size; i++) { - if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0) + if (bindings[i]._function != 0) return true; } return false; @@ -616,16 +610,17 @@ public: /// The amount of written items into the output bindings buffer. Can be equal or less than input bindingsCount capacity. int32 GetBindings(FunctionType* buffer, int32 bufferSize) const { + ScopeLock lock(_locker); int32 count = 0; - const intptr size = Platform::AtomicRead((intptr volatile*)&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; for (intptr i = 0; i < size && i < bufferSize; i++) { - buffer[count]._function = (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings[i]._function); + buffer[count]._function = (StubSignature)bindings[i]._function; if (buffer[count]._function != nullptr) { - buffer[count]._callee = (void*)Platform::AtomicRead((intptr volatile*)&bindings[i]._callee); - buffer[count]._lambda = (typename FunctionType::Lambda*)Platform::AtomicRead((intptr volatile*)&bindings[i]._lambda); + buffer[count]._callee = (void*)bindings[i]._callee; + buffer[count]._lambda = (typename FunctionType::Lambda*)bindings[i]._lambda; if (buffer[count]._lambda) buffer[count].LambdaCtor(); count++; @@ -640,13 +635,14 @@ public: /// A list of parameters for the function invocation. void operator()(Params ... params) const { - const intptr size = Platform::AtomicRead((intptr volatile*)&_size); - FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); + ScopeLock lock(_locker); // TODO: We probably don't need to hold the lock during the function calls, only when iterating through the bindings? + const intptr size = _size; + FunctionType* bindings = (FunctionType*)_ptr; for (intptr i = 0; i < size; i++) { - auto function = (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings->_function); - auto callee = (void*)Platform::AtomicRead((intptr volatile*)&bindings->_callee); - if (function != nullptr && function == (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings->_function)) + auto function = (StubSignature)bindings->_function; + auto callee = (void*)bindings->_callee; + if (function != nullptr) function(callee, Forward(params)...); ++bindings; } From 0cb049167ba5649789ff58a34a07b1a16e5b95f6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 18:29:59 +0200 Subject: [PATCH 072/294] Add `NewValue`/`DeleteValue` to `Variant` for owned value storage --- Source/Engine/Core/Types/Variant.cpp | 47 ++++++++++++++++++++++++++++ Source/Engine/Core/Types/Variant.h | 6 ++++ 2 files changed, 53 insertions(+) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 973e494f1..1a9cc6777 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -2912,6 +2912,53 @@ void Variant::Inline() } } +Variant Variant::NewValue(const StringAnsiView& typeName) +{ + Variant v; + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); + if (typeHandle) + { + const ScriptingType& type = typeHandle.GetType(); + switch (type.Type) + { + case ScriptingTypes::Script: + v.SetType(VariantType(VariantType::Object, typeName)); + break; + case ScriptingTypes::Structure: + v.SetType(VariantType(VariantType::Structure, typeName)); + break; + case ScriptingTypes::Enum: + v.SetType(VariantType(VariantType::Enum, typeName)); + v.AsUint64 = 0; + break; + } + } + else if (typeName.HasChars()) + { + LOG(Warning, "Missing scripting type \'{0}\'", String(typeName)); + } + return MoveTemp(v); +} + +void Variant::DeleteValue() +{ + // Delete any object owned by the Variant + switch (Type.Type) + { + case VariantType::Object: + if (AsObject) + { + AsObject->Deleted.Unbind(this); + AsObject->DeleteObject(); + AsObject = nullptr; + } + break; + } + + // Go back to null + SetType(VariantType(VariantType::Null)); +} + bool Variant::CanCast(const Variant& v, const VariantType& to) { if (v.Type == to) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 9776c8e0b..2f744ce77 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -362,6 +362,12 @@ public: // Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject). void Inline(); + // Allocates the Variant of the specific type (eg. structure or object or value). + static Variant NewValue(const StringAnsiView& typeName); + + // Frees the object or data owned by this Variant container (eg. structure or object). + void DeleteValue(); + FORCE_INLINE Variant Cast(const VariantType& to) const { return Cast(*this, to); From dd8e05ed490bce895ec881558484adf4a3e26c52 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 22:27:03 +0200 Subject: [PATCH 073/294] Add C#-only types for Variant value storage from 0cb049167ba5649789ff58a34a07b1a16e5b95f6 --- Source/Engine/Core/Types/Variant.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 1a9cc6777..f1c27fe5c 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -2933,10 +2933,30 @@ Variant Variant::NewValue(const StringAnsiView& typeName) break; } } +#if USE_CSHARP + else if (const auto mclass = Scripting::FindClass(typeName)) + { + // Fallback to C#-only types + if (mclass->IsEnum()) + { + v.SetType(VariantType(VariantType::Enum, typeName)); + v.AsUint64 = 0; + } + else if (mclass->IsValueType()) + { + v.SetType(VariantType(VariantType::Structure, typeName)); + } + else + { + v.SetType(VariantType(VariantType::ManagedObject, typeName)); + } + } +#endif else if (typeName.HasChars()) { LOG(Warning, "Missing scripting type \'{0}\'", String(typeName)); } + v.Inline(); // Wrap in-built types return MoveTemp(v); } From 66678fc2196026cec802deb46177e4d8f245a64e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 22:27:17 +0200 Subject: [PATCH 074/294] Fix log spam from `TypeEditor` if type is missing --- Source/Editor/CustomEditors/Editors/TypeEditor.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index ab17c4ae1..03df18c08 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -464,6 +464,11 @@ namespace FlaxEditor.CustomEditors.Editors /// public class TypeNameEditor : TypeEditorBase { + /// + /// Prevents spamming log if Value contains missing type to skip research in subsequential Refresh ticks. + /// + private string _lastTypeNameError; + /// public override void Initialize(LayoutElementsContainer layout) { @@ -484,8 +489,13 @@ namespace FlaxEditor.CustomEditors.Editors { base.Refresh(); - if (!HasDifferentValues && Values[0] is string asTypename) + if (!HasDifferentValues && Values[0] is string asTypename && + !string.Equals(asTypename, _lastTypeNameError, StringComparison.Ordinal)) + { _element.CustomControl.Value = TypeUtils.GetType(asTypename); + if (_element.CustomControl.Value == null && asTypename.Length != 0) + _lastTypeNameError = asTypename; + } } } } From 6e85bb8f757ae3a204b9705b773e85ca2cab892b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 22:27:52 +0200 Subject: [PATCH 075/294] Add `ScriptingType` to msvc natvis --- Source/flax.natvis | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/flax.natvis b/Source/flax.natvis index 6942745c2..fc545474f 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -215,6 +215,11 @@ + + + {Fullname} ({Type}) + + None From d1e2d6699efce0622c07f1e54601d150fe354667 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Aug 2023 22:28:48 +0200 Subject: [PATCH 076/294] Add bt nodes init --- Source/Engine/AI/BehaviorTree.cpp | 6 ++++++ Source/Engine/AI/BehaviorTreeNode.h | 9 ++++++++- Source/Engine/AI/BehaviorTreeNodes.cpp | 6 ++++++ Source/Engine/AI/BehaviorTreeNodes.h | 5 ++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index aea8105a9..03e663a64 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -146,6 +146,12 @@ Asset::LoadResult BehaviorTree::load() return LoadResult::Failed; } + // Init graph + if (Graph.Root) + { + Graph.Root->Init(this); + } + return LoadResult::Ok; } diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 6e3e29495..ed9523eb1 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -19,11 +19,18 @@ public: API_FIELD() String Name; // TODO: decorators/conditionals - // TODO: init methods // TODO: instance data ctor/dtor // TODO: start/stop methods // TODO: update method + /// + /// Initializes node state. Called after whole tree is loaded and nodes hierarchy is setup. + /// + /// Node owner asset. + API_FUNCTION() virtual void Init(BehaviorTree* tree) + { + } + void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 8cfcb4899..392eae3b2 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -18,3 +18,9 @@ void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier Name.Clear(); // Missing Name is assumes as unnamed node DESERIALIZE(Name); } + +void BehaviorTreeCompoundNode::Init(BehaviorTree* tree) +{ + for (BehaviorTreeNode* child : Children) + child->Init(tree); +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 7a8561a45..2387206b2 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -12,11 +12,14 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeCompoundNode : public Behav { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeCompoundNode, BehaviorTreeNode); -public: /// /// List with all child nodes. /// API_FIELD(Readonly) Array> Children; + +public: + // [BehaviorTreeNode] + void Init(BehaviorTree* tree) override; }; /// From c18625e0172a65ec494190d021671c3cd56e386e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 15:26:31 +0200 Subject: [PATCH 077/294] Add initial Behavior simulation --- Source/Engine/AI/Behavior.cpp | 127 +++++++++++++++++++++++++ Source/Engine/AI/Behavior.h | 84 ++++++++++++++++ Source/Engine/AI/BehaviorKnowledge.h | 35 ++++++- Source/Engine/AI/BehaviorTree.cpp | 7 ++ Source/Engine/AI/BehaviorTree.cs | 53 +++++++++++ Source/Engine/AI/BehaviorTree.h | 3 + Source/Engine/AI/BehaviorTreeNode.h | 56 ++++++++++- Source/Engine/AI/BehaviorTreeNodes.cpp | 48 ++++++++++ Source/Engine/AI/BehaviorTreeNodes.h | 23 ++++- Source/Engine/AI/BehaviorTypes.h | 18 +++- 10 files changed, 444 insertions(+), 10 deletions(-) create mode 100644 Source/Engine/AI/Behavior.cpp create mode 100644 Source/Engine/AI/Behavior.h diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp new file mode 100644 index 000000000..71821a57e --- /dev/null +++ b/Source/Engine/AI/Behavior.cpp @@ -0,0 +1,127 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "Behavior.h" +#include "BehaviorKnowledge.h" +#include "BehaviorTreeNodes.h" +#include "Engine/Engine/Time.h" + +BehaviorKnowledge::~BehaviorKnowledge() +{ + FreeMemory(); +} + +void BehaviorKnowledge::InitMemory(BehaviorTree* tree) +{ + ASSERT_LOW_LAYER(!Tree && tree); + Tree = tree; + Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType); + if (!Memory && tree->Graph.NodesStatesSize) + { + Memory = Allocator::Allocate(tree->Graph.NodesStatesSize); + for (const auto& node : tree->Graph.Nodes) + { + if (node.Instance) + node.Instance->InitState(Behavior, Memory); + } + } +} + +void BehaviorKnowledge::FreeMemory() +{ + if (Memory) + { + ASSERT_LOW_LAYER(Tree); + for (const auto& node : Tree->Graph.Nodes) + { + if (node.Instance) + node.Instance->ReleaseState(Behavior, Memory); + } + Allocator::Free(Memory); + Memory = nullptr; + } + Blackboard.DeleteValue(); + Tree = nullptr; +} + +Behavior::Behavior(const SpawnParams& params) + : Script(params) +{ + _tickLateUpdate = 1; // TODO: run Behavior via Job System (use Engine::UpdateGraph) + _knowledge.Behavior = this; + Tree.Changed.Bind(this); +} + +void Behavior::StartLogic() +{ + // Ensure to have tree loaded on begin play + CHECK(Tree && !Tree->WaitForLoaded()); + BehaviorTree* tree = Tree.Get(); + CHECK(tree->Graph.Root); + + _result = BehaviorUpdateResult::Running; + + // Init knowledge + _knowledge.InitMemory(tree); +} + +void Behavior::StopLogic() +{ + if (_result != BehaviorUpdateResult::Running) + return; + _accumulatedTime = 0.0f; + _result = BehaviorUpdateResult::Success; +} + +void Behavior::ResetLogic() +{ + const bool isActive = _result == BehaviorUpdateResult::Running; + if (isActive) + StopLogic(); + + // Reset state + _knowledge.FreeMemory(); + _accumulatedTime = 0.0f; + _result = BehaviorUpdateResult::Success; + + if (isActive) + StartLogic(); +} + +void Behavior::OnEnable() +{ + if (AutoStart) + StartLogic(); +} + +void Behavior::OnLateUpdate() +{ + if (_result != BehaviorUpdateResult::Running) + return; + const BehaviorTree* tree = Tree.Get(); + if (!tree || !tree->Graph.Root) + { + _result = BehaviorUpdateResult::Failed; + Finished(); + return; + } + + // Update timer + _accumulatedTime += Time::Update.DeltaTime.GetTotalSeconds(); + const float updateDeltaTime = 1.0f / Math::Max(tree->Graph.Root->UpdateFPS * UpdateRateScale, ZeroTolerance); + if (_accumulatedTime < updateDeltaTime) + return; + _accumulatedTime -= updateDeltaTime; + + // Update tree + BehaviorUpdateContext context; + context.Behavior = this; + context.Knowledge = &_knowledge; + context.Memory = _knowledge.Memory; + context.DeltaTime = updateDeltaTime; + const BehaviorUpdateResult result = tree->Graph.Root->Update(context); + if (result != BehaviorUpdateResult::Running) + { + _result = result; + Finished(); + } +} diff --git a/Source/Engine/AI/Behavior.h b/Source/Engine/AI/Behavior.h new file mode 100644 index 000000000..a98fce4d5 --- /dev/null +++ b/Source/Engine/AI/Behavior.h @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "BehaviorTree.h" +#include "BehaviorKnowledge.h" +#include "BehaviorTypes.h" +#include "Engine/Scripting/Script.h" +#include "Engine/Content/AssetReference.h" + +/// +/// Behavior instance script that runs Behavior Tree execution. +/// +API_CLASS() class FLAXENGINE_API Behavior : public Script +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE(Behavior); + +private: + BehaviorKnowledge _knowledge; + float _accumulatedTime = 0.0f; + BehaviorUpdateResult _result = BehaviorUpdateResult::Success; + void* _memory = nullptr; + +public: + /// + /// Behavior Tree asset to use for logic execution. + /// + API_FIELD(Attributes="EditorOrder(0)") + AssetReference Tree; + + /// + /// If checked, auto starts the logic on begin play. + /// + API_FIELD(Attributes="EditorOrder(10)") + bool AutoStart = true; + + /// + /// The behavior logic update rate scale (multiplies the UpdateFPS defined in Behavior Tree root node). Can be used to improve performance via LOD to reduce updates frequency (eg. by 0.5) for behaviors far from player. + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 10, 0.01f)") + float UpdateRateScale = 1.0f; + +public: + /// + /// Gets the current behavior knowledge instance. Empty if not started. + /// + API_PROPERTY() BehaviorKnowledge* GetKnowledge() + { + return &_knowledge; + } + + /// + /// Gets the last behavior tree execution result. + /// + API_PROPERTY() BehaviorUpdateResult GetResult() const + { + return _result; + } + + /// + /// Event called when behavior tree execution ends with a result. + /// + API_EVENT() Action Finished; + + /// + /// Starts the logic. + /// + API_FUNCTION() void StartLogic(); + + /// + /// Stops the logic. + /// + API_FUNCTION() void StopLogic(); + + /// + /// Resets the behavior logic by clearing knowledge (clears blackboard and removes goals) and resetting execution state (goes back to root). + /// + API_FUNCTION() void ResetLogic(); + + // [Script] + void OnEnable() override; + void OnLateUpdate() override; +}; diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index 95917fb45..48f77ffa9 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -4,15 +4,48 @@ #include "Engine/Scripting/ScriptingObject.h" +class Behavior; +class BehaviorTree; + /// /// Behavior logic component knowledge data container. Contains blackboard values, sensors data and goals storage for Behavior Tree execution. /// API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorKnowledge, ScriptingObject); + ~BehaviorKnowledge(); + + /// + /// Owning Behavior instance (constant). + /// + API_FIELD(ReadOnly) Behavior* Behavior = nullptr; + + /// + /// Used Behavior Tree asset (defines blackboard and memory constraints). + /// + API_FIELD(ReadOnly) BehaviorTree* Tree = nullptr; + + /// + /// Raw memory chunk with all Behavior Tree nodes state. + /// + API_FIELD(ReadOnly) void* Memory = nullptr; + + /// + /// Instance of the behaviour blackboard (structure or class). + /// + API_FIELD() Variant Blackboard; - // TODO: blackboard // TODO: sensors data // TODO: goals // TODO: GetGoal/HasGoal + + /// + /// Initializes the knowledge for a certain tree. + /// + void InitMemory(BehaviorTree* tree); + + /// + /// Releases the memory of the knowledge. + /// + void FreeMemory(); }; diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index 03e663a64..c83665ead 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -36,6 +36,12 @@ bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta) } } } + if (node.Instance) + { + // Count total states memory size + node.Instance->_memoryOffset = NodesStatesSize; + NodesStatesSize += node.Instance->GetStateSize(); + } } return false; @@ -46,6 +52,7 @@ void BehaviorTreeGraph::Clear() VisjectGraph::Clear(); Root = nullptr; + NodesStatesSize = 0; } bool BehaviorTreeGraph::onNodeLoaded(Node* n) diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs index fcaf22846..20f6e95b9 100644 --- a/Source/Engine/AI/BehaviorTree.cs +++ b/Source/Engine/AI/BehaviorTree.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if FLAX_EDITOR using System; using FlaxEngine.Utilities; @@ -39,4 +41,55 @@ namespace FlaxEngine } #endif } + + unsafe partial class BehaviorTreeNode + { + /// + /// Gets the size in bytes of the given typed node state structure. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetStateSize() + { + // C# nodes state is stored via pinned GCHandle to support holding managed references (eg. string or Vector3[]) + return sizeof(IntPtr); + } + + /// + /// Sets the node state at the given memory address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AllocState(IntPtr memory, object state) + { + var handle = GCHandle.Alloc(state); + var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer(); + Unsafe.Write(ptr, (IntPtr)handle); + } + + /// + /// Returns the typed node state at the given memory address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T GetState(IntPtr memory) where T : struct + { + var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer(); + var handle = GCHandle.FromIntPtr(Unsafe.Read(ptr)); + var state = handle.Target; +#if !BUILD_RELEASE + if (state == null) + throw new NullReferenceException(); +#endif + return ref Unsafe.Unbox(state); + } + + /// + /// Frees the allocated node state at the given memory address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FreeState(IntPtr memory) + { + var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer(); + var handle = GCHandle.FromIntPtr(Unsafe.Read(ptr)); + handle.Free(); + } + } } diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h index d3af415e4..cedd437b2 100644 --- a/Source/Engine/AI/BehaviorTree.h +++ b/Source/Engine/AI/BehaviorTree.h @@ -5,6 +5,7 @@ #include "Engine/Content/BinaryAsset.h" #include "Engine/Visject/VisjectGraph.h" +class BehaviorKnowledge; class BehaviorTreeNode; class BehaviorTreeRootNode; @@ -28,6 +29,8 @@ class BehaviorTreeGraph : public VisjectGraph public: // Instance of the graph root node. BehaviorTreeRootNode* Root = nullptr; + // Total size of the nodes states memory. + int32 NodesStatesSize = 0; // [VisjectGraph] bool Load(ReadStream* stream, bool loadMeta) override; diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index ed9523eb1..ac62d7b5f 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -11,6 +11,11 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableScriptingObject { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); + friend class BehaviorTreeGraph; + +protected: + // Raw memory byte offset from the start of the behavior memory block. + API_FIELD(ReadOnly) int32 _memoryOffset = 0; public: /// @@ -18,11 +23,6 @@ public: /// API_FIELD() String Name; - // TODO: decorators/conditionals - // TODO: instance data ctor/dtor - // TODO: start/stop methods - // TODO: update method - /// /// Initializes node state. Called after whole tree is loaded and nodes hierarchy is setup. /// @@ -31,6 +31,52 @@ public: { } + /// + /// Gets the node instance state size. A chunk of the valid memory is passed via InitState to setup that memory chunk (one per-behavior). + /// + API_FUNCTION() virtual int32 GetStateSize() const + { + return 0; + } + + /// + /// Initializes node instance state. Called when starting logic simulation for a given behavior. + /// + /// Behavior to simulate. + /// Pointer to pre-allocated memory for this node to use (call constructor of the state container). + API_FUNCTION() virtual void InitState(Behavior* behavior, void* memory) + { + } + + /// + /// Cleanups node instance state. Called when stopping logic simulation for a given behavior. + /// + /// Behavior to simulate. + /// Pointer to pre-allocated memory for this node to use (call destructor of the state container). + API_FUNCTION() virtual void ReleaseState(Behavior* behavior, void* memory) + { + } + + /// + /// Updates node logic. + /// + /// Behavior update context data. + /// Operation result enum. + API_FUNCTION() virtual BehaviorUpdateResult Update(BehaviorUpdateContext context) + { + return BehaviorUpdateResult::Success; + } + + // [SerializableScriptingObject] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +public: + // Returns the typed node state at the given memory address. + template + T* GetState(void* memory) const + { + ASSERT((int32)sizeof(T) <= GetStateSize()); + return reinterpret_cast((byte*)memory + _memoryOffset); + } }; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 392eae3b2..01a0c7978 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -24,3 +24,51 @@ void BehaviorTreeCompoundNode::Init(BehaviorTree* tree) for (BehaviorTreeNode* child : Children) child->Init(tree); } + +BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext context) +{ + auto result = BehaviorUpdateResult::Success; + for (int32 i = 0; i < Children.Count() && result == BehaviorUpdateResult::Success; i++) + { + BehaviorTreeNode* child = Children[i]; + result = child->Update(context); + } + return result; +} + +int32 BehaviorTreeSequenceNode::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeSequenceNode::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + new(state)State(); +} + +BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext context) +{ + auto state = GetState(context.Memory); + + if (state->CurrentChildIndex >= Children.Count()) + return BehaviorUpdateResult::Success; + if (state->CurrentChildIndex == -1) + return BehaviorUpdateResult::Failed; + + auto result = Children[state->CurrentChildIndex]->Update(context); + + switch (result) + { + case BehaviorUpdateResult::Success: + state->CurrentChildIndex++; // Move to the next node + if (state->CurrentChildIndex < Children.Count()) + result = BehaviorUpdateResult::Running; // Keep on running to the next child on the next update + break; + case BehaviorUpdateResult::Failed: + state->CurrentChildIndex = -1; // Mark whole sequence as failed + break; + } + + return result; +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 2387206b2..67d19099a 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -20,25 +20,42 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeCompoundNode : public Behav public: // [BehaviorTreeNode] void Init(BehaviorTree* tree) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; }; /// -/// Sequence node updates all its children as long as they return success. If any child fails, the sequence is failed. +/// Sequence node updates all its children (from left to right) as long as they return success. If any child fails, the sequence is failed. /// API_CLASS() class FLAXENGINE_API BehaviorTreeSequenceNode : public BehaviorTreeCompoundNode { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSequenceNode, BehaviorTreeCompoundNode); + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + +private: + struct State + { + int32 CurrentChildIndex = 0; + }; }; /// /// Root node of the behavior tree. Contains logic properties and definitions for the runtime. /// -API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTreeCompoundNode +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTreeSequenceNode { - DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeRootNode, BehaviorTreeCompoundNode); + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeRootNode, BehaviorTreeSequenceNode); API_AUTO_SERIALIZATION(); // Full typename of the blackboard data type (structure or class). Spawned for each instance of the behavior. API_FIELD(Attributes="EditorOrder(0), TypeReference(\"\", \"IsValidBlackboardType\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.TypeNameEditor\")") StringAnsi BlackboardType; + + // The target amount of the behavior logic updates per second. + API_FIELD(Attributes="EditorOrder(10)") + float UpdateFPS = 10.0f; }; diff --git a/Source/Engine/AI/BehaviorTypes.h b/Source/Engine/AI/BehaviorTypes.h index 5887460e4..554cdf57a 100644 --- a/Source/Engine/AI/BehaviorTypes.h +++ b/Source/Engine/AI/BehaviorTypes.h @@ -4,6 +4,7 @@ #include "Engine/Scripting/ScriptingType.h" +class Behavior; class BehaviorTree; class BehaviorTreeNode; class BehaviorKnowledge; @@ -15,10 +16,25 @@ API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext { DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext); + /// + /// Behavior to simulate. + /// + API_FIELD() Behavior* Behavior; + + /// + /// Behavior's logic knowledge container (data, goals and sensors). + /// + API_FIELD() BehaviorKnowledge* Knowledge; + + /// + /// Current instance memory buffer location (updated while moving down the tree). + /// + API_FIELD() void* Memory; + /// /// Simulation time delta (in seconds) since the last update. /// - float DeltaTime; + API_FIELD() float DeltaTime; }; /// From 77f7d55f5af42a3e612b3c87537cdfc1a54e128f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 15:26:50 +0200 Subject: [PATCH 078/294] Fix log spam from `TypeEditor` if type is missing --- Source/Editor/CustomEditors/Editors/TypeEditor.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index 03df18c08..38800a738 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -492,9 +492,15 @@ namespace FlaxEditor.CustomEditors.Editors if (!HasDifferentValues && Values[0] is string asTypename && !string.Equals(asTypename, _lastTypeNameError, StringComparison.Ordinal)) { - _element.CustomControl.Value = TypeUtils.GetType(asTypename); - if (_element.CustomControl.Value == null && asTypename.Length != 0) - _lastTypeNameError = asTypename; + try + { + _element.CustomControl.Value = TypeUtils.GetType(asTypename); + } + finally + { + if (_element.CustomControl.Value == null && asTypename.Length != 0) + _lastTypeNameError = asTypename; + } } } } From 5f79fdd5f9c6181260c9438b7a6ae71381a8ff67 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 15:27:02 +0200 Subject: [PATCH 079/294] Fix bt node sizing on spawm --- Source/Editor/Surface/Archetypes/BehaviorTree.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index f86bfaecf..8ca466423 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -121,6 +121,14 @@ namespace FlaxEditor.Surface.Archetypes UpdateTitle(); } + /// + public override void OnSpawned() + { + base.OnSpawned(); + + ResizeAuto(); + } + /// public override void ResizeAuto() { From c15a48b0b462c8ef83efd19cf1531b78a2a245bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 15:29:00 +0200 Subject: [PATCH 080/294] Allow Scripting Object as BT blackboard but don't support plain classes --- Source/Engine/AI/BehaviorTree.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs index 20f6e95b9..bd342f3d2 100644 --- a/Source/Engine/AI/BehaviorTree.cs +++ b/Source/Engine/AI/BehaviorTree.cs @@ -16,7 +16,7 @@ namespace FlaxEngine #if FLAX_EDITOR private static bool IsValidBlackboardType(ScriptType type) { - if (ScriptType.FlaxObject.IsAssignableFrom(type)) + if (new ScriptType(typeof(SceneObject)).IsAssignableFrom(type)) return false; if (type.Type != null) { @@ -35,7 +35,7 @@ namespace FlaxEngine !type.IsAbstract && !type.IsArray && !type.IsVoid && - (type.IsClass || type.IsStructure) && + (type.IsStructure || ScriptType.FlaxObject.IsAssignableFrom(type)) && type.IsPublic && type.CanCreateInstance; } From 70228ca3552f57f5371e229d92f3d1e307ecbd2a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 15:29:11 +0200 Subject: [PATCH 081/294] Various improvements to Variant --- Source/Engine/Core/Types/Variant.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index f1c27fe5c..0012913a3 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -124,13 +124,6 @@ VariantType::VariantType(Types type, MClass* klass) VariantType::VariantType(const StringAnsiView& typeName) { - // Check case for array - if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive)) - { - new(this) VariantType(Array, StringAnsiView(typeName.Get(), typeName.Length() - 2)); - return; - } - // Try using in-built type for (uint32 i = 0; i < ARRAY_COUNT(InBuiltTypesTypeNames); i++) { @@ -141,6 +134,13 @@ VariantType::VariantType(const StringAnsiView& typeName) } } + // Check case for array + if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive)) + { + new(this) VariantType(Array, StringAnsiView(typeName.Get(), typeName.Length() - 2)); + return; + } + // Try using scripting type const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); if (typeHandle) @@ -166,7 +166,10 @@ VariantType::VariantType(const StringAnsiView& typeName) #if USE_CSHARP if (const auto mclass = Scripting::FindClass(typeName)) { - new(this) VariantType(ManagedObject, typeName); + if (mclass->IsEnum()) + new(this) VariantType(Enum, typeName); + else + new(this) VariantType(ManagedObject, typeName); return; } #endif @@ -2922,6 +2925,7 @@ Variant Variant::NewValue(const StringAnsiView& typeName) switch (type.Type) { case ScriptingTypes::Script: + case ScriptingTypes::Class: v.SetType(VariantType(VariantType::Object, typeName)); break; case ScriptingTypes::Structure: From fad825690ebb7f18ae51a9db35f8c08a2f2c1ac4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 15:44:30 +0200 Subject: [PATCH 082/294] Add BehaviorTree nodes sorting by X position on a surface --- Source/Engine/AI/BehaviorTree.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index c83665ead..e2330097e 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -3,6 +3,7 @@ #include "BehaviorTree.h" #include "BehaviorTreeNode.h" #include "BehaviorTreeNodes.h" +#include "Engine/Core/Collections/Sorting.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Serialization/JsonSerializer.h" @@ -12,6 +13,17 @@ REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false); +bool SortBehaviorTreeChildren(GraphBox* const& a, GraphBox* const& b) +{ + auto aNode = (BehaviorTreeGraph::Node*)a->Parent; + auto bNode = (BehaviorTreeGraph::Node*)b->Parent; + auto aEntry = aNode->Meta.GetEntry(11); + auto bEntry = bNode->Meta.GetEntry(11); + auto aX = aEntry && aEntry->Data.HasItems() ? ((Float2*)aEntry->Data.Get())->X : (float)aNode->ID; + auto bX = bEntry && bEntry->Data.HasItems() ? ((Float2*)bEntry->Data.Get())->X : (float)bNode->ID; + return aX < bX; +} + BehaviorTreeGraphNode::~BehaviorTreeGraphNode() { SAFE_DELETE(Instance); @@ -27,7 +39,13 @@ bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta) { if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) { - for (const GraphBox* childBox : node.Boxes[1].Connections) + auto& children = node.Boxes[1].Connections; + + // Sort children from left to right (based on placement on a graph surface) + Sorting::QuickSort(children.Get(), children.Count(), SortBehaviorTreeChildren); + + // Find all children (of output box) + for (const GraphBox* childBox : children) { const Node* child = childBox ? (Node*)childBox->Parent : nullptr; if (child && child->Instance) From 14ffd0aa08659d9eb73cdff5278deafb9ca0e22c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 16:10:07 +0200 Subject: [PATCH 083/294] Add Variant new value init for objects --- Source/Engine/Core/Types/Variant.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 0012913a3..8c5c37628 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -1075,9 +1075,7 @@ Variant& Variant::operator=(const Variant& other) case VariantType::Object: AsObject = other.AsObject; if (other.AsObject) - { AsObject->Deleted.Bind(this); - } break; case VariantType::Asset: AsAsset = other.AsAsset; @@ -2925,8 +2923,10 @@ Variant Variant::NewValue(const StringAnsiView& typeName) switch (type.Type) { case ScriptingTypes::Script: - case ScriptingTypes::Class: v.SetType(VariantType(VariantType::Object, typeName)); + v.AsObject = type.Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), typeHandle)); + if (v.AsObject) + v.AsObject->Deleted.Bind(&v); break; case ScriptingTypes::Structure: v.SetType(VariantType(VariantType::Structure, typeName)); @@ -2935,6 +2935,9 @@ Variant Variant::NewValue(const StringAnsiView& typeName) v.SetType(VariantType(VariantType::Enum, typeName)); v.AsUint64 = 0; break; + default: + LOG(Error, "Unsupported scripting type '{}' for Variant", typeName.ToString()); + break; } } #if USE_CSHARP @@ -2953,6 +2956,15 @@ Variant Variant::NewValue(const StringAnsiView& typeName) else { v.SetType(VariantType(VariantType::ManagedObject, typeName)); + MObject* instance = mclass->CreateInstance(); + if (instance) + { +#if USE_NETCORE + v.AsUint64 = MCore::GCHandle::New(instance); +#else + v.AsUint = MCore::GCHandle::New(instance); +#endif + } } } #endif From 1e3e75cb99fd9ff9155b6b5eb1ed8899519cc973 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 21:24:19 +0200 Subject: [PATCH 084/294] Add relevancy to Behavior Tree nodes --- Source/Engine/AI/Behavior.cpp | 15 +++---- Source/Engine/AI/BehaviorKnowledge.h | 13 +++--- Source/Engine/AI/BehaviorTree.cpp | 60 +++++++++++++++++--------- Source/Engine/AI/BehaviorTree.h | 5 +++ Source/Engine/AI/BehaviorTreeNode.h | 6 +++ Source/Engine/AI/BehaviorTreeNodes.cpp | 28 +++++++++++- 6 files changed, 90 insertions(+), 37 deletions(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index 71821a57e..f08b733ad 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -15,30 +15,27 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree) ASSERT_LOW_LAYER(!Tree && tree); Tree = tree; Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType); + RelevantNodes.Resize(tree->Graph.NodesCount, false); + RelevantNodes.SetAll(false); if (!Memory && tree->Graph.NodesStatesSize) - { Memory = Allocator::Allocate(tree->Graph.NodesStatesSize); - for (const auto& node : tree->Graph.Nodes) - { - if (node.Instance) - node.Instance->InitState(Behavior, Memory); - } - } } void BehaviorKnowledge::FreeMemory() { if (Memory) { + // Release any outstanding nodes state and memory ASSERT_LOW_LAYER(Tree); for (const auto& node : Tree->Graph.Nodes) { - if (node.Instance) + if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex]) node.Instance->ReleaseState(Behavior, Memory); } Allocator::Free(Memory); Memory = nullptr; } + RelevantNodes.Clear(); Blackboard.DeleteValue(); Tree = nullptr; } @@ -118,7 +115,7 @@ void Behavior::OnLateUpdate() context.Knowledge = &_knowledge; context.Memory = _knowledge.Memory; context.DeltaTime = updateDeltaTime; - const BehaviorUpdateResult result = tree->Graph.Root->Update(context); + const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) { _result = result; diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index 48f77ffa9..a39e70eea 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -2,6 +2,8 @@ #pragma once +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Collections/BitArray.h" #include "Engine/Scripting/ScriptingObject.h" class Behavior; @@ -28,17 +30,18 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject /// /// Raw memory chunk with all Behavior Tree nodes state. /// - API_FIELD(ReadOnly) void* Memory = nullptr; + void* Memory = nullptr; + + /// + /// Array with per-node bit indicating whether node is relevant (active in graph with state created). + /// + BitArray<> RelevantNodes; /// /// Instance of the behaviour blackboard (structure or class). /// API_FIELD() Variant Blackboard; - // TODO: sensors data - // TODO: goals - // TODO: GetGoal/HasGoal - /// /// Initializes the knowledge for a certain tree. /// diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index e2330097e..653e44335 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -15,6 +15,7 @@ REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false); bool SortBehaviorTreeChildren(GraphBox* const& a, GraphBox* const& b) { + // Sort by node X coordinate on surface auto aNode = (BehaviorTreeGraph::Node*)a->Parent; auto bNode = (BehaviorTreeGraph::Node*)b->Parent; auto aEntry = aNode->Meta.GetEntry(11); @@ -35,32 +36,19 @@ bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta) return true; // Build node instances hierarchy + Node* root = nullptr; for (Node& node : Nodes) { - if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) + if (node.Instance == Root) { - auto& children = node.Boxes[1].Connections; - - // Sort children from left to right (based on placement on a graph surface) - Sorting::QuickSort(children.Get(), children.Count(), SortBehaviorTreeChildren); - - // Find all children (of output box) - for (const GraphBox* childBox : children) - { - const Node* child = childBox ? (Node*)childBox->Parent : nullptr; - if (child && child->Instance) - { - nodeCompound->Children.Add(child->Instance); - } - } - } - if (node.Instance) - { - // Count total states memory size - node.Instance->_memoryOffset = NodesStatesSize; - NodesStatesSize += node.Instance->GetStateSize(); + root = &node; + break; } } + if (root) + { + LoadRecursive(*root); + } return false; } @@ -70,6 +58,7 @@ void BehaviorTreeGraph::Clear() VisjectGraph::Clear(); Root = nullptr; + NodesCount = 0; NodesStatesSize = 0; } @@ -103,6 +92,35 @@ bool BehaviorTreeGraph::onNodeLoaded(Node* n) return VisjectGraph::onNodeLoaded(n); } +void BehaviorTreeGraph::LoadRecursive(Node& node) +{ + // Count total states memory size + ASSERT_LOW_LAYER(node.Instance); + node.Instance->_memoryOffset = NodesStatesSize; + node.Instance->_executionIndex = NodesCount; + NodesStatesSize += node.Instance->GetStateSize(); + NodesCount++; + + if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) + { + auto& children = node.Boxes[1].Connections; + + // Sort children from left to right (based on placement on a graph surface) + Sorting::QuickSort(children.Get(), children.Count(), SortBehaviorTreeChildren); + + // Find all children (of output box) + for (const GraphBox* childBox : children) + { + Node* child = childBox ? (Node*)childBox->Parent : nullptr; + if (child && child->Instance) + { + nodeCompound->Children.Add(child->Instance); + LoadRecursive(*child); + } + } + } +} + BehaviorTree::BehaviorTree(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) { diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h index cedd437b2..f81228162 100644 --- a/Source/Engine/AI/BehaviorTree.h +++ b/Source/Engine/AI/BehaviorTree.h @@ -29,6 +29,8 @@ class BehaviorTreeGraph : public VisjectGraph public: // Instance of the graph root node. BehaviorTreeRootNode* Root = nullptr; + // Total count of used nodes. + int32 NodesCount = 0; // Total size of the nodes states memory. int32 NodesStatesSize = 0; @@ -36,6 +38,9 @@ public: bool Load(ReadStream* stream, bool loadMeta) override; void Clear() override; bool onNodeLoaded(Node* n) override; + +private: + void LoadRecursive(Node& node); }; /// diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index ac62d7b5f..69e3bcb46 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -12,10 +12,13 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); friend class BehaviorTreeGraph; + friend class BehaviorKnowledge; protected: // Raw memory byte offset from the start of the behavior memory block. API_FIELD(ReadOnly) int32 _memoryOffset = 0; + // Execution index of the node within tree. + API_FIELD(ReadOnly) int32 _executionIndex = -1; public: /// @@ -67,6 +70,9 @@ public: return BehaviorUpdateResult::Success; } + // Helper utility to update node with state creation/cleanup depending on node relevancy. + BehaviorUpdateResult InvokeUpdate(const BehaviorUpdateContext& context); + // [SerializableScriptingObject] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 01a0c7978..783ac9157 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -1,8 +1,32 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "BehaviorTreeNodes.h" +#include "BehaviorKnowledge.h" #include "Engine/Serialization/Serialization.h" +BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context) +{ + ASSERT_LOW_LAYER(_executionIndex != -1); + if (context.Knowledge->RelevantNodes.Get(_executionIndex) == false) + { + // Node becomes relevant so initialize it's state + context.Knowledge->RelevantNodes.Set(_executionIndex, true); + InitState(context.Behavior, context.Memory); + } + + // Node-specific update + const BehaviorUpdateResult result = Update(context); + + // Check if node is not relevant anymore + if (result != BehaviorUpdateResult::Running) + { + context.Knowledge->RelevantNodes.Set(_executionIndex, false); + ReleaseState(context.Behavior, context.Memory); + } + + return result; +} + void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj) { SerializableScriptingObject::Serialize(stream, otherObj); @@ -31,7 +55,7 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont for (int32 i = 0; i < Children.Count() && result == BehaviorUpdateResult::Success; i++) { BehaviorTreeNode* child = Children[i]; - result = child->Update(context); + result = child->InvokeUpdate(context); } return result; } @@ -56,7 +80,7 @@ BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext cont if (state->CurrentChildIndex == -1) return BehaviorUpdateResult::Failed; - auto result = Children[state->CurrentChildIndex]->Update(context); + auto result = Children[state->CurrentChildIndex]->InvokeUpdate(context); switch (result) { From 32c47949fa7a01d04e542601e0324ae20b1bd580 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 18 Aug 2023 13:32:06 +0200 Subject: [PATCH 085/294] Fix error when scripting structure uses `StringAnsi` field --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index da94a0056..2552aac77 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -151,6 +151,8 @@ namespace Flax.Build.Bindings return value; if (typeInfo.Type == "String") return $"Variant(StringView({value}))"; + if (typeInfo.Type == "StringAnsi") + return $"Variant(StringAnsiView({value}))"; if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference" || @@ -207,6 +209,8 @@ namespace Flax.Build.Bindings return value; if (typeInfo.Type == "String") return $"(StringView){value}"; + if (typeInfo.Type == "StringAnsi") + return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference") From e36bf6e19ababb7c75cba91eb88311f2f3d8e9e4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 19 Aug 2023 16:59:39 +0200 Subject: [PATCH 086/294] Add `MarshalAs` tag to APi structs/classes for custom marshaling via implicit casting --- .../Tools/Flax.Build/Bindings/ApiTypeInfo.cs | 3 +++ .../Bindings/BindingsGenerator.Api.cs | 3 +++ .../Bindings/BindingsGenerator.CSharp.cs | 8 +++++- .../Bindings/BindingsGenerator.Cache.cs | 2 +- .../Bindings/BindingsGenerator.Cpp.cs | 26 ++++++++++++++----- .../Bindings/BindingsGenerator.Parsing.cs | 6 +++++ 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index d63830be5..b0167c024 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -22,6 +22,7 @@ namespace Flax.Build.Bindings public string[] Comment; public bool IsInBuild; public bool IsDeprecated; + public string MarshalAs; internal bool IsInited; internal TypedefInfo Instigator; @@ -151,6 +152,7 @@ namespace Flax.Build.Bindings BindingsGenerator.Write(writer, Namespace); BindingsGenerator.Write(writer, Attributes); BindingsGenerator.Write(writer, Comment); + BindingsGenerator.Write(writer, MarshalAs); writer.Write(IsInBuild); writer.Write(IsDeprecated); BindingsGenerator.Write(writer, Tags); @@ -164,6 +166,7 @@ namespace Flax.Build.Bindings Namespace = BindingsGenerator.Read(reader, Namespace); Attributes = BindingsGenerator.Read(reader, Attributes); Comment = BindingsGenerator.Read(reader, Comment); + MarshalAs = BindingsGenerator.Read(reader, MarshalAs); IsInBuild = reader.ReadBoolean(); IsDeprecated = reader.ReadBoolean(); Tags = BindingsGenerator.Read(reader, Tags); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index fe17d82e0..4a0070710 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -196,6 +196,9 @@ namespace Flax.Build.Bindings var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { + if (apiType.MarshalAs != null) + return UsePassByReference(buildData, new TypeInfo(apiType.MarshalAs), caller); + // Skip for scripting objects if (apiType.IsScriptingObject) return false; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index f399dd67c..c6f4c37ef 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -420,6 +420,8 @@ namespace Flax.Build.Bindings apiTypeParent = apiTypeParent.Parent; } + if (apiType.MarshalAs != null) + return GenerateCSharpManagedToNativeType(buildData, new TypeInfo(apiType.MarshalAs), caller); if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } @@ -509,7 +511,11 @@ namespace Flax.Build.Bindings } else { - returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); + var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); + if (apiType != null && apiType.MarshalAs != null) + returnValueType = GenerateCSharpNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller); + else + returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); } #if USE_NETCORE diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index c2a785c9c..900d04e95 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 19; + private const int CacheVersion = 20; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 2552aac77..3aee2cedc 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -582,6 +582,9 @@ namespace Flax.Build.Bindings { CppReferencesFiles.Add(apiType.File); + if (apiType.MarshalAs != null) + return GenerateCppWrapperNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, functionInfo); + // Scripting Object if (apiType.IsScriptingObject) { @@ -791,6 +794,9 @@ namespace Flax.Build.Bindings if (apiType != null) { + if (apiType.MarshalAs != null) + return GenerateCppWrapperManagedToNative(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, out apiType, functionInfo, out needLocalVariable); + // Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr) { @@ -2370,7 +2376,9 @@ namespace Flax.Build.Bindings CppUsedNonPodTypes.Add(structureInfo); contents.AppendLine(" static MObject* Box(void* ptr)"); contents.AppendLine(" {"); - if (structureInfo.IsPod) + if (structureInfo.MarshalAs != null) + contents.AppendLine($" MISSING_CODE(\"Boxing native type {structureInfo.Name} as {structureInfo.MarshalAs}\"); return nullptr;"); // TODO: impl this + else if (structureInfo.IsPod) contents.AppendLine($" return MCore::Object::Box(ptr, {structureTypeNameNative}::TypeInitializer.GetClass());"); else contents.AppendLine($" return MUtils::Box(*({structureTypeNameNative}*)ptr, {structureTypeNameNative}::TypeInitializer.GetClass());"); @@ -2379,7 +2387,9 @@ namespace Flax.Build.Bindings // Unboxing structures from managed object to native data contents.AppendLine(" static void Unbox(void* ptr, MObject* managed)"); contents.AppendLine(" {"); - if (structureInfo.IsPod) + if (structureInfo.MarshalAs != null) + contents.AppendLine($" MISSING_CODE(\"Boxing native type {structureInfo.Name} as {structureInfo.MarshalAs}\");"); // TODO: impl this + else if (structureInfo.IsPod) contents.AppendLine($" Platform::MemoryCopy(ptr, MCore::Object::Unbox(managed), sizeof({structureTypeNameNative}));"); else contents.AppendLine($" *({structureTypeNameNative}*)ptr = ToNative(*({GenerateCppManagedWrapperName(structureInfo)}*)MCore::Object::Unbox(managed));"); @@ -2657,6 +2667,13 @@ namespace Flax.Build.Bindings { if (CppUsedNonPodTypesList.Contains(apiType)) return; + if (apiType is ClassStructInfo classStructInfo) + { + if (classStructInfo.MarshalAs != null) + return; + if (classStructInfo.IsTemplate) + throw new Exception($"Cannot use template type '{classStructInfo}' as non-POD type for cross-language bindings."); + } if (apiType is StructureInfo structureInfo) { // Check all fields (if one of them is also non-POD structure then we need to generate wrappers for them too) @@ -2679,11 +2696,6 @@ namespace Flax.Build.Bindings } } } - if (apiType is ClassStructInfo classStructInfo) - { - if (classStructInfo.IsTemplate) - throw new Exception($"Cannot use template type '{classStructInfo}' as non-POD type for cross-language bindings."); - } CppUsedNonPodTypesList.Add(apiType); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index d4fcd9629..e187724af 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -628,6 +628,9 @@ namespace Flax.Build.Bindings case "namespace": desc.Namespace = tag.Value; break; + case "marshalas": + desc.MarshalAs = tag.Value; + break; case "tag": ParseTag(ref desc.Tags, tag); break; @@ -1202,6 +1205,9 @@ namespace Flax.Build.Bindings case "namespace": desc.Namespace = tag.Value; break; + case "marshalas": + desc.MarshalAs = tag.Value; + break; case "tag": ParseTag(ref desc.Tags, tag); break; From de6254b5a533da04e0bccf645849cf515de9b7ba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 19 Aug 2023 17:00:05 +0200 Subject: [PATCH 087/294] Various fixes for scripting bindings codegen --- .../Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 9 ++++++++- .../Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index c6f4c37ef..1414629cc 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -375,6 +375,13 @@ namespace Flax.Build.Bindings // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); var typeName = typeInfo.Type.Replace("::", "."); + if (typeInfo.GenericArgs != null) + { + typeName += '<'; + foreach (var arg in typeInfo.GenericArgs) + typeName += arg.Type.Replace("::", "."); + typeName += '>'; + } if (apiType != null) { // Add reference to the namespace @@ -1320,7 +1327,7 @@ namespace Flax.Build.Bindings contents.AppendLine(string.Join("\n" + indent, (indent + $$""" /// /// Marshaller for type . - /// "); + /// #if FLAX_EDITOR [HideInEditor] #endif diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 3aee2cedc..0acab326a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -849,14 +849,12 @@ namespace Flax.Build.Bindings if (typeInfo.GenericArgs != null) { type += '<'; - for (var i = 0; i < typeInfo.GenericArgs.Count; i++) { if (i != 0) type += ", "; type += typeInfo.GenericArgs[i]; } - type += '>'; } return string.Empty; @@ -1074,6 +1072,10 @@ namespace Flax.Build.Bindings { convertOutputParameter = true; } + else if (apiType.Name == "Variant") + { + convertOutputParameter = true; + } } // BytesContainer else if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null) From 34988065887fdc5ac1fdf48ae083acbb8a1ddd62 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 19 Aug 2023 17:45:42 +0200 Subject: [PATCH 088/294] Add support for accessing scripting properties via `ManagedBinaryModule` fields API --- Source/Engine/Scripting/BinaryModule.cpp | 110 +++++++++++++++++++---- 1 file changed, 93 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 6ffdb14a9..820c919f3 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -9,6 +9,7 @@ #include "ManagedCLR/MClass.h" #include "ManagedCLR/MMethod.h" #include "ManagedCLR/MField.h" +#include "ManagedCLR/MProperty.h" #include "ManagedCLR/MUtils.h" #include "ManagedCLR/MException.h" #include "FlaxEngine.Gen.h" @@ -1324,47 +1325,97 @@ void ManagedBinaryModule::GetMethodSignature(void* method, ScriptingTypeMethodSi #endif } +// Pointers with the highest bit turned on are properties +#if PLATFORM_64BITS +#define ManagedBinaryModuleFieldIsPropertyBit (uintptr)(1ull << 63) +#else +#define ManagedBinaryModuleFieldIsPropertyBit (uintptr)(1ul << 31) +#endif + void* ManagedBinaryModule::FindField(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name) { const ScriptingType& type = typeHandle.GetType(); - return type.ManagedClass ? type.ManagedClass->GetField(name.Get()) : nullptr; + void* result = type.ManagedClass ? type.ManagedClass->GetField(name.Get()) : nullptr; + if (!result && type.ManagedClass) + { + result = type.ManagedClass->GetProperty(name.Get()); + if (result) + result = (void*)((uintptr)result | ManagedBinaryModuleFieldIsPropertyBit); + } + return result; } void ManagedBinaryModule::GetFieldSignature(void* field, ScriptingTypeFieldSignature& fieldSignature) { #if USE_CSHARP - const auto mField = (MField*)field; - fieldSignature.Name = mField->GetName(); - fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType())); - fieldSignature.IsStatic = mField->IsStatic(); + if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit) + { + const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit); + fieldSignature.Name = mProperty->GetName(); + fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mProperty->GetType())); + fieldSignature.IsStatic = mProperty->IsStatic(); + } + else + { + const auto mField = (MField*)field; + fieldSignature.Name = mField->GetName(); + fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType())); + fieldSignature.IsStatic = mField->IsStatic(); + } #endif } bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Variant& result) { #if USE_CSHARP - const auto mField = (MField*)field; + bool isStatic; + MClass* parentClass; + StringAnsiView name; + if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit) + { + const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit); + isStatic = mProperty->IsStatic(); + parentClass = mProperty->GetParentClass(); + name = mProperty->GetName(); + } + else + { + const auto mField = (MField*)field; + isStatic = mField->IsStatic(); + parentClass = mField->GetParentClass(); + name = mField->GetName(); + } // Get instance object MObject* instanceObject = nullptr; - if (!mField->IsStatic()) + if (!isStatic) { // Box instance into C# object instanceObject = MUtils::BoxVariant(instance); // Validate instance - if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(mField->GetParentClass())) + if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(parentClass)) { if (!instanceObject) - LOG(Error, "Failed to get field '{0}.{1}' without object instance", String(mField->GetParentClass()->GetFullName()), String(mField->GetName())); + LOG(Error, "Failed to get '{0}.{1}' without object instance", String(parentClass->GetFullName()), String(name)); else - LOG(Error, "Failed to get field '{0}.{1}' with invalid object instance of type '{2}'", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()), String(MUtils::GetClassFullname(instanceObject))); + LOG(Error, "Failed to get '{0}.{1}' with invalid object instance of type '{2}'", String(parentClass->GetFullName()), String(name), String(MUtils::GetClassFullname(instanceObject))); return true; } } // Get the value - MObject* resultObject = mField->GetValueBoxed(instanceObject); + MObject* resultObject; + if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit) + { + const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit); + resultObject = mProperty->GetValue(instanceObject, nullptr); + } + else + { + const auto mField = (MField*)field; + resultObject = mField->GetValueBoxed(instanceObject); + } result = MUtils::UnboxVariant(resultObject); return false; #else @@ -1375,29 +1426,54 @@ bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Va bool ManagedBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value) { #if USE_CSHARP - const auto mField = (MField*)field; + bool isStatic; + MClass* parentClass; + StringAnsiView name; + if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit) + { + const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit); + isStatic = mProperty->IsStatic(); + parentClass = mProperty->GetParentClass(); + name = mProperty->GetName(); + } + else + { + const auto mField = (MField*)field; + isStatic = mField->IsStatic(); + parentClass = mField->GetParentClass(); + name = mField->GetName(); + } // Get instance object MObject* instanceObject = nullptr; - if (!mField->IsStatic()) + if (!isStatic) { // Box instance into C# object instanceObject = MUtils::BoxVariant(instance); // Validate instance - if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(mField->GetParentClass())) + if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(parentClass)) { if (!instanceObject) - LOG(Error, "Failed to set field '{0}.{1}' without object instance", String(mField->GetParentClass()->GetFullName()), String(mField->GetName())); + LOG(Error, "Failed to set '{0}.{1}' without object instance", String(parentClass->GetFullName()), String(name)); else - LOG(Error, "Failed to set field '{0}.{1}' with invalid object instance of type '{2}'", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()), String(MUtils::GetClassFullname(instanceObject))); + LOG(Error, "Failed to set '{0}.{1}' with invalid object instance of type '{2}'", String(parentClass->GetFullName()), String(name), String(MUtils::GetClassFullname(instanceObject))); return true; } } // Set the value bool failed = false; - mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed)); + if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit) + { + const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit); + mProperty->SetValue(instanceObject, MUtils::BoxVariant(value), nullptr); + } + else + { + const auto mField = (MField*)field; + mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed)); + } return failed; #else return true; From eee53dfbdc454537739ae675664e7d8718ee01fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 19 Aug 2023 19:50:17 +0200 Subject: [PATCH 089/294] Add `BehaviorKnowledgeSelector` for Behavior Knowledge unified data access --- .../BehaviorKnowledgeSelectorEditor.cs | 145 +++++++++++ .../CustomEditors/Editors/GenericEditor.cs | 4 +- Source/Editor/Utilities/Utils.cs | 30 ++- .../Windows/Assets/BehaviorTreeWindow.cs | 21 +- Source/Engine/AI/Behavior.cpp | 35 --- Source/Engine/AI/BehaviorKnowledge.cpp | 168 +++++++++++++ Source/Engine/AI/BehaviorKnowledge.h | 18 ++ Source/Engine/AI/BehaviorKnowledgeSelector.cs | 238 ++++++++++++++++++ Source/Engine/AI/BehaviorKnowledgeSelector.h | 127 ++++++++++ Source/Engine/AI/BehaviorTree.cs | 2 +- Source/Engine/Serialization/JsonConverters.cs | 29 ++- Source/Engine/Serialization/JsonSerializer.cs | 1 + 12 files changed, 775 insertions(+), 43 deletions(-) create mode 100644 Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs create mode 100644 Source/Engine/AI/BehaviorKnowledge.cpp create mode 100644 Source/Engine/AI/BehaviorKnowledgeSelector.cs create mode 100644 Source/Engine/AI/BehaviorKnowledgeSelector.h diff --git a/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs new file mode 100644 index 000000000..0da87c7ec --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs @@ -0,0 +1,145 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using FlaxEditor.GUI; +using FlaxEditor.GUI.Tree; +using FlaxEditor.Scripting; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Custom editor for and . + /// + public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor + { + private ClickableLabel _label; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + _label = layout.ClickableLabel(Path).CustomControl; + _label.RightClick += ShowPicker; + var button = new Button + { + Size = new Float2(16.0f), + Text = "...", + TooltipText = "Edit...", + Parent = _label, + }; + button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); + button.Clicked += ShowPicker; + } + + /// + public override void Refresh() + { + base.Refresh(); + + // Update label + _label.Text = Path; + } + + private string Path + { + get + { + var v = Values[0]; + if (v is BehaviorKnowledgeSelectorAny any) + return any.Path; + if (v is string str) + return str; + var pathField = v.GetType().GetField("Path"); + return pathField.GetValue(v) as string; + } + set + { + var v = Values[0]; + if (v is BehaviorKnowledgeSelectorAny) + v = new BehaviorKnowledgeSelectorAny(value); + else if (v is string) + v = value; + else + { + var pathField = v.GetType().GetField("Path"); + pathField.SetValue(v, value); + } + SetValue(v); + } + } + + private void ShowPicker() + { + // Get Behavior Knowledge to select from + var behaviorTreeWindow = Presenter.Owner as Windows.Assets.BehaviorTreeWindow; + var blackboard = behaviorTreeWindow?.Blackboard; + if (blackboard == null) + return; + var typed = ScriptType.Null; + var valueType = Values[0].GetType(); + if (valueType.Name == "BehaviorKnowledgeSelector`1") + { + // Get typed selector type to show only assignable items + typed = new ScriptType(valueType.GenericTypeArguments[0]); + } + // TODO: add support for selecting goals and sensors + + // Create menu with tree-like structure and search box + var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 0, true); + var blackboardType = TypeUtils.GetObjectType(blackboard); + var items = GenericEditor.GetItemsForType(blackboardType, blackboardType.IsClass, true); + var selected = Path; + var noneNode = new TreeNode + { + Text = "", + TooltipText = "Deselect value", + Parent = tree, + }; + if (string.IsNullOrEmpty(selected)) + tree.Select(noneNode); + var blackboardNode = new TreeNode + { + Text = "Blackboard", + TooltipText = blackboardType.TypeName, + Tag = "Blackboard/", // Ability to select whole blackboard data + Parent = tree, + }; + if (typed && !typed.IsAssignableFrom(blackboardType)) + blackboardNode.Tag = null; + if (string.Equals(selected, (string)blackboardNode.Tag, StringComparison.Ordinal)) + tree.Select(blackboardNode); + foreach (var item in items) + { + if (typed && !typed.IsAssignableFrom(item.Info.ValueType)) + continue; + var path = "Blackboard/" + item.Info.Name; + var node = new TreeNode + { + Text = item.DisplayName, + TooltipText = item.TooltipText, + Tag = path, + Parent = blackboardNode, + }; + if (string.Equals(selected, path, StringComparison.Ordinal)) + tree.Select(node); + // TODO: add support for nested items (eg. field from blackboard structure field) + } + blackboardNode.Expand(true); + tree.SelectedChanged += delegate(List before, List after) + { + if (after.Count == 1) + { + menu.Hide(); + Path = after[0].Tag as string; + } + }; + menu.Show(_label, new Float2(0, _label.Height)); + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index cba2b5a5a..55ac453a9 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors /// Describes object property/field information for custom editors pipeline. /// /// - protected class ItemInfo : IComparable + public class ItemInfo : IComparable { private Options.GeneralOptions.MembersOrder _membersOrder; @@ -248,7 +248,7 @@ namespace FlaxEditor.CustomEditors.Editors /// True if use type properties. /// True if use type fields. /// The items. - protected List GetItemsForType(ScriptType type, bool useProperties, bool useFields) + public static List GetItemsForType(ScriptType type, bool useProperties, bool useFields) { var items = new List(); diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index fe801bbaf..e2022d03c 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -11,6 +11,7 @@ using System.Globalization; using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; @@ -1060,8 +1061,9 @@ namespace FlaxEditor.Utilities /// The search box. /// The tree control. /// Amount of additional space above the search box to put custom UI. + /// Plug automatic tree search delegate. /// The created menu to setup and show. - public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0) + public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0, bool autoSearch = false) { var menu = new ContextMenuBase { @@ -1087,9 +1089,35 @@ namespace FlaxEditor.Utilities { Parent = panel2, }; + if (autoSearch) + { + var s = searchBox; + var t = tree; + searchBox.TextChanged += delegate + { + if (t.IsLayoutLocked) + return; + t.LockChildrenRecursive(); + UpdateSearchPopupFilter(t, s.Text); + t.UnlockChildrenRecursive(); + menu.PerformLayout(); + }; + } return menu; } + /// + /// Updates (recursively) search popup tree structures based on the filter text. + /// + public static void UpdateSearchPopupFilter(Tree tree, string filterText) + { + for (int i = 0; i < tree.Children.Count; i++) + { + if (tree.Children[i] is TreeNode child) + UpdateSearchPopupFilter(child, filterText); + } + } + /// /// Updates (recursively) search popup tree structures based on the filter text. /// diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index f2dde24a8..7c5ff57a4 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -6,8 +6,10 @@ using System.Xml; using FlaxEditor.Content; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; +using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Surface; +using FlaxEditor.Viewport; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -19,7 +21,7 @@ namespace FlaxEditor.Windows.Assets /// /// /// - public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow + public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow, IPresenterOwner { private readonly SplitPanel _split1; private readonly SplitPanel _split2; @@ -44,6 +46,11 @@ namespace FlaxEditor.Windows.Assets /// public Undo Undo => _undo; + /// + /// Current instance of the Behavior Knowledge's blackboard type or null. + /// + public object Blackboard => _knowledgePropertiesEditor.Selection.Count != 0 ? _knowledgePropertiesEditor.Selection[0] : null; + /// public BehaviorTreeWindow(Editor editor, BinaryAssetItem item) : base(editor, item) @@ -81,11 +88,11 @@ namespace FlaxEditor.Windows.Assets _surface.SelectionChanged += OnNodeSelectionChanged; // Properties editors - _nodePropertiesEditor = new CustomEditorPresenter(null); // Surface handles undo for nodes editing + _nodePropertiesEditor = new CustomEditorPresenter(null, null, this); // Surface handles undo for nodes editing _nodePropertiesEditor.Features = FeatureFlags.UseDefault; _nodePropertiesEditor.Panel.Parent = _split2.Panel1; _nodePropertiesEditor.Modified += OnNodePropertyEdited; - _knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned"); // No undo for knowledge editing + _knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned", this); // No undo for knowledge editing _knowledgePropertiesEditor.Features = FeatureFlags.None; _knowledgePropertiesEditor.Panel.Parent = _split2.Panel2; @@ -429,5 +436,13 @@ namespace FlaxEditor.Windows.Assets /// public VisjectSurface VisjectSurface => _surface; + + /// + public EditorViewport PresenterViewport => null; + + /// + public void Select(List nodes) + { + } } } diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index f08b733ad..a8eb761a8 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -5,41 +5,6 @@ #include "BehaviorTreeNodes.h" #include "Engine/Engine/Time.h" -BehaviorKnowledge::~BehaviorKnowledge() -{ - FreeMemory(); -} - -void BehaviorKnowledge::InitMemory(BehaviorTree* tree) -{ - ASSERT_LOW_LAYER(!Tree && tree); - Tree = tree; - Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType); - RelevantNodes.Resize(tree->Graph.NodesCount, false); - RelevantNodes.SetAll(false); - if (!Memory && tree->Graph.NodesStatesSize) - Memory = Allocator::Allocate(tree->Graph.NodesStatesSize); -} - -void BehaviorKnowledge::FreeMemory() -{ - if (Memory) - { - // Release any outstanding nodes state and memory - ASSERT_LOW_LAYER(Tree); - for (const auto& node : Tree->Graph.Nodes) - { - if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex]) - node.Instance->ReleaseState(Behavior, Memory); - } - Allocator::Free(Memory); - Memory = nullptr; - } - RelevantNodes.Clear(); - Blackboard.DeleteValue(); - Tree = nullptr; -} - Behavior::Behavior(const SpawnParams& params) : Script(params) { diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp new file mode 100644 index 000000000..c08a09ffc --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -0,0 +1,168 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "BehaviorKnowledge.h" +#include "BehaviorTree.h" +#include "BehaviorTreeNodes.h" +#include "BehaviorKnowledgeSelector.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/BinaryModule.h" +#include "Engine/Scripting/ManagedCLR/MProperty.h" +#if USE_CSHARP +#include "Engine/Scripting/ManagedCLR/MClass.h" +#include "Engine/Scripting/ManagedCLR/MField.h" +#include "Engine/Scripting/ManagedCLR/MProperty.h" +#include "Engine/Scripting/ManagedCLR/MUtils.h" +#endif + +bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& value, bool set) +{ + if (member.IsEmpty()) + { + // Whole blackboard value + CHECK_RETURN(instance.Type == value.Type, false); + if (set) + instance = value; + else + value = instance; + return true; + } + // TODO: support further path for nested value types (eg. structure field access) + + const StringAnsiView typeName(instance.Type.TypeName); + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); + if (typeHandle) + { + const ScriptingType& type = typeHandle.GetType(); + switch (type.Type) + { + case ScriptingTypes::Structure: + { + const String memberStr(member); + // TODO: let SetField/GetField return boolean status of operation maybe? + if (set) + type.Struct.SetField(instance.AsBlob.Data, memberStr, value); + else + type.Struct.GetField(instance.AsBlob.Data, memberStr, value); + return true; + } + default: + { + if (void* field = typeHandle.Module->FindField(typeHandle, member)) + { + if (set) + return !typeHandle.Module->SetFieldValue(field, instance, value); + else + return !typeHandle.Module->GetFieldValue(field, instance, value); + } + break; + } + } + } +#if USE_CSHARP + if (const auto mClass = Scripting::FindClass(typeName)) + { + MObject* instanceObject = MUtils::BoxVariant(instance); + if (const auto mField = mClass->GetField(member.Get())) + { + bool failed; + if (set) + mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed)); + else + value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject)); + return true; + } + else if (const auto mProperty = mClass->GetProperty(member.Get())) + { + if (set) + mProperty->SetValue(instanceObject, MUtils::BoxVariant(value), nullptr); + else + value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr)); + return true; + } + } +#endif + else + { + LOG(Warning, "Missing scripting type \'{0}\'", String(typeName)); + } + + return false; +} + +bool AccessBehaviorKnowledge(BehaviorKnowledge* knowledge, const StringAnsiView& path, Variant& value, bool set) +{ + const int32 typeEnd = path.Find('/'); + if (typeEnd == -1) + return false; + const StringAnsiView type(path.Get(), typeEnd); + if (type == "Blackboard") + { + const StringAnsiView member(path.Get() + typeEnd + 1, path.Length() - typeEnd - 1); + return AccessVariant(knowledge->Blackboard, member, value, set); + } + // TODO: goals and sensors data access from BehaviorKnowledge via Selector + return false; +} + +bool BehaviorKnowledgeSelectorAny::Set(BehaviorKnowledge* knowledge, const Variant& value) +{ + return knowledge && knowledge->Set(Path, value); +} + +Variant BehaviorKnowledgeSelectorAny::Get(BehaviorKnowledge* knowledge) +{ + Variant value; + if (knowledge) + knowledge->Get(Path, value); + return value; +} + +bool BehaviorKnowledgeSelectorAny::TryGet(BehaviorKnowledge* knowledge, Variant& value) +{ + return knowledge && knowledge->Get(Path, value); +} + +BehaviorKnowledge::~BehaviorKnowledge() +{ + FreeMemory(); +} + +void BehaviorKnowledge::InitMemory(BehaviorTree* tree) +{ + ASSERT_LOW_LAYER(!Tree && tree); + Tree = tree; + Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType); + RelevantNodes.Resize(tree->Graph.NodesCount, false); + RelevantNodes.SetAll(false); + if (!Memory && tree->Graph.NodesStatesSize) + Memory = Allocator::Allocate(tree->Graph.NodesStatesSize); +} + +void BehaviorKnowledge::FreeMemory() +{ + if (Memory) + { + // Release any outstanding nodes state and memory + ASSERT_LOW_LAYER(Tree); + for (const auto& node : Tree->Graph.Nodes) + { + if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex]) + node.Instance->ReleaseState(Behavior, Memory); + } + Allocator::Free(Memory); + Memory = nullptr; + } + RelevantNodes.Clear(); + Blackboard.DeleteValue(); + Tree = nullptr; +} + +bool BehaviorKnowledge::Get(const StringAnsiView& path, Variant& value) +{ + return AccessBehaviorKnowledge(this, path, value, false); +} + +bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value) +{ + return AccessBehaviorKnowledge(this, path, const_cast(value), true); +} diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index a39e70eea..252f85764 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -51,4 +51,22 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject /// Releases the memory of the knowledge. /// void FreeMemory(); + + /// + /// Gets the knowledge item value via selector path. + /// + /// + /// Selector path. + /// Result value (valid only when returned true). + /// True if got value, otherwise false. + API_FUNCTION() bool Get(const StringAnsiView& path, API_PARAM(Out) Variant& value); + + /// + /// Sets the knowledge item value via selector path. + /// + /// + /// Selector path. + /// Value to set. + /// True if set value, otherwise false. + API_FUNCTION() bool Set(const StringAnsiView& path, const Variant& value); }; diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs new file mode 100644 index 000000000..7d5698b6e --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -0,0 +1,238 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))] +#endif + partial struct BehaviorKnowledgeSelectorAny : IComparable, IComparable + { + /// + /// Initializes a new instance of the structure. + /// + /// The selector path. + public BehaviorKnowledgeSelectorAny(string path) + { + Path = path; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The other selector. + public BehaviorKnowledgeSelectorAny(BehaviorKnowledgeSelectorAny other) + { + Path = other.Path; + } + + /// + /// Implicit cast operator from selector to string. + /// + /// Selector + /// Path + public static implicit operator string(BehaviorKnowledgeSelectorAny value) + { + return value.Path; + } + + /// + /// Implicit cast operator from string to selector. + /// + /// Path + /// Selector + public static implicit operator BehaviorKnowledgeSelectorAny(string value) + { + return new BehaviorKnowledgeSelectorAny(value); + } + + /// + /// Sets the selected knowledge value. + /// + /// The knowledge container to access. + /// The value to set. + /// True if set value value, otherwise false. + public bool Set(BehaviorKnowledge knowledge, object value) + { + return knowledge != null && knowledge.Set(Path, value); + } + + /// + /// Gets the selected knowledge value. + /// + /// The knowledge container to access. + /// The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name). + public object Get(BehaviorKnowledge knowledge) + { + object value = null; + if (knowledge != null) + knowledge.Get(Path, out value); + return value; + } + + /// + /// Tries to get the selected knowledge value. Returns true if got value, otherwise false. + /// + /// The knowledge container to access. + /// The output value. + /// True if got value, otherwise false. + public bool TryGet(BehaviorKnowledge knowledge, out object value) + { + value = null; + return knowledge != null && knowledge.Get(Path, out value); + } + + /// + public override string ToString() + { + return Path; + } + + /// + public override int GetHashCode() + { + return Path?.GetHashCode() ?? 0; + } + + /// + public int CompareTo(object obj) + { + if (obj is BehaviorKnowledgeSelectorAny other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(BehaviorKnowledgeSelectorAny other) + { + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + } + + /// + /// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values. + /// +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))] +#endif + public struct BehaviorKnowledgeSelector : IComparable, IComparable, IComparable> + { + /// + /// Selector path that redirects to the specific knowledge value. + /// + public string Path; + + /// + /// Initializes a new instance of the structure. + /// + /// The selector path. + public BehaviorKnowledgeSelector(string path) + { + Path = path; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The other selector. + public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other) + { + Path = other.Path; + } + + /// + /// Implicit cast operator from selector to string. + /// + /// Selector + /// Path + public static implicit operator string(BehaviorKnowledgeSelector value) + { + return value.Path; + } + + /// + /// Implicit cast operator from string to selector. + /// + /// Path + /// Selector + public static implicit operator BehaviorKnowledgeSelector(string value) + { + return new BehaviorKnowledgeSelector(value); + } + + /// + /// Sets the selected knowledge value. + /// + /// The knowledge container to access. + /// The value to set. + /// True if set value value, otherwise false. + public bool Set(BehaviorKnowledge knowledge, T value) + { + return knowledge != null && knowledge.Set(Path, value); + } + + /// + /// Gets the selected knowledge value. + /// + /// The knowledge container to access. + /// The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name). + public T Get(BehaviorKnowledge knowledge) + { + object value = null; + if (knowledge != null) + knowledge.Get(Path, out value); + return (T)value; + } + + /// + /// Tries to get the selected knowledge value. Returns true if got value, otherwise false. + /// + /// The knowledge container to access. + /// The output value. + /// True if got value, otherwise false. + public bool TryGet(BehaviorKnowledge knowledge, out T value) + { + value = default; + object tmp = null; + bool result = knowledge != null && knowledge.Get(Path, out tmp); + if (result) + value = (T)tmp; + return result; + } + + /// + public override string ToString() + { + return Path; + } + + /// + public override int GetHashCode() + { + return Path?.GetHashCode() ?? 0; + } + + /// + public int CompareTo(object obj) + { + if (obj is BehaviorKnowledgeSelectorAny otherAny) + return CompareTo(otherAny); + if (obj is BehaviorKnowledgeSelector other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(BehaviorKnowledgeSelectorAny other) + { + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + + /// + public int CompareTo(BehaviorKnowledgeSelector other) + { + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + } +} diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h new file mode 100644 index 000000000..96241a9d3 --- /dev/null +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/Variant.h" +#include "Engine/Serialization/SerializationFwd.h" + +class BehaviorKnowledge; + +/// +/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values. +/// +API_STRUCT(NoDefault, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelectorAny +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorKnowledgeSelectorAny); + + /// + /// Selector path that redirects to the specific knowledge value. + /// + API_FIELD() StringAnsi Path; + + // Sets the selected knowledge value (as Variant). + bool Set(BehaviorKnowledge* knowledge, const Variant& value); + + // Gets the selected knowledge value (as Variant). + Variant Get(BehaviorKnowledge* knowledge); + + // Tries to get the selected knowledge value (as Variant). Returns true if got value, otherwise false. + bool TryGet(BehaviorKnowledge* knowledge, Variant& value); + + FORCE_INLINE bool operator==(const BehaviorKnowledgeSelectorAny& other) const + { + return Path == other.Path; + } + + BehaviorKnowledgeSelectorAny& operator=(const StringAnsiView& other) noexcept + { + Path = other; + return *this; + } + + BehaviorKnowledgeSelectorAny& operator=(StringAnsi&& other) noexcept + { + Path = MoveTemp(other); + return *this; + } + + operator StringAnsi() const + { + return Path; + } +}; + +/// +/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values. +/// +template +API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelector : BehaviorKnowledgeSelectorAny +{ + using BehaviorKnowledgeSelectorAny::Set; + using BehaviorKnowledgeSelectorAny::Get; + using BehaviorKnowledgeSelectorAny::TryGet; + + // Sets the selected knowledge value (typed). + FORCE_INLINE void Set(BehaviorKnowledge* knowledge, const T& value) + { + BehaviorKnowledgeSelectorAny::Set(knowledge, Variant(value)); + } + + // Gets the selected knowledge value (typed). + FORCE_INLINE T Get(BehaviorKnowledge* knowledge) + { + return (T)BehaviorKnowledgeSelectorAny::Get(knowledge); + } + + // Tries to get the selected knowledge value (typed). Returns true if got value, otherwise false. + FORCE_INLINE bool TryGet(BehaviorKnowledge* knowledge, T& value) + { + Variant variant; + if (BehaviorKnowledgeSelectorAny::TryGet(knowledge, variant)) + { + value = (T)variant; + return true; + } + return false; + } + + BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept + { + Path = other; + return *this; + } + + BehaviorKnowledgeSelector& operator=(StringAnsi&& other) noexcept + { + Path = MoveTemp(other); + return *this; + } + + operator StringAnsi() const + { + return Path; + } +}; + +inline uint32 GetHash(const BehaviorKnowledgeSelectorAny& key) +{ + return GetHash(key.Path); +} + +// @formatter:off +namespace Serialization +{ + inline bool ShouldSerialize(const BehaviorKnowledgeSelectorAny& v, const void* otherObj) + { + return !otherObj || v.Path != ((BehaviorKnowledgeSelectorAny*)otherObj)->Path; + } + inline void Serialize(ISerializable::SerializeStream& stream, const BehaviorKnowledgeSelectorAny& v, const void* otherObj) + { + stream.String(v.Path); + } + inline void Deserialize(ISerializable::DeserializeStream& stream, BehaviorKnowledgeSelectorAny& v, ISerializeModifier* modifier) + { + v.Path = stream.GetTextAnsi(); + } +} +// @formatter:on diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs index bd342f3d2..d35cf2802 100644 --- a/Source/Engine/AI/BehaviorTree.cs +++ b/Source/Engine/AI/BehaviorTree.cs @@ -1,9 +1,9 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #if FLAX_EDITOR -using System; using FlaxEngine.Utilities; using FlaxEditor.Scripting; using FlaxEngine.GUI; diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 40f9cb5a2..5843f7ec6 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -128,7 +128,6 @@ namespace FlaxEngine.Json var result = new SoftTypeReference(); if (reader.TokenType == JsonToken.String) result.TypeName = (string)reader.Value; - return result; } @@ -139,6 +138,34 @@ namespace FlaxEngine.Json } } + /// + /// Serialize as path string in internal format. + /// + /// + internal class BehaviorKnowledgeSelectorAnyConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + writer.WriteValue(((BehaviorKnowledgeSelectorAny)value).Path); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = new BehaviorKnowledgeSelectorAny(); + if (reader.TokenType == JsonToken.String) + result.Path = (string)reader.Value; + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(BehaviorKnowledgeSelectorAny); + } + } + /// /// Serialize as Guid in internal format. /// diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 619862b1f..013a52391 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -124,6 +124,7 @@ namespace FlaxEngine.Json settings.Converters.Add(new SceneReferenceConverter()); settings.Converters.Add(new SoftObjectReferenceConverter()); settings.Converters.Add(new SoftTypeReferenceConverter()); + settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter()); settings.Converters.Add(new MarginConverter()); settings.Converters.Add(new VersionConverter()); settings.Converters.Add(new LocalizedStringConverter()); From a44c1521afe701bfad2f5c033ee8c6349e8a599e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Aug 2023 21:40:03 +0200 Subject: [PATCH 090/294] Fix property value sliders usage in BT editor --- Source/Editor/Surface/Archetypes/BehaviorTree.cs | 8 ++++++++ Source/Editor/Windows/Assets/BehaviorTreeWindow.cs | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index 8ca466423..e055d584d 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -27,6 +27,7 @@ namespace FlaxEditor.Surface.Archetypes private ScriptType _type; private InputBox _input; private OutputBox _output; + internal bool _isValueEditing; public BehaviorTreeNode Instance; @@ -172,6 +173,13 @@ namespace FlaxEditor.Surface.Archetypes { base.OnValuesChanged(); + if (_isValueEditing) + { + // Skip updating instance when it's being edited by user via UI + UpdateTitle(); + return; + } + try { if (Instance != null) diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 7c5ff57a4..8213a4dd7 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -61,7 +61,7 @@ namespace FlaxEditor.Windows.Assets _undo = new Undo(); _undo.UndoDone += OnUndoRedo; _undo.RedoDone += OnUndoRedo; - _undo.ActionDone += OnUndoRedo; + _undo.ActionDone += OnUndoAction; // Split Panels _split1 = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.None) @@ -120,6 +120,12 @@ namespace FlaxEditor.Windows.Assets _nodePropertiesEditor.BuildLayoutOnUpdate(); } + private void OnUndoAction(IUndoAction action) + { + MarkAsEdited(); + UpdateToolstrip(); + } + private void OnNodeSelectionChanged() { // Select node instances to view/edit @@ -149,7 +155,9 @@ namespace FlaxEditor.Windows.Assets { if (nodes[j] is Surface.Archetypes.BehaviorTree.Node node && node.Instance == instance) { + node._isValueEditing = true; node.SetValue(1, FlaxEngine.Json.JsonSerializer.SaveToBytes(instance)); + node._isValueEditing = false; break; } } From 2e9facc4291f60844fa7619a3126bd6eb6be4599 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Aug 2023 21:41:20 +0200 Subject: [PATCH 091/294] Add `Random::RandRange` --- Source/Engine/Core/Random.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Engine/Core/Random.h b/Source/Engine/Core/Random.h index 805c687ab..2fdfc1449 100644 --- a/Source/Engine/Core/Random.h +++ b/Source/Engine/Core/Random.h @@ -14,4 +14,15 @@ namespace Random { return (float)rand() / (float)RAND_MAX; } + + /// + /// Generates a pseudo-random number from specific range. + /// + /// The minimum value (inclusive). + /// The maximum value (inclusive). + /// The random number. + inline float RandRange(float min, float max) + { + return min + (max - min) * Rand(); + } } From fce82247abf2e8614c32495b04e94e5f84852bf2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Aug 2023 21:42:43 +0200 Subject: [PATCH 092/294] Add Delay node to BT --- Source/Engine/AI/BehaviorTreeNodes.cpp | 22 ++++++++++++++++ Source/Engine/AI/BehaviorTreeNodes.h | 35 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 783ac9157..57149aa04 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -1,7 +1,9 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "BehaviorTreeNodes.h" +#include "Behavior.h" #include "BehaviorKnowledge.h" +#include "Engine/Core/Random.h" #include "Engine/Serialization/Serialization.h" BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context) @@ -96,3 +98,23 @@ BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext cont return result; } + +int32 BehaviorTreeDelayNode::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeDelayNode::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + if (!WaitTimeSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft)) + state->TimeLeft = WaitTime; + state->TimeLeft = Random::RandRange(Math::Max(WaitTime - RandomDeviation, 0.0f), WaitTime + RandomDeviation); +} + +BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context) +{ + auto state = GetState(context.Memory); + state->TimeLeft -= context.DeltaTime; + return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Success : BehaviorUpdateResult::Running; +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 67d19099a..d8e447029 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -2,7 +2,9 @@ #pragma once +#include "BehaviorKnowledge.h" #include "BehaviorTreeNode.h" +#include "BehaviorKnowledgeSelector.h" #include "Engine/Core/Collections/Array.h" /// @@ -59,3 +61,36 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTre API_FIELD(Attributes="EditorOrder(10)") float UpdateFPS = 10.0f; }; + +/// +/// Delay node that waits a specific amount of time while executed. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeDelayNode : public BehaviorTreeNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeDelayNode, BehaviorTreeNode); + API_AUTO_SERIALIZATION(); + + // Time in seconds to wait when node gets activated. Unused if WaitTimeSelector is used. + API_FIELD(Attributes="EditorOrder(10), Limit(0)") + float WaitTime = 3.0f; + + // Wait time randomization range to deviate original value. + API_FIELD(Attributes="EditorOrder(20), Limit(0)") + float RandomDeviation = 0.0f; + + // Wait time from behavior's knowledge (blackboard, goal or sensor). If set, overrides WaitTime but still uses RandomDeviation. + API_FIELD(Attributes="EditorOrder(30)") + BehaviorKnowledgeSelector WaitTimeSelector; + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + +private: + struct State + { + float TimeLeft; + }; +}; From a6e503d21bcb8b91b07336afbbf0707bd23cdab1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Aug 2023 00:07:25 +0200 Subject: [PATCH 093/294] Add Selector node to BT --- Source/Engine/AI/BehaviorTreeNodes.cpp | 34 ++++++++++++++++++++++++++ Source/Engine/AI/BehaviorTreeNodes.h | 20 +++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 57149aa04..fa40dd8e1 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -99,6 +99,40 @@ BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext cont return result; } +int32 BehaviorTreeSelectorNode::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeSelectorNode::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + new(state)State(); +} + +BehaviorUpdateResult BehaviorTreeSelectorNode::Update(BehaviorUpdateContext context) +{ + auto state = GetState(context.Memory); + + if (state->CurrentChildIndex >= Children.Count()) + return BehaviorUpdateResult::Failed; + + auto result = Children[state->CurrentChildIndex]->InvokeUpdate(context); + + switch (result) + { + case BehaviorUpdateResult::Success: + return BehaviorUpdateResult::Success; + case BehaviorUpdateResult::Failed: + state->CurrentChildIndex++; // Move to the next node + if (state->CurrentChildIndex < Children.Count()) + result = BehaviorUpdateResult::Running; // Keep on running to the next child on the next update + break; + } + + return result; +} + int32 BehaviorTreeDelayNode::GetStateSize() const { return sizeof(State); diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index d8e447029..9d6683d74 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -45,6 +45,26 @@ private: }; }; +/// +/// Selector node updates all its children (from left to right) until one of them succeeds. If all children fail, the selector fails. +/// +API_CLASS() class FLAXENGINE_API BehaviorTreeSelectorNode : public BehaviorTreeCompoundNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSelectorNode, BehaviorTreeCompoundNode); + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + +private: + struct State + { + int32 CurrentChildIndex = 0; + }; +}; + /// /// Root node of the behavior tree. Contains logic properties and definitions for the runtime. /// From cc5cde5bc79d1288a802f3e38615791a583ef82c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Aug 2023 17:38:48 +0200 Subject: [PATCH 094/294] Add Nested node to BT --- Source/Engine/AI/Behavior.cpp | 1 + Source/Engine/AI/BehaviorTreeNode.h | 1 + Source/Engine/AI/BehaviorTreeNodes.cpp | 100 ++++++++++++++++++++++++- Source/Engine/AI/BehaviorTreeNodes.h | 27 +++++++ Source/Engine/AI/BehaviorTypes.h | 7 +- 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index a8eb761a8..f496c887d 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -79,6 +79,7 @@ void Behavior::OnLateUpdate() context.Behavior = this; context.Knowledge = &_knowledge; context.Memory = _knowledge.Memory; + context.RelevantNodes = &_knowledge.RelevantNodes; context.DeltaTime = updateDeltaTime; const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 69e3bcb46..80126902d 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -13,6 +13,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject); friend class BehaviorTreeGraph; friend class BehaviorKnowledge; + friend class BehaviorTreeSubTreeNode; protected: // Raw memory byte offset from the start of the behavior memory block. diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index fa40dd8e1..d4d3ce02f 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -4,15 +4,49 @@ #include "Behavior.h" #include "BehaviorKnowledge.h" #include "Engine/Core/Random.h" +#include "Engine/Scripting/Scripting.h" +#if USE_CSHARP +#include "Engine/Scripting/ManagedCLR/MClass.h" +#endif #include "Engine/Serialization/Serialization.h" +bool IsAssignableFrom(const StringAnsiView& to, const StringAnsiView& from) +{ + // Special case of null + if (to.IsEmpty()) + return from.IsEmpty(); + if (from.IsEmpty()) + return false; + + // Exact typename math + if (to == from) + return true; + + // Scripting Type match + const ScriptingTypeHandle typeHandleTo = Scripting::FindScriptingType(to); + const ScriptingTypeHandle typeHandleFrom = Scripting::FindScriptingType(from); + if (typeHandleTo && typeHandleFrom) + return typeHandleTo.IsAssignableFrom(typeHandleFrom); + +#if USE_CSHARP + // MClass match + const auto mclassTo = Scripting::FindClass(to); + const auto mclassFrom = Scripting::FindClass(from); + if (mclassTo && mclassFrom) + return mclassTo == mclassFrom || mclassFrom->IsSubClassOf(mclassTo); +#endif + + return false; +} + BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context) { ASSERT_LOW_LAYER(_executionIndex != -1); - if (context.Knowledge->RelevantNodes.Get(_executionIndex) == false) + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; + if (relevantNodes.Get(_executionIndex) == false) { // Node becomes relevant so initialize it's state - context.Knowledge->RelevantNodes.Set(_executionIndex, true); + relevantNodes.Set(_executionIndex, true); InitState(context.Behavior, context.Memory); } @@ -22,7 +56,7 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& // Check if node is not relevant anymore if (result != BehaviorUpdateResult::Running) { - context.Knowledge->RelevantNodes.Set(_executionIndex, false); + relevantNodes.Set(_executionIndex, false); ReleaseState(context.Behavior, context.Memory); } @@ -152,3 +186,63 @@ BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context state->TimeLeft -= context.DeltaTime; return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Success : BehaviorUpdateResult::Running; } + +int32 BehaviorTreeSubTreeNode::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeSubTreeNode::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + new(state)State(); + const BehaviorTree* tree = Tree.Get(); + if (!tree || tree->WaitForLoaded()) + return; + state->Memory.Resize(tree->Graph.NodesStatesSize); + state->RelevantNodes.Resize(tree->Graph.NodesCount, false); + state->RelevantNodes.SetAll(false); +} + +void BehaviorTreeSubTreeNode::ReleaseState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + const BehaviorTree* tree = Tree.Get(); + if (tree && tree->IsLoaded()) + { + for (const auto& node : tree->Graph.Nodes) + { + if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes[node.Instance->_executionIndex]) + node.Instance->ReleaseState(behavior, state->Memory.Get()); + } + } + state->~State(); +} + +BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(BehaviorUpdateContext context) +{ + const BehaviorTree* tree = Tree.Get(); + if (!tree || !tree->Graph.Root) + return BehaviorUpdateResult::Failed; + const StringAnsiView treeBlackboardType = tree->Graph.Root->BlackboardType; + if (treeBlackboardType.HasChars()) + { + // Validate if nested tree blackboard data matches (the same type or base type) + const VariantType& blackboardType = context.Knowledge->Blackboard.Type; + if (IsAssignableFrom(treeBlackboardType, StringAnsiView(blackboardType.GetTypeName()))) + { + LOG(Error, "Cannot use nested '{}' with Blackboard of type '{}' inside '{}' with Blackboard of type '{}'", + tree->ToString(), String(treeBlackboardType), + context.Knowledge->Tree->ToString(), blackboardType.ToString()); + return BehaviorUpdateResult::Failed; + } + } + + // Override memory with custom one for the subtree + auto state = GetState(context.Memory); + context.Memory = state->Memory.Get(); + context.RelevantNodes = &state->RelevantNodes; + + // Run nested tree + return tree->Graph.Root->InvokeUpdate(context); +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 9d6683d74..e28d0b81b 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -6,6 +6,7 @@ #include "BehaviorTreeNode.h" #include "BehaviorKnowledgeSelector.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Content/AssetReference.h" /// /// Base class for compound Behavior Tree nodes that composite child nodes. @@ -114,3 +115,29 @@ private: float TimeLeft; }; }; + +/// +/// Sub-tree node runs a nested Behavior Tree within this tree. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeSubTreeNode : public BehaviorTreeNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSubTreeNode, BehaviorTreeNode); + API_AUTO_SERIALIZATION(); + + // Nested behavior tree to execute within this node. + API_FIELD(Attributes="EditorOrder(10), Limit(0)") + AssetReference Tree; + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + void ReleaseState(Behavior* behavior, void* memory) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + + struct State + { + Array Memory; + BitArray<> RelevantNodes; + }; +}; diff --git a/Source/Engine/AI/BehaviorTypes.h b/Source/Engine/AI/BehaviorTypes.h index 554cdf57a..bfbde2c26 100644 --- a/Source/Engine/AI/BehaviorTypes.h +++ b/Source/Engine/AI/BehaviorTypes.h @@ -12,7 +12,7 @@ class BehaviorKnowledge; /// /// Behavior update context state. /// -API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext +API_STRUCT(NoDefault) struct FLAXENGINE_API BehaviorUpdateContext { DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext); @@ -31,6 +31,11 @@ API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext /// API_FIELD() void* Memory; + /// + /// Pointer to array with per-node bit indicating whether node is relevant (active in graph with state created). + /// + API_FIELD() void* RelevantNodes; + /// /// Simulation time delta (in seconds) since the last update. /// From 863b6338ce74107ac301f554dd7d55d7ed797b96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Aug 2023 18:28:05 +0200 Subject: [PATCH 095/294] Fix missing asset refs from BT nodes data --- Source/Engine/AI/BehaviorTree.cpp | 19 +++++++++++++++++++ Source/Engine/AI/BehaviorTree.h | 8 +------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index 653e44335..acc089036 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -5,6 +5,7 @@ #include "BehaviorTreeNodes.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Content/JsonAsset.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Serialization/JsonSerializer.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -174,6 +175,24 @@ bool BehaviorTree::SaveSurface(const BytesContainer& data) return false; } +void BehaviorTree::GetReferences(Array& output) const +{ + // Base + BinaryAsset::GetReferences(output); + + Graph.GetReferences(output); + + // Extract refs from serialized nodes data + for (const BehaviorTreeGraphNode& n : Graph.Nodes) + { + if (n.Instance == nullptr) + continue; + const Variant& data = n.Values[1]; + if (data.Type == VariantType::Blob) + JsonAssetBase::GetReferences(StringAnsiView((char*)data.AsBlob.Data, data.AsBlob.Length), output); + } +} + #endif Asset::LoadResult BehaviorTree::load() diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h index f81228162..0888be64d 100644 --- a/Source/Engine/AI/BehaviorTree.h +++ b/Source/Engine/AI/BehaviorTree.h @@ -82,13 +82,7 @@ public: public: // [BinaryAsset] #if USE_EDITOR - void GetReferences(Array& output) const override - { - // Base - BinaryAsset::GetReferences(output); - - Graph.GetReferences(output); - } + void GetReferences(Array& output) const override; #endif protected: From d86eb5a4c21744606de4f7c636fd380460c6443b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Aug 2023 10:45:50 +0200 Subject: [PATCH 096/294] Add drag&drop for nested BT spawning in Editor --- Source/Editor/Surface/BehaviorTreeSurface.cs | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 30b548a5d..512a55638 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using FlaxEditor.Content; using FlaxEditor.Scripting; using FlaxEditor.Surface.ContextMenu; using FlaxEditor.Surface.Elements; @@ -145,6 +146,41 @@ namespace FlaxEditor.Surface base.CanUseNodeType(groupArchetype, nodeArchetype); } + /// + protected override bool ValidateDragItem(AssetItem assetItem) + { + if (assetItem.IsOfType()) + return true; + return base.ValidateDragItem(assetItem); + } + + /// + protected override void HandleDragDropAssets(List objects, DragDropEventArgs args) + { + for (int i = 0; i < objects.Count; i++) + { + var assetItem = objects[i]; + SurfaceNode node = null; + + if (assetItem.IsOfType()) + { + var instance = new BehaviorTreeSubTreeNode(); + instance.Name = Utilities.Utils.GetPropertyNameUI(assetItem.ShortName); + instance.Tree = (BehaviorTree)assetItem.LoadAsync(); + node = Context.SpawnNode(19, 1, args.SurfaceLocation, new object[] + { + typeof(BehaviorTreeSubTreeNode).FullName, + FlaxEngine.Json.JsonSerializer.SaveToBytes(instance), + }); + FlaxEngine.Object.Destroy(instance); + } + + if (node != null) + args.SurfaceLocation.X += node.Width + 10; + } + base.HandleDragDropAssets(objects, args); + } + /// public override void OnDestroy() { From 3259af3368bbdf4213b5c077f3eae0a787380b28 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Aug 2023 11:12:05 +0200 Subject: [PATCH 097/294] Add Force Finish node to BT --- Source/Engine/AI/Behavior.cpp | 9 +++++---- Source/Engine/AI/Behavior.h | 3 ++- Source/Engine/AI/BehaviorTreeNodes.cpp | 6 ++++++ Source/Engine/AI/BehaviorTreeNodes.h | 16 ++++++++++++++++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index f496c887d..0c7fb6e67 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -26,12 +26,12 @@ void Behavior::StartLogic() _knowledge.InitMemory(tree); } -void Behavior::StopLogic() +void Behavior::StopLogic(BehaviorUpdateResult result) { - if (_result != BehaviorUpdateResult::Running) + if (_result != BehaviorUpdateResult::Running || result == BehaviorUpdateResult::Running) return; _accumulatedTime = 0.0f; - _result = BehaviorUpdateResult::Success; + _result = result; } void Behavior::ResetLogic() @@ -83,8 +83,9 @@ void Behavior::OnLateUpdate() context.DeltaTime = updateDeltaTime; const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) - { _result = result; + if (_result != BehaviorUpdateResult::Running) + { Finished(); } } diff --git a/Source/Engine/AI/Behavior.h b/Source/Engine/AI/Behavior.h index a98fce4d5..6800eb9a1 100644 --- a/Source/Engine/AI/Behavior.h +++ b/Source/Engine/AI/Behavior.h @@ -71,7 +71,8 @@ public: /// /// Stops the logic. /// - API_FUNCTION() void StopLogic(); + /// The logic result. + API_FUNCTION() void StopLogic(BehaviorUpdateResult result = BehaviorUpdateResult::Success); /// /// Resets the behavior logic by clearing knowledge (clears blackboard and removes goals) and resetting execution state (goes back to root). diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index d4d3ce02f..f7d03fdda 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -246,3 +246,9 @@ BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(BehaviorUpdateContext conte // Run nested tree return tree->Graph.Root->InvokeUpdate(context); } + +BehaviorUpdateResult BehaviorTreeForceFinishNode::Update(BehaviorUpdateContext context) +{ + context.Behavior->StopLogic(Result); + return Result; +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index e28d0b81b..347aa2b49 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -141,3 +141,19 @@ public: BitArray<> RelevantNodes; }; }; + +/// +/// Forces behavior execution end with a specific result (eg. force fail). +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeForceFinishNode : public BehaviorTreeNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeForceFinishNode, BehaviorTreeNode); + API_AUTO_SERIALIZATION(); + + // Execution result. + API_FIELD() BehaviorUpdateResult Result = BehaviorUpdateResult::Success; + +public: + // [BehaviorTreeNode] + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; +}; From 72488c4250a39e3f0381808c990f50a4341cb49e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 22 Aug 2023 21:14:33 -0500 Subject: [PATCH 098/294] Update cs script temple to use non-indented namespace. --- Content/Editor/Scripting/ScriptTemplate.cs | 50 +++++++++---------- .../Editor/Content/Proxy/CSharpScriptProxy.cs | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs index 2a150306d..8d43ad590 100644 --- a/Content/Editor/Scripting/ScriptTemplate.cs +++ b/Content/Editor/Scripting/ScriptTemplate.cs @@ -3,34 +3,34 @@ using System.Collections.Generic; using FlaxEngine; namespace %namespace% + +/// +/// %class% Script. +/// +public class %class% : Script { - /// - /// %class% Script. - /// - public class %class% : Script + /// + public override void OnStart() { - /// - public override void OnStart() - { - // Here you can add code that needs to be called when script is created, just before the first game update - } - - /// - public override void OnEnable() - { - // Here you can add code that needs to be called when script is enabled (eg. register for events) - } + // Here you can add code that needs to be called when script is created, just before the first game update + } + + /// + public override void OnEnable() + { + // Here you can add code that needs to be called when script is enabled (eg. register for events) + } - /// - public override void OnDisable() - { - // Here you can add code that needs to be called when script is disabled (eg. unregister from events) - } + /// + public override void OnDisable() + { + // Here you can add code that needs to be called when script is disabled (eg. unregister from events) + } - /// - public override void OnUpdate() - { - // Here you can add code that needs to be called every frame - } + /// + public override void OnUpdate() + { + // Here you can add code that needs to be called every frame } } + diff --git a/Source/Editor/Content/Proxy/CSharpScriptProxy.cs b/Source/Editor/Content/Proxy/CSharpScriptProxy.cs index 24c44aa20..09440d43a 100644 --- a/Source/Editor/Content/Proxy/CSharpScriptProxy.cs +++ b/Source/Editor/Content/Proxy/CSharpScriptProxy.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Content var copyrightComment = string.IsNullOrEmpty(gameSettings.CopyrightNotice) ? string.Empty : string.Format("// {0}{1}{1}", gameSettings.CopyrightNotice, Environment.NewLine); scriptTemplate = scriptTemplate.Replace("%copyright%", copyrightComment); scriptTemplate = scriptTemplate.Replace("%class%", scriptName); - scriptTemplate = scriptTemplate.Replace("%namespace%", scriptNamespace); + scriptTemplate = scriptTemplate.Replace("%namespace%", $"{scriptNamespace};"); // Save File.WriteAllText(outputPath, scriptTemplate, Encoding.UTF8); From 3295d58684894d39945ee6e803b1f3fe237ab6de Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 22 Aug 2023 21:34:36 -0500 Subject: [PATCH 099/294] Simplify --- Content/Editor/Scripting/ScriptTemplate.cs | 2 +- Source/Editor/Content/Proxy/CSharpScriptProxy.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs index 8d43ad590..663acf05f 100644 --- a/Content/Editor/Scripting/ScriptTemplate.cs +++ b/Content/Editor/Scripting/ScriptTemplate.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using FlaxEngine; -namespace %namespace% +namespace %namespace%; /// /// %class% Script. diff --git a/Source/Editor/Content/Proxy/CSharpScriptProxy.cs b/Source/Editor/Content/Proxy/CSharpScriptProxy.cs index 09440d43a..24c44aa20 100644 --- a/Source/Editor/Content/Proxy/CSharpScriptProxy.cs +++ b/Source/Editor/Content/Proxy/CSharpScriptProxy.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Content var copyrightComment = string.IsNullOrEmpty(gameSettings.CopyrightNotice) ? string.Empty : string.Format("// {0}{1}{1}", gameSettings.CopyrightNotice, Environment.NewLine); scriptTemplate = scriptTemplate.Replace("%copyright%", copyrightComment); scriptTemplate = scriptTemplate.Replace("%class%", scriptName); - scriptTemplate = scriptTemplate.Replace("%namespace%", $"{scriptNamespace};"); + scriptTemplate = scriptTemplate.Replace("%namespace%", scriptNamespace); // Save File.WriteAllText(outputPath, scriptTemplate, Encoding.UTF8); From 73c0758410b7695da9033d721493aa1aa4ad8db1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 23 Aug 2023 13:43:46 +0200 Subject: [PATCH 100/294] Add releasing nested nodes state when BT tree goes irrelevant --- Source/Engine/AI/BehaviorTreeNode.h | 5 +++++ Source/Engine/AI/BehaviorTreeNodes.cpp | 24 ++++++++++++++++++++++-- Source/Engine/AI/BehaviorTreeNodes.h | 5 +++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 80126902d..1d435dc6c 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -14,6 +14,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS friend class BehaviorTreeGraph; friend class BehaviorKnowledge; friend class BehaviorTreeSubTreeNode; + friend class BehaviorTreeCompoundNode; protected: // Raw memory byte offset from the start of the behavior memory block. @@ -86,4 +87,8 @@ public: ASSERT((int32)sizeof(T) <= GetStateSize()); return reinterpret_cast((byte*)memory + _memoryOffset); } + +protected: + virtual void InvokeReleaseState(const BehaviorUpdateContext& context); +}; }; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index f7d03fdda..97b63a05b 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -56,8 +56,7 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& // Check if node is not relevant anymore if (result != BehaviorUpdateResult::Running) { - relevantNodes.Set(_executionIndex, false); - ReleaseState(context.Behavior, context.Memory); + InvokeReleaseState(context); } return result; @@ -79,6 +78,13 @@ void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier DESERIALIZE(Name); } +void BehaviorTreeNode::InvokeReleaseState(const BehaviorUpdateContext& context) +{ + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; + relevantNodes.Set(_executionIndex, false); + ReleaseState(context.Behavior, context.Memory); +} + void BehaviorTreeCompoundNode::Init(BehaviorTree* tree) { for (BehaviorTreeNode* child : Children) @@ -96,6 +102,20 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont return result; } +void BehaviorTreeCompoundNode::InvokeReleaseState(const BehaviorUpdateContext& context) +{ + const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes; + for (BehaviorTreeNode* child : Children) + { + if (relevantNodes.Get(child->_executionIndex) == true) + { + child->InvokeReleaseState(context); + } + } + + BehaviorTreeNode::InvokeReleaseState(context); +} + int32 BehaviorTreeSequenceNode::GetStateSize() const { return sizeof(State); diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 347aa2b49..723db818c 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -6,6 +6,7 @@ #include "BehaviorTreeNode.h" #include "BehaviorKnowledgeSelector.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/BitArray.h" #include "Engine/Content/AssetReference.h" /// @@ -24,6 +25,10 @@ public: // [BehaviorTreeNode] void Init(BehaviorTree* tree) override; BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + +protected: + // [BehaviorTreeNode] + void InvokeReleaseState(const BehaviorUpdateContext& context) override; }; /// From c58dc512914ab587cbab1b7105379e1f6b3e5c94 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 10:12:40 +0200 Subject: [PATCH 101/294] Various improvements to Visject Surface --- Source/Editor/Surface/SurfaceNode.cs | 4 +- .../Surface/VisjectSurface.CopyPaste.cs | 1 - Source/Editor/Surface/VisjectSurface.Input.cs | 7 +- Source/Editor/Surface/VisjectSurface.cs | 125 ++---------------- .../Editor/Surface/VisjectSurfaceContext.cs | 4 +- 5 files changed, 19 insertions(+), 122 deletions(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index eee753655..633b41373 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -923,7 +923,7 @@ namespace FlaxEditor.Surface OnValuesChanged(); Surface?.MarkAsEdited(graphEdited); - if (Surface?.Undo != null) + if (Surface != null) Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited)); _isDuringValuesEditing = false; @@ -950,7 +950,7 @@ namespace FlaxEditor.Surface OnValuesChanged(); Surface.MarkAsEdited(graphEdited); - if (Surface?.Undo != null) + if (Surface != null) Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited)); _isDuringValuesEditing = false; diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index f9e37dc20..aee617ad3 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Undo; using FlaxEngine; diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 062a0d603..c83c3fa48 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -243,8 +243,11 @@ namespace FlaxEditor.Surface node.Location += delta; _leftMouseDownPos = location; _movingNodesDelta += delta; - Cursor = CursorType.SizeAll; - MarkAsEdited(false); + if (_movingNodes.Count > 0) + { + Cursor = CursorType.SizeAll; + MarkAsEdited(false); + } } // Handled diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 0275586a4..5104adc27 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -730,11 +730,18 @@ namespace FlaxEditor.Surface /// True if use undo/redo action for node removing. public void Delete(IEnumerable controls, bool withUndo = true) { + if (!CanEdit || controls == null || !controls.Any()) + return; + var selectionChanged = false; List nodes = null; foreach (var control in controls) { - selectionChanged |= control.IsSelected; + if (control.IsSelected) + { + selectionChanged = true; + control.IsSelected = false; + } if (control is SurfaceNode node) { @@ -802,54 +809,7 @@ namespace FlaxEditor.Surface { if (!CanEdit) return; - - var node = control as SurfaceNode; - if (node == null) - { - Context.OnControlDeleted(control); - MarkAsEdited(); - return; - } - - if ((node.Archetype.Flags & NodeFlags.NoRemove) != 0) - return; - - if (control.IsSelected) - { - control.IsSelected = false; - SelectionChanged?.Invoke(); - } - - if (Undo == null || !withUndo) - { - // Remove node - node.RemoveConnections(); - Nodes.Remove(node); - Context.OnControlDeleted(node); - } - else - { - var actions = new List(); - - // Break connections for node - { - var action = new EditNodeConnections(Context, node); - node.RemoveConnections(); - action.End(); - actions.Add(action); - } - - // Remove node - { - var action = new AddRemoveNodeAction(node, false); - action.Do(); - actions.Add(action); - } - - Undo.AddAction(new MultiUndoAction(actions, "Remove node")); - } - - MarkAsEdited(); + Delete(new[] { control }, withUndo); } /// @@ -859,72 +819,7 @@ namespace FlaxEditor.Surface { if (!CanEdit) return; - bool edited = false; - - List nodes = null; - for (int i = 0; i < _rootControl.Children.Count; i++) - { - if (_rootControl.Children[i] is SurfaceNode node) - { - if (node.IsSelected && (node.Archetype.Flags & NodeFlags.NoRemove) == 0) - { - if (nodes == null) - nodes = new List(); - nodes.Add(node); - edited = true; - } - } - else if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected) - { - i--; - Context.OnControlDeleted(control); - edited = true; - } - } - - if (nodes != null) - { - if (Undo == null) - { - // Remove all nodes - foreach (var node in nodes) - { - node.RemoveConnections(); - Nodes.Remove(node); - Context.OnControlDeleted(node); - } - } - else - { - var actions = new List(); - - // Break connections for all nodes - foreach (var node in nodes) - { - var action = new EditNodeConnections(Context, node); - node.RemoveConnections(); - action.End(); - actions.Add(action); - } - - // Remove all nodes - foreach (var node in nodes) - { - var action = new AddRemoveNodeAction(node, false); - action.Do(); - actions.Add(action); - } - - Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes")); - } - } - - if (edited) - { - MarkAsEdited(); - } - - SelectionChanged?.Invoke(); + Delete(SelectedControls, true); } /// diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 0a4b59412..1c8754325 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -395,8 +395,8 @@ namespace FlaxEditor.Surface OnControlSpawned(node); // Undo action - if (Surface != null && Surface.Undo != null) - Surface.Undo.AddAction(new AddRemoveNodeAction(node, true)); + if (Surface != null) + Surface.AddBatchedUndoAction(new AddRemoveNodeAction(node, true)); MarkAsModified(); From 60e5c5446c83032d9e8aa698c1f18d2a84ceb398 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 10:41:02 +0200 Subject: [PATCH 102/294] Add `SealedNodes` feature to Visject Surface nodes --- Source/Editor/Surface/SurfaceNode.cs | 5 +++++ .../Surface/VisjectSurface.CopyPaste.cs | 19 ++++++++++++++++++ Source/Editor/Surface/VisjectSurface.cs | 20 ++++++++++++++++++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 633b41373..1ebc1c1c5 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -390,6 +390,11 @@ namespace FlaxEditor.Surface UpdateBoxesTypes(); } + /// + /// Array of nodes that are sealed to this node - sealed nodes are duplicated/copied/pasted/removed in a batch. Null if unused. + /// + public virtual SurfaceNode[] SealedNodes => null; + /// /// Gets a value indicating whether this node uses dependent boxes. /// diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index aee617ad3..7df90f298 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -65,6 +65,25 @@ namespace FlaxEditor.Surface return; } + // Collect sealed nodes to be copied as well + foreach (var control in selection.ToArray()) + { + if (control is SurfaceNode node) + { + var sealedNodes = node.SealedNodes; + if (sealedNodes != null) + { + foreach (var sealedNode in sealedNodes) + { + if (sealedNode != null && !selection.Contains(sealedNode)) + { + selection.Add(sealedNode); + } + } + } + } + } + var dataModel = new DataModel(); var dataModelNodes = new List(selection.Count); var dataModelComments = new List(); diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 5104adc27..012d98778 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -749,7 +749,25 @@ namespace FlaxEditor.Surface { if (nodes == null) nodes = new List(); - nodes.Add(node); + var sealedNodes = node.SealedNodes; + if (sealedNodes != null) + { + foreach (var sealedNode in sealedNodes) + { + if (sealedNode != null) + { + if (sealedNode.IsSelected) + { + selectionChanged = true; + sealedNode.IsSelected = false; + } + if (!nodes.Contains(sealedNode)) + nodes.Add(sealedNode); + } + } + } + if (!nodes.Contains(node)) + nodes.Add(node); } } else From 952fe6151584271c3ab9e86c9f1faadd7976031d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 10:43:22 +0200 Subject: [PATCH 103/294] Add `SurfaceNodeActions` for more contextual surface nodes scripting --- .../Archetypes/Animation.MultiBlend.cs | 4 +- .../Archetypes/Animation.StateMachine.cs | 20 ++--- Source/Editor/Surface/Archetypes/Animation.cs | 8 +- .../Editor/Surface/Archetypes/Comparisons.cs | 8 +- Source/Editor/Surface/Archetypes/Constants.cs | 12 +-- Source/Editor/Surface/Archetypes/Flow.cs | 16 ++-- Source/Editor/Surface/Archetypes/Function.cs | 80 +++++++++---------- Source/Editor/Surface/Archetypes/Material.cs | 4 +- Source/Editor/Surface/Archetypes/Packing.cs | 8 +- .../Editor/Surface/Archetypes/Parameters.cs | 16 ++-- .../Surface/Archetypes/ParticleModules.cs | 24 +++--- Source/Editor/Surface/Archetypes/Particles.cs | 8 +- Source/Editor/Surface/Archetypes/Textures.cs | 4 +- Source/Editor/Surface/Archetypes/Tools.cs | 44 +++++----- Source/Editor/Surface/SurfaceComment.cs | 8 +- Source/Editor/Surface/SurfaceControl.cs | 8 +- Source/Editor/Surface/SurfaceNode.cs | 8 +- .../Surface/Undo/AddRemoveNodeAction.cs | 6 +- .../Surface/VisjectSurface.CopyPaste.cs | 6 +- Source/Editor/Surface/VisjectSurface.cs | 4 +- .../VisjectSurfaceContext.Serialization.cs | 54 ++++++++++--- .../Editor/Surface/VisjectSurfaceContext.cs | 12 +-- .../Windows/AssetReferencesGraphWindow.cs | 6 +- 23 files changed, 199 insertions(+), 169 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 4d3239205..9773e9695 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -542,9 +542,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateUI(); } diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index f044cd6ff..a540ab697 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -174,17 +174,17 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateUI(); } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); // Ensure to have unique name var title = StateMachineTitle; @@ -741,9 +741,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); LoadTransitions(); @@ -1432,9 +1432,9 @@ namespace FlaxEditor.Surface.Archetypes public override int TransitionsDataIndex => 2; /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); // Ensure to have unique name var title = StateTitle; @@ -1452,9 +1452,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateTitle(); } diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 6364106e4..16b7993de 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -49,9 +49,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); if (Surface != null) UpdateTitle(); @@ -162,9 +162,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); // Peek deserialized boxes _blendPoses.Clear(); diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs index dc90c2123..a0efbd92f 100644 --- a/Source/Editor/Surface/Archetypes/Comparisons.cs +++ b/Source/Editor/Surface/Archetypes/Comparisons.cs @@ -50,9 +50,9 @@ namespace FlaxEditor.Surface.Archetypes { } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); // Restore saved input boxes layout if (Values[0] is byte[] data) @@ -62,9 +62,9 @@ namespace FlaxEditor.Surface.Archetypes } } - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateBoxes(); GetBox(0).CurrentTypeChanged += box => UpdateBoxes(); diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs index 72a77ea4d..cbe7822e3 100644 --- a/Source/Editor/Surface/Archetypes/Constants.cs +++ b/Source/Editor/Surface/Archetypes/Constants.cs @@ -43,9 +43,9 @@ namespace FlaxEditor.Surface.Archetypes box.CurrentType = new ScriptType(Values[0].GetType()); } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); var box = (OutputBox)GetBox(0); if (Values[0] == null) @@ -100,9 +100,9 @@ namespace FlaxEditor.Surface.Archetypes base.OnValuesChanged(); } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); _output = (OutputBox)Elements[0]; _typePicker = new TypePickerControl @@ -238,9 +238,9 @@ namespace FlaxEditor.Surface.Archetypes base.OnValuesChanged(); } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); _output = (OutputBox)Elements[0]; _keyTypePicker = new TypePickerControl diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs index 11eec534a..b0922b1b7 100644 --- a/Source/Editor/Surface/Archetypes/Flow.cs +++ b/Source/Editor/Surface/Archetypes/Flow.cs @@ -25,9 +25,9 @@ namespace FlaxEditor.Surface.Archetypes { } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); // Restore saved output boxes layout var count = (int)Values[0]; @@ -35,9 +35,9 @@ namespace FlaxEditor.Surface.Archetypes AddBox(true, i + 1, i, string.Empty, new ScriptType(typeof(void)), true); } - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); _removeButton = new Button(0, 0, 20, 20) { @@ -107,9 +107,9 @@ namespace FlaxEditor.Surface.Archetypes { } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); // Restore saved output boxes layout if (Values[0] is byte[] data) @@ -119,9 +119,9 @@ namespace FlaxEditor.Surface.Archetypes } } - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateBoxes(); GetBox(1).CurrentTypeChanged += box => UpdateBoxes(); diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index cffb2ad6f..54b561e4c 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -54,9 +54,9 @@ namespace FlaxEditor.Surface.Archetypes protected abstract Asset LoadSignature(Guid id, out string[] types, out string[] names); /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); FlaxEngine.Content.AssetReloading += OnAssetReloading; FlaxEngine.Content.AssetDisposing += OnContentAssetDisposing; @@ -275,17 +275,17 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); _nameField.Text = SignatureName; } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); // Ensure to have unique name var name = SignatureName; @@ -397,9 +397,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); _outputBox = GetBox(0); _outputBox.CurrentType = SignatureType; @@ -466,9 +466,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); _inputBox = GetBox(0); _inputBox.CurrentType = SignatureType; @@ -634,18 +634,18 @@ namespace FlaxEditor.Surface.Archetypes protected override Color FooterColor => new Color(200, 11, 112); /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); Title = SurfaceUtils.GetMethodDisplayName((string)Values[0]); UpdateSignature(); } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); // Update the boxes connection types to match the signature // Do it after surface load so connections can receive update on type changes of the method parameter @@ -663,9 +663,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); var method = GetMethod(); _parameters = null; @@ -999,9 +999,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); var method = GetMethod(out _, out _, out var parameters); if (method && parameters != null) @@ -1028,18 +1028,18 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); Title = SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); // Update the boxes connection types to match the signature // Do it after surface load so connections can receive update on type changes of the method parameter @@ -1182,9 +1182,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); UpdateSignature(); } @@ -1700,9 +1700,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); LoadSignature(); @@ -1715,9 +1715,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); // Setup initial signature var defaultSignature = _signature.Node == null; @@ -1743,7 +1743,7 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnDeleted() + public override void OnDeleted(SurfaceNodeActions action) { // Send event for (int i = 0; i < Surface.Nodes.Count; i++) @@ -1752,7 +1752,7 @@ namespace FlaxEditor.Surface.Archetypes node.OnFunctionDeleted(this); } - base.OnDeleted(); + base.OnDeleted(action); } /// @@ -1906,9 +1906,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); @@ -1959,9 +1959,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); @@ -2135,9 +2135,9 @@ namespace FlaxEditor.Surface.Archetypes UpdateUI(); } - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); // Find reflection information about event _signature = null; @@ -2193,11 +2193,11 @@ namespace FlaxEditor.Surface.Archetypes { } - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { Title = "Bind " + (string)Values[1]; - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); } } @@ -2208,11 +2208,11 @@ namespace FlaxEditor.Surface.Archetypes { } - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { Title = "Unbind " + (string)Values[1]; - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); } } diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 1a797c67d..b85d1c9d4 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -197,9 +197,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); // Fix emissive box (it's a strange error) GetBox(3).CurrentType = new ScriptType(typeof(Float3)); diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 5a5e9cd5b..0abae2d0b 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -30,9 +30,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); _in0 = (InputBox)GetBox(0); _in1 = (InputBox)GetBox(1); @@ -111,9 +111,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); // Update title and the tooltip var typeName = (string)Values[0]; diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs index c7f343cfb..59987f0aa 100644 --- a/Source/Editor/Surface/Archetypes/Parameters.cs +++ b/Source/Editor/Surface/Archetypes/Parameters.cs @@ -426,9 +426,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); if (Surface != null) { @@ -438,9 +438,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateTitle(); } @@ -832,9 +832,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); if (Surface != null) UpdateCombo(); @@ -842,9 +842,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); if (Surface != null) { diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index ad5b45eab..e5be3d35d 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -268,20 +268,20 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { _enabled.Checked = ModuleEnabled; _enabled.StateChanged += OnEnabledStateChanged; - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); ParticleSurface?.ArrangeModulesNodes(); } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); ParticleSurface.ArrangeModulesNodes(); } @@ -295,11 +295,11 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnDeleted() + public override void OnDeleted(SurfaceNodeActions action) { ParticleSurface.ArrangeModulesNodes(); - base.OnDeleted(); + base.OnDeleted(action); } /// @@ -324,9 +324,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateOutputBoxType(); } @@ -381,9 +381,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateInputBox(); } @@ -416,9 +416,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateTextBox(); } diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index dd861d4af..e49921bec 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -214,9 +214,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); if (Surface == null) return; @@ -265,9 +265,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateOutputBoxType(); } diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index 258bfd41a..18c21ecea 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -38,9 +38,9 @@ namespace FlaxEditor.Surface.Archetypes UpdateUI(); } - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); UpdateUI(); } diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index d6c5d8c30..ac7341cd6 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -193,9 +193,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); var upperLeft = GetBox(0).BottomLeft; var upperRight = GetBox(1).BottomRight; @@ -477,9 +477,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); var upperLeft = GetBox(0).BottomLeft; var upperRight = GetBox(1).BottomRight; @@ -657,9 +657,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); UpdateCombo(); } @@ -682,9 +682,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); var type = ScriptType.Null; if (Context.Surface is VisualScriptSurface visjectSurface) @@ -710,9 +710,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); UpdateOutputBox(); } @@ -763,9 +763,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); UpdateOutputBox(); } @@ -822,9 +822,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); if (Surface != null) _picker.ValueTypeName = (string)Values[0]; @@ -881,9 +881,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); if (Surface != null) _picker.ValueTypeName = (string)Values[0]; @@ -932,9 +932,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); if (Surface != null) _picker.ValueTypeName = (string)Values[0]; @@ -984,9 +984,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); + base.OnLoaded(action); if (Surface != null) _picker.ValueTypeName = (string)Values[0]; @@ -1047,9 +1047,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); _input = (InputBox)GetBox(0); _output = (OutputBox)GetBox(1); diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs index 4256fa17f..ff38b1aa8 100644 --- a/Source/Editor/Surface/SurfaceComment.cs +++ b/Source/Editor/Surface/SurfaceComment.cs @@ -59,9 +59,9 @@ namespace FlaxEditor.Surface } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); // Read node data Title = TitleValue; @@ -70,9 +70,9 @@ namespace FlaxEditor.Surface } /// - public override void OnSpawned() + public override void OnSpawned(SurfaceNodeActions action) { - base.OnSpawned(); + base.OnSpawned(action); // Randomize color Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f); diff --git a/Source/Editor/Surface/SurfaceControl.cs b/Source/Editor/Surface/SurfaceControl.cs index 8c207676c..56e2ee8f6 100644 --- a/Source/Editor/Surface/SurfaceControl.cs +++ b/Source/Editor/Surface/SurfaceControl.cs @@ -122,14 +122,14 @@ namespace FlaxEditor.Surface /// /// Called when control gets loaded and added to surface. /// - public virtual void OnLoaded() + public virtual void OnLoaded(SurfaceNodeActions action) { } /// /// Called when surface gets loaded and nodes boxes are connected. /// - public virtual void OnSurfaceLoaded() + public virtual void OnSurfaceLoaded(SurfaceNodeActions action) { UpdateRectangles(); } @@ -137,14 +137,14 @@ namespace FlaxEditor.Surface /// /// Called after adding the control to the surface after user spawn (eg. add comment, add new node, etc.). /// - public virtual void OnSpawned() + public virtual void OnSpawned(SurfaceNodeActions action) { } /// /// Called on removing the control from the surface after user delete/cut operation (eg. delete comment, cut node, etc.). /// - public virtual void OnDeleted() + public virtual void OnDeleted(SurfaceNodeActions action) { Dispose(); } diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 1ebc1c1c5..4f628b6af 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -884,9 +884,9 @@ namespace FlaxEditor.Surface } /// - public override void OnSurfaceLoaded() + public override void OnSurfaceLoaded(SurfaceNodeActions action) { - base.OnSurfaceLoaded(); + base.OnSurfaceLoaded(action); UpdateBoxesTypes(); @@ -898,11 +898,11 @@ namespace FlaxEditor.Surface } /// - public override void OnDeleted() + public override void OnDeleted(SurfaceNodeActions action) { RemoveConnections(); - base.OnDeleted(); + base.OnDeleted(action); } /// diff --git a/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs b/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs index b529ea3c0..ba6770287 100644 --- a/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs +++ b/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs @@ -67,8 +67,8 @@ namespace FlaxEditor.Surface.Undo else if (_nodeValues != null && _nodeValues.Length != 0) throw new InvalidOperationException("Invalid node values."); node.Location = _nodeLocation; - context.OnControlLoaded(node); - node.OnSurfaceLoaded(); + context.OnControlLoaded(node, SurfaceNodeActions.Undo); + node.OnSurfaceLoaded(SurfaceNodeActions.Undo); context.MarkAsModified(); } @@ -89,7 +89,7 @@ namespace FlaxEditor.Surface.Undo // Remove node context.Nodes.Remove(node); - context.OnControlDeleted(node); + context.OnControlDeleted(node, SurfaceNodeActions.Undo); context.MarkAsModified(); } diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index 7df90f298..fd27315e6 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -373,7 +373,7 @@ namespace FlaxEditor.Surface } } - Context.OnControlLoaded(node); + Context.OnControlLoaded(node, SurfaceNodeActions.Paste); } // Setup connections @@ -413,11 +413,11 @@ namespace FlaxEditor.Surface // Post load foreach (var node in nodes) { - node.Value.OnSurfaceLoaded(); + node.Value.OnSurfaceLoaded(SurfaceNodeActions.Paste); } foreach (var node in nodes) { - node.Value.OnSpawned(); + node.Value.OnSpawned(SurfaceNodeActions.Paste); } // Add undo action diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 012d98778..2b965dfbb 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -772,7 +772,7 @@ namespace FlaxEditor.Surface } else { - Context.OnControlDeleted(control); + Context.OnControlDeleted(control, SurfaceNodeActions.User); } } if (selectionChanged) @@ -787,7 +787,7 @@ namespace FlaxEditor.Surface { node.RemoveConnections(); Nodes.Remove(node); - Context.OnControlDeleted(node); + Context.OnControlDeleted(node, SurfaceNodeActions.User); } } else diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs index f2059c581..7d75e006b 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs @@ -13,6 +13,32 @@ using Utils = FlaxEditor.Utilities.Utils; namespace FlaxEditor.Surface { + /// + /// Types of surface actions. + /// + public enum SurfaceNodeActions + { + /// + /// Node has been created by surface load. + /// + Load, + + /// + /// Node has been created/deleted by user action. + /// + User, + + /// + /// Node has been created/deleted via undo. + /// + Undo, + + /// + /// Node has been pasted. + /// + Paste, + } + /// /// The missing node. Cached the node group, type and stored values information. /// @@ -135,7 +161,7 @@ namespace FlaxEditor.Surface if (comment == null) throw new InvalidOperationException("Failed to create comment."); - OnControlLoaded(comment); + OnControlLoaded(comment, SurfaceNodeActions.Load); } } } @@ -144,7 +170,7 @@ namespace FlaxEditor.Surface for (int i = 0; i < RootControl.Children.Count; i++) { if (RootControl.Children[i] is SurfaceControl control) - control.OnSurfaceLoaded(); + control.OnSurfaceLoaded(SurfaceNodeActions.Load); } RootControl.UnlockChildrenRecursive(); @@ -650,7 +676,7 @@ namespace FlaxEditor.Surface // Meta node.Meta.Load(stream); - OnControlLoaded(node); + OnControlLoaded(node, SurfaceNodeActions.Load); } } else if (version == 7000) @@ -813,7 +839,7 @@ namespace FlaxEditor.Surface // Meta node.Meta.Load(stream); - OnControlLoaded(node); + OnControlLoaded(node, SurfaceNodeActions.Load); } } else @@ -856,9 +882,10 @@ namespace FlaxEditor.Surface /// Called when control gets added to the surface as spawn operation (eg. add new comment or add new node). /// /// The control. - public virtual void OnControlSpawned(SurfaceControl control) + /// The action node. + public virtual void OnControlSpawned(SurfaceControl control, SurfaceNodeActions action) { - control.OnSpawned(); + control.OnSpawned(action); ControlSpawned?.Invoke(control); if (Surface != null && control is SurfaceNode node) Surface.OnNodeSpawned(node); @@ -868,10 +895,11 @@ namespace FlaxEditor.Surface /// Called when control gets removed from the surface as delete/cut operation (eg. remove comment or cut node). /// /// The control. - public virtual void OnControlDeleted(SurfaceControl control) + /// The action node. + public virtual void OnControlDeleted(SurfaceControl control, SurfaceNodeActions action) { ControlDeleted?.Invoke(control); - control.OnDeleted(); + control.OnDeleted(action); if (control is SurfaceNode node) Surface.OnNodeDeleted(node); } @@ -880,16 +908,17 @@ namespace FlaxEditor.Surface /// Called when control gets loaded and should be added to the surface. Handles surface nodes initialization. /// /// The control. - public virtual void OnControlLoaded(SurfaceControl control) + /// The action node. + public virtual void OnControlLoaded(SurfaceControl control, SurfaceNodeActions action) { if (control is SurfaceNode node) { // Initialize node - OnNodeLoaded(node); + OnNodeLoaded(node, action); } // Link control - control.OnLoaded(); + control.OnLoaded(action); control.Parent = RootControl; if (control is SurfaceComment) @@ -903,7 +932,8 @@ namespace FlaxEditor.Surface /// Called when node gets loaded and should be added to the surface. Creates node elements from the archetype. /// /// The node. - public virtual void OnNodeLoaded(SurfaceNode node) + /// The action node. + public virtual void OnNodeLoaded(SurfaceNode node, SurfaceNodeActions action) { // Create child elements of the node based on it's archetype int elementsCount = node.Archetype.Elements?.Length ?? 0; diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 1c8754325..36e811c48 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -315,9 +315,9 @@ namespace FlaxEditor.Surface } // Initialize - OnControlLoaded(comment); - comment.OnSurfaceLoaded(); - OnControlSpawned(comment); + OnControlLoaded(comment, SurfaceNodeActions.User); + comment.OnSurfaceLoaded(SurfaceNodeActions.User); + OnControlSpawned(comment, SurfaceNodeActions.User); MarkAsModified(); @@ -389,10 +389,10 @@ namespace FlaxEditor.Surface throw new InvalidOperationException("Invalid node custom values."); } node.Location = location; - OnControlLoaded(node); + OnControlLoaded(node, SurfaceNodeActions.User); beforeSpawned?.Invoke(node); - node.OnSurfaceLoaded(); - OnControlSpawned(node); + node.OnSurfaceLoaded(SurfaceNodeActions.User); + OnControlSpawned(node, SurfaceNodeActions.User); // Undo action if (Surface != null) diff --git a/Source/Editor/Windows/AssetReferencesGraphWindow.cs b/Source/Editor/Windows/AssetReferencesGraphWindow.cs index de5b86f96..d49896e2e 100644 --- a/Source/Editor/Windows/AssetReferencesGraphWindow.cs +++ b/Source/Editor/Windows/AssetReferencesGraphWindow.cs @@ -127,9 +127,9 @@ namespace FlaxEditor.Windows Nodes.AddRange(nodes); foreach (var node in nodes) { - Context.OnControlLoaded(node); - node.OnSurfaceLoaded(); - Context.OnControlSpawned(node); + Context.OnControlLoaded(node, SurfaceNodeActions.Load); + node.OnSurfaceLoaded(SurfaceNodeActions.Load); + Context.OnControlSpawned(node, SurfaceNodeActions.Load); } ShowWholeGraph(); UnlockChildrenRecursive(); From 5f581bf15679840536f887dafc17c55c552990cc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 10:43:59 +0200 Subject: [PATCH 104/294] Add `OnPasted` to Visject Surface nodes for custom post-paste logic --- Source/Editor/Surface/SurfaceNode.cs | 8 ++++++++ Source/Editor/Surface/VisjectSurface.CopyPaste.cs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 4f628b6af..65b481c75 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -395,6 +395,14 @@ namespace FlaxEditor.Surface /// public virtual SurfaceNode[] SealedNodes => null; + /// + /// Called after adding the control to the surface after paste. + /// + /// The nodes IDs mapping (original node ID to pasted node ID). Can be sued to update internal node's data after paste operation from the original data. + public virtual void OnPasted(System.Collections.Generic.Dictionary idsMapping) + { + } + /// /// Gets a value indicating whether this node uses dependent boxes. /// diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index fd27315e6..62aaccc73 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -419,6 +419,10 @@ namespace FlaxEditor.Surface { node.Value.OnSpawned(SurfaceNodeActions.Paste); } + foreach (var node in nodes) + { + node.Value.OnPasted(idsMapping); + } // Add undo action if (Undo != null && nodes.Count > 0) From 0c206564be23adfdd2bb226f7bdae0ef739a4327 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 13:02:29 +0200 Subject: [PATCH 105/294] Fix generic typename to be properly converted to C# --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 1414629cc..5c10831ef 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -379,7 +379,7 @@ namespace Flax.Build.Bindings { typeName += '<'; foreach (var arg in typeInfo.GenericArgs) - typeName += arg.Type.Replace("::", "."); + typeName += GenerateCSharpNativeToManaged(buildData, arg, caller); typeName += '>'; } if (apiType != null) From 69ab69c5ccfa8757f4921218c2e91ef0b807fe53 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 13:05:54 +0200 Subject: [PATCH 106/294] Add Decorators support to BT graph --- .../Editor/Surface/Archetypes/BehaviorTree.cs | 536 ++++++++++++++---- Source/Editor/Surface/BehaviorTreeSurface.cs | 5 + .../Windows/Assets/BehaviorTreeWindow.cs | 4 +- Source/Engine/AI/BehaviorTree.cpp | 23 +- Source/Engine/AI/BehaviorTreeNode.h | 39 +- Source/Engine/AI/BehaviorTreeNodes.cpp | 78 ++- Source/Engine/AI/BehaviorTreeNodes.h | 2 +- 7 files changed, 566 insertions(+), 121 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index e055d584d..7623c9915 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEngine; @@ -17,26 +19,16 @@ namespace FlaxEditor.Surface.Archetypes public static class BehaviorTree { /// - /// Customized for the Behavior Tree node. + /// Base class for Behavior Tree nodes wrapped inside . /// - internal class Node : SurfaceNode + internal class NodeBase : SurfaceNode { - private const float ConnectionAreaMargin = 12.0f; - private const float ConnectionAreaHeight = 12.0f; - - private ScriptType _type; - private InputBox _input; - private OutputBox _output; + protected ScriptType _type; internal bool _isValueEditing; public BehaviorTreeNode Instance; - internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) - { - return new Node(id, context, nodeArch, groupArch); - } - - internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + protected NodeBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) { } @@ -48,11 +40,13 @@ namespace FlaxEditor.Surface.Archetypes title = title.Substring(12); if (title.EndsWith("Node")) title = title.Substring(0, title.Length - 4); + if (title.EndsWith("Decorator")) + title = title.Substring(0, title.Length - 9); title = Utilities.Utils.GetPropertyNameUI(title); return title; } - private void UpdateTitle() + protected virtual void UpdateTitle() { string title = null; if (Instance != null) @@ -66,34 +60,18 @@ namespace FlaxEditor.Surface.Archetypes var typeName = (string)Values[0]; title = "Missing Type " + typeName; } - if (title != Title) - { - Title = title; - ResizeAuto(); - } + Title = title; } - /// - public override void OnLoaded() + public override void OnLoaded(SurfaceNodeActions action) { - base.OnLoaded(); - - // Setup boxes - _input = (InputBox)GetBox(0); - _output = (OutputBox)GetBox(1); + base.OnLoaded(action); // Setup node type and data - var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste; - var flags = Archetype.Flags & ~flagsRoot; var typeName = (string)Values[0]; _type = TypeUtils.GetType(typeName); if (_type != null) { - bool isRoot = _type.Type == typeof(BehaviorTreeRootNode); - _input.Enabled = _input.Visible = !isRoot; - _output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type); - if (isRoot) - flags |= flagsRoot; TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type); try { @@ -112,6 +90,259 @@ namespace FlaxEditor.Surface.Archetypes { Instance = null; } + + UpdateTitle(); + } + + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + // Skip updating instance when it's being edited by user via UI + if (!_isValueEditing) + { + try + { + if (Instance != null) + { + // Reload node instance from data + var instanceData = (byte[])Values[1]; + if (instanceData == null || instanceData.Length == 0) + { + // Recreate instance data to default state if previous state was empty + var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType + instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance); + } + FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber); + } + } + catch (Exception ex) + { + Editor.LogError("Failed to load Behavior Tree node of type " + _type); + Editor.LogWarning(ex); + } + } + + UpdateTitle(); + } + + public override void OnSpawned(SurfaceNodeActions action) + { + base.OnSpawned(action); + + ResizeAuto(); + } + + public override void OnDestroy() + { + if (IsDisposing) + return; + _type = ScriptType.Null; + Object.Destroy(ref Instance); + + base.OnDestroy(); + } + } + + /// + /// Customized for the Behavior Tree node. + /// + internal class Node : NodeBase + { + private const float ConnectionAreaMargin = 12.0f; + private const float ConnectionAreaHeight = 12.0f; + private const float DecoratorsMarginX = 5.0f; + private const float DecoratorsMarginY = 2.0f; + + private InputBox _input; + private OutputBox _output; + internal List _decorators; + + internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + { + return new Node(id, context, nodeArch, groupArch); + } + + internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + public unsafe List DecoratorIds + { + get + { + var result = new List(); + var ids = Values.Length >= 3 ? Values[2] as byte[] : null; + if (ids != null) + { + fixed (byte* data = ids) + { + uint* ptr = (uint*)data; + int count = ids.Length / sizeof(uint); + for (int i = 0; i < count; i++) + result.Add(ptr[i]); + } + } + return result; + } + set + { + var ids = new byte[sizeof(uint) * value.Count]; + if (value != null) + { + fixed (byte* data = ids) + { + uint* ptr = (uint*)data; + for (var i = 0; i < value.Count; i++) + ptr[i] = value[i]; + } + } + SetValue(2, ids); + } + } + + public unsafe List Decorators + { + get + { + if (_decorators == null) + { + _decorators = new List(); + var ids = Values.Length >= 3 ? Values[2] as byte[] : null; + if (ids != null) + { + fixed (byte* data = ids) + { + uint* ptr = (uint*)data; + int count = ids.Length / sizeof(uint); + for (int i = 0; i < count; i++) + { + var decorator = Surface.FindNode(ptr[i]) as Decorator; + if (decorator != null) + _decorators.Add(decorator); + } + } + } + } + return _decorators; + } + set + { + _decorators = null; + var ids = new byte[sizeof(uint) * value.Count]; + if (value != null) + { + fixed (byte* data = ids) + { + uint* ptr = (uint*)data; + for (var i = 0; i < value.Count; i++) + ptr[i] = value[i].ID; + } + } + SetValue(2, ids); + } + } + + public override unsafe SurfaceNode[] SealedNodes + { + get + { + SurfaceNode[] result = null; + var ids = Values.Length >= 3 ? Values[2] as byte[] : null; + if (ids != null) + { + fixed (byte* data = ids) + { + uint* ptr = (uint*)data; + int count = ids.Length / sizeof(uint); + result = new SurfaceNode[count]; + for (int i = 0; i < count; i++) + { + var decorator = Surface.FindNode(ptr[i]) as Decorator; + if (decorator != null) + result[i] = decorator; + } + } + } + return result; + } + } + + public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location) + { + base.OnShowSecondaryContextMenu(menu, location); + + if (!Surface.CanEdit) + return; + + menu.AddSeparator(); + + var nodeTypes = Editor.Instance.CodeEditing.BehaviorTreeNodes.Get(); + + if (_input.Enabled) // Root node cannot have decorators + { + var decorators = menu.AddChildMenu("Add Decorator"); + var decoratorType = new ScriptType(typeof(BehaviorTreeDecorator)); + foreach (var nodeType in nodeTypes) + { + if (nodeType != decoratorType && decoratorType.IsAssignableFrom(nodeType)) + { + var button = decorators.ContextMenu.AddButton(GetTitle(nodeType)); + button.Tag = nodeType; + button.TooltipText = Editor.Instance.CodeDocs.GetTooltip(nodeType); + button.ButtonClicked += OnAddDecoratorButtonClicked; + } + } + } + } + + private void OnAddDecoratorButtonClicked(ContextMenuButton button) + { + var nodeType = (ScriptType)button.Tag; + + // Spawn decorator + var decorator = Context.SpawnNode(19, 3, Location, new object[] + { + nodeType.TypeName, + Utils.GetEmptyArray(), + }); + + // Add decorator to the node + var decorators = Decorators; + decorators.Add((Decorator)decorator); + Decorators = decorators; + } + + public override void OnValuesChanged() + { + // Reject cached value + _decorators = null; + + base.OnValuesChanged(); + + ResizeAuto(); + } + + public override void OnLoaded(SurfaceNodeActions action) + { + base.OnLoaded(action); + + // Setup boxes + _input = (InputBox)GetBox(0); + _output = (OutputBox)GetBox(1); + + // Setup node type and data + var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste; + var flags = Archetype.Flags & ~flagsRoot; + if (_type != null) + { + bool isRoot = _type.Type == typeof(BehaviorTreeRootNode); + _input.Enabled = _input.Visible = !isRoot; + _output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type); + if (isRoot) + flags |= flagsRoot; + } if (Archetype.Flags != flags) { // Apply custom flags @@ -119,18 +350,60 @@ namespace FlaxEditor.Surface.Archetypes Archetype.Flags = flags; } - UpdateTitle(); - } - - /// - public override void OnSpawned() - { - base.OnSpawned(); - ResizeAuto(); } - /// + public override unsafe void OnPasted(Dictionary idsMapping) + { + base.OnPasted(idsMapping); + + // Update decorators + var ids = Values.Length >= 3 ? Values[2] as byte[] : null; + if (ids != null) + { + _decorators = null; + fixed (byte* data = ids) + { + uint* ptr = (uint*)data; + int count = ids.Length / sizeof(uint); + for (int i = 0; i < count; i++) + { + if (idsMapping.TryGetValue(ptr[i], out var id)) + { + // Fix previous parent node to re-apply layout (in case it was forced by spawned decorator) + var decorator = Surface.FindNode(ptr[i]) as Decorator; + var decoratorNode = decorator?.Node; + if (decoratorNode != null) + decoratorNode.ResizeAuto(); + + // Update mapping to the new node + ptr[i] = id; + } + } + } + Values[2] = ids; + ResizeAuto(); + } + } + + public override void OnSurfaceLoaded(SurfaceNodeActions action) + { + base.OnSurfaceLoaded(action); + + ResizeAuto(); + Surface.NodeDeleted += OnNodeDeleted; + } + + private void OnNodeDeleted(SurfaceNode node) + { + if (node is Decorator decorator && Decorators.Contains(decorator)) + { + // Decorator was spawned (eg. via undo) + _decorators = null; + ResizeAuto(); + } + } + public override void ResizeAuto() { if (Surface == null) @@ -144,74 +417,134 @@ namespace FlaxEditor.Surface.Archetypes height += ConnectionAreaHeight; if (_output != null && _output.Visible) height += ConnectionAreaHeight; + var decorators = Decorators; + foreach (var decorator in decorators) + { + decorator.ResizeAuto(); + height += decorator.Height + DecoratorsMarginY; + width = Mathf.Max(width, decorator.Width + 2 * DecoratorsMarginX); + } Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize); - if (_input != null && _input.Visible) - _input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight); - if (_output != null && _output.Visible) - _output.Bounds = new Rectangle(ConnectionAreaMargin, Height - ConnectionAreaHeight, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight); + UpdateRectangles(); } - /// protected override void UpdateRectangles() { - base.UpdateRectangles(); - - // Update boxes placement + Rectangle bounds = Bounds; + if (_input != null && _input.Visible) + { + _input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight); + bounds.Location.Y += _input.Height; + } + var decorators = Decorators; + var indexInParent = IndexInParent; + foreach (var decorator in decorators) + { + decorator.Bounds = new Rectangle(bounds.Location.X + DecoratorsMarginX, bounds.Location.Y, bounds.Width - 2 * DecoratorsMarginX, decorator.Height); + bounds.Location.Y += decorator.Height + DecoratorsMarginY; + if (decorator.IndexInParent < indexInParent) + decorator.IndexInParent = indexInParent + 1; // Push elements above the node + } const float footerSize = FlaxEditor.Surface.Constants.NodeFooterSize; const float headerSize = FlaxEditor.Surface.Constants.NodeHeaderSize; const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin; const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize; - _headerRect = new Rectangle(0, 0, Width, headerSize); - if (_input != null && _input.Visible) - _headerRect.Y += ConnectionAreaHeight; - _footerRect = new Rectangle(0, _headerRect.Bottom, Width, footerSize); - _closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize); + _headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize); + _closeButtonRect = new Rectangle(bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize); + _footerRect = new Rectangle(0, _headerRect.Bottom, bounds.Width, footerSize); + if (_output != null && _output.Visible) + _output.Bounds = new Rectangle(ConnectionAreaMargin, bounds.Height - ConnectionAreaHeight, bounds.Width - ConnectionAreaMargin * 2, ConnectionAreaHeight); } - /// - public override void OnValuesChanged() + protected override void OnLocationChanged() { - base.OnValuesChanged(); + base.OnLocationChanged(); - if (_isValueEditing) - { - // Skip updating instance when it's being edited by user via UI - UpdateTitle(); - return; - } + // Sync attached elements placement + UpdateRectangles(); + } + } - try + /// + /// Customized for the Behavior Tree decorator. + /// + internal class Decorator : NodeBase + { + internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + { + return new Decorator(id, context, nodeArch, groupArch); + } + + internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + public Node Node + { + get { - if (Instance != null) + foreach (var node in Surface.Nodes) { - // Reload node instance from data - var instanceData = (byte[])Values[1]; - if (instanceData == null || instanceData.Length == 0) - { - // Recreate instance data to default state if previous state was empty - var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType - instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance); - } - FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber); - UpdateTitle(); + if (node is Node n && n.DecoratorIds.Contains(ID)) + return n; + } + return null; + } + } + + protected override Color FooterColor => Color.Transparent; + + protected override Float2 CalculateNodeSize(float width, float height) + { + return new Float2(width, height + FlaxEditor.Surface.Constants.NodeHeaderSize); + } + + protected override void UpdateRectangles() + { + base.UpdateRectangles(); + + _footerRect = Rectangle.Empty; + } + + protected override void UpdateTitle() + { + var title = Title; + + base.UpdateTitle(); + + // Update parent node on title change + if (title != Title) + Node?.ResizeAuto(); + } + + public override void Draw() + { + base.Draw(); + + // Outline + if (!_isSelected) + { + var style = Style.Current; + var rect = new Rectangle(Float2.Zero, Size); + Render2D.DrawRectangle(rect, style.BorderHighlighted); + } + } + + public override void OnSurfaceLoaded(SurfaceNodeActions action) + { + base.OnSurfaceLoaded(action); + + if (action == SurfaceNodeActions.Undo) + { + // Update parent node layout when restoring decorator from undo + var node = Node; + if (node != null) + { + node._decorators = null; + node.ResizeAuto(); } } - catch (Exception ex) - { - Editor.LogError("Failed to load Behavior Tree node of type " + _type); - Editor.LogWarning(ex); - } - } - - /// - public override void OnDestroy() - { - if (IsDisposing) - return; - _type = ScriptType.Null; - Object.Destroy(ref Instance); - - base.OnDestroy(); } } @@ -222,13 +555,14 @@ namespace FlaxEditor.Surface.Archetypes { new NodeArchetype { - TypeID = 1, + TypeID = 1, // Task Node Create = Node.Create, Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI, DefaultValues = new object[] { string.Empty, // Type Name Utils.GetEmptyArray(), // Instance Data + null, // List of Decorator Nodes IDs }, Size = new Float2(100, 0), Elements = new[] @@ -239,7 +573,7 @@ namespace FlaxEditor.Surface.Archetypes }, new NodeArchetype { - TypeID = 2, + TypeID = 2, // Root Node Create = Node.Create, Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI, DefaultValues = new object[] @@ -254,6 +588,18 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1), } }, + new NodeArchetype + { + TypeID = 3, // Decorator Node + Create = Decorator.Create, + Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove, + DefaultValues = new object[] + { + string.Empty, // Type Name + Utils.GetEmptyArray(), // Instance Data + }, + Size = new Float2(100, 0), + }, }; } } diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 512a55638..9b201d3f8 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -71,6 +71,10 @@ namespace FlaxEditor.Surface scriptType == typeof(BehaviorTreeRootNode)) return; + // Nodes-only + if (new ScriptType(typeof(BehaviorTreeDecorator)).IsAssignableFrom(scriptType)) + return; + // Create group archetype var groupKey = new KeyValuePair("Behavior Tree", 19); if (!cache.TryGetValue(groupKey, out var group)) @@ -171,6 +175,7 @@ namespace FlaxEditor.Surface { typeof(BehaviorTreeSubTreeNode).FullName, FlaxEngine.Json.JsonSerializer.SaveToBytes(instance), + null, }); FlaxEngine.Object.Destroy(instance); } diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 8213a4dd7..07c024adf 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -135,7 +135,7 @@ namespace FlaxEditor.Windows.Assets { for (var i = 0; i < nodes.Count; i++) { - if (nodes[i] is Surface.Archetypes.BehaviorTree.Node node && node.IsSelected && node.Instance) + if (nodes[i] is Surface.Archetypes.BehaviorTree.NodeBase node && node.IsSelected && node.Instance) selection.Add(node.Instance); } } @@ -153,7 +153,7 @@ namespace FlaxEditor.Windows.Assets // Sync instance data with surface node value storage for (var j = 0; j < nodes.Count; j++) { - if (nodes[j] is Surface.Archetypes.BehaviorTree.Node node && node.Instance == instance) + if (nodes[j] is Surface.Archetypes.BehaviorTree.NodeBase node && node.Instance == instance) { node._isValueEditing = true; node.SetValue(1, FlaxEngine.Json.JsonSerializer.SaveToBytes(instance)); diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index acc089036..3196084dc 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -65,7 +65,7 @@ void BehaviorTreeGraph::Clear() bool BehaviorTreeGraph::onNodeLoaded(Node* n) { - if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2)) + if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2 || n->TypeID == 3)) { // Create node instance object ScriptingTypeHandle type = Scripting::FindScriptingType((StringAnsiView)n->Values[0]); @@ -102,6 +102,26 @@ void BehaviorTreeGraph::LoadRecursive(Node& node) NodesStatesSize += node.Instance->GetStateSize(); NodesCount++; + if (node.TypeID == 1 && node.Values.Count() >= 3) + { + // Load node decorators + const auto& decoratorIds = node.Values[2]; + if (decoratorIds.Type.Type == VariantType::Blob && decoratorIds.AsBlob.Length) + { + const Span ids((uint32*)decoratorIds.AsBlob.Data, decoratorIds.AsBlob.Length / sizeof(uint32)); + for (int32 i = 0; i < ids.Length(); i++) + { + Node* decorator = GetNode(ids[i]); + if (decorator && decorator->Instance) + { + ASSERT_LOW_LAYER(decorator->Instance->Is()); + node.Instance->_decorators.Add((BehaviorTreeDecorator*)decorator->Instance); + decorator->Instance->_parent = node.Instance; + LoadRecursive(*decorator); + } + } + } + } if (auto* nodeCompound = ScriptingObject::Cast(node.Instance)) { auto& children = node.Boxes[1].Connections; @@ -116,6 +136,7 @@ void BehaviorTreeGraph::LoadRecursive(Node& node) if (child && child->Instance) { nodeCompound->Children.Add(child->Instance); + child->Instance->_parent = nodeCompound; LoadRecursive(*child); } } diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 1d435dc6c..845879059 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/SerializableScriptingObject.h" #include "BehaviorTypes.h" @@ -21,6 +22,11 @@ protected: API_FIELD(ReadOnly) int32 _memoryOffset = 0; // Execution index of the node within tree. API_FIELD(ReadOnly) int32 _executionIndex = -1; + // Parent node that owns this node (parent composite or decorator attachment node). + API_FIELD(ReadOnly) BehaviorTreeNode* _parent = nullptr; + +private: + Array> _decorators; public: /// @@ -74,6 +80,10 @@ public: // Helper utility to update node with state creation/cleanup depending on node relevancy. BehaviorUpdateResult InvokeUpdate(const BehaviorUpdateContext& context); + // Helper utility to make node relevant and init it state. + void BecomeRelevant(const BehaviorUpdateContext& context); + // Helper utility to make node irrelevant and release its state (including any nested nodes). + virtual void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly = false); // [SerializableScriptingObject] void Serialize(SerializeStream& stream, const void* otherObj) override; @@ -87,8 +97,31 @@ public: ASSERT((int32)sizeof(T) <= GetStateSize()); return reinterpret_cast((byte*)memory + _memoryOffset); } +}; -protected: - virtual void InvokeReleaseState(const BehaviorUpdateContext& context); -}; +/// +/// Base class for Behavior Tree node decorators. Decorators can implement conditional filtering or override node logic and execution flow. +/// +API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeDecorator : public BehaviorTreeNode +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeDecorator, BehaviorTreeNode); + + /// + /// Checks if the node can be updated (eg. decorator can block it depending on the gameplay conditions or its state). + /// + /// Behavior update context data. + /// True if can update, otherwise false to block it. + API_FUNCTION() virtual bool CanUpdate(BehaviorUpdateContext context) + { + return true; + } + + /// + /// Called after node update to post-process result or perform additional action. + /// + /// Behavior update context data. + /// The node update result. Can be modified by the decorator (eg. to force success). + API_FUNCTION() virtual void PostUpdate(BehaviorUpdateContext context, API_PARAM(ref) BehaviorUpdateResult& result) + { + } }; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 97b63a05b..4dedc0606 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -42,26 +42,72 @@ bool IsAssignableFrom(const StringAnsiView& to, const StringAnsiView& from) BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context) { ASSERT_LOW_LAYER(_executionIndex != -1); - BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; - if (relevantNodes.Get(_executionIndex) == false) + const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes; + + // Check decorators if node can be executed + for (BehaviorTreeDecorator* decorator : _decorators) { - // Node becomes relevant so initialize it's state - relevantNodes.Set(_executionIndex, true); - InitState(context.Behavior, context.Memory); + ASSERT_LOW_LAYER(decorator->_executionIndex != -1); + if (relevantNodes.Get(decorator->_executionIndex) == false) + decorator->BecomeRelevant(context); + if (!decorator->CanUpdate(context)) + { + return BehaviorUpdateResult::Failed; + } } + // Make node relevant before update + if (relevantNodes.Get(_executionIndex) == false) + BecomeRelevant(context); + // Node-specific update - const BehaviorUpdateResult result = Update(context); + for (BehaviorTreeDecorator* decorator : _decorators) + { + decorator->Update(context); + } + BehaviorUpdateResult result = Update(context); + for (BehaviorTreeDecorator* decorator : _decorators) + { + decorator->PostUpdate(context, result); + } // Check if node is not relevant anymore if (result != BehaviorUpdateResult::Running) - { - InvokeReleaseState(context); - } + BecomeIrrelevant(context); return result; } +void BehaviorTreeNode::BecomeRelevant(const BehaviorUpdateContext& context) +{ + // Initialize state + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; + ASSERT_LOW_LAYER(relevantNodes.Get(_executionIndex) == false); + relevantNodes.Set(_executionIndex, true); + InitState(context.Behavior, context.Memory); +} + +void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) +{ + // Release state + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; + ASSERT_LOW_LAYER(relevantNodes.Get(_executionIndex) == true); + relevantNodes.Set(_executionIndex, false); + ReleaseState(context.Behavior, context.Memory); + + if (nodeOnly) + return; + + // Release decorators + for (BehaviorTreeDecorator* decorator : _decorators) + { + if (relevantNodes.Get(decorator->_executionIndex) == true) + { + decorator->BecomeIrrelevant(context); + } + } +} + void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj) { SerializableScriptingObject::Serialize(stream, otherObj); @@ -78,13 +124,6 @@ void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier DESERIALIZE(Name); } -void BehaviorTreeNode::InvokeReleaseState(const BehaviorUpdateContext& context) -{ - BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; - relevantNodes.Set(_executionIndex, false); - ReleaseState(context.Behavior, context.Memory); -} - void BehaviorTreeCompoundNode::Init(BehaviorTree* tree) { for (BehaviorTreeNode* child : Children) @@ -102,18 +141,19 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont return result; } -void BehaviorTreeCompoundNode::InvokeReleaseState(const BehaviorUpdateContext& context) +void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) { + // Make any nested nodes irrelevant as well const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes; for (BehaviorTreeNode* child : Children) { if (relevantNodes.Get(child->_executionIndex) == true) { - child->InvokeReleaseState(context); + child->BecomeIrrelevant(context); } } - BehaviorTreeNode::InvokeReleaseState(context); + BehaviorTreeNode::BecomeIrrelevant(context, nodeOnly); } int32 BehaviorTreeSequenceNode::GetStateSize() const diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 723db818c..ffe477542 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -28,7 +28,7 @@ public: protected: // [BehaviorTreeNode] - void InvokeReleaseState(const BehaviorUpdateContext& context) override; + void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) override; }; /// From d2034622cb1131a7a862a6c982a8cf5cfe41973b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 13:06:55 +0200 Subject: [PATCH 107/294] Add `Invert`, `ForceSuccess`, `ForceFailed` and `Loop` decorators to BT --- Source/Engine/AI/BehaviorTreeNodes.cpp | 49 ++++++++++++++++++++ Source/Engine/AI/BehaviorTreeNodes.h | 64 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 4dedc0606..df424a864 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -312,3 +312,52 @@ BehaviorUpdateResult BehaviorTreeForceFinishNode::Update(BehaviorUpdateContext c context.Behavior->StopLogic(Result); return Result; } + +void BehaviorTreeInvertDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +{ + if (result == BehaviorUpdateResult::Success) + result = BehaviorUpdateResult::Failed; + else if (result == BehaviorUpdateResult::Failed) + result = BehaviorUpdateResult::Success; +} + +void BehaviorTreeForceSuccessDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +{ + if (result != BehaviorUpdateResult::Running) + result = BehaviorUpdateResult::Success; +} + +void BehaviorTreeForceFailedDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +{ + if (result != BehaviorUpdateResult::Running) + result = BehaviorUpdateResult::Failed; +} + +int32 BehaviorTreeLoopDecorator::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeLoopDecorator::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + if (!LoopCountSelector.TryGet(behavior->GetKnowledge(), state->Loops)) + state->Loops = LoopCount; +} + +void BehaviorTreeLoopDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +{ + // Continue looping only if node succeeds + if (result == BehaviorUpdateResult::Success) + { + auto state = GetState(context.Memory); + state->Loops--; + if (state->Loops > 0) + { + // Keep running in a loop but reset node's state (preserve decorators state including Loops counter) + result = BehaviorUpdateResult::Running; + _parent->BecomeIrrelevant(context, true); + _parent->BecomeRelevant(context); + } + } +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index ffe477542..71bae05f0 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -162,3 +162,67 @@ public: // [BehaviorTreeNode] BehaviorUpdateResult Update(BehaviorUpdateContext context) override; }; + +/// +/// Inverts node's result - fails if node succeeded or succeeds if node failed. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeInvertDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeInvertDecorator, BehaviorTreeDecorator); + +public: + // [BehaviorTreeNode] + void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; +}; + +/// +/// Forces node to success - even if it failed. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeForceSuccessDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeForceSuccessDecorator, BehaviorTreeDecorator); + +public: + // [BehaviorTreeNode] + void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; +}; + +/// +/// Forces node to fail - even if it succeeded. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeForceFailedDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeForceFailedDecorator, BehaviorTreeDecorator); + +public: + // [BehaviorTreeNode] + void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; +}; + +/// +/// Loops node execution multiple times as long as it doesn't fail. Returns the last iteration result. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeLoopDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeLoopDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // Amount of times to execute the node. Unused if LoopCountSelector is used. + API_FIELD(Attributes="EditorOrder(10), Limit(0)") + int32 LoopCount = 3; + + // Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount. + API_FIELD(Attributes="EditorOrder(20)") + BehaviorKnowledgeSelector LoopCountSelector; + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + + struct State + { + int32 Loops; + }; +}; From 8c1dfb308717ef65d3fe1511531291a5b8ab9821 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 16:41:01 +0200 Subject: [PATCH 108/294] Add `Time Limit` and `Cooldown` decorators to BT --- Source/Engine/AI/Behavior.cpp | 4 ++ Source/Engine/AI/Behavior.h | 1 + Source/Engine/AI/BehaviorTree.cpp | 3 +- Source/Engine/AI/BehaviorTreeNode.h | 4 +- Source/Engine/AI/BehaviorTreeNodes.cpp | 93 ++++++++++++++++++++++---- Source/Engine/AI/BehaviorTreeNodes.h | 68 ++++++++++++++++++- Source/Engine/AI/BehaviorTypes.h | 5 ++ 7 files changed, 159 insertions(+), 19 deletions(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index 0c7fb6e67..c992c721b 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -31,6 +31,7 @@ void Behavior::StopLogic(BehaviorUpdateResult result) if (_result != BehaviorUpdateResult::Running || result == BehaviorUpdateResult::Running) return; _accumulatedTime = 0.0f; + _totalTime = 0; _result = result; } @@ -43,6 +44,7 @@ void Behavior::ResetLogic() // Reset state _knowledge.FreeMemory(); _accumulatedTime = 0.0f; + _totalTime = 0; _result = BehaviorUpdateResult::Success; if (isActive) @@ -73,6 +75,7 @@ void Behavior::OnLateUpdate() if (_accumulatedTime < updateDeltaTime) return; _accumulatedTime -= updateDeltaTime; + _totalTime += updateDeltaTime; // Update tree BehaviorUpdateContext context; @@ -81,6 +84,7 @@ void Behavior::OnLateUpdate() context.Memory = _knowledge.Memory; context.RelevantNodes = &_knowledge.RelevantNodes; context.DeltaTime = updateDeltaTime; + context.Time = _totalTime; const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) _result = result; diff --git a/Source/Engine/AI/Behavior.h b/Source/Engine/AI/Behavior.h index 6800eb9a1..7aad11c99 100644 --- a/Source/Engine/AI/Behavior.h +++ b/Source/Engine/AI/Behavior.h @@ -19,6 +19,7 @@ API_CLASS() class FLAXENGINE_API Behavior : public Script private: BehaviorKnowledge _knowledge; float _accumulatedTime = 0.0f; + float _totalTime = 0.0f; BehaviorUpdateResult _result = BehaviorUpdateResult::Success; void* _memory = nullptr; diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index 3196084dc..b3fc99ccf 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -112,9 +112,8 @@ void BehaviorTreeGraph::LoadRecursive(Node& node) for (int32 i = 0; i < ids.Length(); i++) { Node* decorator = GetNode(ids[i]); - if (decorator && decorator->Instance) + if (decorator && decorator->Instance && decorator->Instance->Is()) { - ASSERT_LOW_LAYER(decorator->Instance->Is()); node.Instance->_decorators.Add((BehaviorTreeDecorator*)decorator->Instance); decorator->Instance->_parent = node.Instance; LoadRecursive(*decorator); diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 845879059..192db8f7e 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -81,9 +81,9 @@ public: // Helper utility to update node with state creation/cleanup depending on node relevancy. BehaviorUpdateResult InvokeUpdate(const BehaviorUpdateContext& context); // Helper utility to make node relevant and init it state. - void BecomeRelevant(const BehaviorUpdateContext& context); + virtual void BecomeRelevant(const BehaviorUpdateContext& context); // Helper utility to make node irrelevant and release its state (including any nested nodes). - virtual void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly = false); + virtual void BecomeIrrelevant(const BehaviorUpdateContext& context); // [SerializableScriptingObject] void Serialize(SerializeStream& stream, const void* otherObj) override; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index df424a864..c92331ce9 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -60,12 +60,21 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& if (relevantNodes.Get(_executionIndex) == false) BecomeRelevant(context); - // Node-specific update + // Update decorators + bool decoratorFailed = false; for (BehaviorTreeDecorator* decorator : _decorators) { - decorator->Update(context); + decoratorFailed |= decorator->Update(context) == BehaviorUpdateResult::Failed; } - BehaviorUpdateResult result = Update(context); + + // Node-specific update + BehaviorUpdateResult result; + if (decoratorFailed) + result = BehaviorUpdateResult::Failed; + else + result = Update(context); + + // Post-process result from decorators for (BehaviorTreeDecorator* decorator : _decorators) { decorator->PostUpdate(context, result); @@ -87,7 +96,7 @@ void BehaviorTreeNode::BecomeRelevant(const BehaviorUpdateContext& context) InitState(context.Behavior, context.Memory); } -void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) +void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context) { // Release state BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; @@ -95,9 +104,6 @@ void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bo relevantNodes.Set(_executionIndex, false); ReleaseState(context.Behavior, context.Memory); - if (nodeOnly) - return; - // Release decorators for (BehaviorTreeDecorator* decorator : _decorators) { @@ -141,7 +147,7 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont return result; } -void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) +void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& context) { // Make any nested nodes irrelevant as well const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes; @@ -153,7 +159,7 @@ void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& con } } - BehaviorTreeNode::BecomeIrrelevant(context, nodeOnly); + BehaviorTreeNode::BecomeIrrelevant(context); } int32 BehaviorTreeSequenceNode::GetStateSize() const @@ -237,7 +243,7 @@ void BehaviorTreeDelayNode::InitState(Behavior* behavior, void* memory) auto state = GetState(memory); if (!WaitTimeSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft)) state->TimeLeft = WaitTime; - state->TimeLeft = Random::RandRange(Math::Max(WaitTime - RandomDeviation, 0.0f), WaitTime + RandomDeviation); + state->TimeLeft = Random::RandRange(Math::Max(state->TimeLeft - RandomDeviation, 0.0f), state->TimeLeft + RandomDeviation); } BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context) @@ -272,7 +278,7 @@ void BehaviorTreeSubTreeNode::ReleaseState(Behavior* behavior, void* memory) { for (const auto& node : tree->Graph.Nodes) { - if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes[node.Instance->_executionIndex]) + if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes.HasItems() && state->RelevantNodes[node.Instance->_executionIndex]) node.Instance->ReleaseState(behavior, state->Memory.Get()); } } @@ -354,10 +360,69 @@ void BehaviorTreeLoopDecorator::PostUpdate(BehaviorUpdateContext context, Behavi state->Loops--; if (state->Loops > 0) { - // Keep running in a loop but reset node's state (preserve decorators state including Loops counter) + // Keep running in a loop but reset node's state (preserve self state) result = BehaviorUpdateResult::Running; - _parent->BecomeIrrelevant(context, true); - _parent->BecomeRelevant(context); + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; + relevantNodes.Set(_executionIndex, false); + _parent->BecomeIrrelevant(context); + relevantNodes.Set(_executionIndex, true); } } } + +int32 BehaviorTreeTimeLimitDecorator::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeTimeLimitDecorator::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + if (!MaxDurationSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft)) + state->TimeLeft = MaxDuration; + state->TimeLeft = Random::RandRange(Math::Max(state->TimeLeft - RandomDeviation, 0.0f), state->TimeLeft + RandomDeviation); +} + +BehaviorUpdateResult BehaviorTreeTimeLimitDecorator::Update(BehaviorUpdateContext context) +{ + auto state = GetState(context.Memory); + state->TimeLeft -= context.DeltaTime; + return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Failed : BehaviorUpdateResult::Success; +} + +int32 BehaviorTreeCooldownDecorator::GetStateSize() const +{ + return sizeof(State); +} + +void BehaviorTreeCooldownDecorator::InitState(Behavior* behavior, void* memory) +{ + auto state = GetState(memory); + state->EndTime = 0; // Allow to entry on start +} + +void BehaviorTreeCooldownDecorator::ReleaseState(Behavior* behavior, void* memory) +{ + // Preserve the decorator's state to keep cooldown + BitArray<>& relevantNodes = behavior->GetKnowledge()->RelevantNodes; + relevantNodes.Set(_executionIndex, true); +} + +bool BehaviorTreeCooldownDecorator::CanUpdate(BehaviorUpdateContext context) +{ + auto state = GetState(context.Memory); + return state->EndTime <= context.Time; +} + +void BehaviorTreeCooldownDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +{ + if (result != BehaviorUpdateResult::Running) + { + // Initialize cooldown + auto state = GetState(context.Memory); + if (!MinDurationSelector.TryGet(context.Knowledge, state->EndTime)) + state->EndTime = MinDuration; + state->EndTime = Random::RandRange(Math::Max(state->EndTime - RandomDeviation, 0.0f), state->EndTime + RandomDeviation); + state->EndTime += context.Time; + } +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 71bae05f0..2cd2d6f25 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -28,7 +28,7 @@ public: protected: // [BehaviorTreeNode] - void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) override; + void BecomeIrrelevant(const BehaviorUpdateContext& context) override; }; /// @@ -226,3 +226,69 @@ public: int32 Loops; }; }; + +/// +/// Limits maximum duration of the node execution time (in seconds). Node will fail if it runs out of time. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeTimeLimitDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeTimeLimitDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // Maximum node execution time (in seconds). Unused if MaxDurationSelector is used. + API_FIELD(Attributes="EditorOrder(10), Limit(0)") + float MaxDuration = 3.0; + + // Duration time randomization range to deviate original value. + API_FIELD(Attributes="EditorOrder(20), Limit(0)") + float RandomDeviation = 0.0f; + + // Maximum node execution time (in seconds) from behavior's knowledge (blackboard, goal or sensor). If set, overrides MaxDuration but still uses RandomDeviation. + API_FIELD(Attributes="EditorOrder(20)") + BehaviorKnowledgeSelector MaxDurationSelector; + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + + struct State + { + float TimeLeft; + }; +}; + +/// +/// Adds cooldown in between node executions. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeCooldownDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeCooldownDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // Minimum cooldown time (in seconds). Unused if MinDurationSelector is used. + API_FIELD(Attributes="EditorOrder(10), Limit(0)") + float MinDuration = 3.0; + + // Duration time randomization range to deviate original value. + API_FIELD(Attributes="EditorOrder(20), Limit(0)") + float RandomDeviation = 0.0f; + + // Minimum cooldown time (in seconds) from behavior's knowledge (blackboard, goal or sensor). If set, overrides MinDuration but still uses RandomDeviation. + API_FIELD(Attributes="EditorOrder(20)") + BehaviorKnowledgeSelector MinDurationSelector; + +public: + // [BehaviorTreeNode] + int32 GetStateSize() const override; + void InitState(Behavior* behavior, void* memory) override; + void ReleaseState(Behavior* behavior, void* memory) override; + bool CanUpdate(BehaviorUpdateContext context) override; + void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + + struct State + { + float EndTime; + }; +}; diff --git a/Source/Engine/AI/BehaviorTypes.h b/Source/Engine/AI/BehaviorTypes.h index bfbde2c26..80133f53a 100644 --- a/Source/Engine/AI/BehaviorTypes.h +++ b/Source/Engine/AI/BehaviorTypes.h @@ -40,6 +40,11 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API BehaviorUpdateContext /// Simulation time delta (in seconds) since the last update. /// API_FIELD() float DeltaTime; + + /// + /// Simulation time (in seconds) since the first update of the Behavior (sum of all deltas since the start). + /// + API_FIELD() float Time; }; /// From a27cb4e215edaf00ffd08d3490d8c42ee0eee440 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 16:51:25 +0200 Subject: [PATCH 109/294] Refactor BT nodes methods to always use context structure as input --- Source/Engine/AI/BehaviorKnowledge.cpp | 9 ++- Source/Engine/AI/BehaviorTreeNode.h | 20 +++--- Source/Engine/AI/BehaviorTreeNodes.cpp | 86 ++++++++++++++------------ Source/Engine/AI/BehaviorTreeNodes.h | 44 ++++++------- 4 files changed, 85 insertions(+), 74 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index c08a09ffc..fc2504688 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -144,10 +144,17 @@ void BehaviorKnowledge::FreeMemory() { // Release any outstanding nodes state and memory ASSERT_LOW_LAYER(Tree); + BehaviorUpdateContext context; + context.Behavior = Behavior; + context.Knowledge = this; + context.Memory = Memory; + context.RelevantNodes = &RelevantNodes; + context.DeltaTime = 0.0f; + context.Time = 0.0f; for (const auto& node : Tree->Graph.Nodes) { if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex]) - node.Instance->ReleaseState(Behavior, Memory); + node.Instance->ReleaseState(context); } Allocator::Free(Memory); Memory = nullptr; diff --git a/Source/Engine/AI/BehaviorTreeNode.h b/Source/Engine/AI/BehaviorTreeNode.h index 192db8f7e..3ea05977e 100644 --- a/Source/Engine/AI/BehaviorTreeNode.h +++ b/Source/Engine/AI/BehaviorTreeNode.h @@ -51,20 +51,18 @@ public: } /// - /// Initializes node instance state. Called when starting logic simulation for a given behavior. + /// Initializes node instance state. Called when starting logic simulation for a given behavior. Call constructor of the state container. /// - /// Behavior to simulate. - /// Pointer to pre-allocated memory for this node to use (call constructor of the state container). - API_FUNCTION() virtual void InitState(Behavior* behavior, void* memory) + /// Behavior update context data. + API_FUNCTION() virtual void InitState(const BehaviorUpdateContext& context) { } /// - /// Cleanups node instance state. Called when stopping logic simulation for a given behavior. + /// Cleanups node instance state. Called when stopping logic simulation for a given behavior. Call destructor of the state container. /// - /// Behavior to simulate. - /// Pointer to pre-allocated memory for this node to use (call destructor of the state container). - API_FUNCTION() virtual void ReleaseState(Behavior* behavior, void* memory) + /// Behavior update context data. + API_FUNCTION() virtual void ReleaseState(const BehaviorUpdateContext& context) { } @@ -73,7 +71,7 @@ public: /// /// Behavior update context data. /// Operation result enum. - API_FUNCTION() virtual BehaviorUpdateResult Update(BehaviorUpdateContext context) + API_FUNCTION() virtual BehaviorUpdateResult Update(const BehaviorUpdateContext& context) { return BehaviorUpdateResult::Success; } @@ -111,7 +109,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeDecorator : public Behavior /// /// Behavior update context data. /// True if can update, otherwise false to block it. - API_FUNCTION() virtual bool CanUpdate(BehaviorUpdateContext context) + API_FUNCTION() virtual bool CanUpdate(const BehaviorUpdateContext& context) { return true; } @@ -121,7 +119,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeDecorator : public Behavior /// /// Behavior update context data. /// The node update result. Can be modified by the decorator (eg. to force success). - API_FUNCTION() virtual void PostUpdate(BehaviorUpdateContext context, API_PARAM(ref) BehaviorUpdateResult& result) + API_FUNCTION() virtual void PostUpdate(const BehaviorUpdateContext& context, API_PARAM(ref) BehaviorUpdateResult& result) { } }; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index c92331ce9..a134697fe 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -93,7 +93,7 @@ void BehaviorTreeNode::BecomeRelevant(const BehaviorUpdateContext& context) BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; ASSERT_LOW_LAYER(relevantNodes.Get(_executionIndex) == false); relevantNodes.Set(_executionIndex, true); - InitState(context.Behavior, context.Memory); + InitState(context); } void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context) @@ -102,7 +102,7 @@ void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context) BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; ASSERT_LOW_LAYER(relevantNodes.Get(_executionIndex) == true); relevantNodes.Set(_executionIndex, false); - ReleaseState(context.Behavior, context.Memory); + ReleaseState(context); // Release decorators for (BehaviorTreeDecorator* decorator : _decorators) @@ -136,7 +136,7 @@ void BehaviorTreeCompoundNode::Init(BehaviorTree* tree) child->Init(tree); } -BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeCompoundNode::Update(const BehaviorUpdateContext& context) { auto result = BehaviorUpdateResult::Success; for (int32 i = 0; i < Children.Count() && result == BehaviorUpdateResult::Success; i++) @@ -167,13 +167,13 @@ int32 BehaviorTreeSequenceNode::GetStateSize() const return sizeof(State); } -void BehaviorTreeSequenceNode::InitState(Behavior* behavior, void* memory) +void BehaviorTreeSequenceNode::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); + auto state = GetState(context.Memory); new(state)State(); } -BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeSequenceNode::Update(const BehaviorUpdateContext& context) { auto state = GetState(context.Memory); @@ -204,13 +204,13 @@ int32 BehaviorTreeSelectorNode::GetStateSize() const return sizeof(State); } -void BehaviorTreeSelectorNode::InitState(Behavior* behavior, void* memory) +void BehaviorTreeSelectorNode::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); + auto state = GetState(context.Memory); new(state)State(); } -BehaviorUpdateResult BehaviorTreeSelectorNode::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeSelectorNode::Update(const BehaviorUpdateContext& context) { auto state = GetState(context.Memory); @@ -238,15 +238,15 @@ int32 BehaviorTreeDelayNode::GetStateSize() const return sizeof(State); } -void BehaviorTreeDelayNode::InitState(Behavior* behavior, void* memory) +void BehaviorTreeDelayNode::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); - if (!WaitTimeSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft)) + auto state = GetState(context.Memory); + if (!WaitTimeSelector.TryGet(context.Knowledge, state->TimeLeft)) state->TimeLeft = WaitTime; state->TimeLeft = Random::RandRange(Math::Max(state->TimeLeft - RandomDeviation, 0.0f), state->TimeLeft + RandomDeviation); } -BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeDelayNode::Update(const BehaviorUpdateContext& context) { auto state = GetState(context.Memory); state->TimeLeft -= context.DeltaTime; @@ -258,9 +258,9 @@ int32 BehaviorTreeSubTreeNode::GetStateSize() const return sizeof(State); } -void BehaviorTreeSubTreeNode::InitState(Behavior* behavior, void* memory) +void BehaviorTreeSubTreeNode::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); + auto state = GetState(context.Memory); new(state)State(); const BehaviorTree* tree = Tree.Get(); if (!tree || tree->WaitForLoaded()) @@ -270,22 +270,27 @@ void BehaviorTreeSubTreeNode::InitState(Behavior* behavior, void* memory) state->RelevantNodes.SetAll(false); } -void BehaviorTreeSubTreeNode::ReleaseState(Behavior* behavior, void* memory) +void BehaviorTreeSubTreeNode::ReleaseState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); + auto state = GetState(context.Memory); const BehaviorTree* tree = Tree.Get(); if (tree && tree->IsLoaded()) { + // Override memory with custom one for the subtree + BehaviorUpdateContext subContext = context; + subContext.Memory = state->Memory.Get(); + subContext.RelevantNodes = &state->RelevantNodes; + for (const auto& node : tree->Graph.Nodes) { if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes.HasItems() && state->RelevantNodes[node.Instance->_executionIndex]) - node.Instance->ReleaseState(behavior, state->Memory.Get()); + node.Instance->ReleaseState(subContext); } } state->~State(); } -BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(const BehaviorUpdateContext& context) { const BehaviorTree* tree = Tree.Get(); if (!tree || !tree->Graph.Root) @@ -306,20 +311,21 @@ BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(BehaviorUpdateContext conte // Override memory with custom one for the subtree auto state = GetState(context.Memory); - context.Memory = state->Memory.Get(); - context.RelevantNodes = &state->RelevantNodes; + BehaviorUpdateContext subContext = context; + subContext.Memory = state->Memory.Get(); + subContext.RelevantNodes = &state->RelevantNodes; // Run nested tree - return tree->Graph.Root->InvokeUpdate(context); + return tree->Graph.Root->InvokeUpdate(subContext); } -BehaviorUpdateResult BehaviorTreeForceFinishNode::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeForceFinishNode::Update(const BehaviorUpdateContext& context) { context.Behavior->StopLogic(Result); return Result; } -void BehaviorTreeInvertDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +void BehaviorTreeInvertDecorator::PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) { if (result == BehaviorUpdateResult::Success) result = BehaviorUpdateResult::Failed; @@ -327,13 +333,13 @@ void BehaviorTreeInvertDecorator::PostUpdate(BehaviorUpdateContext context, Beha result = BehaviorUpdateResult::Success; } -void BehaviorTreeForceSuccessDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +void BehaviorTreeForceSuccessDecorator::PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) { if (result != BehaviorUpdateResult::Running) result = BehaviorUpdateResult::Success; } -void BehaviorTreeForceFailedDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +void BehaviorTreeForceFailedDecorator::PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) { if (result != BehaviorUpdateResult::Running) result = BehaviorUpdateResult::Failed; @@ -344,14 +350,14 @@ int32 BehaviorTreeLoopDecorator::GetStateSize() const return sizeof(State); } -void BehaviorTreeLoopDecorator::InitState(Behavior* behavior, void* memory) +void BehaviorTreeLoopDecorator::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); - if (!LoopCountSelector.TryGet(behavior->GetKnowledge(), state->Loops)) + auto state = GetState(context.Memory); + if (!LoopCountSelector.TryGet(context.Knowledge, state->Loops)) state->Loops = LoopCount; } -void BehaviorTreeLoopDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +void BehaviorTreeLoopDecorator::PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) { // Continue looping only if node succeeds if (result == BehaviorUpdateResult::Success) @@ -375,15 +381,15 @@ int32 BehaviorTreeTimeLimitDecorator::GetStateSize() const return sizeof(State); } -void BehaviorTreeTimeLimitDecorator::InitState(Behavior* behavior, void* memory) +void BehaviorTreeTimeLimitDecorator::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); - if (!MaxDurationSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft)) + auto state = GetState(context.Memory); + if (!MaxDurationSelector.TryGet(context.Knowledge, state->TimeLeft)) state->TimeLeft = MaxDuration; state->TimeLeft = Random::RandRange(Math::Max(state->TimeLeft - RandomDeviation, 0.0f), state->TimeLeft + RandomDeviation); } -BehaviorUpdateResult BehaviorTreeTimeLimitDecorator::Update(BehaviorUpdateContext context) +BehaviorUpdateResult BehaviorTreeTimeLimitDecorator::Update(const BehaviorUpdateContext& context) { auto state = GetState(context.Memory); state->TimeLeft -= context.DeltaTime; @@ -395,26 +401,26 @@ int32 BehaviorTreeCooldownDecorator::GetStateSize() const return sizeof(State); } -void BehaviorTreeCooldownDecorator::InitState(Behavior* behavior, void* memory) +void BehaviorTreeCooldownDecorator::InitState(const BehaviorUpdateContext& context) { - auto state = GetState(memory); + auto state = GetState(context.Memory); state->EndTime = 0; // Allow to entry on start } -void BehaviorTreeCooldownDecorator::ReleaseState(Behavior* behavior, void* memory) +void BehaviorTreeCooldownDecorator::ReleaseState(const BehaviorUpdateContext& context) { // Preserve the decorator's state to keep cooldown - BitArray<>& relevantNodes = behavior->GetKnowledge()->RelevantNodes; + BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes; relevantNodes.Set(_executionIndex, true); } -bool BehaviorTreeCooldownDecorator::CanUpdate(BehaviorUpdateContext context) +bool BehaviorTreeCooldownDecorator::CanUpdate(const BehaviorUpdateContext& context) { auto state = GetState(context.Memory); return state->EndTime <= context.Time; } -void BehaviorTreeCooldownDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) +void BehaviorTreeCooldownDecorator::PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) { if (result != BehaviorUpdateResult::Running) { diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 2cd2d6f25..10eca71ea 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -24,7 +24,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeCompoundNode : public Behav public: // [BehaviorTreeNode] void Init(BehaviorTree* tree) override; - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; protected: // [BehaviorTreeNode] @@ -41,8 +41,8 @@ API_CLASS() class FLAXENGINE_API BehaviorTreeSequenceNode : public BehaviorTreeC public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + void InitState(const BehaviorUpdateContext& context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; private: struct State @@ -61,8 +61,8 @@ API_CLASS() class FLAXENGINE_API BehaviorTreeSelectorNode : public BehaviorTreeC public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + void InitState(const BehaviorUpdateContext& context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; private: struct State @@ -111,8 +111,8 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeDelayNode : public BehaviorTr public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + void InitState(const BehaviorUpdateContext& context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; private: struct State @@ -136,9 +136,9 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeSubTreeNode : public Behavior public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - void ReleaseState(Behavior* behavior, void* memory) override; - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + void InitState(const BehaviorUpdateContext& context) override; + void ReleaseState(const BehaviorUpdateContext& context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; struct State { @@ -160,7 +160,7 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeForceFinishNode : public Beha public: // [BehaviorTreeNode] - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; }; /// @@ -172,7 +172,7 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeInvertDecorator : public Beha public: // [BehaviorTreeNode] - void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + void PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) override; }; /// @@ -184,7 +184,7 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeForceSuccessDecorator : publi public: // [BehaviorTreeNode] - void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + void PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) override; }; /// @@ -196,7 +196,7 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeForceFailedDecorator : public public: // [BehaviorTreeNode] - void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + void PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) override; }; /// @@ -218,8 +218,8 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeLoopDecorator : public Behavi public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + void InitState(const BehaviorUpdateContext& context) override; + void PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) override; struct State { @@ -250,8 +250,8 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeTimeLimitDecorator : public B public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - BehaviorUpdateResult Update(BehaviorUpdateContext context) override; + void InitState(const BehaviorUpdateContext& context) override; + BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override; struct State { @@ -282,10 +282,10 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeCooldownDecorator : public Be public: // [BehaviorTreeNode] int32 GetStateSize() const override; - void InitState(Behavior* behavior, void* memory) override; - void ReleaseState(Behavior* behavior, void* memory) override; - bool CanUpdate(BehaviorUpdateContext context) override; - void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override; + void InitState(const BehaviorUpdateContext& context) override; + void ReleaseState(const BehaviorUpdateContext& context) override; + bool CanUpdate(const BehaviorUpdateContext& context) override; + void PostUpdate(const BehaviorUpdateContext& context, BehaviorUpdateResult& result) override; struct State { From 2026c84baae687f40341c86b80dcf6b9c5a46326 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Aug 2023 16:56:28 +0200 Subject: [PATCH 110/294] Hide node close button when read-only (eg. play mode) --- Source/Editor/Surface/SurfaceNode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 65b481c75..944cea6d0 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -1030,9 +1030,9 @@ namespace FlaxEditor.Surface Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button - if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0) + if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit) { - Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); + Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); } // Footer From 992cc381d5bf2ae0ebf68ea0c4d3f7bb454a1865 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Aug 2023 10:22:09 +0200 Subject: [PATCH 111/294] Simplify Variant code with define for ManagedObject handle --- Source/Engine/Core/Types/Variant.cpp | 136 ++++++++------------------- 1 file changed, 40 insertions(+), 96 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index dcb319ec5..1cc401e4b 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -28,6 +28,12 @@ #include "Engine/Utilities/Crc.h" #include "Engine/Utilities/StringConverter.h" +#if USE_NETCORE +#define MANAGED_GC_HANDLE AsUint64 +#else +#define MANAGED_GC_HANDLE AsUint +#endif + namespace { const char* InBuiltTypesTypeNames[40] = @@ -498,13 +504,8 @@ Variant::Variant(Variant&& other) noexcept other.AsDictionary = nullptr; break; case VariantType::ManagedObject: -#if USE_NETCORE - AsUint64 = other.AsUint64; - other.AsUint64 = 0; -#elif USE_MONO - AsUint = other.AsUint; - other.AsUint = 0; -#endif + MANAGED_GC_HANDLE = other.MANAGED_GC_HANDLE; + other.MANAGED_GC_HANDLE = 0; break; case VariantType::Null: case VariantType::Void: @@ -625,11 +626,7 @@ Variant::Variant(Asset* v) Variant::Variant(MObject* v) : Type(VariantType::ManagedObject, v ? MCore::Object::GetClass(v) : nullptr) { -#if USE_NETCORE - AsUint64 = v ? MCore::GCHandle::New(v) : 0; -#else - AsUint = v ? MCore::GCHandle::New(v) : 0; -#endif + MANAGED_GC_HANDLE = v ? MCore::GCHandle::New(v) : 0; } #else @@ -968,13 +965,8 @@ Variant::~Variant() Delete(AsDictionary); break; case VariantType::ManagedObject: -#if USE_NETCORE - if (AsUint64) - MCore::GCHandle::Free(AsUint64); -#elif USE_MONO - if (AsUint) - MCore::GCHandle::Free(AsUint); -#endif + if (MANAGED_GC_HANDLE) + MCore::GCHandle::Free(MANAGED_GC_HANDLE); break; default: ; } @@ -1027,13 +1019,8 @@ Variant& Variant::operator=(Variant&& other) other.AsDictionary = nullptr; break; case VariantType::ManagedObject: -#if USE_NETCORE - AsUint64 = other.AsUint64; - other.AsUint64 = 0; -#elif USE_MONO - AsUint = other.AsUint; - other.AsUint = 0; -#endif + MANAGED_GC_HANDLE = other.MANAGED_GC_HANDLE; + other.MANAGED_GC_HANDLE = 0; break; case VariantType::Null: case VariantType::Void: @@ -1105,11 +1092,7 @@ Variant& Variant::operator=(const Variant& other) AsDictionary = New>(*other.AsDictionary); break; case VariantType::ManagedObject: -#if USE_NETCORE - AsUint64 = other.AsUint64 ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint64)) : 0; -#elif USE_MONO - AsUint = other.AsUint ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint)) : 0; -#endif + MANAGED_GC_HANDLE = other.MANAGED_GC_HANDLE ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.MANAGED_GC_HANDLE)) : 0; break; case VariantType::Null: case VariantType::Void: @@ -1234,11 +1217,9 @@ bool Variant::operator==(const Variant& other) const return false; return AsBlob.Length == other.AsBlob.Length && StringUtils::Compare(static_cast(AsBlob.Data), static_cast(other.AsBlob.Data), AsBlob.Length - 1) == 0; case VariantType::ManagedObject: -#if USE_NETCORE +#if USE_CSHARP // TODO: invoke C# Equality logic? - return AsUint64 == other.AsUint64 || MCore::GCHandle::GetTarget(AsUint64) == MCore::GCHandle::GetTarget(other.AsUint64); -#elif USE_MONO - return AsUint == other.AsUint || MCore::GCHandle::GetTarget(AsUint) == MCore::GCHandle::GetTarget(other.AsUint); + return MANAGED_GC_HANDLE == other.MANAGED_GC_HANDLE || MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE) == MCore::GCHandle::GetTarget(other.MANAGED_GC_HANDLE); #else return false; #endif @@ -1331,10 +1312,8 @@ Variant::operator bool() const case VariantType::Asset: return AsAsset != nullptr; case VariantType::ManagedObject: -#if USE_NETCORE - return AsUint64 != 0 && MCore::GCHandle::GetTarget(AsUint64) != nullptr; -#elif USE_MONO - return AsUint != 0 && MCore::GCHandle::GetTarget(AsUint) != nullptr; +#if USE_CSHARP + return MANAGED_GC_HANDLE != 0 && MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE) != nullptr; #else return false; #endif @@ -1605,10 +1584,8 @@ Variant::operator void*() const case VariantType::Blob: return AsBlob.Data; case VariantType::ManagedObject: -#if USE_NETCORE - return AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr; -#elif USE_MONO - return AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr; +#if USE_CSHARP + return MANAGED_GC_HANDLE ? MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE) : nullptr; #else return nullptr; #endif @@ -1654,10 +1631,8 @@ Variant::operator ScriptingObject*() const Variant::operator MObject*() const { -#if USE_NETCORE - return Type.Type == VariantType::ManagedObject && AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr; -#elif USE_MONO - return Type.Type == VariantType::ManagedObject && AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr; +#if USE_CSHARP + return Type.Type == VariantType::ManagedObject && MANAGED_GC_HANDLE ? MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE) : nullptr; #else return nullptr; #endif @@ -2370,12 +2345,9 @@ void Variant::SetType(const VariantType& type) Delete(AsDictionary); break; case VariantType::ManagedObject: -#if USE_NETCORE - if (AsUint64) - MCore::GCHandle::Free(AsUint64); -#elif USE_MONO - if (AsUint) - MCore::GCHandle::Free(AsUint); +#if USE_CSHARP + if (MANAGED_GC_HANDLE) + MCore::GCHandle::Free(MANAGED_GC_HANDLE); #endif break; default: ; @@ -2430,10 +2402,8 @@ void Variant::SetType(const VariantType& type) AsDictionary = New>(); break; case VariantType::ManagedObject: -#if USE_NETCORE - AsUint64 = 0; -#elif USE_MONO - AsUint = 0; +#if USE_CSHARP + MANAGED_GC_HANDLE = 0; #endif break; case VariantType::Structure: @@ -2487,12 +2457,9 @@ void Variant::SetType(VariantType&& type) Delete(AsDictionary); break; case VariantType::ManagedObject: -#if USE_NETCORE - if (AsUint64) - MCore::GCHandle::Free(AsUint64); -#elif USE_MONO - if (AsUint) - MCore::GCHandle::Free(AsUint); +#if USE_CSHARP + if (MANAGED_GC_HANDLE) + MCore::GCHandle::Free(MANAGED_GC_HANDLE); #endif break; default: ; @@ -2547,10 +2514,8 @@ void Variant::SetType(VariantType&& type) AsDictionary = New>(); break; case VariantType::ManagedObject: -#if USE_NETCORE - AsUint64 = 0; -#elif USE_MONO - AsUint = 0; +#if USE_CSHARP + MANAGED_GC_HANDLE = 0; #endif break; case VariantType::Structure: @@ -2680,21 +2645,13 @@ void Variant::SetManagedObject(MObject* object) { if (Type.Type != VariantType::ManagedObject) SetType(VariantType(VariantType::ManagedObject, MCore::Object::GetClass(object))); -#if USE_NETCORE - AsUint64 = MCore::GCHandle::New(object); -#else - AsUint = MCore::GCHandle::New(object); -#endif + MANAGED_GC_HANDLE = MCore::GCHandle::New(object); } else { if (Type.Type != VariantType::ManagedObject || Type.TypeName) SetType(VariantType(VariantType::ManagedObject)); -#if USE_NETCORE - AsUint64 = 0; -#elif USE_MONO - AsUint = 0; -#endif + MANAGED_GC_HANDLE = 0; } #endif } @@ -2807,10 +2764,8 @@ String Variant::ToString() const case VariantType::Typename: return String((const char*)AsBlob.Data, AsBlob.Length ? AsBlob.Length - 1 : 0); case VariantType::ManagedObject: -#if USE_NETCORE - return AsUint64 ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint64)))) : TEXT("null"); -#elif USE_MONO - return AsUint ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint)))) : TEXT("null"); +#if USE_CSHARP + return MANAGED_GC_HANDLE ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE)))) : TEXT("null"); #else return String::Empty; #endif @@ -2981,11 +2936,7 @@ Variant Variant::NewValue(const StringAnsiView& typeName) MObject* instance = mclass->CreateInstance(); if (instance) { -#if USE_NETCORE - v.AsUint64 = MCore::GCHandle::New(instance); -#else - v.AsUint = MCore::GCHandle::New(instance); -#endif + v.MANAGED_GC_HANDLE = MCore::GCHandle::New(instance); } } } @@ -3930,12 +3881,7 @@ void Variant::AllocStructure() Platform::MemoryCopy(AsBlob.Data, data, AsBlob.Length); #else Type.Type = VariantType::ManagedObject; - -#if USE_NETCORE - AsUint64 = MCore::GCHandle::New(instance); -#else - AsUint = MCore::GCHandle::New(instance); -#endif + MANAGED_GC_HANDLE = MCore::GCHandle::New(instance); #endif } else @@ -4027,10 +3973,8 @@ uint32 GetHash(const Variant& key) case VariantType::Typename: return GetHash((const char*)key.AsBlob.Data); case VariantType::ManagedObject: -#if USE_NETCORE - return key.AsUint64 ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint64)) : 0; -#elif USE_MONO - return key.AsUint ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint)) : 0; +#if USE_CSHARP + return key.MANAGED_GC_HANDLE ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.MANAGED_GC_HANDLE)) : 0; #else return 0; #endif From cc7e93e2ee40dbf929b88ebe51c12d8e3917b1a6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Aug 2023 10:22:33 +0200 Subject: [PATCH 112/294] Don't recheck decorators conditions if the node is already relevant --- Source/Engine/AI/BehaviorTreeNodes.cpp | 29 ++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index a134697fe..05b631530 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -44,21 +44,24 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& ASSERT_LOW_LAYER(_executionIndex != -1); const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes; - // Check decorators if node can be executed - for (BehaviorTreeDecorator* decorator : _decorators) - { - ASSERT_LOW_LAYER(decorator->_executionIndex != -1); - if (relevantNodes.Get(decorator->_executionIndex) == false) - decorator->BecomeRelevant(context); - if (!decorator->CanUpdate(context)) - { - return BehaviorUpdateResult::Failed; - } - } - - // Make node relevant before update + // If node is not yet relevant if (relevantNodes.Get(_executionIndex) == false) + { + // Check decorators if node can be executed + for (BehaviorTreeDecorator* decorator : _decorators) + { + ASSERT_LOW_LAYER(decorator->_executionIndex != -1); + if (relevantNodes.Get(decorator->_executionIndex) == false) + decorator->BecomeRelevant(context); + if (!decorator->CanUpdate(context)) + { + return BehaviorUpdateResult::Failed; + } + } + + // Make node relevant BecomeRelevant(context); + } // Update decorators bool decoratorFailed = false; From b31f2622141759bdd6812a906c4154712a0ebc4e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Aug 2023 10:25:24 +0200 Subject: [PATCH 113/294] Add `Knowledge Conditional` and `Knowledge Values Conditional` decorators to BT --- Source/Engine/AI/BehaviorKnowledge.cpp | 25 +++++++++++-- Source/Engine/AI/BehaviorKnowledge.h | 10 ++++++ Source/Engine/AI/BehaviorTreeNodes.cpp | 10 ++++++ Source/Engine/AI/BehaviorTreeNodes.h | 50 ++++++++++++++++++++++++++ Source/Engine/AI/BehaviorTypes.h | 19 ++++++++++ Source/Engine/Core/Types/Variant.cpp | 11 ++++++ 6 files changed, 123 insertions(+), 2 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index fc2504688..1ad5319c4 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -68,7 +68,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val if (set) mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed)); else - value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject)); + value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject)); return true; } else if (const auto mProperty = mClass->GetProperty(member.Get())) @@ -76,7 +76,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val if (set) mProperty->SetValue(instanceObject, MUtils::BoxVariant(value), nullptr); else - value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr)); + value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr)); return true; } } @@ -173,3 +173,24 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value) { return AccessBehaviorKnowledge(this, path, const_cast(value), true); } + +bool BehaviorKnowledge::CompareValues(float a, float b, BehaviorValueComparison comparison) +{ + switch (comparison) + { + case BehaviorValueComparison::Equal: + return Math::NearEqual(a, b); + case BehaviorValueComparison::NotEqual: + return Math::NotNearEqual(a, b); + case BehaviorValueComparison::Less: + return a < b; + case BehaviorValueComparison::LessEqual: + return a <= b; + case BehaviorValueComparison::Greater: + return a > b; + case BehaviorValueComparison::GreaterEqual: + return a >= b; + default: + return false; + } +} diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index 252f85764..c4b515d43 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -8,6 +8,7 @@ class Behavior; class BehaviorTree; +enum class BehaviorValueComparison; /// /// Behavior logic component knowledge data container. Contains blackboard values, sensors data and goals storage for Behavior Tree execution. @@ -69,4 +70,13 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject /// Value to set. /// True if set value, otherwise false. API_FUNCTION() bool Set(const StringAnsiView& path, const Variant& value); + + /// + /// Compares two values and returns the comparision result. + /// + /// The left operand. + /// The right operand. + /// The comparison function. + /// True if comparision passed, otherwise false. + API_FUNCTION() static bool CompareValues(float a, float b, BehaviorValueComparison comparison); }; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 05b631530..1706a154d 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -435,3 +435,13 @@ void BehaviorTreeCooldownDecorator::PostUpdate(const BehaviorUpdateContext& cont state->EndTime += context.Time; } } + +bool BehaviorTreeKnowledgeConditionalDecorator::CanUpdate(const BehaviorUpdateContext& context) +{ + return BehaviorKnowledge::CompareValues((float)ValueA.Get(context.Knowledge), ValueB, Comparison); +} + +bool BehaviorTreeKnowledgeValuesConditionalDecorator::CanUpdate(const BehaviorUpdateContext& context) +{ + return BehaviorKnowledge::CompareValues((float)ValueA.Get(context.Knowledge), (float)ValueB.Get(context.Knowledge), Comparison); +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 10eca71ea..6d9716988 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -292,3 +292,53 @@ public: float EndTime; }; }; + +/// +/// Checks certain knowledge value to conditionally enter the node. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeKnowledgeConditionalDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeKnowledgeConditionalDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // The first value from behavior's knowledge (blackboard, goal or sensor) to use for comparision. + API_FIELD(Attributes="EditorOrder(0)") + BehaviorKnowledgeSelectorAny ValueA; + + // The second value to use for comparision. + API_FIELD(Attributes="EditorOrder(10)") + float ValueB = 0.0f; + + // Values comparision mode. + API_FIELD(Attributes="EditorOrder(20)") + BehaviorValueComparison Comparison = BehaviorValueComparison::Equal; + +public: + // [BehaviorTreeNode] + bool CanUpdate(const BehaviorUpdateContext& context) override; +}; + +/// +/// Checks certain knowledge value to conditionally enter the node. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeKnowledgeValuesConditionalDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeKnowledgeValuesConditionalDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // The first value from behavior's knowledge (blackboard, goal or sensor) to use for comparision. + API_FIELD(Attributes="EditorOrder(0)") + BehaviorKnowledgeSelectorAny ValueA; + + // The second value from behavior's knowledge (blackboard, goal or sensor) to use for comparision. + API_FIELD(Attributes="EditorOrder(10)") + BehaviorKnowledgeSelectorAny ValueB; + + // Values comparision mode. + API_FIELD(Attributes="EditorOrder(20)") + BehaviorValueComparison Comparison = BehaviorValueComparison::Equal; + +public: + // [BehaviorTreeNode] + bool CanUpdate(const BehaviorUpdateContext& context) override; +}; diff --git a/Source/Engine/AI/BehaviorTypes.h b/Source/Engine/AI/BehaviorTypes.h index 80133f53a..38aa65f98 100644 --- a/Source/Engine/AI/BehaviorTypes.h +++ b/Source/Engine/AI/BehaviorTypes.h @@ -59,3 +59,22 @@ API_ENUM() enum class BehaviorUpdateResult // Action failed. Failed, }; + +/// +/// Comparison function modes for behavior knowledge values. +/// +API_ENUM() enum class BehaviorValueComparison +{ + // If A is equal to B, the comparison passes. + Equal, + // If A is not equal to B, the comparison passes. + NotEqual, + // If A is less than the B, the comparison passes. + Less, + // If A is less than or equal to the B, the comparison passes. + LessEqual, + // If A is greater than the B, the comparison passes. + Greater, + // If A is greater than or equal to the B, the comparison passes. + GreaterEqual, +}; diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 1cc401e4b..846a98992 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -1506,6 +1506,7 @@ Variant::operator float() const case VariantType::Float3: return AsFloat3().X; case VariantType::Float4: + case VariantType::Color: return AsFloat4().X; case VariantType::Double2: return (float)AsDouble2().X; @@ -1519,6 +1520,16 @@ Variant::operator float() const return (float)AsInt3().X; case VariantType::Int4: return (float)AsInt4().X; + case VariantType::Pointer: + return AsPointer ? 1.0f : 0.0f; + case VariantType::Object: + return AsObject ? 1.0f : 0.0f; + case VariantType::Asset: + return AsAsset ? 1.0f : 0.0f; + case VariantType::Blob: + return AsBlob.Length > 0 ? 1.0f : 0.0f; + case VariantType::ManagedObject: + return MANAGED_GC_HANDLE ? 1.0f : 0.0f; default: return 0; } From 99547a1ff48b8f29f385569ab23df7efb1d30f57 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Aug 2023 10:42:53 +0200 Subject: [PATCH 114/294] Add `Has Tag` decorator --- Source/Engine/AI/BehaviorKnowledgeSelector.h | 5 ++-- Source/Engine/AI/BehaviorTreeNodes.cpp | 11 ++++++++ Source/Engine/AI/BehaviorTreeNodes.h | 28 ++++++++++++++++++++ Source/Engine/Core/Types/VariantValueCast.h | 25 +++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 Source/Engine/Core/Types/VariantValueCast.h diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h index 96241a9d3..d27f5e963 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.h +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Types/VariantValueCast.h" #include "Engine/Serialization/SerializationFwd.h" class BehaviorKnowledge; @@ -70,7 +71,7 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi // Gets the selected knowledge value (typed). FORCE_INLINE T Get(BehaviorKnowledge* knowledge) { - return (T)BehaviorKnowledgeSelectorAny::Get(knowledge); + return TVariantValueCast::Cast(BehaviorKnowledgeSelectorAny::Get(knowledge)); } // Tries to get the selected knowledge value (typed). Returns true if got value, otherwise false. @@ -79,7 +80,7 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi Variant variant; if (BehaviorKnowledgeSelectorAny::TryGet(knowledge, variant)) { - value = (T)variant; + value = TVariantValueCast::Cast(variant); return true; } return false; diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index 1706a154d..20082c9d7 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -8,6 +8,7 @@ #if USE_CSHARP #include "Engine/Scripting/ManagedCLR/MClass.h" #endif +#include "Engine/Level/Actor.h" #include "Engine/Serialization/Serialization.h" bool IsAssignableFrom(const StringAnsiView& to, const StringAnsiView& from) @@ -445,3 +446,13 @@ bool BehaviorTreeKnowledgeValuesConditionalDecorator::CanUpdate(const BehaviorUp { return BehaviorKnowledge::CompareValues((float)ValueA.Get(context.Knowledge), (float)ValueB.Get(context.Knowledge), Comparison); } + +bool BehaviorTreeHasTagDecorator::CanUpdate(const BehaviorUpdateContext& context) +{ + bool result = false; + ::Actor* actor; + if (Actor.TryGet(context.Knowledge, actor) && actor) + result = actor->HasTag(Tag); + result ^= Invert; + return result; +} diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 6d9716988..c8fd25e63 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -8,6 +8,9 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/BitArray.h" #include "Engine/Content/AssetReference.h" +#include "Engine/Level/Tags.h" + +class Actor; /// /// Base class for compound Behavior Tree nodes that composite child nodes. @@ -342,3 +345,28 @@ public: // [BehaviorTreeNode] bool CanUpdate(const BehaviorUpdateContext& context) override; }; + +/// +/// Checks if certain actor (from knowledge) has a given tag assigned. +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeHasTagDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeHasTagDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // The actor value from behavior's knowledge (blackboard, goal or sensor) to check against tag ownership. + API_FIELD(Attributes="EditorOrder(0)") + BehaviorKnowledgeSelector Actor; + + // The tag to check. + API_FIELD(Attributes="EditorOrder(10)") + Tag Tag; + + // If checked, inverts condition - checks if actor doesn't have a tag. + API_FIELD(Attributes="EditorOrder(20)") + bool Invert = false; + +public: + // [BehaviorTreeNode] + bool CanUpdate(const BehaviorUpdateContext& context) override; +}; diff --git a/Source/Engine/Core/Types/VariantValueCast.h b/Source/Engine/Core/Types/VariantValueCast.h new file mode 100644 index 000000000..562c15254 --- /dev/null +++ b/Source/Engine/Core/Types/VariantValueCast.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Templates.h" + +// Helper utility to read Variant value with automatic casting to the template type (eg. cast ScriptingObject* to Actor*). +template +struct TVariantValueCast +{ + FORCE_INLINE static T Cast(const Variant& v) + { + return (T)v; + } +}; + +template +struct TVariantValueCast::Value>::Type> +{ + FORCE_INLINE static T* Cast(const Variant& v) + { + return ScriptingObject::Cast((ScriptingObject*)v); + } +}; From 6ab2e540a32a2ce6827d98452aefb4484971acc1 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:50:56 +0200 Subject: [PATCH 115/294] Bug Fixes script and actors not having connect transform in on awake and in on start join auto anchor having wrong anchor because was including scale fixed selection do to changes to transform accessibility ? --- Source/Editor/Viewport/EditorViewport.cs | 6 +-- Source/Engine/Level/Level.cpp | 17 +++++---- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 4 +- Source/Engine/Level/Prefabs/Prefab.cpp | 2 +- Source/Engine/Level/Prefabs/PrefabManager.cpp | 37 +++++++++---------- Source/Engine/Level/Prefabs/PrefabManager.h | 8 ++-- .../Engine/Networking/NetworkReplicator.cpp | 2 +- Source/Engine/Physics/Joints/Joint.cpp | 9 +++-- Source/Engine/Physics/Joints/Joint.h | 2 +- 9 files changed, 42 insertions(+), 45 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ad7e06ba5..19f272c40 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -898,16 +898,14 @@ namespace FlaxEditor.Viewport ivp.Invert(); // Create near and far points - var nearPoint = new Vector3(mousePosition, 0.0f); var farPoint = new Vector3(mousePosition, 1.0f); - viewport.Unproject(ref nearPoint, ref ivp, out nearPoint); viewport.Unproject(ref farPoint, ref ivp, out farPoint); // Create direction vector - Vector3 direction = farPoint - nearPoint; + Vector3 direction = farPoint; direction.Normalize(); - return new Ray(nearPoint + viewOrigin, direction); + return new Ray(position, direction); } /// diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 587d20331..f79f4558d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -993,7 +993,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // /\ all above this has to be done on an any thread // \/ all below this has to be done on multiple threads at once - + { PROFILE_CPU_NAMED("Deserialize"); @@ -1016,8 +1016,16 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // Synchronize prefab instances (prefab may have objects removed or reordered so deserialized instances need to synchronize with it) // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); + // Cache transformations + { + PROFILE_CPU_NAMED("Cache Transform"); + + scene->OnTransformChanged(); + } + // Initialize scene objects { PROFILE_CPU_NAMED("Initialize"); @@ -1039,13 +1047,6 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou } } - // Cache transformations - { - PROFILE_CPU_NAMED("Cache Transform"); - - scene->OnTransformChanged(); - } - // /\ all above this has to be done on an any thread // \/ all below this has to be done on a main thread diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 53453afe9..5ee303c9b 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -1229,14 +1229,14 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData) { ScopeLock lock(Locker); _isCreatingDefaultInstance = true; - _defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache, true); + _defaultInstance = PrefabManager::SpawnPrefab(this,Transform::Identity, nullptr, &ObjectsCache, true); _isCreatingDefaultInstance = false; } // Instantiate prefab instance from prefab (default spawning logic) // Note: it will get any added or removed objects from the nested prefabs // TODO: try to optimize by using recreated default instance to ApplyAllInternal (will need special path there if apply is done with default instance to unlink it instead of destroying) - const auto targetActor = PrefabManager::SpawnPrefab(this, nullptr, nullptr, true); + const auto targetActor = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, nullptr, true); if (targetActor == nullptr) { LOG(Warning, "Failed to instantiate default prefab instance from changes synchronization."); diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index c2c2463b2..63a6b8071 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -76,7 +76,7 @@ Actor* Prefab::GetDefaultInstance() _isCreatingDefaultInstance = true; // Instantiate objects from prefab (default spawning logic) - _defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache); + _defaultInstance = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, &ObjectsCache); _isCreatingDefaultInstance = false; return _defaultInstance; diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index a2b148f28..2c81f9058 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -39,42 +39,37 @@ PrefabManagerService PrefabManagerServiceInstance; Actor* PrefabManager::SpawnPrefab(Prefab* prefab) { Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; - return SpawnPrefab(prefab, parent, nullptr); + return SpawnPrefab(prefab,Transform::Identity, parent, nullptr); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position) { - auto instance = SpawnPrefab(prefab); - if (instance) - instance->SetPosition(position); - return instance; + Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; + return SpawnPrefab(prefab, Transform(position), parent, nullptr); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position, const Quaternion& rotation) { - auto instance = SpawnPrefab(prefab); - if (instance) - instance->SetTransform(Transform(position, rotation, instance->GetScale())); - return instance; + Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; + return SpawnPrefab(prefab, Transform(position, rotation), parent, nullptr); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position, const Quaternion& rotation, const Vector3& scale) { - auto instance = SpawnPrefab(prefab); - if (instance) - instance->SetTransform(Transform(position, rotation, scale)); - return instance; + Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; + return SpawnPrefab(prefab, Transform(position, rotation, scale), parent, nullptr); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform) { - auto instance = SpawnPrefab(prefab); - if (instance) - instance->SetTransform(transform); - return instance; + Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; + return SpawnPrefab(prefab, transform, parent, nullptr); } - -Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization) +Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent) +{ + return SpawnPrefab(prefab, Transform::Identity, parent, nullptr); +} +Actor* PrefabManager::SpawnPrefab(Prefab* prefab,const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization) { PROFILE_CPU_NAMED("Prefab.Spawn"); if (prefab == nullptr) @@ -183,6 +178,10 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryChildren.Add(root); + //move root to right location + if (transform != Transform::Identity) + root->SetTransform(transform); + // Link actors hierarchy for (int32 i = 0; i < sceneObjects->Count(); i++) { diff --git a/Source/Engine/Level/Prefabs/PrefabManager.h b/Source/Engine/Level/Prefabs/PrefabManager.h index 0ff87536f..9705e7ce0 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.h +++ b/Source/Engine/Level/Prefabs/PrefabManager.h @@ -70,20 +70,18 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager /// The prefab asset. /// The parent actor to add spawned object instance. Can be null to just deserialize contents of the prefab. /// The created actor (root) or null if failed. - API_FUNCTION() static Actor* SpawnPrefab(Prefab* prefab, Actor* parent) - { - return SpawnPrefab(prefab, parent, nullptr); - } + API_FUNCTION() static Actor* SpawnPrefab(Prefab* prefab, Actor* parent); /// /// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay). /// /// The prefab asset. + /// prefab instance transform. /// The parent actor to add spawned object instance. Can be null to just deserialize contents of the prefab. /// The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script). /// True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications. /// The created actor (root) or null if failed. - static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization = false); + static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization = false); #if USE_EDITOR diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 529e6460b..6a9f9c29b 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1738,7 +1738,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString()); return; } - prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr); + prefabInstance = PrefabManager::SpawnPrefab(prefab, Transform::Identity, nullptr, nullptr); if (!prefabInstance) { NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString()); diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index 2c7d15727..6fd2d5a7f 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -146,7 +146,7 @@ void Joint::Create() if (_enableAutoAnchor && target) { // Place target anchor at the joint location - desc.Pos1 = Target->GetTransform().WorldToLocal(GetPosition()); + desc.Pos1 = (Target->GetOrientation() * (GetPosition() - Target->GetPosition())) + Target->GetPosition(); desc.Rot1 = WorldToLocal(Target->GetOrientation(), GetOrientation()); } _joint = CreateJoint(desc); @@ -158,7 +158,7 @@ void Joint::Create() void Joint::OnJointBreak() { - JointBreak(); + JointBreak(this); } void Joint::Delete() @@ -197,8 +197,9 @@ Vector3 Joint::GetTargetPosition() const if (Target) { if (_enableAutoAnchor) - position = Target->GetTransform().WorldToLocal(GetPosition()); - position = Target->GetOrientation() * position + Target->GetPosition(); + position = (Target->GetOrientation() * (GetPosition() - Target->GetPosition())) + Target->GetPosition(); + else + position = Target->GetOrientation() * position + Target->GetPosition(); } return position; } diff --git a/Source/Engine/Physics/Joints/Joint.h b/Source/Engine/Physics/Joints/Joint.h index f96e47b71..cc310786c 100644 --- a/Source/Engine/Physics/Joints/Joint.h +++ b/Source/Engine/Physics/Joints/Joint.h @@ -163,7 +163,7 @@ public: /// /// Occurs when a joint gets broken during simulation. /// - API_EVENT() Action JointBreak; + API_EVENT() Delegate JointBreak; /// /// Called by the physics system when joint gets broken. From 12877318162b47359b4e383b0ff3b2de748408f6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Aug 2023 14:54:40 +0200 Subject: [PATCH 116/294] Add reordering and reparenting decorators (with undo) --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 45 ++-- Source/Editor/GUI/Drag/DragScripts.cs | 15 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 240 ++++++++++++++++-- 3 files changed, 233 insertions(+), 67 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index bf82e19da..ad76ac295 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -254,27 +254,15 @@ namespace FlaxEditor.CustomEditors.Dedicated /// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts. /// /// - internal class ScriptDragIcon : Image + internal class DragImage : Image { - private ScriptsEditor _editor; private bool _isMouseDown; private Float2 _mouseDownPos; /// - /// Gets the target script. + /// Action called when drag event should start. /// - public Script Script => (Script)Tag; - - /// - /// Initializes a new instance of the class. - /// - /// The script editor. - /// The target script. - public ScriptDragIcon(ScriptsEditor editor, Script script) - { - Tag = script; - _editor = editor; - } + public Action Drag; /// public override void OnMouseEnter(Float2 location) @@ -287,11 +275,10 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override void OnMouseLeave() { - // Check if start drag drop if (_isMouseDown) { - DoDrag(); _isMouseDown = false; + Drag(this); } base.OnMouseLeave(); @@ -300,11 +287,10 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override void OnMouseMove(Float2 location) { - // Check if start drag drop if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f) { - DoDrag(); _isMouseDown = false; + Drag(this); } base.OnMouseMove(location); @@ -315,8 +301,8 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (button == MouseButton.Left) { - // Clear flag _isMouseDown = false; + return true; } return base.OnMouseUp(location, button); @@ -327,21 +313,13 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (button == MouseButton.Left) { - // Set flag _isMouseDown = true; _mouseDownPos = location; + return true; } return base.OnMouseDown(location, button); } - - private void DoDrag() - { - var script = Script; - _editor.OnScriptDragChange(true, script); - DoDragDrop(DragScripts.GetDragData(script)); - _editor.OnScriptDragChange(false, script); - } } internal class ScriptArrangeBar : Control @@ -639,7 +617,7 @@ namespace FlaxEditor.CustomEditors.Dedicated _scriptToggles[i] = scriptToggle; // Add drag button to the group - var scriptDrag = new ScriptDragIcon(this, script) + var scriptDrag = new DragImage { TooltipText = "Script reference", AutoFocus = true, @@ -650,6 +628,13 @@ namespace FlaxEditor.CustomEditors.Dedicated Margin = new Margin(1), Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12), Tag = script, + Drag = img => + { + var s = (Script)img.Tag; + OnScriptDragChange(true, s); + img.DoDragDrop(DragScripts.GetDragData(s)); + OnScriptDragChange(false, s); + } }; // Add settings button to the group diff --git a/Source/Editor/GUI/Drag/DragScripts.cs b/Source/Editor/GUI/Drag/DragScripts.cs index e72d28479..875875ef3 100644 --- a/Source/Editor/GUI/Drag/DragScripts.cs +++ b/Source/Editor/GUI/Drag/DragScripts.cs @@ -58,7 +58,6 @@ namespace FlaxEditor.GUI.Drag { if (item == null) throw new ArgumentNullException(); - return new DragDataText(DragPrefix + item.ID.ToString("N")); } @@ -71,11 +70,9 @@ namespace FlaxEditor.GUI.Drag { if (items == null) throw new ArgumentNullException(); - string text = DragPrefix; foreach (var item in items) text += item.ID.ToString("N") + '\n'; - return new DragDataText(text); } @@ -83,9 +80,7 @@ namespace FlaxEditor.GUI.Drag /// Tries to parse the drag data. /// /// The data. - /// - /// Gathered objects or empty IEnumerable if cannot get any valid. - /// + /// Gathered objects or empty IEnumerable if cannot get any valid. public override IEnumerable