From fd4b9a5a9f18cdfa5c52aea56dd819431fb4be36 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 16 Dec 2022 16:29:43 -0600 Subject: [PATCH 01/80] 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 02/80] 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 03/80] 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 04/80] 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 05/80] 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 06/80] 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 07/80] 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 08/80] 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 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 09/80] 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 10/80] 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 72488c4250a39e3f0381808c990f50a4341cb49e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 22 Aug 2023 21:14:33 -0500 Subject: [PATCH 11/80] 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 12/80] 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 23b6b93726c371cb88d72013acc58998348cf3f6 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:13:32 -0400 Subject: [PATCH 13/80] add collision type to model importer --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 19f203667..922627b77 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1079,7 +1079,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op #if COMPILE_WITH_PHYSICS_COOKING // Create collision CollisionCooking::Argument arg; - arg.Type = CollisionDataType::TriangleMesh; + arg.Type = options.CollisionType; arg.OverrideModelData = &collisionModel; auto assetPath = autoImportOutput / StringUtils::GetFileNameWithoutExtension(path) + TEXT("Collision") ASSET_FILES_EXTENSION_WITH_DOT; if (CreateCollisionData::CookMeshCollision(assetPath, arg)) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 9dc5bf067..ab5f79ddf 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -6,6 +6,7 @@ #include "Engine/Core/Config.h" #include "Engine/Content/Assets/ModelBase.h" +#include "Engine/Physics/CollisionData.h" #if USE_EDITOR #include "Engine/Core/ISerializable.h" #include "Engine/Graphics/Models/ModelData.h" @@ -262,6 +263,9 @@ public: // If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") String CollisionMeshesPrefix = TEXT(""); + // The type of collision that should be generated if has collision prefix especified + API_FIELD(Attributes = "EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") + CollisionDataType CollisionType = CollisionDataType::TriangleMesh; public: // Transform From 2b1266147cd5ab91d514e135397ae738cfbf15b2 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:02:19 -0400 Subject: [PATCH 14/80] Add new condition from split object bugfix on master. --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 27423adb0..2b3beae2f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Content/Content.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR #include "Engine/Core/Types/DateTime.h" @@ -984,6 +985,15 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op auto assetPath = autoImportOutput / filename + ASSET_FILES_EXTENSION_WITH_DOT; if (options.ImportMaterialsAsInstances) { + if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) + { + // Find that asset create previously + AssetInfo info; + if (Content::GetAssetInfo(assetPath, info)) + material.AssetID = info.ID; + continue; + } + AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID); MaterialInstance* materialInstance = (MaterialInstance*)LoadAsset(assetPath, MaterialInstance::TypeInitializer); if (materialInstance->WaitForLoaded()) @@ -1021,6 +1031,15 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op materialOptions.Info.CullMode = CullMode::TwoSided; if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; + + if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) + { + // Find that asset create previously + AssetInfo info; + if (Content::GetAssetInfo(assetPath, info)) + material.AssetID = info.ID; + continue; + } AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); } #endif From 10bcb2600503f6cab181a94aaafeb69cca0f6646 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 23 Sep 2023 18:29:27 +0300 Subject: [PATCH 15/80] Fix invalid characters in generated C# launch settings paths --- .../Projects/VisualStudio/VisualStudioProjectGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index ecf70f9f8..74ade53ac 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -558,8 +558,8 @@ namespace Flax.Build.Projects.VisualStudio { var profiles = new Dictionary(); var profile = new StringBuilder(); - var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), $"Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")); - var workspacePath = Utilities.NormalizePath(solutionDirectory); + var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), $"Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")).Replace('\\', '/'); + var workspacePath = Utilities.NormalizePath(solutionDirectory).Replace('\\', '/'); foreach (var project in projects) { if (project.Type == TargetType.DotNetCore) From 038a3603e41f4d44323655657607dfc447967253 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sun, 24 Sep 2023 19:33:03 +0200 Subject: [PATCH 16/80] - Possible nodes get filtered now depending on the available node ports --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 14 ++++-- .../Surface/ContextMenu/VisjectCMGroup.cs | 39 ++++++++++++++--- .../Surface/ContextMenu/VisjectCMItem.cs | 43 ++++++++++++++++++- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index bae62556e..24cb29cb9 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -419,7 +419,7 @@ namespace FlaxEditor.Surface.ContextMenu Profiler.BeginEvent("VisjectCM.OnSearchFilterChanged"); - if (string.IsNullOrEmpty(_searchBox.Text)) + if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null) { ResetView(); Profiler.EndEvent(); @@ -430,7 +430,7 @@ namespace FlaxEditor.Surface.ContextMenu LockChildrenRecursive(); for (int i = 0; i < _groups.Count; i++) { - _groups[i].UpdateFilter(_searchBox.Text); + _groups[i].UpdateFilter(_searchBox.Text, _selectedBox); _groups[i].UpdateItemSort(_selectedBox); } SortGroups(); @@ -503,12 +503,19 @@ namespace FlaxEditor.Surface.ContextMenu _searchBox.Clear(); SelectedItem = null; for (int i = 0; i < _groups.Count; i++) + { _groups[i].ResetView(); + } UnlockChildrenRecursive(); SortGroups(); PerformLayout(); + for (int i = 0; i < _groups.Count; i++) + { + _groups[i].EvaluateVisibilityWithBox(_selectedBox); + } + Profiler.EndEvent(); } @@ -626,10 +633,11 @@ namespace FlaxEditor.Surface.ContextMenu // Prepare UpdateSurfaceParametersGroup(); ResetView(); + _panel1.VScrollBar.TargetValue = 0; Focus(); _waitingForInput = true; - + base.OnShow(); } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index 446840f2c..cd189ab94 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -66,7 +66,7 @@ namespace FlaxEditor.Surface.ContextMenu { if (_children[i] is VisjectCMItem item) { - item.UpdateFilter(null); + item.UpdateFilter(null, null); item.UpdateScore(null); } } @@ -84,7 +84,7 @@ namespace FlaxEditor.Surface.ContextMenu /// Updates the filter. /// /// The filter text. - public void UpdateFilter(string filterText) + public void UpdateFilter(string filterText, Box selectedBox) { Profiler.BeginEvent("VisjectCMGroup.UpdateFilter"); @@ -94,13 +94,13 @@ namespace FlaxEditor.Surface.ContextMenu { if (_children[i] is VisjectCMItem item) { - item.UpdateFilter(filterText); + item.UpdateFilter(filterText, selectedBox); isAnyVisible |= item.Visible; } } // Update header title - if (QueryFilterHelper.Match(filterText, HeaderText)) + /*if (QueryFilterHelper.Match(filterText, HeaderText)) { for (int i = 0; i < _children.Count; i++) { @@ -110,7 +110,7 @@ namespace FlaxEditor.Surface.ContextMenu } } isAnyVisible = true; - } + }*/ // Update itself if (isAnyVisible) @@ -128,6 +128,35 @@ namespace FlaxEditor.Surface.ContextMenu Profiler.EndEvent(); } + public void EvaluateVisibilityWithBox(Box selectedBox) + { + if (selectedBox == null) + { + Visible = true; + return; + } + + bool isAnyVisible = false; + for (int i = 0; i < _children.Count; i++) + { + if (_children[i] is VisjectCMItem item) + { + isAnyVisible |= item.IsCompatibleWithBox(selectedBox); + } + } + + // Update itself + if (isAnyVisible) + { + Visible = true; + } + else + { + // Hide group if none of the items matched the filter + Visible = false; + } + } + /// /// Updates the sorting of the s of this /// Also updates the diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index b68d529a1..b2d5fd7a8 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -113,12 +113,51 @@ namespace FlaxEditor.Surface.ContextMenu return false; } + public bool IsCompatibleWithBox(Box box) + { + if (box == null) + return true; + + if(_archetype?.Elements == null) + return false; + + bool isCompatible = false; + foreach (NodeElementArchetype element in _archetype.Elements) + { + if(element.Type != NodeElementType.Output && element.Type != NodeElementType.Input) + continue; + + if ((box.IsOutput && element.Type == NodeElementType.Output) || (!box.IsOutput && element.Type == NodeElementType.Input)) + continue; + + bool checkCompatibility = box.CanUseType(element.ConnectionsType);; + if (!checkCompatibility) + { + if ((element.ConnectionsType == null || element.ConnectionsType == typeof(void)) && box.CurrentType != typeof(FlaxEngine.Object)) + checkCompatibility = true; + } + isCompatible |= checkCompatibility; + + /*if(!isCompatible) + Debug.Log($"Is {_archetype.Title} cant connect type {element.ConnectionsType}");*/ + } + + Visible = isCompatible; + return isCompatible; + } + /// /// Updates the filter. /// /// The filter text. - public void UpdateFilter(string filterText) + public void UpdateFilter(string filterText, Box selectedBox) { + if (selectedBox != null) + { + if (!IsCompatibleWithBox(selectedBox)) + return; + } + _isStartsWithMatch = _isFullMatch = false; if (filterText == null) { @@ -192,7 +231,7 @@ namespace FlaxEditor.Surface.ContextMenu } } } - + /// public override void Draw() { From 8d39d51f900dd879f2975b299c2e192050508f97 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sun, 24 Sep 2023 20:22:44 +0200 Subject: [PATCH 17/80] - Added profiling --- Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs | 4 ++++ Source/Editor/Surface/ContextMenu/VisjectCMItem.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index cd189ab94..9b5da5913 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -136,6 +136,8 @@ namespace FlaxEditor.Surface.ContextMenu return; } + Profiler.BeginEvent("VisjectCMGroup.EvaluateVisibilityWithBox"); + bool isAnyVisible = false; for (int i = 0; i < _children.Count; i++) { @@ -155,6 +157,8 @@ namespace FlaxEditor.Surface.ContextMenu // Hide group if none of the items matched the filter Visible = false; } + + Profiler.EndEvent(); } /// diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index b2d5fd7a8..dc13a0e2b 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -121,6 +121,8 @@ namespace FlaxEditor.Surface.ContextMenu if(_archetype?.Elements == null) return false; + Profiler.BeginEvent("VisjectCMItem.IsCompatibleWithBox"); + bool isCompatible = false; foreach (NodeElementArchetype element in _archetype.Elements) { @@ -129,11 +131,11 @@ namespace FlaxEditor.Surface.ContextMenu if ((box.IsOutput && element.Type == NodeElementType.Output) || (!box.IsOutput && element.Type == NodeElementType.Input)) continue; - - bool checkCompatibility = box.CanUseType(element.ConnectionsType);; + + bool checkCompatibility = ((element.ConnectionsType == null || element.ConnectionsType == typeof(void)) && box.CurrentType != typeof(FlaxEngine.Object)); if (!checkCompatibility) { - if ((element.ConnectionsType == null || element.ConnectionsType == typeof(void)) && box.CurrentType != typeof(FlaxEngine.Object)) + if (box.CanUseType(element.ConnectionsType)) checkCompatibility = true; } isCompatible |= checkCompatibility; @@ -143,6 +145,8 @@ namespace FlaxEditor.Surface.ContextMenu } Visible = isCompatible; + + Profiler.EndEvent(); return isCompatible; } From 50ebd5cb8727a3d54fcde07f5cf3f30ffa50d153 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sun, 24 Sep 2023 20:31:22 +0200 Subject: [PATCH 18/80] - Fixed massive UI freeze because of perform layout oversight --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 24cb29cb9..3a3dfe027 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -506,15 +506,14 @@ namespace FlaxEditor.Surface.ContextMenu { _groups[i].ResetView(); } - UnlockChildrenRecursive(); - - SortGroups(); - PerformLayout(); - for (int i = 0; i < _groups.Count; i++) { _groups[i].EvaluateVisibilityWithBox(_selectedBox); } + UnlockChildrenRecursive(); + + SortGroups(); + PerformLayout(); Profiler.EndEvent(); } From 33c51d0a8eaac7ec6190d3c784b4189baadfd873 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Mon, 25 Sep 2023 19:10:05 +0200 Subject: [PATCH 19/80] - Filtering is functioning now for all default groups --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 3 - .../Surface/ContextMenu/VisjectCMGroup.cs | 2 +- .../Surface/ContextMenu/VisjectCMItem.cs | 110 ++++++++++++++++-- 3 files changed, 99 insertions(+), 16 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 3a3dfe027..1cec9554b 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -505,9 +505,6 @@ namespace FlaxEditor.Surface.ContextMenu for (int i = 0; i < _groups.Count; i++) { _groups[i].ResetView(); - } - for (int i = 0; i < _groups.Count; i++) - { _groups[i].EvaluateVisibilityWithBox(_selectedBox); } UnlockChildrenRecursive(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index 9b5da5913..9560aecbb 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -143,7 +143,7 @@ namespace FlaxEditor.Surface.ContextMenu { if (_children[i] is VisjectCMItem item) { - isAnyVisible |= item.IsCompatibleWithBox(selectedBox); + isAnyVisible |= item.CanConnectTo(selectedBox); } } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index dc13a0e2b..4030017a7 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Utilities; using FlaxEngine; @@ -113,31 +114,45 @@ namespace FlaxEditor.Surface.ContextMenu return false; } - public bool IsCompatibleWithBox(Box box) + public bool CanConnectTo(Box startBox) { - if (box == null) + if (startBox == null) return true; if(_archetype?.Elements == null) return false; - Profiler.BeginEvent("VisjectCMItem.IsCompatibleWithBox"); - bool isCompatible = false; foreach (NodeElementArchetype element in _archetype.Elements) { if(element.Type != NodeElementType.Output && element.Type != NodeElementType.Input) continue; - if ((box.IsOutput && element.Type == NodeElementType.Output) || (!box.IsOutput && element.Type == NodeElementType.Input)) + if ((startBox.IsOutput && element.Type == NodeElementType.Output) || (!startBox.IsOutput && element.Type == NodeElementType.Input)) continue; - - bool checkCompatibility = ((element.ConnectionsType == null || element.ConnectionsType == typeof(void)) && box.CurrentType != typeof(FlaxEngine.Object)); - if (!checkCompatibility) + + ScriptType inType; + ScriptType outType; + ConnectionsHint hint; + if (startBox.IsOutput) { - if (box.CanUseType(element.ConnectionsType)) - checkCompatibility = true; + inType = element.ConnectionsType; + outType = startBox.CurrentType; + hint = _archetype.ConnectionsHints; } + else + { + inType = startBox.CurrentType; + outType = element.ConnectionsType; + hint = startBox.ParentNode.Archetype.ConnectionsHints; + } + + bool checkCompatibility = CanCastToType(inType, outType, hint); + /*if (!checkCompatibility) + { + /*checkCompatibility = element.ConnectionsType == null && startBox.CurrentType != ScriptType.Object;#1# + checkCompatibility = + }*/ isCompatible |= checkCompatibility; /*if(!isCompatible) @@ -146,9 +161,80 @@ namespace FlaxEditor.Surface.ContextMenu Visible = isCompatible; - Profiler.EndEvent(); return isCompatible; } + + private bool CanCastToType(ScriptType currentType, ScriptType type, ConnectionsHint hint) + { + if (VisjectSurface.CanUseDirectCastStatic(type, currentType, false)) + return true; + + var connectionsHints = hint; + if (currentType == ScriptType.Null && connectionsHints != ConnectionsHint.None) + { + if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything) + return true; + if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void)) + return true; + if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum) + return true; + if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray) + return true; + if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary) + return true; + if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector) + { + var t = type.Type; + if (t == typeof(Vector2) || + t == typeof(Vector3) || + t == typeof(Vector4) || + t == typeof(Float2) || + t == typeof(Float3) || + t == typeof(Float4) || + t == typeof(Double2) || + t == typeof(Double3) || + t == typeof(Double4) || + t == typeof(Int2) || + t == typeof(Int3) || + t == typeof(Int4) || + t == typeof(Color)) + { + return true; + } + } + if ((connectionsHints & ConnectionsHint.Scalar) == ConnectionsHint.Scalar) + { + var t = type.Type; + if (t == typeof(bool) || + t == typeof(char) || + t == typeof(byte) || + t == typeof(short) || + t == typeof(ushort) || + t == typeof(int) || + t == typeof(uint) || + t == typeof(long) || + t == typeof(ulong) || + t == typeof(float) || + t == typeof(double)) + { + return true; + } + } + } + + return CanCast(type, currentType); + } + + private static bool CanCast(ScriptType oB, ScriptType iB) + { + if (oB == iB) + return true; + if (oB == ScriptType.Null || iB == ScriptType.Null) + return false; + return (oB.Type != typeof(void) && oB.Type != typeof(FlaxEngine.Object)) && + (iB.Type != typeof(void) && iB.Type != typeof(FlaxEngine.Object)) && + oB.IsAssignableFrom(iB); + } /// /// Updates the filter. @@ -158,7 +244,7 @@ namespace FlaxEditor.Surface.ContextMenu { if (selectedBox != null) { - if (!IsCompatibleWithBox(selectedBox)) + if (!CanConnectTo(selectedBox)) return; } From 3b393ef4db27e2ccb629ba006646be973ba2fe0a Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Mon, 25 Sep 2023 21:58:05 +0200 Subject: [PATCH 20/80] Groups now get filtered by box type when added after the context menu was opened --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 5 +++++ .../Editor/Surface/ContextMenu/VisjectCMItem.cs | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 1cec9554b..04130fc1e 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -288,6 +288,10 @@ namespace FlaxEditor.Surface.ContextMenu OnSearchFilterChanged(); } } + else + { + group.EvaluateVisibilityWithBox(_selectedBox); + } Profiler.EndEvent(); } @@ -321,6 +325,7 @@ namespace FlaxEditor.Surface.ContextMenu Parent = group }; } + group.EvaluateVisibilityWithBox(_selectedBox); group.SortChildren(); group.Parent = _groupsPanel; _groups.Add(group); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 4030017a7..00854ef5a 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -117,10 +117,23 @@ namespace FlaxEditor.Surface.ContextMenu public bool CanConnectTo(Box startBox) { if (startBox == null) - return true; + { + Visible = true; + return true; + } + + if (_archetype.Title == "GetChild") + { + Debug.Log(""); + Debug.Log(_archetype.Create == null); + Debug.Log(_archetype.Create.GetType() == typeof(FlaxEditor.Surface.Archetypes.Function.MethodOverrideNode)); + } - if(_archetype?.Elements == null) + if (_archetype?.Elements == null) + { + Visible = false; return false; + } bool isCompatible = false; foreach (NodeElementArchetype element in _archetype.Elements) From d8e2b06c3828e53cea73ee98f4183166c54bcbb7 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Mon, 25 Sep 2023 23:26:33 +0200 Subject: [PATCH 21/80] - Minor cleanup --- .../Editor/Surface/ContextMenu/VisjectCMItem.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 00854ef5a..7b0ce6c85 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -121,13 +121,6 @@ namespace FlaxEditor.Surface.ContextMenu Visible = true; return true; } - - if (_archetype.Title == "GetChild") - { - Debug.Log(""); - Debug.Log(_archetype.Create == null); - Debug.Log(_archetype.Create.GetType() == typeof(FlaxEditor.Surface.Archetypes.Function.MethodOverrideNode)); - } if (_archetype?.Elements == null) { @@ -161,15 +154,7 @@ namespace FlaxEditor.Surface.ContextMenu } bool checkCompatibility = CanCastToType(inType, outType, hint); - /*if (!checkCompatibility) - { - /*checkCompatibility = element.ConnectionsType == null && startBox.CurrentType != ScriptType.Object;#1# - checkCompatibility = - }*/ isCompatible |= checkCompatibility; - - /*if(!isCompatible) - Debug.Log($"Is {_archetype.Title} cant connect type {element.ConnectionsType}");*/ } Visible = isCompatible; From 0f53c486ede20258d564c1927a1e444a8bdd8a8f Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:05:50 -0400 Subject: [PATCH 22/80] Fixed formatting to match master branch --- Source/Editor/Content/Import/ModelImportEntry.cs | 1 - Source/Engine/Content/Content.cpp | 1 - Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 1 - 3 files changed, 3 deletions(-) diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index a29036c88..bbb384562 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; using System.Collections.Generic; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.Scripting; diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 4ad97ebc2..eabf58cf2 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -28,7 +28,6 @@ #if ENABLE_ASSETS_DISCOVERY #include "Engine/Core/Collections/HashSet.h" #endif -#include TimeSpan Content::AssetsUpdateInterval = TimeSpan::FromMilliseconds(500); TimeSpan Content::AssetsUnloadInterval = TimeSpan::FromSeconds(10); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index f91c1b4b3..215eadaba 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -542,7 +542,6 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb else aMaterial = aMesh->getMaterial(0); } - mesh.MaterialSlotIndex = data.AddMaterial(result, aMaterial); // Vertex positions From f06af4d58970691d05778c6083f1f59ecd8b6961 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:09:57 -0400 Subject: [PATCH 23/80] Visual studio auto-formatting fixed by just editing it in notepad :) --- .../Editor/Managed/ManagedEditor.Internal.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 49a25f4e7..49c3af87e 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -118,7 +118,7 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_IsDevInstance() #if COMPILE_WITH_DEV_ENV return true; #else - return false; + return false; #endif } @@ -151,7 +151,7 @@ DEFINE_INTERNAL_CALL(int32) EditorInternal_ReadOutputLogs(MArray** outMessages, int64* outLogTimesPtr = MCore::Array::GetAddress(*outLogTimes); while (count < maxCount && ptr != end) { - auto type = (byte) * (int32*)ptr; + auto type = (byte)*(int32*)ptr; ptr += 4; auto time = *(int64*)ptr; @@ -338,7 +338,7 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanExport(MString* pathObj) return AssetsExportingManager::CanExport(path); #else - return false; + return false; #endif } @@ -355,7 +355,7 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_Export(MString* inputPathObj, MString* return AssetsExportingManager::Export(inputPath, outputFolder); #else - return false; + return false; #endif } @@ -413,8 +413,8 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CookMeshCollision(MString* pathObj, Co arg.ConvexVertexLimit = convexVertexLimit; return CreateCollisionData::CookMeshCollision(path, arg); #else - LOG(Warning, "Collision cooking is disabled."); - return true; + LOG(Warning, "Collision cooking is disabled."); + return true; #endif } @@ -522,7 +522,7 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa continue; switch (e.Type) { - // Keyboard events + // Keyboard events case InputDevice::EventType::Char: window->OnCharInput(e.CharData.Char); break; @@ -532,7 +532,7 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa case InputDevice::EventType::KeyUp: window->OnKeyUp(e.KeyData.Key); break; - // Mouse events + // Mouse events case InputDevice::EventType::MouseDown: window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button); break; @@ -829,4 +829,4 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String // Get options from model FileSystem::NormalizePath(assetPath); return ImportModelFile::TryGetImportOptions(assetPath, options); -} +} \ No newline at end of file From c4da34a463c5d0d88018154a83dcf7f5cd710ff7 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Wed, 27 Sep 2023 16:24:33 +0200 Subject: [PATCH 24/80] - Implemented function node and bind/unbind node compatiblility/filtering - Added NodeTypeHint enum --- Source/Editor/Surface/Archetypes/Function.cs | 10 +++ .../Surface/ContextMenu/VisjectCMItem.cs | 74 ++++++++++++++++--- Source/Editor/Surface/NodeArchetype.cs | 20 +++++ Source/Editor/Surface/VisualScriptSurface.cs | 3 +- 4 files changed, 97 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index cffb2ad6f..d027dd8f1 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -2228,6 +2228,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Function Input", Description = "The graph function input data", Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaPaste, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2247,6 +2248,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Function Output", Description = "The graph function output data", Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaPaste, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2265,6 +2267,7 @@ namespace FlaxEditor.Surface.Archetypes Title = string.Empty, Description = "Overrides the base class method with custom implementation", Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2279,6 +2282,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new InvokeMethodNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2302,6 +2306,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new ReturnNode(id, context, arch, groupArch), Title = "Return", Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(100, 40), DefaultValues = new object[] { @@ -2320,6 +2325,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "New Function", Description = "Adds a new function to the script", Flags = NodeFlags.VisualScriptGraph, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 20), DefaultValues = new object[] { @@ -2332,6 +2338,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new GetFieldNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2347,6 +2354,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new SetFieldNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2363,6 +2371,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(260, 60), DefaultValues = new object[] { @@ -2385,6 +2394,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(260, 60), DefaultValues = new object[] { diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 7b0ce6c85..f8b7d9d51 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Utilities; namespace FlaxEditor.Surface.ContextMenu { @@ -121,13 +123,70 @@ namespace FlaxEditor.Surface.ContextMenu Visible = true; return true; } - - if (_archetype?.Elements == null) + + if (_archetype == null) { Visible = false; return false; } - + + bool isCompatible = false; + if (_archetype.Elements != null) + { + isCompatible = CheckElementsCompatibility(startBox); + } + + if (_archetype.NodeTypeHint == NodeTypeHint.FunctionNode) + { + isCompatible = false; + ScriptMemberInfo memberInfo = ScriptMemberInfo.Null; + + if (_archetype.Tag is ScriptMemberInfo info) + { + memberInfo = info; + } + else if(_archetype.DefaultValues is { Length: > 1 }) + { + var eventName = (string)_archetype.DefaultValues[1]; + var eventType = TypeUtils.GetType((string)_archetype.DefaultValues[0]); + memberInfo = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + } + + if (memberInfo != ScriptMemberInfo.Null) + { + if (startBox.IsOutput) + { + var parameters = memberInfo.GetParameters(); + ScriptType outType = startBox.CurrentType; + + if (!memberInfo.IsStatic) + { + var scriptType = TypeUtils.GetType((string)_archetype.DefaultValues[0]); + isCompatible |= CanCastToType(scriptType, outType, _archetype.ConnectionsHints); + } + + if (!memberInfo.IsEvent) + { + for (int i = 0; i < parameters.Length; i++) + { + ScriptType inType = parameters[i].Type; + isCompatible |= CanCastToType(inType, outType, _archetype.ConnectionsHints); + } + } + } + else + { + + } + } + } + + Visible = isCompatible; + return isCompatible; + } + + private bool CheckElementsCompatibility(Box startBox) + { bool isCompatible = false; foreach (NodeElementArchetype element in _archetype.Elements) { @@ -153,15 +212,12 @@ namespace FlaxEditor.Surface.ContextMenu hint = startBox.ParentNode.Archetype.ConnectionsHints; } - bool checkCompatibility = CanCastToType(inType, outType, hint); - isCompatible |= checkCompatibility; + isCompatible |= CanCastToType(inType, outType, hint); } - - Visible = isCompatible; - + return isCompatible; } - + private bool CanCastToType(ScriptType currentType, ScriptType type, ConnectionsHint hint) { if (VisjectSurface.CanUseDirectCastStatic(type, currentType, false)) diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 6dc923ce2..1cdaee2d2 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -73,6 +73,20 @@ namespace FlaxEditor.Surface All = Scalar | Vector | Enum | Anything | Value | Array | Dictionary, } + [HideInEditor] + public enum NodeTypeHint + { + /// + /// Is Node. + /// + Default = 0, + + /// + /// Is Function Node. + /// + FunctionNode = 1, + } + /// /// Surface node archetype description. /// @@ -152,6 +166,11 @@ namespace FlaxEditor.Surface /// public ConnectionsHint ConnectionsHints; + /// + /// Node Type hints. + /// + public NodeTypeHint NodeTypeHint; + /// /// Array with independent boxes IDs. /// @@ -193,6 +212,7 @@ namespace FlaxEditor.Surface DefaultValues = (object[])DefaultValues?.Clone(), DefaultType = DefaultType, ConnectionsHints = ConnectionsHints, + NodeTypeHint = NodeTypeHint, IndependentBoxes = (int[])IndependentBoxes?.Clone(), DependentBoxes = (int[])DependentBoxes?.Clone(), Elements = (NodeElementArchetype[])Elements?.Clone(), diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index e961f686d..b6c0112a9 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -370,7 +370,7 @@ namespace FlaxEditor.Surface 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; @@ -589,6 +589,7 @@ namespace FlaxEditor.Surface node.DefaultValues[0] = name; node.DefaultValues[1] = parameters.Length; node.Title = "Override " + name; + node.Tag = member; nodes.Add(node); } } From 63c213aec010198d8d750d528b93ec93db46ef20 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Wed, 27 Sep 2023 17:04:35 +0200 Subject: [PATCH 25/80] - Support for Impulse ports --- Source/Editor/Surface/ContextMenu/VisjectCMItem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index f8b7d9d51..370c5014a 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -138,7 +138,6 @@ namespace FlaxEditor.Surface.ContextMenu if (_archetype.NodeTypeHint == NodeTypeHint.FunctionNode) { - isCompatible = false; ScriptMemberInfo memberInfo = ScriptMemberInfo.Null; if (_archetype.Tag is ScriptMemberInfo info) @@ -154,11 +153,17 @@ namespace FlaxEditor.Surface.ContextMenu if (memberInfo != ScriptMemberInfo.Null) { + if(memberInfo.IsEvent) + isCompatible = false; + if (startBox.IsOutput) { var parameters = memberInfo.GetParameters(); ScriptType outType = startBox.CurrentType; + if (startBox.CurrentType.IsVoid && memberInfo.ValueType.IsVoid) + isCompatible = true; + if (!memberInfo.IsStatic) { var scriptType = TypeUtils.GetType((string)_archetype.DefaultValues[0]); From 155d11c07b2427f7ac6e074244a849dd0fb7060c Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Wed, 27 Sep 2023 19:28:06 +0200 Subject: [PATCH 26/80] - Filtering now also applies when dragging a connection from an input port --- .../Surface/ContextMenu/VisjectCMItem.cs | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 370c5014a..be69d675c 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -156,32 +156,37 @@ namespace FlaxEditor.Surface.ContextMenu if(memberInfo.IsEvent) isCompatible = false; - if (startBox.IsOutput) - { - var parameters = memberInfo.GetParameters(); - ScriptType outType = startBox.CurrentType; - - if (startBox.CurrentType.IsVoid && memberInfo.ValueType.IsVoid) - isCompatible = true; - - if (!memberInfo.IsStatic) - { - var scriptType = TypeUtils.GetType((string)_archetype.DefaultValues[0]); - isCompatible |= CanCastToType(scriptType, outType, _archetype.ConnectionsHints); - } - - if (!memberInfo.IsEvent) - { - for (int i = 0; i < parameters.Length; i++) - { - ScriptType inType = parameters[i].Type; - isCompatible |= CanCastToType(inType, outType, _archetype.ConnectionsHints); - } - } - } + if (startBox.CurrentType.IsVoid && memberInfo.ValueType.IsVoid) + isCompatible = true; else { + if (startBox.IsOutput) + { + var parameters = memberInfo.GetParameters(); + ScriptType outType = startBox.CurrentType; + if (!memberInfo.IsStatic) + { + var scriptType = memberInfo.DeclaringType; + isCompatible |= CanCastToType(scriptType, outType, _archetype.ConnectionsHints); + } + + if (!memberInfo.IsEvent) + { + for (int i = 0; i < parameters.Length; i++) + { + ScriptType inType = parameters[i].Type; + isCompatible |= CanCastToType(inType, outType, _archetype.ConnectionsHints); + } + } + } + else + { + ScriptType inType = startBox.CurrentType; + ScriptType outType = memberInfo.ValueType; + + isCompatible |= CanCastToType(inType, outType, _archetype.ConnectionsHints); + } } } } From 9acee407469f45c474dcfee0eb7a8d3a38489326 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 28 Sep 2023 20:53:47 +0200 Subject: [PATCH 27/80] - Added context sensitive toggle gui --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 04130fc1e..0d4a35e51 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -40,6 +40,8 @@ namespace FlaxEditor.Surface.ContextMenu public delegate List ParameterGetterDelegate(); private readonly List _groups = new List(16); + private CheckBox _contextSensitiveToggle; + private bool _contextSensitiveSearchEnabled = true; private readonly TextBox _searchBox; private bool _waitingForInput; private VisjectCMGroup _surfaceParametersGroup; @@ -127,7 +129,7 @@ namespace FlaxEditor.Surface.ContextMenu _parameterSetNodeArchetype = info.ParameterSetNodeArchetype ?? Archetypes.Parameters.Nodes[3]; // Context menu dimensions - Size = new Float2(320, 248); + Size = new Float2(300, 400); var headerPanel = new Panel(ScrollBars.None) { @@ -139,17 +141,40 @@ namespace FlaxEditor.Surface.ContextMenu }; // Title bar + var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10); var titleLabel = new Label { - Width = Width - 8, + Width = Width * 0.5f - 8f, Height = 20, X = 4, Parent = headerPanel, Text = "Select Node", - HorizontalAlignment = TextAlignment.Center, - Font = new FontReference(Style.Current.FontLarge.Asset, 10), + HorizontalAlignment = TextAlignment.Near, + Font = titleFontReference, }; + // Context sensitive toggle + var contextSensitiveLabel = new Label + { + Width = Width * 0.5f - 28, + Height = 20, + X = Width * 0.5f, + Parent = headerPanel, + Text = "Context Sensitive", + HorizontalAlignment = TextAlignment.Far, + Font = titleFontReference, + }; + + _contextSensitiveToggle = new CheckBox + { + Width = 20, + Height = 20, + X = Width - 24, + Parent = headerPanel, + Checked = _contextSensitiveSearchEnabled, + }; + _contextSensitiveToggle.StateChanged += OnContextSensitiveToggleStateChanged; + // Search box _searchBox = new SearchBox(false, 2, 22) { @@ -251,6 +276,11 @@ namespace FlaxEditor.Surface.ContextMenu } } + private void OnContextSensitiveToggleStateChanged(CheckBox checkBox) + { + _contextSensitiveSearchEnabled = checkBox.Checked; + } + /// /// Adds the group archetype to add to the menu. /// @@ -768,5 +798,12 @@ namespace FlaxEditor.Surface.ContextMenu { return GetPreviousSiblings(item).OfType(); } + + /// + public override void OnDestroy() + { + _contextSensitiveToggle.StateChanged -= OnContextSensitiveToggleStateChanged; + base.OnDestroy(); + } } } From 84b240216f38b0184851b0ddda23d1ca274fa9e4 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 28 Sep 2023 21:31:58 +0200 Subject: [PATCH 28/80] - Implemented context sensitive toggle functionality - Item list now updates on the fly when toggleing context sensitivity - Added profiling - Fixed a highlighting bug - Minor cleanup --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 40 ++++++++++++++----- .../Surface/ContextMenu/VisjectCMGroup.cs | 7 ++++ .../Surface/ContextMenu/VisjectCMItem.cs | 5 ++- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 0d4a35e51..67148a5a4 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -161,6 +161,7 @@ namespace FlaxEditor.Surface.ContextMenu X = Width * 0.5f, Parent = headerPanel, Text = "Context Sensitive", + TooltipText = "Should the nodes be filtered to only show those that can be connected in the current context?", HorizontalAlignment = TextAlignment.Far, Font = titleFontReference, }; @@ -276,11 +277,6 @@ namespace FlaxEditor.Surface.ContextMenu } } - private void OnContextSensitiveToggleStateChanged(CheckBox checkBox) - { - _contextSensitiveSearchEnabled = checkBox.Checked; - } - /// /// Adds the group archetype to add to the menu. /// @@ -318,7 +314,7 @@ namespace FlaxEditor.Surface.ContextMenu OnSearchFilterChanged(); } } - else + else if(_contextSensitiveSearchEnabled) { group.EvaluateVisibilityWithBox(_selectedBox); } @@ -355,7 +351,8 @@ namespace FlaxEditor.Surface.ContextMenu Parent = group }; } - group.EvaluateVisibilityWithBox(_selectedBox); + if(_contextSensitiveSearchEnabled) + group.EvaluateVisibilityWithBox(_selectedBox); group.SortChildren(); group.Parent = _groupsPanel; _groups.Add(group); @@ -465,7 +462,7 @@ namespace FlaxEditor.Surface.ContextMenu LockChildrenRecursive(); for (int i = 0; i < _groups.Count; i++) { - _groups[i].UpdateFilter(_searchBox.Text, _selectedBox); + _groups[i].UpdateFilter(_searchBox.Text, _contextSensitiveSearchEnabled ? _selectedBox : null); _groups[i].UpdateItemSort(_selectedBox); } SortGroups(); @@ -484,6 +481,30 @@ namespace FlaxEditor.Surface.ContextMenu Profiler.EndEvent(); } + private void OnContextSensitiveToggleStateChanged(CheckBox checkBox) + { + Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged"); + _contextSensitiveSearchEnabled = checkBox.Checked; + + LockChildrenRecursive(); + for (int i = 0; i < _groups.Count; i++) + { + _groups[i].UpdateFilter(_searchBox.Text, _contextSensitiveSearchEnabled ? _selectedBox : null); + } + SortGroups(); + UnlockChildrenRecursive(); + + Profiler.BeginEvent("VisjectCM.Layout"); + if (SelectedItem == null || !SelectedItem.VisibleInHierarchy) + SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem; + PerformLayout(); + if (SelectedItem != null) + _panel1.ScrollViewTo(SelectedItem); + Profiler.EndEvent(); + + Profiler.EndEvent(); + } + /// /// Sort the groups and keeps in sync /// @@ -540,7 +561,8 @@ namespace FlaxEditor.Surface.ContextMenu for (int i = 0; i < _groups.Count; i++) { _groups[i].ResetView(); - _groups[i].EvaluateVisibilityWithBox(_selectedBox); + if(_contextSensitiveSearchEnabled) + _groups[i].EvaluateVisibilityWithBox(_selectedBox); } UnlockChildrenRecursive(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index 9560aecbb..d11d51223 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -132,6 +132,13 @@ namespace FlaxEditor.Surface.ContextMenu { if (selectedBox == null) { + for (int i = 0; i < _children.Count; i++) + { + if (_children[i] is VisjectCMItem item) + { + item.CanConnectTo(null); + } + } Visible = true; return; } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index be69d675c..4aa5c6ce8 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -309,11 +309,14 @@ namespace FlaxEditor.Surface.ContextMenu if (selectedBox != null) { if (!CanConnectTo(selectedBox)) + { + _highlights?.Clear(); return; + } } _isStartsWithMatch = _isFullMatch = false; - if (filterText == null) + if (string.IsNullOrEmpty(filterText)) { // Clear filter _highlights?.Clear(); From a4970b7fced7f1b6b81849b8527f6118a7665941 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 29 Sep 2023 16:25:34 +0200 Subject: [PATCH 29/80] - Group names now get filtered again (currently doing a second filtering pass through the items) - More cleanup --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 45 ++++++++----------- .../Surface/ContextMenu/VisjectCMGroup.cs | 8 ++-- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 67148a5a4..9955f0434 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -450,7 +450,25 @@ namespace FlaxEditor.Surface.ContextMenu return; Profiler.BeginEvent("VisjectCM.OnSearchFilterChanged"); + UpdateFilters(); + _searchBox.Focus(); + Profiler.EndEvent(); + } + + private void OnContextSensitiveToggleStateChanged(CheckBox checkBox) + { + // Skip events during setup or init stuff + if (IsLayoutLocked) + return; + Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged"); + _contextSensitiveSearchEnabled = checkBox.Checked; + UpdateFilters(); + Profiler.EndEvent(); + } + + private void UpdateFilters() + { if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null) { ResetView(); @@ -475,33 +493,6 @@ namespace FlaxEditor.Surface.ContextMenu PerformLayout(); if (SelectedItem != null) _panel1.ScrollViewTo(SelectedItem); - _searchBox.Focus(); - Profiler.EndEvent(); - - Profiler.EndEvent(); - } - - private void OnContextSensitiveToggleStateChanged(CheckBox checkBox) - { - Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged"); - _contextSensitiveSearchEnabled = checkBox.Checked; - - LockChildrenRecursive(); - for (int i = 0; i < _groups.Count; i++) - { - _groups[i].UpdateFilter(_searchBox.Text, _contextSensitiveSearchEnabled ? _selectedBox : null); - } - SortGroups(); - UnlockChildrenRecursive(); - - Profiler.BeginEvent("VisjectCM.Layout"); - if (SelectedItem == null || !SelectedItem.VisibleInHierarchy) - SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem; - PerformLayout(); - if (SelectedItem != null) - _panel1.ScrollViewTo(SelectedItem); - Profiler.EndEvent(); - Profiler.EndEvent(); } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index d11d51223..efb8ab458 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -100,17 +100,17 @@ namespace FlaxEditor.Surface.ContextMenu } // Update header title - /*if (QueryFilterHelper.Match(filterText, HeaderText)) + if (QueryFilterHelper.Match(filterText, HeaderText)) { for (int i = 0; i < _children.Count; i++) { if (_children[i] is VisjectCMItem item) { - item.Visible = true; + item.UpdateFilter(null, selectedBox); + isAnyVisible |= item.Visible; } } - isAnyVisible = true; - }*/ + } // Update itself if (isAnyVisible) From 4bf46c3af3a353ee45635c30f70054373f680bd8 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 29 Sep 2023 17:19:38 +0200 Subject: [PATCH 30/80] - More cleanup and comments --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 1 - .../Surface/ContextMenu/VisjectCMItem.cs | 106 +++++------------- Source/Editor/Surface/Elements/Box.cs | 54 +-------- .../Surface/VisjectSurface.Connecting.cs | 64 +++++++++++ 4 files changed, 93 insertions(+), 132 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 9955f0434..c07173aec 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 4aa5c6ce8..b0ff74272 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -80,7 +80,7 @@ namespace FlaxEditor.Surface.ContextMenu if (!Visible) return; - if (selectedBox != null && CanConnectTo(selectedBox, NodeArchetype)) + if (selectedBox != null && CanConnectTo(selectedBox)) SortScore += 1; if (Data != null) SortScore += 1; @@ -95,29 +95,14 @@ namespace FlaxEditor.Surface.ContextMenu textRect = new Rectangle(22, 0, Width - 24, Height); } - private bool CanConnectTo(Box startBox, NodeArchetype nodeArchetype) - { - if (startBox == null) - return false; - if (!startBox.IsOutput) - return false; // For now, I'm only handing the output box case - - if (nodeArchetype.Elements != null) - { - for (int i = 0; i < nodeArchetype.Elements.Length; i++) - { - if (nodeArchetype.Elements[i].Type == NodeElementType.Input && - startBox.CanUseType(nodeArchetype.Elements[i].ConnectionsType)) - { - return true; - } - } - } - return false; - } - + /// + /// Checks if this context menu item can be connected to a given box, before a node is actually spawned. + /// + /// The connected box + /// True if the connected box is compatible with this item public bool CanConnectTo(Box startBox) { + // Is compatible if box is null for reset reasons if (startBox == null) { Visible = true; @@ -131,15 +116,20 @@ namespace FlaxEditor.Surface.ContextMenu } bool isCompatible = false; + + // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items if (_archetype.Elements != null) { isCompatible = CheckElementsCompatibility(startBox); } + // Check compatibility based on the archetype tag or name. This handles custom groups and items, mainly function nodes for visual scripting if (_archetype.NodeTypeHint == NodeTypeHint.FunctionNode) { ScriptMemberInfo memberInfo = ScriptMemberInfo.Null; + // Check if the archetype tag already has a member info otherwise try to fetch it via the archetype type and name + // Only really the InvokeMethod Nodes have a member info in their tag if (_archetype.Tag is ScriptMemberInfo info) { memberInfo = info; @@ -155,22 +145,29 @@ namespace FlaxEditor.Surface.ContextMenu { if(memberInfo.IsEvent) isCompatible = false; - + + // Box was dragged from an impulse port and the member info can be invoked so it is compatible if (startBox.CurrentType.IsVoid && memberInfo.ValueType.IsVoid) - isCompatible = true; + { + isCompatible = true; + } else { + // When the startBox is output we only need to check the input parameters if (startBox.IsOutput) { var parameters = memberInfo.GetParameters(); ScriptType outType = startBox.CurrentType; - + + // non static members have an instance input parameter if (!memberInfo.IsStatic) { var scriptType = memberInfo.DeclaringType; isCompatible |= CanCastToType(scriptType, outType, _archetype.ConnectionsHints); } + // We ignore event members here since they only have output parameters, which are currently not declared as such + // TODO: Fix bug where event member parameters 'IsOut' is set to false and not true if (!memberInfo.IsEvent) { for (int i = 0; i < parameters.Length; i++) @@ -182,6 +179,7 @@ namespace FlaxEditor.Surface.ContextMenu } else { + // When the startBox is input we only have to check the output type of the method ScriptType inType = startBox.CurrentType; ScriptType outType = memberInfo.ValueType; @@ -232,60 +230,10 @@ namespace FlaxEditor.Surface.ContextMenu { if (VisjectSurface.CanUseDirectCastStatic(type, currentType, false)) return true; - - var connectionsHints = hint; - if (currentType == ScriptType.Null && connectionsHints != ConnectionsHint.None) - { - if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything) - return true; - if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void)) - return true; - if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum) - return true; - if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray) - return true; - if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary) - return true; - if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector) - { - var t = type.Type; - if (t == typeof(Vector2) || - t == typeof(Vector3) || - t == typeof(Vector4) || - t == typeof(Float2) || - t == typeof(Float3) || - t == typeof(Float4) || - t == typeof(Double2) || - t == typeof(Double3) || - t == typeof(Double4) || - t == typeof(Int2) || - t == typeof(Int3) || - t == typeof(Int4) || - t == typeof(Color)) - { - return true; - } - } - if ((connectionsHints & ConnectionsHint.Scalar) == ConnectionsHint.Scalar) - { - var t = type.Type; - if (t == typeof(bool) || - t == typeof(char) || - t == typeof(byte) || - t == typeof(short) || - t == typeof(ushort) || - t == typeof(int) || - t == typeof(uint) || - t == typeof(long) || - t == typeof(ulong) || - t == typeof(float) || - t == typeof(double)) - { - return true; - } - } - } - + + if(VisjectSurface.IsTypeCompatible(currentType, type, hint)) + return true; + return CanCast(type, currentType); } diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index e771fa71c..5032b6048 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -231,58 +231,8 @@ namespace FlaxEditor.Surface.Elements } // Check using connection hints - var connectionsHints = ParentNode.Archetype.ConnectionsHints; - if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None) - { - if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything) - return true; - if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void)) - return true; - if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum) - return true; - if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray) - return true; - if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary) - return true; - if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector) - { - var t = type.Type; - if (t == typeof(Vector2) || - t == typeof(Vector3) || - t == typeof(Vector4) || - t == typeof(Float2) || - t == typeof(Float3) || - t == typeof(Float4) || - t == typeof(Double2) || - t == typeof(Double3) || - t == typeof(Double4) || - t == typeof(Int2) || - t == typeof(Int3) || - t == typeof(Int4) || - t == typeof(Color)) - { - return true; - } - } - if ((connectionsHints & ConnectionsHint.Scalar) == ConnectionsHint.Scalar) - { - var t = type.Type; - if (t == typeof(bool) || - t == typeof(char) || - t == typeof(byte) || - t == typeof(short) || - t == typeof(ushort) || - t == typeof(int) || - t == typeof(uint) || - t == typeof(long) || - t == typeof(ulong) || - t == typeof(float) || - t == typeof(double)) - { - return true; - } - } - } + if(VisjectSurface.IsTypeCompatible(Archetype.ConnectionsType, type, ParentNode.Archetype.ConnectionsHints)) + return true; // Check independent and if there is box with bigger potential because it may block current one from changing type var parentArch = ParentNode.Archetype; diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 2fbff7cb8..449f88f8c 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -136,6 +136,70 @@ namespace FlaxEditor.Surface return result; } + /// + /// Checks if a type is compatible with another type and can be casted by using a connection hint + /// + /// Source type + /// Type to check compatibility with + /// Hint to check if casting is possible + /// True if the source type is compatible with the target type + public static bool IsTypeCompatible(ScriptType from, ScriptType to, ConnectionsHint hint) + { + if (from == ScriptType.Null && hint != ConnectionsHint.None) + { + if ((hint & ConnectionsHint.Anything) == ConnectionsHint.Anything) + return true; + if ((hint & ConnectionsHint.Value) == ConnectionsHint.Value && to.Type != typeof(void)) + return true; + if ((hint & ConnectionsHint.Enum) == ConnectionsHint.Enum && to.IsEnum) + return true; + if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array && to.IsArray) + return true; + if ((hint & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && to.IsDictionary) + return true; + if ((hint & ConnectionsHint.Vector) == ConnectionsHint.Vector) + { + var t = to.Type; + if (t == typeof(Vector2) || + t == typeof(Vector3) || + t == typeof(Vector4) || + t == typeof(Float2) || + t == typeof(Float3) || + t == typeof(Float4) || + t == typeof(Double2) || + t == typeof(Double3) || + t == typeof(Double4) || + t == typeof(Int2) || + t == typeof(Int3) || + t == typeof(Int4) || + t == typeof(Color)) + { + return true; + } + } + if ((hint & ConnectionsHint.Scalar) == ConnectionsHint.Scalar) + { + var t = to.Type; + if (t == typeof(bool) || + t == typeof(char) || + t == typeof(byte) || + t == typeof(short) || + t == typeof(ushort) || + t == typeof(int) || + t == typeof(uint) || + t == typeof(long) || + t == typeof(ulong) || + t == typeof(float) || + t == typeof(double)) + { + return true; + } + } + } + + return false; + } + /// /// Checks if can use direct conversion from one type to another. /// From 091d34b20d22a9cf6bbde017c8dc84064203f498 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 29 Sep 2023 17:26:56 +0200 Subject: [PATCH 31/80] - Even more cleanup and comments --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 7 +++---- Source/Editor/Surface/ContextMenu/VisjectCMItem.cs | 2 +- Source/Editor/Surface/NodeArchetype.cs | 3 +++ Source/Editor/Surface/VisualScriptSurface.cs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index c07173aec..36f71a1a7 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -494,7 +494,7 @@ namespace FlaxEditor.Surface.ContextMenu _panel1.ScrollViewTo(SelectedItem); Profiler.EndEvent(); } - + /// /// Sort the groups and keeps in sync /// @@ -558,7 +558,7 @@ namespace FlaxEditor.Surface.ContextMenu SortGroups(); PerformLayout(); - + Profiler.EndEvent(); } @@ -676,11 +676,10 @@ namespace FlaxEditor.Surface.ContextMenu // Prepare UpdateSurfaceParametersGroup(); ResetView(); - _panel1.VScrollBar.TargetValue = 0; Focus(); _waitingForInput = true; - + base.OnShow(); } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index b0ff74272..950303800 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -336,7 +336,7 @@ namespace FlaxEditor.Surface.ContextMenu } } } - + /// public override void Draw() { diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 1cdaee2d2..4255b7199 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -73,6 +73,9 @@ namespace FlaxEditor.Surface All = Scalar | Vector | Enum | Anything | Value | Array | Dictionary, } + /// + /// Node type hint. Helps to distinguish special archetypes + /// [HideInEditor] public enum NodeTypeHint { diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index b6c0112a9..04c4da9d2 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -370,7 +370,7 @@ namespace FlaxEditor.Surface 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; From b5dc91656819b072c82d0080c3e44dffd9f2938e Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 29 Sep 2023 17:44:31 +0200 Subject: [PATCH 32/80] - Moved ScriptType casting check from Box to ScriptType class - Even more cleanup and comments, wowzers --- Source/Editor/Scripting/ScriptType.cs | 27 ++++++++ .../Surface/ContextMenu/VisjectCMItem.cs | 65 ++++++++----------- Source/Editor/Surface/Elements/Box.cs | 21 ++---- 3 files changed, 60 insertions(+), 53 deletions(-) diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 454c3a5d2..e3de348d2 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -1393,5 +1393,32 @@ namespace FlaxEditor.Scripting return _custom.GetMembers(name, MemberTypes.Method, bindingAttr).FirstOrDefault(); return ScriptMemberInfo.Null; } + + /// + /// Basic check to see if a type could be casted to another type + /// + /// Source type + /// Target type + /// True if the type can be casted + public static bool CanCast(ScriptType from, ScriptType to) + { + if (from == to) + return true; + if (from == Null || to == Null) + return false; + return (from.Type != typeof(void) && from.Type != typeof(FlaxEngine.Object)) && + (to.Type != typeof(void) && to.Type != typeof(FlaxEngine.Object)) && + from.IsAssignableFrom(to); + } + + /// + /// Basic check to see if this type could be casted to another type + /// + /// Target type + /// True if the type can be casted + public bool CanCastTo(ScriptType to) + { + return CanCast(this, to); + } } } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 950303800..e3f4b0e66 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Utilities; @@ -117,12 +116,6 @@ namespace FlaxEditor.Surface.ContextMenu bool isCompatible = false; - // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items - if (_archetype.Elements != null) - { - isCompatible = CheckElementsCompatibility(startBox); - } - // Check compatibility based on the archetype tag or name. This handles custom groups and items, mainly function nodes for visual scripting if (_archetype.NodeTypeHint == NodeTypeHint.FunctionNode) { @@ -134,18 +127,18 @@ namespace FlaxEditor.Surface.ContextMenu { memberInfo = info; } - else if(_archetype.DefaultValues is { Length: > 1 }) + else if(_archetype.DefaultValues is { Length: > 1 }) // We have to check since VisualScriptFunctionNode and ReturnNode don't have a name and type { var eventName = (string)_archetype.DefaultValues[1]; var eventType = TypeUtils.GetType((string)_archetype.DefaultValues[0]); - memberInfo = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + memberInfo = eventType.GetMember(eventName, System.Reflection.MemberTypes.Event, + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.Static | + System.Reflection.BindingFlags.Instance); } if (memberInfo != ScriptMemberInfo.Null) { - if(memberInfo.IsEvent) - isCompatible = false; - // Box was dragged from an impulse port and the member info can be invoked so it is compatible if (startBox.CurrentType.IsVoid && memberInfo.ValueType.IsVoid) { @@ -163,7 +156,7 @@ namespace FlaxEditor.Surface.ContextMenu if (!memberInfo.IsStatic) { var scriptType = memberInfo.DeclaringType; - isCompatible |= CanCastToType(scriptType, outType, _archetype.ConnectionsHints); + isCompatible |= CanCastType(scriptType, outType, _archetype.ConnectionsHints); } // We ignore event members here since they only have output parameters, which are currently not declared as such @@ -173,7 +166,7 @@ namespace FlaxEditor.Surface.ContextMenu for (int i = 0; i < parameters.Length; i++) { ScriptType inType = parameters[i].Type; - isCompatible |= CanCastToType(inType, outType, _archetype.ConnectionsHints); + isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); } } } @@ -183,11 +176,16 @@ namespace FlaxEditor.Surface.ContextMenu ScriptType inType = startBox.CurrentType; ScriptType outType = memberInfo.ValueType; - isCompatible |= CanCastToType(inType, outType, _archetype.ConnectionsHints); + isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); } } } } + else if (_archetype.Elements != null) + { + // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items + isCompatible = CheckElementsCompatibility(startBox); + } Visible = isCompatible; return isCompatible; @@ -198,54 +196,47 @@ namespace FlaxEditor.Surface.ContextMenu bool isCompatible = false; foreach (NodeElementArchetype element in _archetype.Elements) { + // Ignore all elements that aren't inputs or outputs (e.g. input fields) if(element.Type != NodeElementType.Output && element.Type != NodeElementType.Input) continue; + // Ignore elements with the same direction as the box if ((startBox.IsOutput && element.Type == NodeElementType.Output) || (!startBox.IsOutput && element.Type == NodeElementType.Input)) continue; - ScriptType inType; - ScriptType outType; + ScriptType fromType; + ScriptType toType; ConnectionsHint hint; if (startBox.IsOutput) { - inType = element.ConnectionsType; - outType = startBox.CurrentType; + fromType = element.ConnectionsType; + toType = startBox.CurrentType; hint = _archetype.ConnectionsHints; } else { - inType = startBox.CurrentType; - outType = element.ConnectionsType; + fromType = startBox.CurrentType; + toType = element.ConnectionsType; hint = startBox.ParentNode.Archetype.ConnectionsHints; } - isCompatible |= CanCastToType(inType, outType, hint); + isCompatible |= CanCastType(fromType, toType, hint); } return isCompatible; } - private bool CanCastToType(ScriptType currentType, ScriptType type, ConnectionsHint hint) + private bool CanCastType(ScriptType from, ScriptType to, ConnectionsHint hint) { - if (VisjectSurface.CanUseDirectCastStatic(type, currentType, false)) + // Yes, from and to are switched on purpose + if (VisjectSurface.CanUseDirectCastStatic(to, from, false)) return true; - if(VisjectSurface.IsTypeCompatible(currentType, type, hint)) + if(VisjectSurface.IsTypeCompatible(from, to, hint)) return true; - return CanCast(type, currentType); - } - - private static bool CanCast(ScriptType oB, ScriptType iB) - { - if (oB == iB) - return true; - if (oB == ScriptType.Null || iB == ScriptType.Null) - return false; - return (oB.Type != typeof(void) && oB.Type != typeof(FlaxEngine.Object)) && - (iB.Type != typeof(void) && iB.Type != typeof(FlaxEngine.Object)) && - oB.IsAssignableFrom(iB); + // Same here + return to.CanCastTo(from); } /// diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 5032b6048..6a265a21e 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -91,7 +91,7 @@ namespace FlaxEditor.Surface.Elements _currentType = value; // Check if will need to update box connections due to type change - if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !CanCast(prev, _currentType)) + if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !prev.CanCastTo(_currentType)) { // Remove all invalid connections and update those which still can be valid var connections = Connections.ToArray(); @@ -246,7 +246,7 @@ namespace FlaxEditor.Surface.Elements var b = ParentNode.GetBox(boxes[i]); // Check if its the same and tested type matches the default value type - if (b == this && CanCast(parentArch.DefaultType, type)) + if (b == this && parentArch.DefaultType.CanCastTo(type)) { // Can return true; @@ -668,17 +668,6 @@ namespace FlaxEditor.Surface.Elements } } - private static bool CanCast(ScriptType oB, ScriptType iB) - { - if (oB == iB) - return true; - if (oB == ScriptType.Null || iB == ScriptType.Null) - return false; - return (oB.Type != typeof(void) && oB.Type != typeof(FlaxEngine.Object)) && - (iB.Type != typeof(void) && iB.Type != typeof(FlaxEngine.Object)) && - oB.IsAssignableFrom(iB); - } - /// public bool AreConnected(IConnectionInstigator other) { @@ -737,7 +726,7 @@ namespace FlaxEditor.Surface.Elements { if (!iB.CanUseType(oB.CurrentType)) { - if (!CanCast(oB.CurrentType, iB.CurrentType)) + if (!oB.CurrentType.CanCastTo(iB.CurrentType)) { // Cannot return false; @@ -748,7 +737,7 @@ namespace FlaxEditor.Surface.Elements { if (!oB.CanUseType(iB.CurrentType)) { - if (!CanCast(oB.CurrentType, iB.CurrentType)) + if (!oB.CurrentType.CanCastTo(iB.CurrentType)) { // Cannot return false; @@ -821,7 +810,7 @@ namespace FlaxEditor.Surface.Elements bool useCaster = false; if (!iB.CanUseType(oB.CurrentType)) { - if (CanCast(oB.CurrentType, iB.CurrentType)) + if (oB.CurrentType.CanCastTo(iB.CurrentType)) useCaster = true; else return; From 3befe4bb4a036b7fff278bffb7d90de88baf4194 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Fri, 29 Sep 2023 21:53:34 +0200 Subject: [PATCH 33/80] - Fixed a bug where items with a string match didn't get highlighted anymore when the group name matched - Remouved double filtering when group name matched - Started fixing asynchronous Un/Packing nodes filtering --- Source/Editor/Surface/Archetypes/Packing.cs | 2 + .../Surface/ContextMenu/VisjectCMGroup.cs | 21 +--- .../Surface/ContextMenu/VisjectCMItem.cs | 109 +++++++++--------- Source/Editor/Surface/NodeArchetype.cs | 5 + 4 files changed, 67 insertions(+), 70 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 5a5e9cd5b..54524dc13 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -353,6 +353,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch), Description = "Makes the structure data to from the components.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.PackingNode, Size = new Float2(180, 20), DefaultValues = new object[] { @@ -463,6 +464,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch), Description = "Breaks the structure data to allow extracting components from it.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, + NodeTypeHint = NodeTypeHint.PackingNode, Size = new Float2(180, 20), DefaultValues = new object[] { diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index efb8ab458..9ab67f856 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -90,28 +90,16 @@ namespace FlaxEditor.Surface.ContextMenu // Update items bool isAnyVisible = false; + bool groupHeaderMatches = QueryFilterHelper.Match(filterText, HeaderText); for (int i = 0; i < _children.Count; i++) { if (_children[i] is VisjectCMItem item) { - item.UpdateFilter(filterText, selectedBox); + item.UpdateFilter(filterText, selectedBox, groupHeaderMatches); isAnyVisible |= item.Visible; } } - // Update header title - if (QueryFilterHelper.Match(filterText, HeaderText)) - { - for (int i = 0; i < _children.Count; i++) - { - if (_children[i] is VisjectCMItem item) - { - item.UpdateFilter(null, selectedBox); - isAnyVisible |= item.Visible; - } - } - } - // Update itself if (isAnyVisible) { @@ -136,7 +124,7 @@ namespace FlaxEditor.Surface.ContextMenu { if (_children[i] is VisjectCMItem item) { - item.CanConnectTo(null); + item.Visible = true; } } Visible = true; @@ -150,7 +138,8 @@ namespace FlaxEditor.Surface.ContextMenu { if (_children[i] is VisjectCMItem item) { - isAnyVisible |= item.CanConnectTo(selectedBox); + item.Visible = item.CanConnectTo(selectedBox); + isAnyVisible |= item.Visible; } } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index e3f4b0e66..65ba19909 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -103,29 +103,23 @@ namespace FlaxEditor.Surface.ContextMenu { // Is compatible if box is null for reset reasons if (startBox == null) - { - Visible = true; return true; - } if (_archetype == null) - { - Visible = false; return false; - } bool isCompatible = false; // Check compatibility based on the archetype tag or name. This handles custom groups and items, mainly function nodes for visual scripting - if (_archetype.NodeTypeHint == NodeTypeHint.FunctionNode) + if (_archetype.NodeTypeHint is NodeTypeHint.FunctionNode) { ScriptMemberInfo memberInfo = ScriptMemberInfo.Null; // Check if the archetype tag already has a member info otherwise try to fetch it via the archetype type and name // Only really the InvokeMethod Nodes have a member info in their tag - if (_archetype.Tag is ScriptMemberInfo info) + if (_archetype.Tag is ScriptMemberInfo tagInfo) { - memberInfo = info; + memberInfo = tagInfo; } else if(_archetype.DefaultValues is { Length: > 1 }) // We have to check since VisualScriptFunctionNode and ReturnNode don't have a name and type { @@ -139,46 +133,7 @@ namespace FlaxEditor.Surface.ContextMenu if (memberInfo != ScriptMemberInfo.Null) { - // Box was dragged from an impulse port and the member info can be invoked so it is compatible - if (startBox.CurrentType.IsVoid && memberInfo.ValueType.IsVoid) - { - isCompatible = true; - } - else - { - // When the startBox is output we only need to check the input parameters - if (startBox.IsOutput) - { - var parameters = memberInfo.GetParameters(); - ScriptType outType = startBox.CurrentType; - - // non static members have an instance input parameter - if (!memberInfo.IsStatic) - { - var scriptType = memberInfo.DeclaringType; - isCompatible |= CanCastType(scriptType, outType, _archetype.ConnectionsHints); - } - - // We ignore event members here since they only have output parameters, which are currently not declared as such - // TODO: Fix bug where event member parameters 'IsOut' is set to false and not true - if (!memberInfo.IsEvent) - { - for (int i = 0; i < parameters.Length; i++) - { - ScriptType inType = parameters[i].Type; - isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); - } - } - } - else - { - // When the startBox is input we only have to check the output type of the method - ScriptType inType = startBox.CurrentType; - ScriptType outType = memberInfo.ValueType; - - isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); - } - } + isCompatible |= CheckMemberInfoCompatibility(startBox, memberInfo); } } else if (_archetype.Elements != null) @@ -186,11 +141,56 @@ namespace FlaxEditor.Surface.ContextMenu // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items isCompatible = CheckElementsCompatibility(startBox); } - - Visible = isCompatible; + return isCompatible; } + private bool CheckMemberInfoCompatibility(Box startBox, ScriptMemberInfo info) + { + bool isCompatible = false; + // Box was dragged from an impulse port and the member info can be invoked so it is compatible + if (startBox.CurrentType.IsVoid && info.ValueType.IsVoid) + { + isCompatible = true; + } + else + { + // When the startBox is output we only need to check the input parameters + if (startBox.IsOutput && !info.IsField) + { + var parameters = info.GetParameters(); + ScriptType outType = startBox.CurrentType; + + // non static members have an instance input parameter + if (!info.IsStatic) + { + var scriptType = info.DeclaringType; + isCompatible |= CanCastType(scriptType, outType, _archetype.ConnectionsHints); + } + + // We ignore event members here since they only have output parameters, which are currently not declared as such + // TODO: Fix bug where event member parameters 'IsOut' is set to false and not true + if (!info.IsEvent) + { + for (int i = 0; i < parameters.Length; i++) + { + ScriptType inType = parameters[i].Type; + isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); + } + } + } + else + { + // When the startBox is input we only have to check the output type of the method + ScriptType inType = startBox.CurrentType; + ScriptType outType = info.ValueType; + + isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); + } + } + return isCompatible; + } + private bool CheckElementsCompatibility(Box startBox) { bool isCompatible = false; @@ -243,11 +243,12 @@ namespace FlaxEditor.Surface.ContextMenu /// Updates the filter. /// /// The filter text. - public void UpdateFilter(string filterText, Box selectedBox) + public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false) { if (selectedBox != null) { - if (!CanConnectTo(selectedBox)) + Visible = CanConnectTo(selectedBox); + if (!Visible) { _highlights?.Clear(); return; @@ -319,7 +320,7 @@ namespace FlaxEditor.Surface.ContextMenu Data = data; } - else + else if(!groupHeaderMatches) { // Hide _highlights?.Clear(); diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 4255b7199..146618ae7 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -88,6 +88,11 @@ namespace FlaxEditor.Surface /// Is Function Node. /// FunctionNode = 1, + + /// + /// Is custom Packing Node. + /// + PackingNode = 2, } /// From 35f641955b0587a3adc61b7296f46f7dc209c1e6 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 30 Sep 2023 13:20:04 +0200 Subject: [PATCH 34/80] - Removed NodeTypeHint - Added delegates to check compatiblity with custom archetypes - Added compatibility check to InvokeMethod archetype --- Source/Editor/Surface/Archetypes/Function.cs | 53 +++++++++++++++---- Source/Editor/Surface/Archetypes/Packing.cs | 2 - .../Surface/ContextMenu/VisjectCMItem.cs | 46 ++++++++-------- Source/Editor/Surface/NodeArchetype.cs | 47 +++++++--------- .../Surface/VisjectSurface.Connecting.cs | 11 ++++ 5 files changed, 92 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index d027dd8f1..422ebf35b 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -773,7 +773,7 @@ namespace FlaxEditor.Surface.Archetypes Values[4] = GetSignatureData(memberInfo, memberInfo.GetParameters()); } } - + private SignatureInfo LoadSignature() { var signature = new SignatureInfo(); @@ -1151,6 +1151,45 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + if (nodeArch.Tag is not ScriptMemberInfo memberInfo) + return false; + if(memberInfo.ValueType.IsVoid && outputType.IsVoid) + return true; + if (!memberInfo.IsStatic) + { + if (VisjectSurface.FullCastCheck(memberInfo.DeclaringType, outputType, hint)) + return true; + } + foreach (var param in memberInfo.GetParameters()) + { + if(param.IsOut) + continue; + if (VisjectSurface.FullCastCheck(param.Type, outputType, hint)) + return true; + } + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + if (nodeArch.Tag is not ScriptMemberInfo memberInfo) + return false; + if(memberInfo.ValueType.IsVoid && inputType.IsVoid) + return true; + if (VisjectSurface.FullCastCheck(memberInfo.ValueType, inputType, hint)) + return true; + foreach (var param in memberInfo.GetParameters()) + { + if(!param.IsOut) + continue; + if (VisjectSurface.FullCastCheck(param.Type, inputType, hint)) + return true; + } + return false; + } } private sealed class ReturnNode : SurfaceNode @@ -2228,7 +2267,6 @@ namespace FlaxEditor.Surface.Archetypes Title = "Function Input", Description = "The graph function input data", Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaPaste, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2248,7 +2286,6 @@ namespace FlaxEditor.Surface.Archetypes Title = "Function Output", Description = "The graph function output data", Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaPaste, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2267,7 +2304,6 @@ namespace FlaxEditor.Surface.Archetypes Title = string.Empty, Description = "Overrides the base class method with custom implementation", Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2280,9 +2316,10 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 4, Create = (id, context, arch, groupArch) => new InvokeMethodNode(id, context, arch, groupArch), + IsInputCompatible = InvokeMethodNode.IsInputCompatible, + IsOutputCompatible = InvokeMethodNode.IsOutputCompatible, Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2306,7 +2343,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new ReturnNode(id, context, arch, groupArch), Title = "Return", Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(100, 40), DefaultValues = new object[] { @@ -2325,7 +2361,6 @@ namespace FlaxEditor.Surface.Archetypes Title = "New Function", Description = "Adds a new function to the script", Flags = NodeFlags.VisualScriptGraph, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 20), DefaultValues = new object[] { @@ -2338,7 +2373,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new GetFieldNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2354,7 +2388,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new SetFieldNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(240, 60), DefaultValues = new object[] { @@ -2371,7 +2404,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(260, 60), DefaultValues = new object[] { @@ -2394,7 +2426,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch), Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.FunctionNode, Size = new Float2(260, 60), DefaultValues = new object[] { diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 54524dc13..5a5e9cd5b 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -353,7 +353,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch), Description = "Makes the structure data to from the components.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.PackingNode, Size = new Float2(180, 20), DefaultValues = new object[] { @@ -464,7 +463,6 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch), Description = "Breaks the structure data to allow extracting components from it.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, - NodeTypeHint = NodeTypeHint.PackingNode, Size = new Float2(180, 20), DefaultValues = new object[] { diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 65ba19909..1d645a94e 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -109,9 +109,23 @@ namespace FlaxEditor.Surface.ContextMenu return false; bool isCompatible = false; + + if (startBox.IsOutput && _archetype.IsInputCompatible != null) + { + isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints); + } + else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null) + { + isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints); + } + else if (_archetype.Elements != null) + { + // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items + isCompatible = CheckElementsCompatibility(startBox); + } // Check compatibility based on the archetype tag or name. This handles custom groups and items, mainly function nodes for visual scripting - if (_archetype.NodeTypeHint is NodeTypeHint.FunctionNode) + /*if (_archetype.NodeTypeHint is NodeTypeHint.FunctionNode) { ScriptMemberInfo memberInfo = ScriptMemberInfo.Null; @@ -135,12 +149,8 @@ namespace FlaxEditor.Surface.ContextMenu { isCompatible |= CheckMemberInfoCompatibility(startBox, memberInfo); } - } - else if (_archetype.Elements != null) - { - // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items - isCompatible = CheckElementsCompatibility(startBox); - } + }*/ + /*else */ return isCompatible; } @@ -165,17 +175,16 @@ namespace FlaxEditor.Surface.ContextMenu if (!info.IsStatic) { var scriptType = info.DeclaringType; - isCompatible |= CanCastType(scriptType, outType, _archetype.ConnectionsHints); + isCompatible |= VisjectSurface.FullCastCheck(scriptType, outType, _archetype.ConnectionsHints); } // We ignore event members here since they only have output parameters, which are currently not declared as such - // TODO: Fix bug where event member parameters 'IsOut' is set to false and not true if (!info.IsEvent) { for (int i = 0; i < parameters.Length; i++) { ScriptType inType = parameters[i].Type; - isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); + isCompatible |= VisjectSurface.FullCastCheck(inType, outType, _archetype.ConnectionsHints); } } } @@ -185,7 +194,7 @@ namespace FlaxEditor.Surface.ContextMenu ScriptType inType = startBox.CurrentType; ScriptType outType = info.ValueType; - isCompatible |= CanCastType(inType, outType, _archetype.ConnectionsHints); + isCompatible |= VisjectSurface.FullCastCheck(inType, outType, _archetype.ConnectionsHints); } } return isCompatible; @@ -220,25 +229,12 @@ namespace FlaxEditor.Surface.ContextMenu hint = startBox.ParentNode.Archetype.ConnectionsHints; } - isCompatible |= CanCastType(fromType, toType, hint); + isCompatible |= VisjectSurface.FullCastCheck(fromType, toType, hint); } return isCompatible; } - private bool CanCastType(ScriptType from, ScriptType to, ConnectionsHint hint) - { - // Yes, from and to are switched on purpose - if (VisjectSurface.CanUseDirectCastStatic(to, from, false)) - return true; - - if(VisjectSurface.IsTypeCompatible(from, to, hint)) - return true; - - // Same here - return to.CanCastTo(from); - } - /// /// Updates the filter. /// diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 146618ae7..1a7f88f20 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -72,28 +72,6 @@ namespace FlaxEditor.Surface /// All = Scalar | Vector | Enum | Anything | Value | Array | Dictionary, } - - /// - /// Node type hint. Helps to distinguish special archetypes - /// - [HideInEditor] - public enum NodeTypeHint - { - /// - /// Is Node. - /// - Default = 0, - - /// - /// Is Function Node. - /// - FunctionNode = 1, - - /// - /// Is custom Packing Node. - /// - PackingNode = 2, - } /// /// Surface node archetype description. @@ -111,6 +89,11 @@ namespace FlaxEditor.Surface /// The created node object. public delegate SurfaceNode CreateCustomNodeFunc(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch); + /// + /// Checks if the given type is compatible with the given node archetype. Used for custom nodes + /// + public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint); + /// /// Unique node type ID within a single group. /// @@ -121,6 +104,16 @@ namespace FlaxEditor.Surface /// public CreateCustomNodeFunc Create; + /// + /// Function for asynchronously loaded nodes to check if input ports are compatible, for filtering. + /// + public IsCompatible IsInputCompatible; + + /// + /// Function for asynchronously loaded nodes to check if output ports are compatible, for filtering. + /// + public IsCompatible IsOutputCompatible; + /// /// Default initial size of the node. /// @@ -130,7 +123,7 @@ namespace FlaxEditor.Surface /// Custom set of flags. /// public NodeFlags Flags; - + /// /// Title text. /// @@ -173,11 +166,6 @@ namespace FlaxEditor.Surface /// Connections hints. /// public ConnectionsHint ConnectionsHints; - - /// - /// Node Type hints. - /// - public NodeTypeHint NodeTypeHint; /// /// Array with independent boxes IDs. @@ -211,6 +199,8 @@ namespace FlaxEditor.Surface { TypeID = TypeID, Create = Create, + IsInputCompatible = IsInputCompatible, + IsOutputCompatible = IsOutputCompatible, Size = Size, Flags = Flags, Title = Title, @@ -220,7 +210,6 @@ namespace FlaxEditor.Surface DefaultValues = (object[])DefaultValues?.Clone(), DefaultType = DefaultType, ConnectionsHints = ConnectionsHints, - NodeTypeHint = NodeTypeHint, IndependentBoxes = (int[])IndependentBoxes?.Clone(), DependentBoxes = (int[])DependentBoxes?.Clone(), Elements = (NodeElementArchetype[])Elements?.Clone(), diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 449f88f8c..9ca4f82e8 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -199,6 +199,17 @@ namespace FlaxEditor.Surface return false; } + + public static bool FullCastCheck(ScriptType from, ScriptType to, ConnectionsHint hint) + { + // Yes, from and to are switched on purpose + if (CanUseDirectCastStatic(to, from, false)) + return true; + if(IsTypeCompatible(from, to, hint)) + return true; + // Same here + return to.CanCastTo(from); + } /// /// Checks if can use direct conversion from one type to another. From 719efc4a99d3f5d3d6ec8b98c2411c947fab7c3c Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 30 Sep 2023 13:45:21 +0200 Subject: [PATCH 35/80] - Added Input/Output compatibility check to event based nodes - Removed a huge chunk of compatibility checking code out of CMItem --- Source/Editor/Surface/Archetypes/Function.cs | 41 ++++++++++ .../Surface/ContextMenu/VisjectCMItem.cs | 74 ------------------- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 422ebf35b..975267ad2 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -2223,6 +2223,43 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + // Event based nodes always have a pulse input, so it's always compatible with void + if (outputType.IsVoid) + return true; + + var eventName = (string)nodeArch.DefaultValues[1]; + var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); + var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) + { + if (!member.IsStatic) + { + if (VisjectSurface.FullCastCheck(eventType, outputType, hint)) + return true; + } + } + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + // Event based nodes always have a pulse output, so it's always compatible with void + if (inputType.IsVoid) + return true; + + var eventName = (string)nodeArch.DefaultValues[1]; + var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); + var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) + { + if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint)) + return true; + } + return false; + } } private sealed class BindEventNode : EventBaseNode @@ -2402,6 +2439,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 9, Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch), + IsInputCompatible = EventBaseNode.IsInputCompatible, + IsOutputCompatible = EventBaseNode.IsOutputCompatible, Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(260, 60), @@ -2424,6 +2463,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 10, Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch), + IsInputCompatible = EventBaseNode.IsInputCompatible, + IsOutputCompatible = EventBaseNode.IsOutputCompatible, Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(260, 60), diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 1d645a94e..70c60f9bf 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -109,7 +109,6 @@ namespace FlaxEditor.Surface.ContextMenu return false; bool isCompatible = false; - if (startBox.IsOutput && _archetype.IsInputCompatible != null) { isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints); @@ -124,79 +123,6 @@ namespace FlaxEditor.Surface.ContextMenu isCompatible = CheckElementsCompatibility(startBox); } - // Check compatibility based on the archetype tag or name. This handles custom groups and items, mainly function nodes for visual scripting - /*if (_archetype.NodeTypeHint is NodeTypeHint.FunctionNode) - { - ScriptMemberInfo memberInfo = ScriptMemberInfo.Null; - - // Check if the archetype tag already has a member info otherwise try to fetch it via the archetype type and name - // Only really the InvokeMethod Nodes have a member info in their tag - if (_archetype.Tag is ScriptMemberInfo tagInfo) - { - memberInfo = tagInfo; - } - else if(_archetype.DefaultValues is { Length: > 1 }) // We have to check since VisualScriptFunctionNode and ReturnNode don't have a name and type - { - var eventName = (string)_archetype.DefaultValues[1]; - var eventType = TypeUtils.GetType((string)_archetype.DefaultValues[0]); - memberInfo = eventType.GetMember(eventName, System.Reflection.MemberTypes.Event, - System.Reflection.BindingFlags.Public | - System.Reflection.BindingFlags.Static | - System.Reflection.BindingFlags.Instance); - } - - if (memberInfo != ScriptMemberInfo.Null) - { - isCompatible |= CheckMemberInfoCompatibility(startBox, memberInfo); - } - }*/ - /*else */ - - return isCompatible; - } - - private bool CheckMemberInfoCompatibility(Box startBox, ScriptMemberInfo info) - { - bool isCompatible = false; - // Box was dragged from an impulse port and the member info can be invoked so it is compatible - if (startBox.CurrentType.IsVoid && info.ValueType.IsVoid) - { - isCompatible = true; - } - else - { - // When the startBox is output we only need to check the input parameters - if (startBox.IsOutput && !info.IsField) - { - var parameters = info.GetParameters(); - ScriptType outType = startBox.CurrentType; - - // non static members have an instance input parameter - if (!info.IsStatic) - { - var scriptType = info.DeclaringType; - isCompatible |= VisjectSurface.FullCastCheck(scriptType, outType, _archetype.ConnectionsHints); - } - - // We ignore event members here since they only have output parameters, which are currently not declared as such - if (!info.IsEvent) - { - for (int i = 0; i < parameters.Length; i++) - { - ScriptType inType = parameters[i].Type; - isCompatible |= VisjectSurface.FullCastCheck(inType, outType, _archetype.ConnectionsHints); - } - } - } - else - { - // When the startBox is input we only have to check the output type of the method - ScriptType inType = startBox.CurrentType; - ScriptType outType = info.ValueType; - - isCompatible |= VisjectSurface.FullCastCheck(inType, outType, _archetype.ConnectionsHints); - } - } return isCompatible; } From 1dc01cd0238142c3e4336a824f8b96f0e8c7b0d2 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 30 Sep 2023 20:07:33 +0200 Subject: [PATCH 36/80] - Added compatibility checks to packing structures nodes - Added compatibility checks to more function nodes --- Source/Editor/Surface/Archetypes/Function.cs | 135 ++++++++++++++++++- Source/Editor/Surface/Archetypes/Packing.cs | 82 +++++++++++ 2 files changed, 212 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 975267ad2..cf680c06e 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1156,14 +1156,19 @@ namespace FlaxEditor.Surface.Archetypes { if (nodeArch.Tag is not ScriptMemberInfo memberInfo) return false; - if(memberInfo.ValueType.IsVoid && outputType.IsVoid) - return true; + if (!memberInfo.IsStatic) { if (VisjectSurface.FullCastCheck(memberInfo.DeclaringType, outputType, hint)) return true; } - foreach (var param in memberInfo.GetParameters()) + + var parameters = memberInfo.GetParameters(); + bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid); + if (outputType.IsVoid) + return !isPure; + + foreach (var param in parameters) { if(param.IsOut) continue; @@ -1177,10 +1182,14 @@ namespace FlaxEditor.Surface.Archetypes { if (nodeArch.Tag is not ScriptMemberInfo memberInfo) return false; - if(memberInfo.ValueType.IsVoid && inputType.IsVoid) - return true; if (VisjectSurface.FullCastCheck(memberInfo.ValueType, inputType, hint)) return true; + + var parameters = memberInfo.GetParameters(); + bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid); + if (inputType.IsVoid) + return !isPure; + foreach (var param in memberInfo.GetParameters()) { if(!param.IsOut) @@ -1816,6 +1825,16 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + return inputType.IsVoid; + } } private abstract class FieldNodeBase : SurfaceNode @@ -1952,6 +1971,64 @@ namespace FlaxEditor.Surface.Archetypes Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); + if (scriptType == ScriptType.Null) + return false; + + var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + foreach (var member in members) + { + if (!SurfaceUtils.IsValidVisualScriptField(member)) + continue; + + if (member) + { + if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint)) + return true; + } + else + { + var isStatic = (bool)nodeArch.DefaultValues[3]; + if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint)) + return true; + } + break; + } + + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); + if (scriptType == ScriptType.Null) + return false; + + var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + foreach (var member in members) + { + if (!SurfaceUtils.IsValidVisualScriptField(member)) + continue; + + if (member) + { + if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint)) + return true; + } + else + { + var typeName = (string)nodeArch.DefaultValues[2]; + if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), inputType, hint)) + return true; + } + break; + } + + return false; + } } private sealed class SetFieldNode : FieldNodeBase @@ -2005,6 +2082,48 @@ namespace FlaxEditor.Surface.Archetypes Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + if(outputType.IsVoid) + return true; + + var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); + if (scriptType == ScriptType.Null) + return false; + + var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + foreach (var member in members) + { + if (!SurfaceUtils.IsValidVisualScriptField(member)) + continue; + + if (member) + { + if (VisjectSurface.FullCastCheck(member.ValueType, outputType, hint)) + return true; + if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint)) + return true; + } + else + { + var typeName = (string)nodeArch.DefaultValues[2]; + if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), outputType, hint)) + return true; + var isStatic = (bool)nodeArch.DefaultValues[3]; + if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint)) + return true; + } + break; + } + + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + return inputType.IsVoid; + } } private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode @@ -2395,6 +2514,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 6, Create = (id, context, arch, groupArch) => new VisualScriptFunctionNode(id, context, arch, groupArch), + IsInputCompatible = VisualScriptFunctionNode.IsInputCompatible, + IsOutputCompatible = VisualScriptFunctionNode.IsOutputCompatible, Title = "New Function", Description = "Adds a new function to the script", Flags = NodeFlags.VisualScriptGraph, @@ -2408,6 +2529,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 7, Create = (id, context, arch, groupArch) => new GetFieldNode(id, context, arch, groupArch), + IsInputCompatible = GetFieldNode.IsInputCompatible, + IsOutputCompatible = GetFieldNode.IsOutputCompatible, Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(240, 60), @@ -2423,6 +2546,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 8, Create = (id, context, arch, groupArch) => new SetFieldNode(id, context, arch, groupArch), + IsInputCompatible = SetFieldNode.IsInputCompatible, + IsOutputCompatible = SetFieldNode.IsOutputCompatible, Title = string.Empty, Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(240, 60), diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 5a5e9cd5b..ccabaaa6d 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -207,6 +207,26 @@ namespace FlaxEditor.Surface.Archetypes AddElement(box); } } + + protected static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + // Event based nodes always have a pulse input, so it's always compatible with void + if (outputType.IsVoid) + return true; + + var eventName = (string)nodeArch.DefaultValues[1]; + var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); + var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) + { + if (!member.IsStatic) + { + if (VisjectSurface.FullCastCheck(eventType, outputType, hint)) + return true; + } + } + return false; + } } private sealed class PackStructureNode : StructureNode @@ -216,6 +236,35 @@ namespace FlaxEditor.Surface.Archetypes : base(id, context, nodeArch, groupArch, false) { } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + var typeName = (string)nodeArch.DefaultValues[0]; + var type = TypeUtils.GetType(typeName); + if (type) + { + var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray(); + var fieldsLength = fields.Length; + for (var i = 0; i < fieldsLength; i++) + { + if (VisjectSurface.FullCastCheck(fields[i].ValueType, outputType, hint)) + return true; + } + } + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + var typeName = (string)nodeArch.DefaultValues[0]; + var type = TypeUtils.GetType(typeName); + if (type) + { + if (VisjectSurface.FullCastCheck(type, inputType, hint)) + return true; + } + return false; + } } private sealed class UnpackStructureNode : StructureNode @@ -225,6 +274,35 @@ namespace FlaxEditor.Surface.Archetypes : base(id, context, nodeArch, groupArch, true) { } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + var typeName = (string)nodeArch.DefaultValues[0]; + var type = TypeUtils.GetType(typeName); + if (type) + { + if (VisjectSurface.FullCastCheck(type, outputType, hint)) + return true; + } + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + var typeName = (string)nodeArch.DefaultValues[0]; + var type = TypeUtils.GetType(typeName); + if (type) + { + var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray(); + var fieldsLength = fields.Length; + for (var i = 0; i < fieldsLength; i++) + { + if (VisjectSurface.FullCastCheck(fields[i].ValueType, inputType, hint)) + return true; + } + } + return false; + } } /// @@ -351,6 +429,8 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 26, Title = "Pack Structure", Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch), + IsInputCompatible = PackStructureNode.IsInputCompatible, + IsOutputCompatible = PackStructureNode.IsOutputCompatible, Description = "Makes the structure data to from the components.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(180, 20), @@ -461,6 +541,8 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 36, Title = "Unpack Structure", Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch), + IsInputCompatible = UnpackStructureNode.IsInputCompatible, + IsOutputCompatible = UnpackStructureNode.IsOutputCompatible, Description = "Breaks the structure data to allow extracting components from it.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(180, 20), From 2e09c4fb6381aa91e64f16ce0558a66ab0856a7b Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 30 Sep 2023 20:22:49 +0200 Subject: [PATCH 37/80] - Made visject items a tiny tiny tiny bit taller --- Source/Editor/Surface/ContextMenu/VisjectCMItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 70c60f9bf..28a9392b2 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -58,7 +58,7 @@ namespace FlaxEditor.Surface.ContextMenu /// The group archetype. /// The archetype. public VisjectCMItem(VisjectCMGroup group, GroupArchetype groupArchetype, NodeArchetype archetype) - : base(0, 0, 120, 12) + : base(0, 0, 120, 14) { Group = group; _groupArchetype = groupArchetype; From aca6d7110d7d6504ef938baa195840a5eea77755 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 30 Sep 2023 20:27:51 +0200 Subject: [PATCH 38/80] - Cleanup and comments --- Source/Editor/Surface/Archetypes/Function.cs | 2 +- Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs | 4 ++-- Source/Editor/Surface/NodeArchetype.cs | 6 +++--- Source/Editor/Surface/VisjectSurface.Connecting.cs | 7 +++++++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index cf680c06e..d56d6d790 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -773,7 +773,7 @@ namespace FlaxEditor.Surface.Archetypes Values[4] = GetSignatureData(memberInfo, memberInfo.GetParameters()); } } - + private SignatureInfo LoadSignature() { var signature = new SignatureInfo(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index 9ab67f856..aab9f3112 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -153,10 +153,10 @@ namespace FlaxEditor.Surface.ContextMenu // Hide group if none of the items matched the filter Visible = false; } - + Profiler.EndEvent(); } - + /// /// Updates the sorting of the s of this /// Also updates the diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 1a7f88f20..e2a9c84fa 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.Surface /// All = Scalar | Vector | Enum | Anything | Value | Array | Dictionary, } - + /// /// Surface node archetype description. /// @@ -123,7 +123,7 @@ namespace FlaxEditor.Surface /// Custom set of flags. /// public NodeFlags Flags; - + /// /// Title text. /// @@ -166,7 +166,7 @@ namespace FlaxEditor.Surface /// Connections hints. /// public ConnectionsHint ConnectionsHints; - + /// /// Array with independent boxes IDs. /// diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 9ca4f82e8..145bdfedf 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -200,6 +200,13 @@ namespace FlaxEditor.Surface return false; } + /// + /// Checks if a type is compatible with another type and can be casted by using a connection hint + /// + /// Source type + /// Target type + /// Connection hint + /// True if any method of casting or compatibility check succeeds public static bool FullCastCheck(ScriptType from, ScriptType to, ConnectionsHint hint) { // Yes, from and to are switched on purpose From 07d13f0144e115ce2eed05c1b9a7db4f7473281f Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 30 Sep 2023 23:06:26 +0200 Subject: [PATCH 39/80] - Implemented very basic support for method override nodes - Cleanup --- Source/Editor/Surface/Archetypes/Function.cs | 12 ++++++++++++ Source/Editor/Surface/VisualScriptSurface.cs | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index d56d6d790..de89ab50c 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -744,6 +744,16 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } + + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) + { + return false; + } + + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) + { + return inputType.IsVoid; + } } private sealed class InvokeMethodNode : SurfaceNode @@ -2460,6 +2470,8 @@ namespace FlaxEditor.Surface.Archetypes Title = string.Empty, Description = "Overrides the base class method with custom implementation", Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste, + IsInputCompatible = MethodOverrideNode.IsInputCompatible, + IsOutputCompatible = MethodOverrideNode.IsOutputCompatible, Size = new Float2(240, 60), DefaultValues = new object[] { diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 04c4da9d2..e961f686d 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -589,7 +589,6 @@ namespace FlaxEditor.Surface node.DefaultValues[0] = name; node.DefaultValues[1] = parameters.Length; node.Title = "Override " + name; - node.Tag = member; nodes.Add(node); } } From 10ea0b066519c6a54844c12cce5695c4b1a05990 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Mon, 2 Oct 2023 03:32:11 -0400 Subject: [PATCH 40/80] Make it clearer what "Restore Materials on Reimport" means. --- Source/Engine/Tools/ModelTool/ModelTool.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 9dc5bf067..e67c90873 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -337,8 +337,8 @@ public: // 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; - // If checked, the importer will try to restore the model material slots. - API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Restore Materials On Reimport\"), VisibleIf(nameof(ShowGeometry))") + // If checked, the importer will try to keep the model's current material slots, instead of importing materials from the source file. + API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Material Slots on Reimport\"), VisibleIf(nameof(ShowGeometry))") bool RestoreMaterialsOnReimport = true; public: // SDF From 78b074e7da62acbd30ace3213c4dabea255f34a1 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 3 Oct 2023 01:14:35 +0200 Subject: [PATCH 41/80] Fix segfault: target can be null when font size is changed --- Source/Editor/Windows/ContentWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index c25316e9a..b8e8a15ed 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -989,7 +989,7 @@ namespace FlaxEditor.Windows } _view.ShowItems(items, _sortType, false, true); } - else + else if (target != null) { // Show folder contents var items = target.Folder.Children; From 7bb3ddefeb15c89d02e6afc5034374fa96ae73de Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 07:51:15 -0500 Subject: [PATCH 42/80] Fix AnimatedModel box. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index dff527924..a211bb42b 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -537,9 +537,10 @@ void AnimatedModel::UpdateBounds() } // Apply margin based on model dimensions - const Vector3 modelBoxSize = SkinnedModel->GetBox().GetSize(); - const Vector3 center = _box.GetCenter(); - const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f; + const auto modelBox = SkinnedModel->GetBox(_transform.GetWorld()); + const Vector3 modelBoxSize = modelBox.GetSize(); + const Vector3 center = modelBox.GetCenter(); + const Vector3 sizeHalf = modelBoxSize * 0.5f; _box = BoundingBox(center - sizeHalf, center + sizeHalf); } else From 60ac0a8196e18434f404cdef0ea809b0110518d6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 08:25:05 -0500 Subject: [PATCH 43/80] Fix box changes from animations. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index a211bb42b..d20e5d7dd 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -526,6 +526,7 @@ void AnimatedModel::UpdateBounds() 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)); + box.Merge(SkinnedModel->GetBox(_transform.GetWorld())); for (int32 boneIndex = 1; boneIndex < bonesCount; boneIndex++) box.Merge(GET_NODE_POS(boneIndex)); _box = box; @@ -533,15 +534,13 @@ void AnimatedModel::UpdateBounds() } else { - _box = SkinnedModel->GetBox(_transform.GetWorld()); + // No animation applied + const auto modelBox = SkinnedModel->GetBox(_transform.GetWorld()); + const Vector3 modelBoxSize = modelBox.GetSize(); + const Vector3 center = modelBox.GetCenter(); + const Vector3 sizeHalf = modelBoxSize * 0.5f; + _box = BoundingBox(center - sizeHalf, center + sizeHalf); } - - // Apply margin based on model dimensions - const auto modelBox = SkinnedModel->GetBox(_transform.GetWorld()); - const Vector3 modelBoxSize = modelBox.GetSize(); - const Vector3 center = modelBox.GetCenter(); - const Vector3 sizeHalf = modelBoxSize * 0.5f; - _box = BoundingBox(center - sizeHalf, center + sizeHalf); } else { From d6861696a5e939607afc59af003cfedde1b1e264 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 09:17:52 -0500 Subject: [PATCH 44/80] Simplify. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index d20e5d7dd..f9440dac9 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -534,12 +534,8 @@ void AnimatedModel::UpdateBounds() } else { - // No animation applied - const auto modelBox = SkinnedModel->GetBox(_transform.GetWorld()); - const Vector3 modelBoxSize = modelBox.GetSize(); - const Vector3 center = modelBox.GetCenter(); - const Vector3 sizeHalf = modelBoxSize * 0.5f; - _box = BoundingBox(center - sizeHalf, center + sizeHalf); + // No animation asset applied + _box = SkinnedModel->GetBox(_transform.GetWorld()); } } else From 9fbc51a22a2d112f40695ad0a036770f8600b076 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 09:21:27 -0500 Subject: [PATCH 45/80] Add back in fixed margin code. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index f9440dac9..66931cc7d 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -537,6 +537,12 @@ void AnimatedModel::UpdateBounds() // No animation asset applied _box = SkinnedModel->GetBox(_transform.GetWorld()); } + + // Apply margin based on model dimensions + const Vector3 modelBoxSize = SkinnedModel->GetBox(_transform.GetWorld()).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); } else { From 36b1dc145293492f8b3d92061484003f1e7bc14f Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 3 Oct 2023 17:37:15 +0300 Subject: [PATCH 46/80] Fix particle effects not working properly at high framerate --- Source/Engine/Particles/Particles.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 52234fd7a..5850b6736 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1283,8 +1283,8 @@ void ParticlesSystem::Job(int32 index) updateBounds = true; } // TODO: if using fixed timestep quantize the dt and accumulate remaining part for the next update? - if (dt <= 1.0f / 240.0f) - return; + //if (dt <= 1.0f / 240.0f) + // return; dt *= effect->SimulationSpeed; instance.Time += dt; const float fps = particleSystem->FramesPerSecond; From b948152a41c64dd0e0c238b0c91993f9eef5621f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 09:40:46 -0500 Subject: [PATCH 47/80] Simplify --- Source/Engine/Level/Actors/AnimatedModel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 66931cc7d..b5ccc291b 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -519,6 +519,7 @@ void AnimatedModel::UpdateBounds() } else if (SkinnedModel && SkinnedModel->IsLoaded()) { + const auto modelBox = SkinnedModel->GetBox(_transform.GetWorld()); if (GraphInstance.NodesPose.Count() != 0) { // Per-bone bounds estimated from positions @@ -526,7 +527,7 @@ void AnimatedModel::UpdateBounds() 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)); - box.Merge(SkinnedModel->GetBox(_transform.GetWorld())); + box.Merge(modelBox); for (int32 boneIndex = 1; boneIndex < bonesCount; boneIndex++) box.Merge(GET_NODE_POS(boneIndex)); _box = box; @@ -535,11 +536,11 @@ void AnimatedModel::UpdateBounds() else { // No animation asset applied - _box = SkinnedModel->GetBox(_transform.GetWorld()); + _box = modelBox; } // Apply margin based on model dimensions - const Vector3 modelBoxSize = SkinnedModel->GetBox(_transform.GetWorld()).GetSize(); + const Vector3 modelBoxSize = modelBox.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); From 4e83a0a757b66563bba7959caf829ebe0bc88d75 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 3 Oct 2023 22:20:44 +0200 Subject: [PATCH 48/80] Fix potential exception in drag and drop handling --- Source/Editor/Content/GUI/ContentView.DragDrop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Content/GUI/ContentView.DragDrop.cs b/Source/Editor/Content/GUI/ContentView.DragDrop.cs index 348e2b443..ffce81e2d 100644 --- a/Source/Editor/Content/GUI/ContentView.DragDrop.cs +++ b/Source/Editor/Content/GUI/ContentView.DragDrop.cs @@ -68,7 +68,7 @@ namespace FlaxEditor.Content.GUI _validDragOver = true; result = DragDropEffect.Copy; } - else if (_dragActors.HasValidDrag) + else if (_dragActors != null && _dragActors.HasValidDrag) { _validDragOver = true; result = DragDropEffect.Move; @@ -94,7 +94,7 @@ namespace FlaxEditor.Content.GUI result = DragDropEffect.Copy; } // Check if drop actor(s) - else if (_dragActors.HasValidDrag) + else if (_dragActors != null && _dragActors.HasValidDrag) { // Import actors var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder; From 68a713fb951aa121a01bd6966407e762cecbdf78 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 3 Oct 2023 22:30:58 +0200 Subject: [PATCH 49/80] Fix model importer to use precomputed Offset Matrix for skeletal model bones #1525 --- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 60 ++----------------- Source/Engine/Tools/ModelTool/ModelTool.cpp | 24 ++++---- Source/ThirdParty/OpenFBX/ofbx.cpp | 2 +- 3 files changed, 21 insertions(+), 65 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 215eadaba..39f5bb974 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -366,9 +366,7 @@ void ProcessNodes(OpenFbxImporterData& data, const ofbx::Object* aNode, int32 pa { node.LodIndex = data.Nodes[parentIndex].LodIndex; if (node.LodIndex == 0) - { node.LodIndex = ModelTool::DetectLodIndex(node.Name); - } ASSERT(Math::IsInRange(node.LodIndex, 0, MODEL_MAX_LODS - 1)); } @@ -416,8 +414,9 @@ Matrix GetOffsetMatrix(OpenFbxImporterData& data, const ofbx::Mesh* mesh, const } } } + //return Matrix::Identity; return ToMatrix(node->getGlobalTransform()); -#elif 1 +#else Matrix t = Matrix::Identity; const int32 boneIdx = data.FindBone(node); int32 idx = data.Bones[boneIdx].NodeIndex; @@ -427,17 +426,6 @@ Matrix GetOffsetMatrix(OpenFbxImporterData& data, const ofbx::Mesh* mesh, const idx = data.Nodes[idx].ParentIndex; } while (idx != -1); return t; -#else - auto* skin = mesh->getGeometry()->getSkin(); - for (int i = 0, c = skin->getClusterCount(); i < c; i++) - { - const ofbx::Cluster* cluster = skin->getCluster(i); - if (cluster->getLink() == node) - { - return ToMatrix(cluster->getTransformLinkMatrix()); - } - } - return Matrix::Identity; #endif } @@ -455,17 +443,14 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg) const auto aMesh = data.Scene->getMesh(i); const auto aGeometry = aMesh->getGeometry(); const ofbx::Skin* skin = aGeometry->getSkin(); - if (skin == nullptr || IsMeshInvalid(aMesh)) continue; - for (int clusterIndex = 0, c = skin->getClusterCount(); clusterIndex < c; clusterIndex++) + for (int clusterIndex = 0, clusterCount = skin->getClusterCount(); clusterIndex < clusterCount; clusterIndex++) { const ofbx::Cluster* cluster = skin->getCluster(clusterIndex); - if (cluster->getIndicesCount() == 0) continue; - const auto link = cluster->getLink(); ASSERT(link != nullptr); @@ -487,7 +472,7 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg) // Add bone boneIndex = data.Bones.Count(); - data.Bones.EnsureCapacity(Math::Max(128, boneIndex + 16)); + data.Bones.EnsureCapacity(256); data.Bones.Resize(boneIndex + 1); auto& bone = data.Bones[boneIndex]; @@ -495,7 +480,7 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg) bone.NodeIndex = nodeIndex; bone.ParentBoneIndex = -1; bone.FbxObj = link; - bone.OffsetMatrix = GetOffsetMatrix(data, aMesh, link); + bone.OffsetMatrix = GetOffsetMatrix(data, aMesh, link) * Matrix::Scaling(data.GlobalSettings.UnitScaleFactor); bone.OffsetMatrix.Invert(); // Mirror offset matrices (RH to LH) @@ -547,9 +532,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb // Vertex positions mesh.Positions.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.Positions.Get()[i] = ToFloat3(vertices[i + firstVertexOffset]); - } // Indices (dummy index buffer) if (vertexCount % 3 != 0) @@ -559,24 +542,18 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb } mesh.Indices.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.Indices.Get()[i] = i; - } // Texture coordinates if (uvs) { mesh.UVs.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.UVs.Get()[i] = ToFloat2(uvs[i + firstVertexOffset]); - } if (data.ConvertRH) { for (int32 v = 0; v < vertexCount; v++) - { mesh.UVs[v].Y = 1.0f - mesh.UVs[v].Y; - } } } @@ -593,16 +570,12 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb { mesh.Normals.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.Normals.Get()[i] = ToFloat3(normals[i + firstVertexOffset]); - } if (data.ConvertRH) { // Mirror normals along the Z axis for (int32 i = 0; i < vertexCount; i++) - { mesh.Normals.Get()[i].Z *= -1.0f; - } } } @@ -615,16 +588,12 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb { mesh.Tangents.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.Tangents.Get()[i] = ToFloat3(tangents[i + firstVertexOffset]); - } if (data.ConvertRH) { // Mirror tangents along the Z axis for (int32 i = 0; i < vertexCount; i++) - { mesh.Tangents.Get()[i].Z *= -1.0f; - } } } @@ -670,15 +639,11 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb { mesh.LightmapUVs.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.LightmapUVs.Get()[i] = ToFloat2(lightmapUVs[i + firstVertexOffset]); - } if (data.ConvertRH) { for (int32 v = 0; v < vertexCount; v++) - { mesh.LightmapUVs[v].Y = 1.0f - mesh.LightmapUVs[v].Y; - } } } else @@ -692,9 +657,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb { mesh.Colors.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - { mesh.Colors.Get()[i] = ToColor(colors[i + firstVertexOffset]); - } } // Blend Indices and Blend Weights @@ -705,13 +668,11 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb mesh.BlendIndices.SetAll(Int4::Zero); mesh.BlendWeights.SetAll(Float4::Zero); - for (int clusterIndex = 0, c = skin->getClusterCount(); clusterIndex < c; clusterIndex++) + for (int clusterIndex = 0, clusterCount = skin->getClusterCount(); clusterIndex < clusterCount; clusterIndex++) { const ofbx::Cluster* cluster = skin->getCluster(clusterIndex); - if (cluster->getIndicesCount() == 0) continue; - const auto link = cluster->getLink(); ASSERT(link != nullptr); @@ -815,15 +776,11 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb { // Mirror positions along the Z axis for (int32 i = 0; i < vertexCount; i++) - { mesh.Positions[i].Z *= -1.0f; - } for (auto& blendShapeData : mesh.BlendShapes) { for (auto& v : blendShapeData.Vertices) - { v.PositionDelta.Z *= -1.0f; - } } } @@ -834,9 +791,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb { // Invert the order for (int32 i = 0; i < mesh.Indices.Count(); i += 3) - { Swap(mesh.Indices[i], mesh.Indices[i + 2]); - } } if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems()) @@ -888,9 +843,7 @@ bool ImportMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofbx { node.LodIndex = data.Nodes[0].LodIndex; if (node.LodIndex == 0) - { node.LodIndex = ModelTool::DetectLodIndex(node.Name); - } ASSERT(Math::IsInRange(node.LodIndex, 0, MODEL_MAX_LODS - 1)); } node.LocalTransform = Transform::Identity; @@ -999,7 +952,6 @@ void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve& curv { if (curveNode == nullptr) return; - const auto keyframes = curve.Resize(info.FramesCount); const auto bone = curveNode->getBone(); Frame localFrame; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 9c9fb00a4..2f3b68f95 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1320,14 +1320,17 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op } } - // Apply import transform on root bones - for (int32 i = 0; i < data.Skeleton.Bones.Count(); i++) + // Apply import transform on bones + Matrix importMatrixInv; + importTransform.GetWorld(importMatrixInv); + importMatrixInv.Invert(); + for (SkeletonBone& bone : data.Skeleton.Bones) { - auto& bone = data.Skeleton.Bones.Get()[i]; if (bone.ParentIndex == -1) { bone.LocalTransform = importTransform.LocalToWorld(bone.LocalTransform); } + bone.OffsetMatrix = importMatrixInv * bone.OffsetMatrix; } } @@ -1363,20 +1366,21 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op // use SkeletonMapping to map bones? // Calculate offset matrix (inverse bind pose transform) for every bone manually - { - for (int32 i = 0; i < data.Skeleton.Bones.Count(); i++) + /*{ + for (SkeletonBone& bone : data.Skeleton.Bones) { Matrix t = Matrix::Identity; - int32 idx = data.Skeleton.Bones[i].NodeIndex; + int32 idx = bone.NodeIndex; do { - t *= data.Skeleton.Nodes[idx].LocalTransform.GetWorld(); - idx = data.Skeleton.Nodes[idx].ParentIndex; + const SkeletonNode& node = data.Skeleton.Nodes[idx]; + t *= node.LocalTransform.GetWorld(); + idx = node.ParentIndex; } while (idx != -1); t.Invert(); - data.Skeleton.Bones[i].OffsetMatrix = t; + bone.OffsetMatrix = t; } - } + }*/ #if USE_SKELETON_NODES_SORTING // Sort skeleton nodes and bones hierarchy (parents first) diff --git a/Source/ThirdParty/OpenFBX/ofbx.cpp b/Source/ThirdParty/OpenFBX/ofbx.cpp index 3723043e9..a15c174d7 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.cpp +++ b/Source/ThirdParty/OpenFBX/ofbx.cpp @@ -3538,7 +3538,7 @@ Object* Object::resolveObjectLink(int idx) const if (connection.to == id && connection.from != 0) { Object* obj = scene.m_object_map.find(connection.from)->second.object; - if (obj) + if (obj && obj->is_node && obj != this && connection.type == Scene::Connection::OBJECT_OBJECT) { if (idx == 0) return obj; --idx; From b5d927baa542f2320b0d23b81708301159a21285 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 16:22:43 -0500 Subject: [PATCH 50/80] Force windows window to be correct size when restoring window on maximize from minimize. --- .../Engine/Platform/Windows/WindowsWindow.cpp | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index d712102fa..906816bf0 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -711,8 +711,27 @@ void WindowsWindow::CheckForWindowResize() // Cache client size RECT rect; GetClientRect(_handle, &rect); - const int32 width = Math::Max(rect.right - rect.left, 0L); - const int32 height = Math::Max(rect.bottom - rect.top, 0L); + int32 width = Math::Max(rect.right - rect.left, 0L); + int32 height = Math::Max(rect.bottom - rect.top, 0L); + + // Check for windows maximized size and see if it needs to adjust position if needed + if (_maximized) + { + // Pick the current monitor data for sizing + const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfoW(monitor, &monitorInfo); + + auto cwidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left; + auto cheight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; + if (width > cwidth && height > cheight) + { + width = cwidth; + height = cheight; + SetWindowPos(_handle, HWND_TOP, monitorInfo.rcWork.left, monitorInfo.rcWork.top, width, height, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + } + } _clientSize = Float2(static_cast(width), static_cast(height)); // Check if window size has been changed From c6bd989744ea7c0082126bb43fe69176fa8e869b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 3 Oct 2023 22:47:13 -0500 Subject: [PATCH 51/80] Add Game Settings open button to menu. --- Source/Editor/Modules/UIModule.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 299865dea..c882efb87 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -553,6 +553,13 @@ namespace FlaxEditor.Modules cm.AddSeparator(); _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); + cm.AddSeparator(); + cm.AddButton("Game Settings", () => + { + var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath); + if(item != null) + Editor.ContentEditing.Open(item); + }); // Scene MenuScene = MainMenu.AddButton("Scene"); From b3b6251c107ee210ef7cfa7d8a44d3298f02acca Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 09:47:48 +0200 Subject: [PATCH 52/80] Add `GlobalInfo` parsing to OpenFBX #1525 --- Source/ThirdParty/OpenFBX/ofbx.cpp | 119 ++++++++++++++++++++--------- Source/ThirdParty/OpenFBX/ofbx.h | 9 +++ 2 files changed, 90 insertions(+), 38 deletions(-) diff --git a/Source/ThirdParty/OpenFBX/ofbx.cpp b/Source/ThirdParty/OpenFBX/ofbx.cpp index a15c174d7..e3cbd6230 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.cpp +++ b/Source/ThirdParty/OpenFBX/ofbx.cpp @@ -1567,6 +1567,7 @@ struct Scene : IScene int getAnimationStackCount() const override { return (int)m_animation_stacks.size(); } int getMeshCount() const override { return (int)m_meshes.size(); } float getSceneFrameRate() const override { return m_scene_frame_rate; } + const GlobalInfo* getGlobalInfo() const override { return &m_info; } const GlobalSettings* getGlobalSettings() const override { return &m_settings; } const Object* const* getAllObjects() const override { return m_all_objects.empty() ? nullptr : &m_all_objects[0]; } @@ -1629,6 +1630,7 @@ struct Scene : IScene Element* m_root_element = nullptr; Root* m_root = nullptr; float m_scene_frame_rate = -1; + GlobalInfo m_info; GlobalSettings m_settings; std::unordered_map m_object_map; std::vector m_all_objects; @@ -2933,6 +2935,66 @@ static float getFramerateFromTimeMode(FrameRate time_mode, float custom_frame_ra } +#define get_property(name, field, type, getter) if (node->first_property->value == name) \ + { \ + IElementProperty* prop = node->getProperty(4); \ + if (prop) \ + { \ + DataView value = prop->getValue(); \ + field = (type)value.getter(); \ + } \ + } +#define get_time_property(name, field, type, getter) if (node->first_property->value == name) \ + { \ + IElementProperty* prop = node->getProperty(4); \ + if (prop) \ + { \ + DataView value = prop->getValue(); \ + field = fbxTimeToSeconds((type)value.getter()); \ + } \ + } +#define get_text_property(name, field) if (node->first_property->value == name) \ + { \ + IElementProperty* prop = node->getProperty(4); \ + if (prop) \ + { \ + DataView value = prop->getValue(); \ + value.toString(field); \ + } \ + } + + +static void parseGlobalInfo(const Element& root, Scene* scene) +{ + for (Element* header = root.child; header; header = header->sibling) + { + if (header->id != "FBXHeaderExtension") + continue; + for (Element* info = header->child; info; info = info->sibling) + { + if (info->id != "SceneInfo") + continue; + for (Element* props70 = info->child; props70; props70 = props70->sibling) + { + if (props70->id != "Properties70") + continue; + for (Element* node = props70->child; node; node = node->sibling) + { + if (!node->first_property) + continue; + get_text_property("Original|ApplicationVendor", scene->m_info.AppVendor, UpVector, toInt); + get_text_property("Original|ApplicationName", scene->m_info.AppName, UpVector, toInt); + get_text_property("Original|ApplicationVersion", scene->m_info.AppVersion, UpVector, toInt); + } + break; + } + break; + } + break; + } +} + + static void parseGlobalSettings(const Element& root, Scene* scene) { for (Element* settings = root.child; settings; settings = settings->sibling) @@ -2947,44 +3009,20 @@ static void parseGlobalSettings(const Element& root, Scene* scene) { if (!node->first_property) continue; - - #define get_property(name, field, type, getter) if(node->first_property->value == name) \ - { \ - IElementProperty* prop = node->getProperty(4); \ - if (prop) \ - { \ - DataView value = prop->getValue(); \ - scene->m_settings.field = (type)value.getter(); \ - } \ - } - - #define get_time_property(name, field, type, getter) if(node->first_property->value == name) \ - { \ - IElementProperty* prop = node->getProperty(4); \ - if (prop) \ - { \ - DataView value = prop->getValue(); \ - scene->m_settings.field = fbxTimeToSeconds((type)value.getter()); \ - } \ - } - - get_property("UpAxis", UpAxis, UpVector, toInt); - get_property("UpAxisSign", UpAxisSign, int, toInt); - get_property("FrontAxis", FrontAxis, FrontVector, toInt); - get_property("FrontAxisSign", FrontAxisSign, int, toInt); - get_property("CoordAxis", CoordAxis, CoordSystem, toInt); - get_property("CoordAxisSign", CoordAxisSign, int, toInt); - get_property("OriginalUpAxis", OriginalUpAxis, int, toInt); - get_property("OriginalUpAxisSign", OriginalUpAxisSign, int, toInt); - get_property("UnitScaleFactor", UnitScaleFactor, float, toDouble); - get_property("OriginalUnitScaleFactor", OriginalUnitScaleFactor, float, toDouble); - get_time_property("TimeSpanStart", TimeSpanStart, u64, toU64); - get_time_property("TimeSpanStop", TimeSpanStop, u64, toU64); - get_property("TimeMode", TimeMode, FrameRate, toInt); - get_property("CustomFrameRate", CustomFrameRate, float, toDouble); - - #undef get_property - + get_property("UpAxis", scene->m_settings.UpAxis, UpVector, toInt); + get_property("UpAxisSign", scene->m_settings.UpAxisSign, int, toInt); + get_property("FrontAxis", scene->m_settings.FrontAxis, FrontVector, toInt); + get_property("FrontAxisSign", scene->m_settings.FrontAxisSign, int, toInt); + get_property("CoordAxis", scene->m_settings.CoordAxis, CoordSystem, toInt); + get_property("CoordAxisSign", scene->m_settings.CoordAxisSign, int, toInt); + get_property("OriginalUpAxis", scene->m_settings.OriginalUpAxis, int, toInt); + get_property("OriginalUpAxisSign", scene->m_settings.OriginalUpAxisSign, int, toInt); + get_property("UnitScaleFactor", scene->m_settings.UnitScaleFactor, float, toDouble); + get_property("OriginalUnitScaleFactor", scene->m_settings.OriginalUnitScaleFactor, float, toDouble); + get_time_property("TimeSpanStart", scene->m_settings.TimeSpanStart, u64, toU64); + get_time_property("TimeSpanStop", scene->m_settings.TimeSpanStop, u64, toU64); + get_property("TimeMode", scene->m_settings.TimeMode, FrameRate, toInt); + get_property("CustomFrameRate", scene->m_settings.CustomFrameRate, float, toDouble); scene->m_scene_frame_rate = getFramerateFromTimeMode(scene->m_settings.TimeMode, scene->m_settings.CustomFrameRate); } break; @@ -2996,6 +3034,10 @@ static void parseGlobalSettings(const Element& root, Scene* scene) } +#undef get_property +#undef get_time_property + + struct ParseGeometryJob { const Element* element; bool triangulate; @@ -3624,6 +3666,7 @@ IScene* load(const u8* data, int size, u64 flags, JobProcessor job_processor, vo if (!parseConnections(*root.getValue(), scene.get())) return nullptr; if (!parseTakes(scene.get())) return nullptr; if (!parseObjects(*root.getValue(), scene.get(), flags, scene->m_allocator, job_processor, job_user_ptr)) return nullptr; + parseGlobalInfo(*root.getValue(), scene.get()); parseGlobalSettings(*root.getValue(), scene.get()); return scene.release(); diff --git a/Source/ThirdParty/OpenFBX/ofbx.h b/Source/ThirdParty/OpenFBX/ofbx.h index 7066659e3..09ca328bb 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.h +++ b/Source/ThirdParty/OpenFBX/ofbx.h @@ -511,6 +511,14 @@ struct GlobalSettings }; +struct GlobalInfo +{ + char AppVendor[128]; + char AppName[128]; + char AppVersion[128]; +}; + + struct IScene { virtual void destroy() = 0; @@ -519,6 +527,7 @@ struct IScene virtual const TakeInfo* getTakeInfo(const char* name) const = 0; virtual int getMeshCount() const = 0; virtual float getSceneFrameRate() const = 0; + virtual const GlobalInfo* getGlobalInfo() const = 0; virtual const GlobalSettings* getGlobalSettings() const = 0; virtual const Mesh* getMesh(int index) const = 0; virtual int getAnimationStackCount() const = 0; From 52b9a995add6ea7b475c1247b4469b4a97978ec5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 10:01:17 +0200 Subject: [PATCH 53/80] Fix bone Offset Matrix from fbx when using root RotationMatrix #1525 --- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 39f5bb974..f1e89d6bc 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -12,6 +12,8 @@ #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Platform/File.h" +#define OPEN_FBX_CONVERT_SPACE 1 + // Import OpenFBX library // Source: https://github.com/nem0/OpenFBX #include @@ -85,12 +87,16 @@ struct OpenFbxImporterData const ModelTool::Options& Options; ofbx::GlobalSettings GlobalSettings; +#if OPEN_FBX_CONVERT_SPACE + Quaternion RootConvertRotation = Quaternion::Identity; Float3 Up; Float3 Front; Float3 Right; bool ConvertRH; +#else + static constexpr bool ConvertRH = false; +#endif float FrameRate; - Quaternion RootConvertRotation = Quaternion::Identity; Array Nodes; Array Bones; @@ -103,7 +109,9 @@ struct OpenFbxImporterData , Path(path) , Options(options) , GlobalSettings(*scene->getGlobalSettings()) +#if OPEN_FBX_CONVERT_SPACE , ConvertRH(GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded) +#endif , Nodes(static_cast(scene->getMeshCount() * 4.0f)) { float frameRate = scene->getSceneFrameRate(); @@ -114,6 +122,7 @@ struct OpenFbxImporterData frameRate = 30.0f; } FrameRate = frameRate; +#if OPEN_FBX_CONVERT_SPACE const float coordAxisSign = GlobalSettings.CoordAxis == ofbx::CoordSystem_LeftHanded ? -1.0f : +1.0f; switch (GlobalSettings.UpAxis) { @@ -170,6 +179,7 @@ struct OpenFbxImporterData break; default: ; } +#endif } bool ImportMaterialTexture(ImportedModelData& result, const ofbx::Material* mat, ofbx::Texture::TextureType textureType, int32& textureIndex, TextureEntry::TypeHint type) const @@ -371,6 +381,7 @@ void ProcessNodes(OpenFbxImporterData& data, const ofbx::Object* aNode, int32 pa } auto transform = ToMatrix(aNode->evalLocal(aNode->getLocalTranslation(), aNode->getLocalRotation())); +#if OPEN_FBX_CONVERT_SPACE if (data.ConvertRH) { // Mirror all base vectors at the local Z axis @@ -386,6 +397,7 @@ void ProcessNodes(OpenFbxImporterData& data, const ofbx::Object* aNode, int32 pa transform.M33 = -transform.M33; transform.M43 = -transform.M43; } +#endif transform.Decompose(node.LocalTransform); data.Nodes.Add(node); @@ -494,6 +506,15 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg) m.M32 = -m.M32; m.M34 = -m.M34; } + + // Convert bone matrix if scene uses root transform + if (!data.RootConvertRotation.IsIdentity()) + { + Matrix m; + Matrix::RotationQuaternion(data.RootConvertRotation, m); + m.Invert(); + bone.OffsetMatrix = m * bone.OffsetMatrix; + } } } } @@ -1095,6 +1116,14 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt } fileData.Resize(0); + // Tweak scene if exported by Blender + auto& globalInfo = *scene->getGlobalInfo(); + if (StringAnsiView(globalInfo.AppName).StartsWith(StringAnsiView("Blender"), StringSearchCase::IgnoreCase)) + { + auto ptr = const_cast(scene->getGlobalSettings()); + ptr->UpAxis = (ofbx::UpVector)((int32)ptr->UpAxis + 1); + } + // Process imported scene context = New(path, options, scene); auto& globalSettings = context->GlobalSettings; @@ -1105,10 +1134,13 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt // Log scene info LOG(Info, "Loaded FBX model, Frame Rate: {0}, Unit Scale Factor: {1}", context->FrameRate, globalSettings.UnitScaleFactor); + LOG(Info, "{0}, {1}, {2}", String(globalInfo.AppName), String(globalInfo.AppVersion), String(globalInfo.AppVendor)); LOG(Info, "Up: {1}{0}", globalSettings.UpAxis == ofbx::UpVector_AxisX ? TEXT("X") : globalSettings.UpAxis == ofbx::UpVector_AxisY ? TEXT("Y") : TEXT("Z"), globalSettings.UpAxisSign == 1 ? TEXT("+") : TEXT("-")); LOG(Info, "Front: {1}{0}", globalSettings.FrontAxis == ofbx::FrontVector_ParityEven ? TEXT("ParityEven") : TEXT("ParityOdd"), globalSettings.FrontAxisSign == 1 ? TEXT("+") : TEXT("-")); LOG(Info, "{0} Handed{1}", globalSettings.CoordAxis == ofbx::CoordSystem_RightHanded ? TEXT("Right") : TEXT("Left"), globalSettings.CoordAxisSign == 1 ? TEXT("") : TEXT(" (negative)")); +#if OPEN_FBX_CONVERT_SPACE LOG(Info, "Imported scene: Up={0}, Front={1}, Right={2}", context->Up, context->Front, context->Right); +#endif // Extract embedded textures if (EnumHasAnyFlags(data.Types, ImportDataTypes::Textures)) @@ -1138,6 +1170,7 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt } } +#if OPEN_FBX_CONVERT_SPACE // Transform nodes to match the engine coordinates system - DirectX (UpVector = +Y, FrontVector = +Z, CoordSystem = -X (LeftHanded)) if (context->Up == Float3(1, 0, 0) && context->Front == Float3(0, 0, 1) && context->Right == Float3(0, 1, 0)) { @@ -1167,15 +1200,16 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt context->RootConvertRotation = Quaternion::Euler(90, 0, 0);*/ if (!context->RootConvertRotation.IsIdentity()) { - for (int32 i = 0; i < context->Nodes.Count(); i++) + for (auto& node : context->Nodes) { - if (context->Nodes[i].ParentIndex == -1) + if (node.ParentIndex == -1) { - context->Nodes[i].LocalTransform.Orientation = context->RootConvertRotation * context->Nodes[i].LocalTransform.Orientation; + node.LocalTransform.Orientation = context->RootConvertRotation * node.LocalTransform.Orientation; break; } } } +#endif } DeleteMe contextCleanup(options.SplitContext ? nullptr : context); From 46353365da53f31857531c356f7278a56421e1d8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 10:24:36 +0200 Subject: [PATCH 54/80] Fix missing debug shapes drawing when skeleton node gets selected #1220 --- Source/Editor/Windows/Assets/SkinnedModelWindow.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index d360e5570..d8790172b 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -321,6 +321,7 @@ namespace FlaxEditor.Windows.Assets var tree = group.Tree(); tree.TreeControl.RightClick += OnTreeNodeRightClick; + tree.TreeControl.SelectedChanged += OnTreeSelectedChanged; for (int i = 0; i < nodes.Length; i++) { if (nodes[i].ParentIndex == -1) @@ -367,6 +368,12 @@ namespace FlaxEditor.Windows.Assets menu.Show(node, location); } + private void OnTreeSelectedChanged(List before, List after) + { + if (after.Count != 0) + ((SkeletonPropertiesProxy)Values[0]).Window._preview.ShowDebugDraw = true; + } + private void OnTreeNodeCopyName(ContextMenuButton b) { Clipboard.Text = (string)b.Tag; From 9fa709aa79785fff61704e77751d81a87617f389 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 12:42:11 +0200 Subject: [PATCH 55/80] Fix importing skinned models with meshes that don't have valid skinning to properly link into the node's bone #1525 --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 54 ++++++++++++++------- Source/Engine/Tools/ModelTool/ModelTool.h | 1 + 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 2f3b68f95..8bd384602 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -841,19 +841,35 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op const auto mesh = data.LODs[0].Meshes[i]; if (mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) { - LOG(Warning, "Imported mesh \'{0}\' has missing skinning data. It may result in invalid rendering.", mesh->Name); - auto indices = Int4::Zero; auto weights = Float4::UnitX; // Check if use a single bone for skinning auto nodeIndex = data.Skeleton.FindNode(mesh->Name); auto boneIndex = data.Skeleton.FindBone(nodeIndex); - if (boneIndex != -1) + if (boneIndex == -1 && nodeIndex != -1 && data.Skeleton.Bones.Count() < MAX_BONES_PER_MODEL) { - LOG(Warning, "Using auto-detected bone {0} (index {1})", data.Skeleton.Nodes[nodeIndex].Name, boneIndex); + // Add missing bone to be used by skinned model from animated nodes pose + boneIndex = data.Skeleton.Bones.Count(); + auto& bone = data.Skeleton.Bones.AddOne(); + bone.ParentIndex = -1; + bone.NodeIndex = nodeIndex; + bone.LocalTransform = CombineTransformsFromNodeIndices(data.Nodes, -1, nodeIndex); + CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex); + LOG(Warning, "Using auto-created bone {0} (index {1}) for mesh \'{2}\'", data.Skeleton.Nodes[nodeIndex].Name, boneIndex, mesh->Name); indices.X = boneIndex; } + else if (boneIndex != -1) + { + // Fallback to already added bone + LOG(Warning, "Using auto-detected bone {0} (index {1}) for mesh \'{2}\'", data.Skeleton.Nodes[nodeIndex].Name, boneIndex, mesh->Name); + indices.X = boneIndex; + } + else + { + // No bone + LOG(Warning, "Imported mesh \'{0}\' has missing skinning data. It may result in invalid rendering.", mesh->Name); + } mesh->BlendIndices.Resize(mesh->Positions.Count()); mesh->BlendWeights.Resize(mesh->Positions.Count()); @@ -1366,20 +1382,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op // use SkeletonMapping to map bones? // Calculate offset matrix (inverse bind pose transform) for every bone manually - /*{ - for (SkeletonBone& bone : data.Skeleton.Bones) - { - Matrix t = Matrix::Identity; - int32 idx = bone.NodeIndex; - do - { - const SkeletonNode& node = data.Skeleton.Nodes[idx]; - t *= node.LocalTransform.GetWorld(); - idx = node.ParentIndex; - } while (idx != -1); - t.Invert(); - bone.OffsetMatrix = t; - } + /*for (SkeletonBone& bone : data.Skeleton.Bones) + { + CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex); }*/ #if USE_SKELETON_NODES_SORTING @@ -1759,4 +1764,17 @@ bool ModelTool::FindTexture(const String& sourcePath, const String& file, String #endif +void ModelTool::CalculateBoneOffsetMatrix(const Array& nodes, Matrix& offsetMatrix, int32 nodeIndex) +{ + offsetMatrix = Matrix::Identity; + int32 idx = nodeIndex; + do + { + const SkeletonNode& node = nodes[idx]; + offsetMatrix *= node.LocalTransform.GetWorld(); + idx = node.ParentIndex; + } while (idx != -1); + offsetMatrix.Invert(); +} + #endif diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 9dc5bf067..4893eb32f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -419,6 +419,7 @@ public: } private: + static void CalculateBoneOffsetMatrix(const Array& nodes, Matrix& offsetMatrix, int32 nodeIndex); #if USE_ASSIMP static bool ImportDataAssimp(const char* path, ImportedModelData& data, Options& options, String& errorMsg); #endif From d68c65d9d613064b2f26c9d09ca21cb0712c765c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 13:50:15 +0200 Subject: [PATCH 56/80] Fix build regression from 9fa709aa79785fff61704e77751d81a87617f389 --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 8bd384602..91cfe74e4 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1762,8 +1762,6 @@ bool ModelTool::FindTexture(const String& sourcePath, const String& file, String return false; } -#endif - void ModelTool::CalculateBoneOffsetMatrix(const Array& nodes, Matrix& offsetMatrix, int32 nodeIndex) { offsetMatrix = Matrix::Identity; @@ -1778,3 +1776,5 @@ void ModelTool::CalculateBoneOffsetMatrix(const Array& nodes, Matr } #endif + +#endif From f1600b30146874183d0cc849ba5206a131027cb4 Mon Sep 17 00:00:00 2001 From: Edu Garcia <28616+Arcnor@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:39:27 +0100 Subject: [PATCH 57/80] Move custom pugixml changes to pugixml_extra --- .../Cooker/Platform/Mac/MacPlatformTools.cpp | 32 +++++----- Source/ThirdParty/pugixml/pugixml.cpp | 21 ------- Source/ThirdParty/pugixml/pugixml.hpp | 2 - Source/ThirdParty/pugixml/pugixml_extra.cpp | 63 +++++++++++++++++++ Source/ThirdParty/pugixml/pugixml_extra.hpp | 18 ++++++ 5 files changed, 98 insertions(+), 38 deletions(-) create mode 100644 Source/ThirdParty/pugixml/pugixml_extra.cpp create mode 100644 Source/ThirdParty/pugixml/pugixml_extra.hpp diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp index b66a96a9b..0008228f7 100644 --- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp @@ -17,7 +17,9 @@ #include "Editor/ProjectInfo.h" #include "Editor/Cooker/GameCooker.h" #include "Editor/Utilities/EditorUtilities.h" -#include + +#include "pugixml/pugixml_extra.hpp" + using namespace pugi; IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform); @@ -170,17 +172,17 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) const String plistPath = data.DataOutputPath / TEXT("Info.plist"); { xml_document doc; - xml_node plist = doc.child_or_append(PUGIXML_TEXT("plist")); + xml_node_extra plist = xml_node_extra(doc).child_or_append(PUGIXML_TEXT("plist")); plist.append_attribute(PUGIXML_TEXT("version")).set_value(PUGIXML_TEXT("1.0")); - xml_node dict = plist.child_or_append(PUGIXML_TEXT("dict")); + xml_node_extra dict = plist.child_or_append(PUGIXML_TEXT("dict")); #define ADD_ENTRY(key, value) \ - dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \ - dict.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT(value)) + dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \ + dict.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT(value)) #define ADD_ENTRY_STR(key, value) \ - dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \ + dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \ { std::u16string valueStr(value.GetText()); \ - dict.append_child(PUGIXML_TEXT("string")).set_child_value(pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); } + dict.append_child_with_value(PUGIXML_TEXT("string"), pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); } ADD_ENTRY("CFBundleDevelopmentRegion", "English"); ADD_ENTRY("CFBundlePackageType", "APPL"); @@ -194,22 +196,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) ADD_ENTRY_STR("CFBundleVersion", projectVersion); ADD_ENTRY_STR("NSHumanReadableCopyright", gameSettings->CopyrightNotice); - dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("CFBundleSupportedPlatforms")); - xml_node CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array")); - CFBundleSupportedPlatforms.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("MacOSX")); + dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("CFBundleSupportedPlatforms")); + xml_node_extra CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array")); + CFBundleSupportedPlatforms.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("MacOSX")); - dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture")); - xml_node LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict")); + dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture")); + xml_node_extra LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict")); switch (_arch) { case ArchitectureType::x64: - LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("x86_64")); + LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("x86_64")); break; case ArchitectureType::ARM64: - LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("arm64")); + LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("arm64")); break; } - LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.15")); + LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("10.15")); #undef ADD_ENTRY #undef ADD_ENTRY_STR diff --git a/Source/ThirdParty/pugixml/pugixml.cpp b/Source/ThirdParty/pugixml/pugixml.cpp index af52e1ea8..56860bb90 100644 --- a/Source/ThirdParty/pugixml/pugixml.cpp +++ b/Source/ThirdParty/pugixml/pugixml.cpp @@ -4799,16 +4799,6 @@ namespace pugi return xml_node(); } - PUGI__FN xml_node xml_node::child_or_append(const char_t* name_) - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return append_child(name_); - } - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const { if (!_root) return xml_attribute(); @@ -4879,17 +4869,6 @@ namespace pugi return PUGIXML_TEXT(""); } - PUGI__FN bool xml_node::set_child_value(const char_t* rhs) - { - if (!_root) return false; - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->value && impl::is_text_node(i)) - return xml_node(i).set_value(rhs); - - return append_child(node_pcdata).set_value(rhs); - } - PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const { return child(name_).child_value(); diff --git a/Source/ThirdParty/pugixml/pugixml.hpp b/Source/ThirdParty/pugixml/pugixml.hpp index fd71dfb1a..d937bff47 100644 --- a/Source/ThirdParty/pugixml/pugixml.hpp +++ b/Source/ThirdParty/pugixml/pugixml.hpp @@ -452,14 +452,12 @@ namespace pugi // Get child, attribute or next/previous sibling with the specified name xml_node child(const char_t* name) const; - xml_node child_or_append(const char_t* name); xml_attribute attribute(const char_t* name) const; xml_node next_sibling(const char_t* name) const; xml_node previous_sibling(const char_t* name) const; // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA const char_t* child_value() const; - bool set_child_value(const char_t* rhs); // Get child value of child with specified name. Equivalent to child(name).child_value(). const char_t* child_value(const char_t* name) const; diff --git a/Source/ThirdParty/pugixml/pugixml_extra.cpp b/Source/ThirdParty/pugixml/pugixml_extra.cpp new file mode 100644 index 000000000..1c823112b --- /dev/null +++ b/Source/ThirdParty/pugixml/pugixml_extra.cpp @@ -0,0 +1,63 @@ +#include "pugixml.hpp" +#include "pugixml_extra.hpp" + +namespace pugi +{ + + // Compare two strings + // This comes from pugi::impl::strequal + bool strequal(const char_t* src, const char_t* dst) + { +#ifdef PUGIXML_WCHAR_MODE + return wcscmp(src, dst) == 0; +#else + return strcmp(src, dst) == 0; +#endif + } + + // This comes from pugi::impl::is_text_node + inline bool is_text_node(const xml_node& node) + { + xml_node_type type = node.type(); + + return type == node_pcdata || type == node_cdata; + } + + xml_node_extra::xml_node_extra(xml_node child) : xml_node(child) + { + } + + xml_node_extra xml_node_extra::child_or_append(const char_t* name_) + { + if (!name_ || !_root) return xml_node_extra(); + + for (xml_node& child : *this) + { + const auto *name = child.name(); + if (name && strequal(name_, name)) return xml_node_extra(child); + } + + return xml_node_extra(append_child(name_)); + } + + bool xml_node_extra::set_child_value(const char_t* rhs) + { + if (!_root) return xml_node(); + + for (xml_node& child : *this) + { + if (child.value() && is_text_node(child)) + { + return child.set_value(rhs); + } + } + + return append_child(node_pcdata).set_value(rhs); + } + + void xml_node_extra::append_child_with_value(const char_t* name_, const char_t* rhs) + { + xml_node_extra child = xml_node_extra(append_child(name_)); + child.append_child(node_pcdata).set_value(rhs); + } +} diff --git a/Source/ThirdParty/pugixml/pugixml_extra.hpp b/Source/ThirdParty/pugixml/pugixml_extra.hpp new file mode 100644 index 000000000..0103120eb --- /dev/null +++ b/Source/ThirdParty/pugixml/pugixml_extra.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "pugixml.hpp" + +namespace pugi +{ + class xml_node_extra : public xml_node + { + public: + xml_node_extra() = default; + xml_node_extra(xml_node child); + + xml_node_extra child_or_append(const char_t* name_); + bool set_child_value(const char_t* rhs); + void append_child_with_value(const char_t* name_, const char_t* rhs); + + }; +} From bed736a8eeb8ab5bad5c82458d9324edde88685e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 15:16:10 +0200 Subject: [PATCH 58/80] Add support for importing embedded textures via `Assimp` importer #669 --- .../Tools/ModelTool/ModelTool.Assimp.cpp | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index 8aa9199fe..d2e26ee68 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/DeleteMe.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Platform/File.h" #include "Engine/Tools/TextureTool/TextureTool.h" // Import Assimp library @@ -515,8 +516,47 @@ bool ImportTexture(ImportedModelData& result, AssimpImporterData& data, aiString bool ImportMaterialTexture(ImportedModelData& result, AssimpImporterData& data, const aiMaterial* aMaterial, aiTextureType aTextureType, int32& textureIndex, TextureEntry::TypeHint type) { aiString aFilename; - return aMaterial->GetTexture(aTextureType, 0, &aFilename, nullptr, nullptr, nullptr, nullptr) == AI_SUCCESS && - ImportTexture(result, data, aFilename, textureIndex, type); + if (aMaterial->GetTexture(aTextureType, 0, &aFilename, nullptr, nullptr, nullptr, nullptr) == AI_SUCCESS) + { + // Check for embedded textures + String filename = String(aFilename.C_Str()).TrimTrailing(); + if (filename.StartsWith(TEXT(AI_EMBEDDED_TEXNAME_PREFIX))) + { + const aiTexture* aTex = data.Scene->GetEmbeddedTexture(aFilename.C_Str()); + const StringView texIndexName(filename.Get() + (ARRAY_COUNT(AI_EMBEDDED_TEXNAME_PREFIX) - 1)); + int32 texIndex; + if (!aTex && !StringUtils::Parse(texIndexName.Get(), texIndexName.Length(), &texIndex) && texIndex >= 0 && texIndex < data.Scene->mNumTextures) + aTex = data.Scene->mTextures[texIndex]; + if (aTex && aTex->mHeight == 0 && aTex->mWidth > 0) + { + // Export embedded texture to temporary file + filename = String::Format(TEXT("{0}_tex_{1}.{2}"), StringUtils::GetFileNameWithoutExtension(data.Path), texIndexName, String(aTex->achFormatHint)); + File::WriteAllBytes(String(StringUtils::GetDirectoryName(data.Path)) / filename, (const byte*)aTex->pcData, (int32)aTex->mWidth); + } + } + + // Find texture file path + String path; + if (ModelTool::FindTexture(data.Path, filename, path)) + return true; + + // Check if already used + textureIndex = 0; + while (textureIndex < result.Textures.Count()) + { + if (result.Textures[textureIndex].FilePath == path) + return true; + textureIndex++; + } + + // Import texture + auto& texture = result.Textures.AddOne(); + texture.FilePath = path; + texture.Type = type; + texture.AssetID = Guid::Empty; + return true; + } + return false; } bool ImportMaterials(ImportedModelData& result, AssimpImporterData& data, String& errorMsg) From fe9b6d73a86a8af864542bdf500b945952f6cccc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 16:54:50 +0200 Subject: [PATCH 59/80] Fix build regression from b3b6251c107ee210ef7cfa7d8a44d3298f02acca --- Source/ThirdParty/OpenFBX/ofbx.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/ThirdParty/OpenFBX/ofbx.cpp b/Source/ThirdParty/OpenFBX/ofbx.cpp index e3cbd6230..e60211e3d 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.cpp +++ b/Source/ThirdParty/OpenFBX/ofbx.cpp @@ -2982,9 +2982,9 @@ static void parseGlobalInfo(const Element& root, Scene* scene) { if (!node->first_property) continue; - get_text_property("Original|ApplicationVendor", scene->m_info.AppVendor, UpVector, toInt); - get_text_property("Original|ApplicationName", scene->m_info.AppName, UpVector, toInt); - get_text_property("Original|ApplicationVersion", scene->m_info.AppVersion, UpVector, toInt); + get_text_property("Original|ApplicationVendor", scene->m_info.AppVendor); + get_text_property("Original|ApplicationName", scene->m_info.AppName); + get_text_property("Original|ApplicationVersion", scene->m_info.AppVersion); } break; } From f1d57e47cb7e86596cf9cdb376d7002d8d0cfe6c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 17:34:14 +0200 Subject: [PATCH 60/80] Fix regression in some context menus on Windows due to recent changes to windows showing after first paint --- Source/Editor/GUI/ContextMenu/ContextMenuBase.cs | 2 +- Source/Engine/Platform/Windows/WindowsWindow.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 7ecd197eb..628af18e1 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -154,6 +154,7 @@ namespace FlaxEditor.GUI.ContextMenu } // Unlock and perform controls update + Location = Float2.Zero; UnlockChildrenRecursive(); PerformLayout(); @@ -162,7 +163,6 @@ namespace FlaxEditor.GUI.ContextMenu var dpiSize = Size * dpiScale; var locationWS = parent.PointToWindow(location); var locationSS = parentWin.PointToScreen(locationWS); - Location = Float2.Zero; var monitorBounds = Platform.GetMonitorBounds(locationSS); var rightBottomLocationSS = locationSS + dpiSize; bool isUp = false, isLeft = false; diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 906816bf0..1bc50f207 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -220,6 +220,12 @@ void WindowsWindow::Show() if (!_settings.HasBorder) { SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); + if (!_settings.IsRegularWindow && _settings.ShowAfterFirstPaint && _settings.StartPosition == WindowStartPosition::Manual) + { + int32 x = Math::TruncToInt(_settings.Position.X); + int32 y = Math::TruncToInt(_settings.Position.Y); + SetWindowPos(_handle, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + } } #endif From f2a3338dce85b2b9a6fb9c7957349b27fdf92e72 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 19:58:10 +0200 Subject: [PATCH 61/80] Improve #859 --- Source/Editor/Windows/GameWindow.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 2e9fa4014..fdedbb3c2 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -466,8 +466,13 @@ namespace FlaxEditor.Windows /// public override void OnMouseLeave() { - Parent?.Focus(); base.OnMouseLeave(); + + // Remove focus from game window when mouse moves out and the cursor is hidden during game + if ((IsFocused || ContainsFocus) && Parent != null && Editor.IsPlayMode && !Screen.CursorVisible) + { + Parent.Focus(); + } } /// From f46cc32715f9faffb4afff5d98fb337c98aa7b73 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 20:15:23 +0200 Subject: [PATCH 62/80] Cleanup code #1115 --- .../Editor/Managed/ManagedEditor.Internal.cpp | 2 +- Source/Engine/Content/Asset.h | 1 - Source/Engine/Content/Content.cpp | 5 -- Source/Engine/Tools/ModelTool/ModelTool.cpp | 63 ++++++++----------- Source/Engine/Tools/ModelTool/ModelTool.h | 7 +-- 5 files changed, 30 insertions(+), 48 deletions(-) diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 49c3af87e..afcbb740a 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -829,4 +829,4 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String // Get options from model FileSystem::NormalizePath(assetPath); return ImportModelFile::TryGetImportOptions(assetPath, options); -} \ No newline at end of file +} diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index 0ecc6db3f..2d2ba4b3f 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -246,4 +246,3 @@ 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 eabf58cf2..6aecd1778 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -447,11 +447,6 @@ 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 ad41028c9..b703b47d1 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -633,8 +633,8 @@ void CreateLinearListFromTree(Array& nodes, Array& mapping) // Customized breadth first tree algorithm (each node has no direct reference to the children so we build the cache for the nodes depth level) const int32 count = nodes.Count(); Array> depths(count); // Pair.First = Depth, Pair.Second = Node Index - depths.SetSize(count); - depths.Set(-1); + depths.Resize(count); + depths.SetAll(-1); for (int32 i = 0; i < count; i++) { // Skip evaluated nodes @@ -653,7 +653,7 @@ void CreateLinearListFromTree(Array& nodes, Array& mapping) } while (end != -1 && lastDepth == -1); // Set the depth (second item is the node index) - depths[i] = MakePair(lastDepth + relativeDepth, i); + depths[i] = ToPair(lastDepth + relativeDepth, i); } for (int32 i = 0; i < count; i++) { @@ -666,7 +666,7 @@ void CreateLinearListFromTree(Array& nodes, Array& mapping) // Extract nodes mapping O(n^2) mapping.EnsureCapacity(count, false); - mapping.SetSize(count); + mapping.Resize(count); for (int32 i = 0; i < count; i++) { int32 newIndex = -1; @@ -964,7 +964,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op case TextureEntry::TypeHint::Normals: textureOptions.Type = TextureFormatType::NormalMap; break; - default:; + default: ; } AssetsImportingManager::ImportIfEdited(texture.FilePath, assetPath, texture.AssetID, &textureOptions); #endif @@ -999,37 +999,34 @@ 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; + + // When splitting imported meshes allow only the first mesh to import assets (mesh[0] is imported after all following ones so import assets during mesh[1]) + if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) + { + // Find that asset create previously + AssetInfo info; + if (Content::GetAssetInfo(assetPath, info)) + material.AssetID = info.ID; + continue; + } + if (options.ImportMaterialsAsInstances) { - if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) - { - // Find that asset create previously - AssetInfo info; - if (Content::GetAssetInfo(assetPath, info)) - material.AssetID = info.ID; - continue; - } - + // Create material instance AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID); - MaterialInstance* materialInstance = (MaterialInstance*)LoadAsset(assetPath, MaterialInstance::TypeInitializer); - if (materialInstance->WaitForLoaded()) + if (MaterialInstance* materialInstance = Content::Load(assetPath)) + { + materialInstance->SetBaseMaterial(options.InstanceToImportAs); + materialInstance->Save(); + } + else { LOG(Error, "Failed to load material instance after creation. ({0})", assetPath); - return true; } - - MaterialBase* materialInstanceOf = options.InstanceToImportAs; - if (materialInstanceOf->WaitForLoaded()) - { - LOG(Error, "Failed to load material to create an instance of. ({0})", options.InstanceToImportAs->GetID()); - return true; - } - - materialInstance->SetBaseMaterial(materialInstanceOf); - materialInstance->Save(); } else { + // Create material CreateMaterial::Options materialOptions; materialOptions.Diffuse.Color = material.Diffuse.Color; if (material.Diffuse.TextureIndex != -1) @@ -1047,15 +1044,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op materialOptions.Info.CullMode = CullMode::TwoSided; if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; - - if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) - { - // Find that asset create previously - AssetInfo info; - if (Content::GetAssetInfo(assetPath, info)) - material.AssetID = info.ID; - continue; - } AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); } #endif @@ -1441,7 +1429,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op bone.NodeIndex = mapping[bone.NodeIndex]; } } - reorder_nodes_and_test_it_out! + reorder_nodes_and_test_it_out + ! #endif } else if (options.Type == ModelType::Animation) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 99ca72df5..36bb9b12c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -12,8 +12,7 @@ #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Animations/AnimationData.h" -#include -#include +#include "Engine/Content/Assets/MaterialBase.h" class JsonWriter; @@ -265,7 +264,7 @@ public: // If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") String CollisionMeshesPrefix = TEXT(""); - // The type of collision that should be generated if has collision prefix especified + // The type of collision that should be generated if has collision prefix specified. API_FIELD(Attributes = "EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") CollisionDataType CollisionType = CollisionDataType::TriangleMesh; @@ -345,7 +344,7 @@ public: 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(); + AssetReference InstanceToImportAs; // 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 7777f73c26f6d4e5a2004aa7cf060db472d7eb87 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 20:20:35 +0200 Subject: [PATCH 63/80] Fix warning --- Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index d2e26ee68..000a2e77b 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -524,7 +524,7 @@ bool ImportMaterialTexture(ImportedModelData& result, AssimpImporterData& data, { const aiTexture* aTex = data.Scene->GetEmbeddedTexture(aFilename.C_Str()); const StringView texIndexName(filename.Get() + (ARRAY_COUNT(AI_EMBEDDED_TEXNAME_PREFIX) - 1)); - int32 texIndex; + uint32 texIndex; if (!aTex && !StringUtils::Parse(texIndexName.Get(), texIndexName.Length(), &texIndex) && texIndex >= 0 && texIndex < data.Scene->mNumTextures) aTex = data.Scene->mTextures[texIndex]; if (aTex && aTex->mHeight == 0 && aTex->mWidth > 0) From 92f677f2380687a69738c6c0ff1c4321601168ac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 22:25:27 +0200 Subject: [PATCH 64/80] Codestyle formatting #1522 --- Source/Editor/Scripting/ScriptType.cs | 2 +- Source/Editor/Surface/Archetypes/Function.cs | 62 +++++++++---------- Source/Editor/Surface/Archetypes/Packing.cs | 10 +-- .../Editor/Surface/ContextMenu/VisjectCM.cs | 14 ++--- .../Surface/ContextMenu/VisjectCMGroup.cs | 4 +- .../Surface/ContextMenu/VisjectCMItem.cs | 22 +++---- Source/Editor/Surface/Elements/Box.cs | 2 +- Source/Editor/Surface/Elements/OutputBox.cs | 16 ++--- Source/Editor/Surface/NodeArchetype.cs | 6 +- Source/Editor/Surface/SurfaceUtils.cs | 4 +- .../Surface/VisjectSurface.Connecting.cs | 4 +- 11 files changed, 74 insertions(+), 72 deletions(-) diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index e3de348d2..b0c3a36dd 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -1393,7 +1393,7 @@ namespace FlaxEditor.Scripting return _custom.GetMembers(name, MemberTypes.Method, bindingAttr).FirstOrDefault(); return ScriptMemberInfo.Null; } - + /// /// Basic check to see if a type could be casted to another type /// diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index de89ab50c..e9d73ae68 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -744,12 +744,12 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { return inputType.IsVoid; @@ -1161,48 +1161,48 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { if (nodeArch.Tag is not ScriptMemberInfo memberInfo) return false; - + if (!memberInfo.IsStatic) { if (VisjectSurface.FullCastCheck(memberInfo.DeclaringType, outputType, hint)) return true; } - + var parameters = memberInfo.GetParameters(); bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid); if (outputType.IsVoid) return !isPure; - + foreach (var param in parameters) { - if(param.IsOut) + if (param.IsOut) continue; if (VisjectSurface.FullCastCheck(param.Type, outputType, hint)) return true; } return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { if (nodeArch.Tag is not ScriptMemberInfo memberInfo) return false; if (VisjectSurface.FullCastCheck(memberInfo.ValueType, inputType, hint)) return true; - + var parameters = memberInfo.GetParameters(); bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid); if (inputType.IsVoid) return !isPure; - + foreach (var param in memberInfo.GetParameters()) { - if(!param.IsOut) + if (!param.IsOut) continue; if (VisjectSurface.FullCastCheck(param.Type, inputType, hint)) return true; @@ -1835,12 +1835,12 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { return inputType.IsVoid; @@ -1981,19 +1981,19 @@ namespace FlaxEditor.Surface.Archetypes Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); if (scriptType == ScriptType.Null) return false; - + var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var member in members) { if (!SurfaceUtils.IsValidVisualScriptField(member)) continue; - + if (member) { if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint)) @@ -2007,22 +2007,22 @@ namespace FlaxEditor.Surface.Archetypes } break; } - + return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); if (scriptType == ScriptType.Null) return false; - + var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var member in members) { if (!SurfaceUtils.IsValidVisualScriptField(member)) continue; - + if (member) { if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint)) @@ -2092,22 +2092,22 @@ namespace FlaxEditor.Surface.Archetypes Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]); UpdateSignature(); } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { - if(outputType.IsVoid) + if (outputType.IsVoid) return true; - + var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); if (scriptType == ScriptType.Null) return false; - + var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var member in members) { if (!SurfaceUtils.IsValidVisualScriptField(member)) continue; - + if (member) { if (VisjectSurface.FullCastCheck(member.ValueType, outputType, hint)) @@ -2126,10 +2126,10 @@ namespace FlaxEditor.Surface.Archetypes } break; } - + return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { return inputType.IsVoid; @@ -2352,13 +2352,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnDestroy(); } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { // Event based nodes always have a pulse input, so it's always compatible with void if (outputType.IsVoid) return true; - + var eventName = (string)nodeArch.DefaultValues[1]; var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); @@ -2372,13 +2372,13 @@ namespace FlaxEditor.Surface.Archetypes } return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { // Event based nodes always have a pulse output, so it's always compatible with void if (inputType.IsVoid) return true; - + var eventName = (string)nodeArch.DefaultValues[1]; var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index ccabaaa6d..e61b8e0df 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -207,13 +207,13 @@ namespace FlaxEditor.Surface.Archetypes AddElement(box); } } - + protected static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { // Event based nodes always have a pulse input, so it's always compatible with void if (outputType.IsVoid) return true; - + var eventName = (string)nodeArch.DefaultValues[1]; var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); @@ -236,7 +236,7 @@ namespace FlaxEditor.Surface.Archetypes : base(id, context, nodeArch, groupArch, false) { } - + internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) { var typeName = (string)nodeArch.DefaultValues[0]; @@ -253,7 +253,7 @@ namespace FlaxEditor.Surface.Archetypes } return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { var typeName = (string)nodeArch.DefaultValues[0]; @@ -286,7 +286,7 @@ namespace FlaxEditor.Surface.Archetypes } return false; } - + internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) { var typeName = (string)nodeArch.DefaultValues[0]; diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 36f71a1a7..342429fc8 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -164,7 +164,7 @@ namespace FlaxEditor.Surface.ContextMenu HorizontalAlignment = TextAlignment.Far, Font = titleFontReference, }; - + _contextSensitiveToggle = new CheckBox { Width = 20, @@ -174,7 +174,7 @@ namespace FlaxEditor.Surface.ContextMenu Checked = _contextSensitiveSearchEnabled, }; _contextSensitiveToggle.StateChanged += OnContextSensitiveToggleStateChanged; - + // Search box _searchBox = new SearchBox(false, 2, 22) { @@ -313,7 +313,7 @@ namespace FlaxEditor.Surface.ContextMenu OnSearchFilterChanged(); } } - else if(_contextSensitiveSearchEnabled) + else if (_contextSensitiveSearchEnabled) { group.EvaluateVisibilityWithBox(_selectedBox); } @@ -350,8 +350,8 @@ namespace FlaxEditor.Surface.ContextMenu Parent = group }; } - if(_contextSensitiveSearchEnabled) - group.EvaluateVisibilityWithBox(_selectedBox); + if (_contextSensitiveSearchEnabled) + group.EvaluateVisibilityWithBox(_selectedBox); group.SortChildren(); group.Parent = _groupsPanel; _groups.Add(group); @@ -459,7 +459,7 @@ namespace FlaxEditor.Surface.ContextMenu // Skip events during setup or init stuff if (IsLayoutLocked) return; - + Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged"); _contextSensitiveSearchEnabled = checkBox.Checked; UpdateFilters(); @@ -551,7 +551,7 @@ namespace FlaxEditor.Surface.ContextMenu for (int i = 0; i < _groups.Count; i++) { _groups[i].ResetView(); - if(_contextSensitiveSearchEnabled) + if (_contextSensitiveSearchEnabled) _groups[i].EvaluateVisibilityWithBox(_selectedBox); } UnlockChildrenRecursive(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index aab9f3112..fd825cdff 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -132,7 +132,7 @@ namespace FlaxEditor.Surface.ContextMenu } Profiler.BeginEvent("VisjectCMGroup.EvaluateVisibilityWithBox"); - + bool isAnyVisible = false; for (int i = 0; i < _children.Count; i++) { @@ -142,7 +142,7 @@ namespace FlaxEditor.Surface.ContextMenu isAnyVisible |= item.Visible; } } - + // Update itself if (isAnyVisible) { diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 28a9392b2..b512f6946 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.Surface.ContextMenu { // Is compatible if box is null for reset reasons if (startBox == null) - return true; + return true; if (_archetype == null) return false; @@ -112,7 +112,7 @@ namespace FlaxEditor.Surface.ContextMenu if (startBox.IsOutput && _archetype.IsInputCompatible != null) { isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints); - } + } else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null) { isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints); @@ -122,23 +122,23 @@ namespace FlaxEditor.Surface.ContextMenu // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items isCompatible = CheckElementsCompatibility(startBox); } - + return isCompatible; } - + private bool CheckElementsCompatibility(Box startBox) { bool isCompatible = false; foreach (NodeElementArchetype element in _archetype.Elements) { // Ignore all elements that aren't inputs or outputs (e.g. input fields) - if(element.Type != NodeElementType.Output && element.Type != NodeElementType.Input) + if (element.Type != NodeElementType.Output && element.Type != NodeElementType.Input) continue; - + // Ignore elements with the same direction as the box if ((startBox.IsOutput && element.Type == NodeElementType.Output) || (!startBox.IsOutput && element.Type == NodeElementType.Input)) continue; - + ScriptType fromType; ScriptType toType; ConnectionsHint hint; @@ -154,13 +154,13 @@ namespace FlaxEditor.Surface.ContextMenu toType = element.ConnectionsType; hint = startBox.ParentNode.Archetype.ConnectionsHints; } - + isCompatible |= VisjectSurface.FullCastCheck(fromType, toType, hint); } return isCompatible; } - + /// /// Updates the filter. /// @@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.ContextMenu return; } } - + _isStartsWithMatch = _isFullMatch = false; if (string.IsNullOrEmpty(filterText)) { @@ -242,7 +242,7 @@ namespace FlaxEditor.Surface.ContextMenu Data = data; } - else if(!groupHeaderMatches) + else if (!groupHeaderMatches) { // Hide _highlights?.Clear(); diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 6a265a21e..a03c8f701 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -231,7 +231,7 @@ namespace FlaxEditor.Surface.Elements } // Check using connection hints - if(VisjectSurface.IsTypeCompatible(Archetype.ConnectionsType, type, ParentNode.Archetype.ConnectionsHints)) + if (VisjectSurface.IsTypeCompatible(Archetype.ConnectionsType, type, ParentNode.Archetype.ConnectionsHints)) return true; // Check independent and if there is box with bigger potential because it may block current one from changing type diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index cf78b559c..8ef64ec77 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.Surface.Elements { // Calculate control points CalculateBezierControlPoints(start, end, out var control1, out var control2); - + // Draw line Render2D.DrawBezier(start, control1, control2, end, color, thickness); @@ -54,16 +54,16 @@ namespace FlaxEditor.Surface.Elements const float maxControlLength = 150f; var dst = (end - start).Length; var yDst = Mathf.Abs(start.Y - end.Y); - + // Calculate control points var minControlDst = dst * 0.5f; var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength); var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f)); - + control1 = new Float2(start.X + controlDst, start.Y); control2 = new Float2(end.X - controlDst, end.Y); } - + /// /// Checks if a point intersects a connection /// @@ -86,11 +86,13 @@ namespace FlaxEditor.Surface.Elements public static bool IntersectsConnection(ref Float2 start, ref Float2 end, ref Float2 point, float distance) { // Pretty much a point in rectangle check - if ((point.X - start.X) * (end.X - point.X) < 0) return false; + if ((point.X - start.X) * (end.X - point.X) < 0) + return false; float offset = Mathf.Sign(end.Y - start.Y) * distance; - if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false; - + if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) + return false; + float squaredDistance = distance; CalculateBezierControlPoints(start, end, out var control1, out var control2); diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index e2a9c84fa..fe54268e2 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Surface /// Checks if the given type is compatible with the given node archetype. Used for custom nodes /// public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint); - + /// /// Unique node type ID within a single group. /// @@ -108,12 +108,12 @@ namespace FlaxEditor.Surface /// Function for asynchronously loaded nodes to check if input ports are compatible, for filtering. /// public IsCompatible IsInputCompatible; - + /// /// Function for asynchronously loaded nodes to check if output ports are compatible, for filtering. /// public IsCompatible IsOutputCompatible; - + /// /// Default initial size of the node. /// diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 08b9c6d63..5f4c3ef07 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -406,8 +406,8 @@ namespace FlaxEditor.Surface internal static bool IsValidVisualScriptType(ScriptType scriptType) { - if (!scriptType.IsPublic || - scriptType.HasAttribute(typeof(HideInEditorAttribute), true) || + if (!scriptType.IsPublic || + scriptType.HasAttribute(typeof(HideInEditorAttribute), true) || scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)) return false; if (scriptType.IsGenericType) diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 145bdfedf..dd34f9c7c 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -212,12 +212,12 @@ namespace FlaxEditor.Surface // Yes, from and to are switched on purpose if (CanUseDirectCastStatic(to, from, false)) return true; - if(IsTypeCompatible(from, to, hint)) + if (IsTypeCompatible(from, to, hint)) return true; // Same here return to.CanCastTo(from); } - + /// /// Checks if can use direct conversion from one type to another. /// From d1c0900ad7c5d3b85c9b9872fa470987543ebb7a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 22:38:06 +0200 Subject: [PATCH 65/80] Fix potential error when loading Guid from Json --- Source/Engine/Serialization/JsonTools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index 07a577033..c395df4f1 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -256,7 +256,7 @@ BoundingBox JsonTools::GetBoundingBox(const Value& value) Guid JsonTools::GetGuid(const Value& value) { - if (value.IsNull()) + if (!value.IsString()) return Guid::Empty; CHECK_RETURN(value.GetStringLength() == 32, Guid::Empty); From b586b5fe4154a24eabab5f53b8e9987d0c47549a Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 4 Oct 2023 15:57:28 -0500 Subject: [PATCH 66/80] Ensure editor font is sent as something. --- Source/Editor/Options/InterfaceOptions.cs | 75 +++++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 5fd8c31c7..a028d47f9 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -166,7 +166,19 @@ namespace FlaxEditor.Options /// Gets or sets the output log text font. /// [EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")] - public FontReference OutputLogTextFont { get; set; } = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + public FontReference OutputLogTextFont + { + get => _outputLogFont; + set + { + if (value == null) + _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + else if (!value.Font) + _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + else + _outputLogFont = value; + } + } /// /// Gets or sets the output log text color. @@ -225,29 +237,82 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); + private FontReference _titleFont = new FontReference(DefaultFont, 18); + private FontReference _largeFont = new FontReference(DefaultFont, 14); + private FontReference _mediumFont = new FontReference(DefaultFont, 9); + private FontReference _smallFont= new FontReference(DefaultFont, 9); + private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); /// /// Gets or sets the title font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")] - public FontReference TitleFont { get; set; } = new FontReference(DefaultFont, 18); + public FontReference TitleFont + { + get => _titleFont; + set + { + if (value == null) + _titleFont = new FontReference(DefaultFont, 18); + else if (!value.Font) + _titleFont.Font = DefaultFont; + else + _titleFont = value; + } + } /// /// Gets or sets the large font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")] - public FontReference LargeFont { get; set; } = new FontReference(DefaultFont, 14); + public FontReference LargeFont + { + get => _largeFont; + set + { + if (value == null) + _largeFont = new FontReference(DefaultFont, 14); + else if (!value.Font) + _largeFont.Font = DefaultFont; + else + _largeFont = value; + } + } /// /// Gets or sets the medium font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")] - public FontReference MediumFont { get; set; } = new FontReference(DefaultFont, 9); + public FontReference MediumFont + { + get => _mediumFont; + set + { + if (value == null) + _mediumFont = new FontReference(DefaultFont, 9); + else if (!value.Font) + _mediumFont.Font = DefaultFont; + else + _mediumFont = value; + } + } /// /// Gets or sets the small font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")] - public FontReference SmallFont { get; set; } = new FontReference(DefaultFont, 9); + public FontReference SmallFont + { + get => _smallFont; + set + { + if (value == null) + _smallFont = new FontReference(DefaultFont, 9); + else if (!value.Font) + _smallFont.Font = DefaultFont; + else + _smallFont = value; + } + } } } From fd679f0af59eb73244c39cb69107faf93b9358b9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 23:11:38 +0200 Subject: [PATCH 67/80] Add automatic game settings apply when saving json file in Editor #1440 --- Source/Engine/Content/JsonAsset.cpp | 12 ++++++++++-- Source/Engine/Content/JsonAsset.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 1fbc2abed..b48ef02e5 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -12,6 +12,7 @@ #include "FlaxEngine.Gen.h" #include "Cache/AssetsCache.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Content/Factories/JsonAssetFactory.h" @@ -126,8 +127,7 @@ void FindIds(ISerializable::DeserializeStream& node, Array& output) } else if (node.IsString()) { - const auto length = node.GetStringLength(); - if (length == 32) + if (node.GetStringLength() == 32) { // Try parse as Guid in format `N` (32 hex chars) Guid id; @@ -362,6 +362,7 @@ void JsonAsset::unload(bool isReloading) #endif Scripting::ScriptsUnload.Unbind(this); DeleteInstance(); + _isAfterReload |= isReloading; JsonAssetBase::unload(isReloading); } @@ -408,6 +409,13 @@ bool JsonAsset::CreateInstance() } } + // Special case for Settings assets to flush them after edited and saved in Editor + if (typeHandle && typeHandle.IsSubclassOf(SettingsBase::TypeInitializer) && _isAfterReload) + { + _isAfterReload = false; + ((SettingsBase*)Instance)->Apply(); + } + return false; } diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index c53649d33..0b6e3ca86 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -118,6 +118,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API JsonAsset : public JsonAssetBase DECLARE_ASSET_HEADER(JsonAsset); private: ScriptingType::Dtor _dtor; + bool _isAfterReload = false; public: /// From 1838c7bba7ce7f45697ca49c47b925ac317dd930 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Oct 2023 23:21:40 +0200 Subject: [PATCH 68/80] Minor cleanup --- Source/Editor/Content/Items/ContentItem.cs | 18 ++++++++--------- .../Dedicated/MissingScriptEditor.cs | 2 +- .../CustomEditors/Dedicated/SplineEditor.cs | 2 +- .../Editors/ActorTransformEditor.cs | 2 +- .../Editors/ObjectSwitcherEditor.cs | 1 - .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- .../CustomEditors/GUI/PropertiesList.cs | 2 +- Source/Editor/Options/GeneralOptions.cs | 4 ++-- Source/Editor/Options/InterfaceOptions.cs | 2 +- Source/Editor/Surface/Archetypes/Packing.cs | 20 ------------------- .../Surface/ContextMenu/VisjectCMGroup.cs | 3 ++- .../Surface/ContextMenu/VisjectCMItem.cs | 3 ++- 12 files changed, 21 insertions(+), 40 deletions(-) diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index 94db3a5b9..6bbdc0a51 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -486,7 +486,7 @@ namespace FlaxEditor.Content else Render2D.FillRectangle(rectangle, Color.Black); } - + /// /// Draws the item thumbnail. /// @@ -684,7 +684,7 @@ namespace FlaxEditor.Content var thumbnailSize = size.X; thumbnailRect = new Rectangle(0, 0, thumbnailSize, thumbnailSize); nameAlignment = TextAlignment.Center; - + if (this is ContentFolder) { // Small shadow @@ -692,7 +692,7 @@ namespace FlaxEditor.Content var color = Color.Black.AlphaMultiplied(0.2f); Render2D.FillRectangle(shadowRect, color); Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f)); - + if (isSelected) Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); else if (IsMouseOver) @@ -706,14 +706,14 @@ namespace FlaxEditor.Content var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1); var color = Color.Black.AlphaMultiplied(0.2f); Render2D.FillRectangle(shadowRect, color); - + Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f)); Render2D.FillRectangle(TextRectangle, style.LightBackground); - + var accentHeight = 2 * view.ViewScale; var barRect = new Rectangle(0, thumbnailRect.Height - accentHeight, clientRect.Width, accentHeight); Render2D.FillRectangle(barRect, Color.DimGray); - + DrawThumbnail(ref thumbnailRect, false); if (isSelected) { @@ -733,18 +733,18 @@ namespace FlaxEditor.Content var thumbnailSize = size.Y - 2 * DefaultMarginSize; thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize); nameAlignment = TextAlignment.Near; - + if (isSelected) Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); else if (IsMouseOver) Render2D.FillRectangle(clientRect, style.BackgroundHighlighted); - + DrawThumbnail(ref thumbnailRect); break; } default: throw new ArgumentOutOfRangeException(); } - + // Draw short name Render2D.PushClip(ref textRect); Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs index 03d5ddd55..a6c4e6623 100644 --- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs @@ -20,7 +20,7 @@ public class MissingScriptEditor : GenericEditor } dropPanel.HeaderTextColor = Color.OrangeRed; - + base.Initialize(layout); } } diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 03d58ef33..7b9b65c5c 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -348,7 +348,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (!CanEditTangent()) return; - + var index = _lastPointSelected.Index; var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation; var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 4aa02ac78..4c153e759 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -88,7 +88,7 @@ namespace FlaxEditor.CustomEditors.Editors LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked; // Add button with the link icon - + _linkButton = new Button { BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32), diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs index 4d0c0f662..2ef993e75 100644 --- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs @@ -160,7 +160,6 @@ namespace FlaxEditor.CustomEditors.Editors var option = _options[comboBox.SelectedIndex]; if (option.Type != null) value = option.Creator(option.Type); - } SetValue(value); RebuildLayoutOnRefresh(); diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 911397785..dbd5d124c 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -634,7 +634,7 @@ namespace FlaxEditor.CustomEditors.Editors var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; - + button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); button.Clicked += ShowPicker; } diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs index 2b2f0a3d3..93aacbd34 100644 --- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs +++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs @@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI { // Clear flag _mouseOverSplitter = false; - + if (_cursorChanged) { Cursor = CursorType.Default; diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 63822a7ca..33124bab0 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Options [DefaultValue(60.0f), Limit(0, 666)] [EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")] public float EditorFPS { get; set; } = 60.0f; - + /// /// Gets or sets The FPS of the editor when the editor window is not focused. Usually set to lower then the editor FPS. /// @@ -203,7 +203,7 @@ namespace FlaxEditor.Options [DefaultValue(5), Limit(1)] [EditorDisplay("Auto Save", "Auto Save Frequency"), EditorOrder(801), Tooltip("The interval between auto saves (in minutes)")] public int AutoSaveFrequency { get; set; } = 5; - + /// /// Gets or sets a value indicating the time before the auto save that the popup shows (in seconds). /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index a028d47f9..95a273f19 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -240,7 +240,7 @@ namespace FlaxEditor.Options private FontReference _titleFont = new FontReference(DefaultFont, 18); private FontReference _largeFont = new FontReference(DefaultFont, 14); private FontReference _mediumFont = new FontReference(DefaultFont, 9); - private FontReference _smallFont= new FontReference(DefaultFont, 9); + private FontReference _smallFont = new FontReference(DefaultFont, 9); private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); /// diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index e61b8e0df..6b9e72e7e 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -207,26 +207,6 @@ namespace FlaxEditor.Surface.Archetypes AddElement(box); } } - - protected static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) - { - // Event based nodes always have a pulse input, so it's always compatible with void - if (outputType.IsVoid) - return true; - - var eventName = (string)nodeArch.DefaultValues[1]; - var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); - var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); - if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) - { - if (!member.IsStatic) - { - if (VisjectSurface.FullCastCheck(eventType, outputType, hint)) - return true; - } - } - return false; - } } private sealed class PackStructureNode : StructureNode diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index fd825cdff..e52e10482 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -84,6 +84,7 @@ namespace FlaxEditor.Surface.ContextMenu /// Updates the filter. /// /// The filter text. + /// The optionally selected box to show hints for it. public void UpdateFilter(string filterText, Box selectedBox) { Profiler.BeginEvent("VisjectCMGroup.UpdateFilter"); @@ -116,7 +117,7 @@ namespace FlaxEditor.Surface.ContextMenu Profiler.EndEvent(); } - public void EvaluateVisibilityWithBox(Box selectedBox) + internal void EvaluateVisibilityWithBox(Box selectedBox) { if (selectedBox == null) { diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index b512f6946..7fd583202 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -8,7 +8,6 @@ using FlaxEditor.Surface.Elements; using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; -using FlaxEngine.Utilities; namespace FlaxEditor.Surface.ContextMenu { @@ -165,6 +164,8 @@ namespace FlaxEditor.Surface.ContextMenu /// Updates the filter. /// /// The filter text. + /// The optionally selected box to show hints for it. + /// True if item's group header got a filter match and item should stay visible. public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false) { if (selectedBox != null) From bc872ebff50808bd824a97f968667d136ee23e9c Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Thu, 5 Oct 2023 00:06:51 +0200 Subject: [PATCH 69/80] fix menu misalignment problem on Linux --- .../Engine/Platform/Linux/LinuxPlatform.cpp | 30 +++++++++++++++++-- Source/Engine/Platform/Linux/LinuxWindow.cpp | 6 +++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index ab314ab72..6195215e3 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -2707,8 +2707,34 @@ Float2 LinuxPlatform::GetDesktopSize() Rectangle LinuxPlatform::GetMonitorBounds(const Float2& screenPos) { - // TODO: do it in a proper way - return Rectangle(Float2::Zero, GetDesktopSize()); + if (!xDisplay) + return Rectangle::Empty; + + int event, err; + const bool ok = X11::XineramaQueryExtension(xDisplay, &event, &err); + if (!ok) + return Rectangle::Empty; + + int count; + int screenIdx = 0; + X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(xDisplay, &count); + if (screenIdx >= count) + return Rectangle::Empty; + // find the screen for this screenPos + for (int i = 0; i < count; i++) + { + if (screenPos.X >= xsi[i].x_org && screenPos.X < xsi[i].x_org+xsi[i].width + && screenPos.Y >= xsi[i].y_org && screenPos.Y < xsi[i].y_org+xsi[i].height) + { + screenIdx = i; + break; + } + } + + Float2 org((float)xsi[screenIdx].x_org, (float)xsi[screenIdx].y_org); + Float2 size((float)xsi[screenIdx].width, (float)xsi[screenIdx].height); + X11::XFree(xsi); + return Rectangle(org, size); } Rectangle LinuxPlatform::GetVirtualDesktopBounds() diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 7694836cb..e6fe63f9c 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -153,7 +153,11 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) hints.max_height = (int)settings.MaximumSize.Y; hints.flags |= USSize; } - X11::XSetNormalHints(display, window, &hints); + // honor the WM placement except for manual (overriding) placements + if (settings.StartPosition == WindowStartPosition::Manual) + { + X11::XSetNormalHints(display, window, &hints); + } // Ensures the child window is always on top of the parent window if (settings.Parent) From 86d1d298206697b80ef83bd75eb93faf2762e2de Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 5 Oct 2023 10:13:49 +0200 Subject: [PATCH 70/80] Fix deadlock regression from fd679f0af59eb73244c39cb69107faf93b9358b9 when applying Game Settings itself --- Source/Engine/Content/Asset.h | 2 +- Source/Engine/Content/JsonAsset.cpp | 21 ++++++++++++++------- Source/Engine/Content/JsonAsset.h | 1 + 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index 2d2ba4b3f..be188b2b9 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -227,7 +227,7 @@ protected: bool onLoad(LoadAssetTask* task); void onLoaded(); - void onLoaded_MainThread(); + virtual void onLoaded_MainThread(); virtual void onUnload_MainThread(); #if USE_EDITOR virtual void onRename(const StringView& newPath) = 0; diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index b48ef02e5..9977a28e1 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -367,6 +367,20 @@ void JsonAsset::unload(bool isReloading) JsonAssetBase::unload(isReloading); } +void JsonAsset::onLoaded_MainThread() +{ + JsonAssetBase::onLoaded_MainThread(); + + // Special case for Settings assets to flush them after edited and saved in Editor + const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length()); + const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length())); + if (Instance && typeHandle && typeHandle.IsSubclassOf(SettingsBase::TypeInitializer) && _isAfterReload) + { + _isAfterReload = false; + ((SettingsBase*)Instance)->Apply(); + } +} + bool JsonAsset::CreateInstance() { ScopeLock lock(Locker); @@ -409,13 +423,6 @@ bool JsonAsset::CreateInstance() } } - // Special case for Settings assets to flush them after edited and saved in Editor - if (typeHandle && typeHandle.IsSubclassOf(SettingsBase::TypeInitializer) && _isAfterReload) - { - _isAfterReload = false; - ((SettingsBase*)Instance)->Apply(); - } - return false; } diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index 0b6e3ca86..a8e89cec0 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -150,6 +150,7 @@ protected: // [JsonAssetBase] LoadResult loadAsset() override; void unload(bool isReloading) override; + void onLoaded_MainThread() override; private: bool CreateInstance(); From 9af6048bec7398973d1eca2cf998d7282518fb1e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 5 Oct 2023 11:35:52 +0200 Subject: [PATCH 71/80] Fix regression from #1312 when passing structure from C++ to C# thunk as already boxed value --- Source/Engine/Engine/NativeInterop.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 500636290..f3bb57665 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -685,8 +685,10 @@ namespace FlaxEngine.Interop T value = default; if (nativePtr == IntPtr.Zero) return value; - - MarshalHelper.ToManaged(ref value, nativePtr, false); + if (typeof(T).IsValueType) + value = (T)ManagedHandle.FromIntPtr(nativePtr).Target; + else + MarshalHelper.ToManaged(ref value, nativePtr, false); return value; } From 546553b82dec3f9516a2e4819c8448f5d4030bce Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 5 Oct 2023 11:44:14 +0200 Subject: [PATCH 72/80] Add some new splash quotes --- Source/Editor/Windows/SplashScreen.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index d2371a540..92894b537 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -127,6 +127,14 @@ const Char* SplashScreenQuotes[] = TEXT("Who is signing all these integers?!"), TEXT("Flax fact: Flax was called Celelej once."), TEXT("Changing text overflow setti-"), + TEXT("Testing tests"), + TEXT("Free hugs"), + TEXT("Think outside the box"), + TEXT("Let's make something fantastic"), + TEXT("Be brave"), + TEXT("Drum roll please"), + TEXT("Good Luck Have Fun"), + TEXT("GG Well Played"), }; SplashScreen::~SplashScreen() From 858baa0ee054770c684956883973e73054b8cde1 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 5 Oct 2023 19:28:32 +0300 Subject: [PATCH 73/80] Use latest supported C# version for building rules assemblies --- Source/Tools/Flax.Build/Build/Assembler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/Assembler.cs b/Source/Tools/Flax.Build/Build/Assembler.cs index be4990087..cbe92dd8d 100644 --- a/Source/Tools/Flax.Build/Build/Assembler.cs +++ b/Source/Tools/Flax.Build/Build/Assembler.cs @@ -154,7 +154,7 @@ namespace Flax.Build // Run the compilation using var memoryStream = new MemoryStream(); - CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9).WithPreprocessorSymbols(PreprocessorSymbols); + CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest).WithPreprocessorSymbols(PreprocessorSymbols); var syntaxTrees = new List(); foreach (var sourceFile in SourceFiles) { From 88d9e60beeb86da4be0063feb8eca26bfa31ccfd Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 5 Oct 2023 19:30:30 +0300 Subject: [PATCH 74/80] Generate Rider-specific user solution configuration files Disables "Use Resharper Build" option by default in generated solutions, Resharper can't detect file changes in project using custom build commands. --- .../VisualStudioProjectGenerator.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index a344eea50..1ba8e6709 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -641,6 +641,44 @@ namespace Flax.Build.Projects.VisualStudio File.WriteAllText(e.Key, profile.ToString(), Encoding.UTF8); } } + + // Generate Rider-specific configuration files + { + StringBuilder dotSettingsFileContent = new StringBuilder(); + string dotSettingsUserFilePath = solution.Path + ".DotSettings.user"; + + // Solution settings (user layer) + bool useResharperBuild = false; // This needs to be disabled for custom build steps to run properly + + if (File.Exists(dotSettingsUserFilePath)) + { + foreach (var line in File.ReadAllLines(dotSettingsUserFilePath)) + { + if (line.Contains(@"/UseMsbuildSolutionBuilder/@EntryValue")) + { + if (!useResharperBuild) + { + dotSettingsFileContent.Append("\t").Append(@"No"); + if (line.Contains("")) + dotSettingsFileContent.Append("\n"); + else + dotSettingsFileContent.Append("\n"); + } + continue; + } + dotSettingsFileContent.Append(line).Append("\n"); + } + } + else + { + dotSettingsFileContent.Append(@"").Append("\n"); + if (!useResharperBuild) + dotSettingsFileContent.Append("\t").Append(@"No"); + dotSettingsFileContent.Append("\n"); + } + + Utilities.WriteFileIfChanged(dotSettingsUserFilePath, dotSettingsFileContent.ToString()); + } } /// From 9870d162e4c5e86706dbc3e91ffe5aa241079e39 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Oct 2023 10:07:57 +0200 Subject: [PATCH 75/80] Fix creating prefabs directly from prefab objects #1432 --- Source/Editor/Modules/PrefabsModule.cs | 54 ++++++++++--------- .../Windows/Assets/PrefabWindow.Hierarchy.cs | 2 +- Source/Editor/Windows/Assets/PrefabWindow.cs | 5 ++ Source/Engine/Level/Prefabs/PrefabManager.cpp | 23 +++++++- Source/Engine/Serialization/JsonTools.cpp | 1 - 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index 7b3ffebb3..bb76e125b 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -37,11 +37,6 @@ namespace FlaxEditor.Modules /// public event Action PrefabApplied; - /// - /// Locally cached actor for prefab creation. - /// - private Actor _prefabCreationActor; - internal PrefabsModule(Editor editor) : base(editor) { @@ -65,13 +60,14 @@ namespace FlaxEditor.Modules /// To create prefab manually (from code) use method. /// /// The scene selection to use. - public void CreatePrefab(List selection) + /// The prefab window that creates it. + public void CreatePrefab(List selection, Windows.Assets.PrefabWindow prefabWindow = null) { if (selection == null) selection = Editor.SceneEditing.Selection; if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab) { - CreatePrefab(actorNode.Actor); + CreatePrefab(actorNode.Actor, true, prefabWindow); } } @@ -92,7 +88,8 @@ namespace FlaxEditor.Modules /// /// The root prefab actor. /// Allow renaming or not - public void CreatePrefab(Actor actor, bool rename) + /// The prefab window that creates it. + public void CreatePrefab(Actor actor, bool rename, Windows.Assets.PrefabWindow prefabWindow = null) { // Skip in invalid states if (!Editor.StateMachine.CurrentState.CanEditContent) @@ -105,42 +102,47 @@ namespace FlaxEditor.Modules PrefabCreating?.Invoke(actor); var proxy = Editor.ContentDatabase.GetProxy(); - _prefabCreationActor = actor; - Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename); + Editor.Windows.ContentWin.NewItem(proxy, actor, contentItem => OnPrefabCreated(contentItem, actor, prefabWindow), actor.Name, rename); } - private void OnPrefabCreated(ContentItem contentItem) + private void OnPrefabCreated(ContentItem contentItem, Actor actor, Windows.Assets.PrefabWindow prefabWindow) { if (contentItem is PrefabItem prefabItem) { PrefabCreated?.Invoke(prefabItem); } - // Skip in invalid states - if (!Editor.StateMachine.CurrentState.CanEditScene) - return; + Undo undo = null; + if (prefabWindow != null) + { + prefabWindow.MarkAsEdited(); + undo = prefabWindow.Undo; + } + else + { + // Skip in invalid states + if (!Editor.StateMachine.CurrentState.CanEditScene) + return; + undo = Editor.Undo; + } // Record undo for prefab creating (backend links the target instance with the prefab) - if (Editor.Undo.Enabled) + if (undo.Enabled) { - if (!_prefabCreationActor) + if (!actor) return; - var actorsList = new List(); - Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor); + Utilities.Utils.GetActorsTree(actorsList, actor); var actions = new IUndoAction[actorsList.Count]; for (int i = 0; i < actorsList.Count; i++) - { - var action = BreakPrefabLinkAction.Linked(actorsList[i]); - actions[i] = action; - } - Undo.AddAction(new MultiUndoAction(actions)); - - _prefabCreationActor = null; + actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]); + undo.AddAction(new MultiUndoAction(actions)); } - Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); + Editor.Windows.PropertiesWin.Presenter.BuildLayout(); + if (prefabWindow != null) + prefabWindow.Presenter.BuildLayout(); } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 5a532e362..6448ebbb2 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -233,7 +233,7 @@ namespace FlaxEditor.Windows.Assets contextMenu.AddSeparator(); - b = contextMenu.AddButton("Create Prefab", () => Editor.Prefabs.CreatePrefab(Selection)); + b = contextMenu.AddButton("Create Prefab", () => Editor.Prefabs.CreatePrefab(Selection, this)); b.Enabled = isSingleActorSelected && (Selection[0] as ActorNode).CanCreatePrefab && Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 659bab249..50aafd46a 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -53,6 +53,11 @@ namespace FlaxEditor.Windows.Assets /// public PrefabWindowViewport Viewport => _viewport; + /// + /// Gets the prefab objects properties editor. + /// + public CustomEditorPresenter Presenter => _propertiesEditor; + /// /// Gets the undo system used by this window for changes tracking. /// diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index a2b148f28..7d3933175 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -321,6 +321,8 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat // Serialize to json data ASSERT(!IsCreatingPrefab); IsCreatingPrefab = true; + const Guid targetPrefabId = targetActor->GetPrefabID(); + const bool hasTargetPrefabId = targetPrefabId.IsValid(); rapidjson_flax::StringBuffer actorsDataBuffer; { CompactJsonWriter writerObj(actorsDataBuffer); @@ -329,7 +331,27 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); + + // Detect when creating prefab from object that is already part of prefab then serialize it as unlinked + const Guid prefabId = obj->GetPrefabID(); + const Guid prefabObjectId = obj->GetPrefabObjectID(); + bool isObjectFromPrefab = targetPrefabId == prefabId && prefabId.IsValid(); // Allow to use other nested prefabs properly (ignore only root object's prefab link) + if (isObjectFromPrefab) + { + //obj->BreakPrefabLink(); + obj->_prefabID = Guid::Empty; + obj->_prefabObjectID = Guid::Empty; + } + writer.SceneObject(obj); + + // Restore broken link + if (hasTargetPrefabId) + { + //obj->LinkPrefab(prefabId, prefabObjectId); + obj->_prefabID = prefabId; + obj->_prefabObjectID = prefabObjectId; + } } writer.EndArray(); } @@ -395,7 +417,6 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat { SceneObject* obj = sceneObjects->At(i); Guid prefabObjectId; - if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId)) { obj->LinkPrefab(assetInfo.ID, prefabObjectId); diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index c395df4f1..3cbe8f65c 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -81,7 +81,6 @@ void JsonTools::ChangeIds(Document& doc, const Dictionary& mapping) ::ChangeIds(doc, doc, mapping); } - Float2 JsonTools::GetFloat2(const Value& value) { Float2 result; From 176123eb1f2ebaddf129ec474e161d8c2517ea73 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Oct 2023 11:40:39 +0200 Subject: [PATCH 76/80] Fix `AudioClip` loading error when buffer start times diff has rounding error --- Source/Engine/Audio/AudioClip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 8750bed03..911a8be33 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -348,7 +348,7 @@ Asset::LoadResult AudioClip::load() #if !BUILD_RELEASE // Validate buffer start times - if (Math::NotNearEqual(_buffersStartTimes[_totalChunks], GetLength())) + if (!Math::NearEqual(_buffersStartTimes[_totalChunks], GetLength(), 1.0f / 60.0f)) { LOG(Warning, "Invalid audio buffers data size. Expected length: {0}s", GetLength()); for (int32 i = 0; i < _totalChunks + 1; i++) From 560f699dd856cea66a8777132c4e1a9f532f5638 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Oct 2023 13:59:03 +0200 Subject: [PATCH 77/80] Fix various issues with OpenAL buffers playback (do proper bit convertion) --- Source/Engine/Audio/AudioClip.cpp | 6 +- .../Engine/Audio/OpenAL/AudioBackendOAL.cpp | 59 +++++++------------ Source/Engine/Tools/AudioTool/AudioTool.cpp | 13 ++-- 3 files changed, 28 insertions(+), 50 deletions(-) diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 911a8be33..d04da7274 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -454,14 +454,12 @@ bool AudioClip::WriteBuffer(int32 chunkIndex) } break; case AudioFormat::Raw: - { data = Span(chunk->Get(), chunk->Size()); - } - break; + break; default: return true; } - info.NumSamples = data.Length() / bytesPerSample; + info.NumSamples = Math::AlignDown(data.Length() / bytesPerSample, info.NumChannels * bytesPerSample); // Convert to Mono if used as 3D source and backend doesn't support it if (Is3D() && info.NumChannels > 1 && EnumHasNoneFlags(AudioBackend::Features(), AudioBackend::FeatureFlags::SpatialMultiChannel)) diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index 84642ac1f..c905c7eef 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -228,11 +228,9 @@ namespace ALC ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) { // TODO: cache enum values in Init()?? - switch (bitDepth) { case 8: - { switch (numChannels) { case 1: @@ -247,13 +245,8 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) return alGetEnumValue("AL_FORMAT_61CHN8"); case 8: return alGetEnumValue("AL_FORMAT_71CHN8"); - default: - CRASH; - return 0; } - } case 16: - { switch (numChannels) { case 1: @@ -268,19 +261,22 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) return alGetEnumValue("AL_FORMAT_61CHN16"); case 8: return alGetEnumValue("AL_FORMAT_71CHN16"); - default: - CRASH; - return 0; } - } case 32: - { switch (numChannels) { case 1: +#ifdef AL_FORMAT_MONO_FLOAT32 + return AL_FORMAT_MONO_FLOAT32; +#else return alGetEnumValue("AL_FORMAT_MONO_FLOAT32"); +#endif case 2: +#ifdef AL_FORMAT_STEREO_FLOAT32 + return AL_FORMAT_STEREO_FLOAT32; +#else return alGetEnumValue("AL_FORMAT_STEREO_FLOAT32"); +#endif case 4: return alGetEnumValue("AL_FORMAT_QUAD32"); case 6: @@ -289,15 +285,9 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) return alGetEnumValue("AL_FORMAT_61CHN32"); case 8: return alGetEnumValue("AL_FORMAT_71CHN32"); - default: - CRASH; - return 0; } } - default: - CRASH; - return 0; - } + return 0; } void AudioBackendOAL::Listener_OnAdd(AudioListener* listener) @@ -607,7 +597,8 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa { PROFILE_CPU(); - // TODO: maybe use temporary buffers per thread to reduce dynamic allocations when uploading data to OpenAL? + // Pick the format for the audio data (it might not be supported natively) + ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth); // Mono or stereo if (info.NumChannels <= 2) @@ -618,28 +609,23 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa { const uint32 bufferSize = info.NumSamples * sizeof(float); float* sampleBufferFloat = (float*)Allocator::Allocate(bufferSize); - AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples); - const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth); + format = GetOpenALBufferFormat(info.NumChannels, 32); alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.SampleRate); ALC_CHECK_ERROR(alBufferData); - Allocator::Free(sampleBufferFloat); } else { LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated."); - const uint32 bufferSize = info.NumSamples * 2; byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize); - AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples); - const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16); + format = GetOpenALBufferFormat(info.NumChannels, 16); alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.SampleRate); ALC_CHECK_ERROR(alBufferData); - Allocator::Free(sampleBuffer16); } } @@ -648,19 +634,15 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa // OpenAL expects unsigned 8-bit data, but engine stores it as signed, so convert const uint32 bufferSize = info.NumSamples * (info.BitDepth / 8); byte* sampleBuffer = (byte*)Allocator::Allocate(bufferSize); - for (uint32 i = 0; i < info.NumSamples; i++) sampleBuffer[i] = ((int8*)samples)[i] + 128; - const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16); alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate); ALC_CHECK_ERROR(alBufferData); - Allocator::Free(sampleBuffer); } - else + else if (format) { - const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth); alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate); ALC_CHECK_ERROR(alBufferData); } @@ -675,10 +657,9 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa { const uint32 bufferSize = info.NumChannels * sizeof(int32); byte* sampleBuffer32 = (byte*)Allocator::Allocate(bufferSize); - AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples); - const ALenum format = GetOpenALBufferFormat(info.NumChannels, 32); + format = GetOpenALBufferFormat(info.NumChannels, 32); alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.SampleRate); ALC_CHECK_ERROR(alBufferData); @@ -693,19 +674,23 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa for (uint32 i = 0; i < info.NumSamples; i++) sampleBuffer[i] = ((int8*)samples)[i] + 128; - const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16); + format = GetOpenALBufferFormat(info.NumChannels, 16); alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate); ALC_CHECK_ERROR(alBufferData); Allocator::Free(sampleBuffer); } - else + else if (format) { - const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth); alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate); ALC_CHECK_ERROR(alBufferData); } } + + if (!format) + { + LOG(Error, "Not suppported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels); + } } const Char* AudioBackendOAL::Base_Name() diff --git a/Source/Engine/Tools/AudioTool/AudioTool.cpp b/Source/Engine/Tools/AudioTool/AudioTool.cpp index a5a5d4663..89d613116 100644 --- a/Source/Engine/Tools/AudioTool/AudioTool.cpp +++ b/Source/Engine/Tools/AudioTool/AudioTool.cpp @@ -231,8 +231,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp for (uint32 i = 0; i < numSamples; i++) { const int8 sample = *(int8*)input; - output[i] = sample / 127.0f; - + output[i] = sample * (1.0f / 127.0f); input++; } } @@ -241,8 +240,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp for (uint32 i = 0; i < numSamples; i++) { const int16 sample = *(int16*)input; - output[i] = sample / 32767.0f; - + output[i] = sample * (1.0f / 32767.0f); input += 2; } } @@ -251,8 +249,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp for (uint32 i = 0; i < numSamples; i++) { const int32 sample = Convert24To32Bits(input); - output[i] = sample / 2147483647.0f; - + output[i] = sample * (1.0f / 2147483647.0f); input += 3; } } @@ -261,8 +258,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp for (uint32 i = 0; i < numSamples; i++) { const int32 sample = *(int32*)input; - output[i] = sample / 2147483647.0f; - + output[i] = sample * (1.0f / 2147483647.0f); input += 4; } } @@ -278,7 +274,6 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa { const float sample = *(float*)input; output[i] = static_cast(sample * 2147483647.0f); - input++; } } From e29d3d02a219683bb5065eb19ca851b1ee2af5a3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Oct 2023 14:08:48 +0200 Subject: [PATCH 78/80] Refactor audio clip import settings to use auto-generated bindings via `AudioTool` --- .../Content/Import/AudioImportSettings.cs | 188 +++++------------- Source/Editor/Editor.cs | 19 -- .../Editor/Managed/ManagedEditor.Internal.cpp | 77 ++----- Source/Editor/Managed/ManagedEditor.h | 20 ++ .../Editor/Windows/Assets/AudioClipWindow.cs | 2 +- .../Engine/ContentImporters/ImportAudio.cpp | 40 +--- Source/Engine/ContentImporters/ImportAudio.h | 24 +-- Source/Engine/Tools/AudioTool/AudioTool.cpp | 37 ++++ Source/Engine/Tools/AudioTool/AudioTool.h | 76 ++++++- 9 files changed, 200 insertions(+), 283 deletions(-) diff --git a/Source/Editor/Content/Import/AudioImportSettings.cs b/Source/Editor/Content/Import/AudioImportSettings.cs index 2fc0a1795..e0bfb6e20 100644 --- a/Source/Editor/Content/Import/AudioImportSettings.cs +++ b/Source/Editor/Content/Import/AudioImportSettings.cs @@ -1,144 +1,52 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System.ComponentModel; -using System.Reflection; -using System.Runtime.InteropServices; +using System.Collections.Generic; +using FlaxEditor.CustomEditors.Editors; +using FlaxEditor.Scripting; using FlaxEngine; -using FlaxEngine.Interop; +using FlaxEngine.Tools; + +namespace FlaxEngine.Tools +{ + partial class AudioTool + { + partial struct Options + { + private bool ShowBtiDepth => Format != AudioFormat.Vorbis; + } + } +} + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + [CustomEditor(typeof(FlaxEngine.Tools.AudioTool.Options)), DefaultEditor] + public class AudioToolOptionsEditor : GenericEditor + { + /// + protected override List GetItemsForType(ScriptType type) + { + // Show both fields and properties + return GetItemsForType(type, true, true); + } + } +} namespace FlaxEditor.Content.Import { /// /// Proxy object to present audio import settings in . /// + [HideInEditor] public class AudioImportSettings { /// - /// A custom set of bit depth audio import sizes. + /// The settings data. /// - public enum CustomBitDepth - { - /// - /// The 8. - /// - _8 = 8, - - /// - /// The 16. - /// - _16 = 16, - - /// - /// The 24. - /// - _24 = 24, - - /// - /// The 32. - /// - _32 = 32, - } - - /// - /// Converts the bit depth to enum. - /// - /// The bit depth. - /// The converted enum. - public static CustomBitDepth ConvertBitDepth(int f) - { - FieldInfo[] fields = typeof(CustomBitDepth).GetFields(); - for (int i = 0; i < fields.Length; i++) - { - var field = fields[i]; - if (field.Name.Equals("value__")) - continue; - - if (f == (int)field.GetRawConstantValue()) - return (CustomBitDepth)f; - } - - return CustomBitDepth._16; - } - - /// - /// The audio data format to import the audio clip as. - /// - [EditorOrder(10), DefaultValue(AudioFormat.Vorbis), Tooltip("The audio data format to import the audio clip as.")] - public AudioFormat Format { get; set; } = AudioFormat.Vorbis; - - /// - /// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality. - /// - [EditorOrder(15), DefaultValue(0.4f), Limit(0, 1, 0.01f), Tooltip("The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.")] - public float CompressionQuality { get; set; } = 0.4f; - - /// - /// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds). - /// - [EditorOrder(20), DefaultValue(false), Tooltip("Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).")] - public bool DisableStreaming { get; set; } = false; - - /// - /// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format. - /// - [EditorOrder(30), DefaultValue(false), EditorDisplay(null, "Is 3D"), Tooltip("Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.")] - public bool Is3D { get; set; } = false; - - /// - /// The size of a single sample in bits. The clip will be converted to this bit depth on import. - /// - [EditorOrder(40), DefaultValue(CustomBitDepth._16), Tooltip("The size of a single sample in bits. The clip will be converted to this bit depth on import.")] - public CustomBitDepth BitDepth { get; set; } = CustomBitDepth._16; - - [StructLayout(LayoutKind.Sequential)] - internal struct InternalOptions - { - [MarshalAs(UnmanagedType.I1)] - public AudioFormat Format; - public byte DisableStreaming; - public byte Is3D; - public int BitDepth; - public float Quality; - } - - internal void ToInternal(out InternalOptions options) - { - options = new InternalOptions - { - Format = Format, - DisableStreaming = (byte)(DisableStreaming ? 1 : 0), - Is3D = (byte)(Is3D ? 1 : 0), - Quality = CompressionQuality, - BitDepth = (int)BitDepth, - }; - } - - internal void FromInternal(ref InternalOptions options) - { - Format = options.Format; - DisableStreaming = options.DisableStreaming != 0; - Is3D = options.Is3D != 0; - CompressionQuality = options.Quality; - BitDepth = ConvertBitDepth(options.BitDepth); - } - - /// - /// Tries the restore the asset import options from the target resource file. - /// - /// The options. - /// The asset path. - /// True settings has been restored, otherwise false. - public static bool TryRestore(ref AudioImportSettings options, string assetPath) - { - if (AudioImportEntry.Internal_GetAudioImportOptions(assetPath, out var internalOptions)) - { - // Restore settings - options.FromInternal(ref internalOptions); - return true; - } - - return false; - } + [EditorDisplay(null, EditorDisplayAttribute.InlineStyle)] + public AudioTool.Options Settings = AudioTool.Options.Default; } /// @@ -147,7 +55,7 @@ namespace FlaxEditor.Content.Import /// public partial class AudioImportEntry : AssetImportEntry { - private AudioImportSettings _settings = new AudioImportSettings(); + private AudioImportSettings _settings = new(); /// /// Initializes a new instance of the class. @@ -157,7 +65,7 @@ namespace FlaxEditor.Content.Import : base(ref request) { // Try to restore target asset Audio import options (useful for fast reimport) - AudioImportSettings.TryRestore(ref _settings, ResultUrl); + Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl); } /// @@ -166,27 +74,23 @@ namespace FlaxEditor.Content.Import /// public override bool TryOverrideSettings(object settings) { - if (settings is AudioImportSettings o) + if (settings is AudioImportSettings s) { - _settings = o; + _settings.Settings = s.Settings; + return true; + } + if (settings is AudioTool.Options o) + { + _settings.Settings = o; return true; } - return false; } /// public override bool Import() { - return Editor.Import(SourceUrl, ResultUrl, _settings); + return Editor.Import(SourceUrl, ResultUrl, _settings.Settings); } - - #region Internal Calls - - [LibraryImport("FlaxEngine", EntryPoint = "AudioImportEntryInternal_GetAudioImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result); - - #endregion } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 06a6f0979..e0c460d0c 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -935,21 +935,6 @@ namespace FlaxEditor Animation = 11, } - /// - /// Imports the audio asset file to the target location. - /// - /// The source file path. - /// The result asset file path. - /// The settings. - /// True if importing failed, otherwise false. - public static bool Import(string inputPath, string outputPath, AudioImportSettings settings) - { - if (settings == null) - throw new ArgumentNullException(); - settings.ToInternal(out var internalOptions); - return Internal_ImportAudio(inputPath, outputPath, ref internalOptions); - } - /// /// Serializes the given object to json asset. /// @@ -1667,10 +1652,6 @@ namespace FlaxEditor [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ImportAudio", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index afcbb740a..d2d2f216a 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -50,43 +50,6 @@ Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af); -// Disable warning C4800: 'const byte': forcing value to bool 'true' or 'false' (performance warning) -#if defined(_MSC_VER) -#pragma warning( push ) -#pragma warning( disable : 4800) -#endif - -struct InternalAudioOptions -{ - AudioFormat Format; - byte DisableStreaming; - byte Is3D; - int32 BitDepth; - float Quality; - - static void Convert(InternalAudioOptions* from, ImportAudio::Options* to) - { - to->Format = from->Format; - to->DisableStreaming = from->DisableStreaming; - to->Is3D = from->Is3D; - to->BitDepth = from->BitDepth; - to->Quality = from->Quality; - } - - static void Convert(ImportAudio::Options* from, InternalAudioOptions* to) - { - to->Format = from->Format; - to->DisableStreaming = from->DisableStreaming; - to->Is3D = from->Is3D; - to->BitDepth = from->BitDepth; - to->Quality = from->Quality; - } -}; - -#if defined(_MSC_VER) -#pragma warning( pop ) -#endif - // Pack log messages into a single scratch buffer to reduce dynamic memory allocations CriticalSection CachedLogDataLocker; Array CachedLogData; @@ -295,16 +258,6 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_CanImport(MString* extensionObj) return importer ? MUtils::ToString(importer->ResultExtension) : nullptr; } -DEFINE_INTERNAL_CALL(bool) EditorInternal_ImportAudio(MString* inputPathObj, MString* outputPathObj, InternalAudioOptions* optionsObj) -{ - ImportAudio::Options options; - InternalAudioOptions::Convert(optionsObj, &options); - String inputPath, outputPath; - MUtils::ToString(inputPathObj, inputPath); - MUtils::ToString(outputPathObj, outputPath); - return ManagedEditor::Import(inputPath, outputPath, &options); -} - DEFINE_INTERNAL_CALL(void) EditorInternal_GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize) { INTERNAL_CALL_CHECK(clip); @@ -765,24 +718,6 @@ DEFINE_INTERNAL_CALL(MTypeObject*) CustomEditorsUtilInternal_GetCustomEditor(MTy return CustomEditorsUtil::GetCustomEditor(targetType); } -DEFINE_INTERNAL_CALL(bool) AudioImportEntryInternal_GetAudioImportOptions(MString* pathObj, InternalAudioOptions* result) -{ - String path; - MUtils::ToString(pathObj, path); - FileSystem::NormalizePath(path); - - ImportAudio::Options options; - if (ImportAudio::TryGetImportOptions(path, options)) - { - // Convert into managed storage - InternalAudioOptions::Convert(&options, result); - - return true; - } - - return false; -} - DEFINE_INTERNAL_CALL(MArray*) LayersAndTagsSettingsInternal_GetCurrentLayers(int* layersCount) { *layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount()); @@ -830,3 +765,15 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String FileSystem::NormalizePath(assetPath); return ImportModelFile::TryGetImportOptions(assetPath, options); } + +bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options) +{ + return Import(inputPath, outputPath, (void*)&options); +} + +bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath) +{ + // Get options from model + FileSystem::NormalizePath(assetPath); + return ImportAudio::TryGetImportOptions(assetPath, options); +} diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index ddd94b47e..93d1723a3 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -7,6 +7,7 @@ #include "Engine/ShadowsOfMordor/Types.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Tools/ModelTool/ModelTool.h" +#include "Engine/Tools/AudioTool/AudioTool.h" namespace CSG { @@ -190,6 +191,25 @@ public: API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) ModelTool::Options& options, String assetPath); #endif +#if COMPILE_WITH_AUDIO_TOOL + /// + /// Imports the audio asset file to the target location. + /// + /// The source file path. + /// The result asset file path. + /// The import settings. + /// True if importing failed, otherwise false. + API_FUNCTION() static bool Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options); + + /// + /// Tries the restore the asset import options from the target resource file. + /// + /// The options. + /// The asset path. + /// True settings has been restored, otherwise false. + API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); +#endif + private: void OnEditorAssemblyLoaded(MAssembly* assembly); diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs index f5f244c58..e6fabc165 100644 --- a/Source/Editor/Windows/Assets/AudioClipWindow.cs +++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs @@ -116,7 +116,7 @@ namespace FlaxEditor.Windows.Assets _window = window; // Try to restore target asset AudioClip import options (useful for fast reimport) - AudioImportSettings.TryRestore(ref ImportSettings, window.Item.Path); + Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path); // Prepare restore data PeekState(); diff --git a/Source/Engine/ContentImporters/ImportAudio.cpp b/Source/Engine/ContentImporters/ImportAudio.cpp index 10915b2d1..7a8c952e4 100644 --- a/Source/Engine/ContentImporters/ImportAudio.cpp +++ b/Source/Engine/ContentImporters/ImportAudio.cpp @@ -18,32 +18,6 @@ #include "Engine/Tools/AudioTool/OggVorbisDecoder.h" #include "Engine/Tools/AudioTool/OggVorbisEncoder.h" #include "Engine/Serialization/JsonWriters.h" -#include "Engine/Scripting/Enums.h" - -String ImportAudio::Options::ToString() const -{ - return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, BitDepth); -} - -void ImportAudio::Options::Serialize(SerializeStream& stream, const void* otherObj) -{ - SERIALIZE_GET_OTHER_OBJ(ImportAudio::Options); - - SERIALIZE(Format); - SERIALIZE(DisableStreaming); - SERIALIZE(Is3D); - SERIALIZE(Quality); - SERIALIZE(BitDepth); -} - -void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - DESERIALIZE(Format); - DESERIALIZE(DisableStreaming); - DESERIALIZE(Is3D); - DESERIALIZE(Quality); - DESERIALIZE(BitDepth); -} bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options) { @@ -112,16 +86,14 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder& sampleBuffer.Link(audioData.Get()); // Convert bit depth if need to - if (options.BitDepth != static_cast(info.BitDepth)) + uint32 outputBitDepth = (uint32)options.BitDepth; + if (outputBitDepth != info.BitDepth) { - const uint32 outBufferSize = info.NumSamples * (options.BitDepth / 8); + const uint32 outBufferSize = info.NumSamples * (outputBitDepth / 8); sampleBuffer.Allocate(outBufferSize); - - AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), options.BitDepth, info.NumSamples); - - info.BitDepth = options.BitDepth; + AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), outputBitDepth, info.NumSamples); + info.BitDepth = outputBitDepth; bytesPerSample = info.BitDepth / 8; - bufferSize = outBufferSize; } @@ -149,7 +121,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder& context.Data.Header.Chunks[chunkIndex]->Data.Copy(dataPtr, dataSize); #define WRITE_DATA(chunkIndex, dataPtr, dataSize) \ - samplesPerChunk[chunkIndex] = (dataSize) / (options.BitDepth / 8); \ + samplesPerChunk[chunkIndex] = (dataSize) / (outputBitDepth / 8); \ switch (options.Format) \ { \ case AudioFormat::Raw: \ diff --git a/Source/Engine/ContentImporters/ImportAudio.h b/Source/Engine/ContentImporters/ImportAudio.h index fb4ee64e3..3f3eda5ed 100644 --- a/Source/Engine/ContentImporters/ImportAudio.h +++ b/Source/Engine/ContentImporters/ImportAudio.h @@ -3,12 +3,12 @@ #pragma once #include "Types.h" -#include "Engine/Tools/AudioTool/AudioDecoder.h" -#include "Engine/Core/ISerializable.h" -#include "Engine/Audio/Config.h" #if COMPILE_WITH_ASSETS_IMPORTER +#include "Engine/Tools/AudioTool/AudioTool.h" +#include "Engine/Tools/AudioTool/AudioDecoder.h" + /// /// Enable/disable caching audio import options /// @@ -20,23 +20,7 @@ class ImportAudio { public: - /// - /// Importing audio options - /// - struct Options : public ISerializable - { - AudioFormat Format = AudioFormat::Vorbis; - bool DisableStreaming = false; - bool Is3D = false; - int32 BitDepth = 16; - float Quality = 0.4f; - - String ToString() const; - - // [ISerializable] - void Serialize(SerializeStream& stream, const void* otherObj) override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; - }; + typedef AudioTool::Options Options; public: /// diff --git a/Source/Engine/Tools/AudioTool/AudioTool.cpp b/Source/Engine/Tools/AudioTool/AudioTool.cpp index 89d613116..3136a5a0d 100644 --- a/Source/Engine/Tools/AudioTool/AudioTool.cpp +++ b/Source/Engine/Tools/AudioTool/AudioTool.cpp @@ -1,14 +1,49 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +#if COMPILE_WITH_AUDIO_TOOL + #include "AudioTool.h" #include "Engine/Core/Core.h" #include "Engine/Core/Memory/Allocation.h" +#if USE_EDITOR +#include "Engine/Serialization/Serialization.h" +#include "Engine/Scripting/Enums.h" +#endif #define CONVERT_TO_MONO_AVG 1 #if !CONVERT_TO_MONO_AVG #include "Engine/Core/Math/Math.h" #endif +#if USE_EDITOR + +String AudioTool::Options::ToString() const +{ + return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, (int32)BitDepth); +} + +void AudioTool::Options::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(AudioTool::Options); + + SERIALIZE(Format); + SERIALIZE(DisableStreaming); + SERIALIZE(Is3D); + SERIALIZE(Quality); + SERIALIZE(BitDepth); +} + +void AudioTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(Format); + DESERIALIZE(DisableStreaming); + DESERIALIZE(Is3D); + DESERIALIZE(Quality); + DESERIALIZE(BitDepth); +} + +#endif + void ConvertToMono8(const int8* input, uint8* output, uint32 numSamples, uint32 numChannels) { for (uint32 i = 0; i < numSamples; i++) @@ -277,3 +312,5 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa input++; } } + +#endif diff --git a/Source/Engine/Tools/AudioTool/AudioTool.h b/Source/Engine/Tools/AudioTool/AudioTool.h index b9525ffa7..cd61ee37a 100644 --- a/Source/Engine/Tools/AudioTool/AudioTool.h +++ b/Source/Engine/Tools/AudioTool/AudioTool.h @@ -2,16 +2,85 @@ #pragma once +#if COMPILE_WITH_AUDIO_TOOL + #include "Engine/Core/Config.h" #include "Engine/Core/Types/BaseTypes.h" +#if USE_EDITOR +#include "Engine/Core/ISerializable.h" +#endif +#include "Engine/Audio/Types.h" /// /// Audio data importing and processing utilities. /// -class FLAXENGINE_API AudioTool +API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API AudioTool { -public: + DECLARE_SCRIPTING_TYPE_MINIMAL(AudioTool); +#if USE_EDITOR + +public: + /// + /// Declares the imported audio clip bit depth. + /// + API_ENUM(Attributes="HideInEditor") enum class BitDepth : int32 + { + // 8-bits per sample. + _8 = 8, + // 16-bits per sample. + _16 = 16, + // 24-bits per sample. + _24 = 24, + // 32-bits per sample. + _32 = 32, + }; + + /// + /// Audio import options. + /// + API_STRUCT(Attributes="HideInEditor") struct FLAXENGINE_API Options : public ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(Options); + + /// + /// The audio data format to import the audio clip as. + /// + API_FIELD(Attributes="EditorOrder(10)") + AudioFormat Format = AudioFormat::Vorbis; + + /// + /// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality. + /// + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Compression Quality\"), Limit(0, 1, 0.01f)") + float Quality = 0.4f; + + /// + /// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds). + /// + API_FIELD(Attributes="EditorOrder(30)") + bool DisableStreaming = false; + + /// + /// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format. + /// + API_FIELD(Attributes="EditorOrder(40), EditorDisplay(null, \"Is 3D\")") + bool Is3D = false; + + /// + /// The size of a single sample in bits. The clip will be converted to this bit depth on import. + /// + API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBtiDepth))") + BitDepth BitDepth = BitDepth::_16; + + String ToString() const; + + // [ISerializable] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + }; + +public: /// /// Converts a set of audio samples using multiple channels into a set of mono samples. /// @@ -58,4 +127,7 @@ public: { return (input[2] << 24) | (input[1] << 16) | (input[0] << 8); } +#endif }; + +#endif From 92e28f66affd5ad03482a7bb283f0ab04a0cb145 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Oct 2023 14:19:22 +0200 Subject: [PATCH 79/80] Fix various issues with audio clip data buffers to reduce artifacts (especially when using 24-bit data) --- Source/Editor/Windows/Assets/AudioClipWindow.cs | 5 +++++ Source/Engine/Audio/AudioSource.cpp | 2 +- Source/Engine/ContentImporters/ImportAudio.cpp | 9 +++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs index e6fabc165..8cf31f416 100644 --- a/Source/Editor/Windows/Assets/AudioClipWindow.cs +++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs @@ -134,6 +134,11 @@ namespace FlaxEditor.Windows.Assets /// public void Reimport() { + if (_window?._previewSource != null) + { + _window._previewSource.Stop(); + _window.UpdateToolstrip(); + } Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)_window.Item, ImportSettings, true); } diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index 2cee52a8a..93c2e18df 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -183,7 +183,7 @@ void AudioSource::Stop() float AudioSource::GetTime() const { - if (_state == States::Stopped || SourceIDs.IsEmpty()) + if (_state == States::Stopped || SourceIDs.IsEmpty() || !Clip->IsLoaded()) return 0.0f; float time = AudioBackend::Source::GetCurrentBufferTime(this); diff --git a/Source/Engine/ContentImporters/ImportAudio.cpp b/Source/Engine/ContentImporters/ImportAudio.cpp index 7a8c952e4..803bf58a8 100644 --- a/Source/Engine/ContentImporters/ImportAudio.cpp +++ b/Source/Engine/ContentImporters/ImportAudio.cpp @@ -64,6 +64,10 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder& } } + // Vorbis uses fixed 16-bit depth + if (options.Format == AudioFormat::Vorbis) + options.BitDepth = AudioTool::BitDepth::_16; + LOG_STR(Info, options.ToString()); // Open the file @@ -155,8 +159,9 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder& else { // Split audio data into a several chunks (uniform data spread) - const int32 MinChunkSize = 1 * 1024 * 1024; // 1 MB - const int32 chunkSize = Math::Max(MinChunkSize, (int32)Math::AlignUp(bufferSize / ASSET_FILE_DATA_CHUNKS, 256)); + const int32 minChunkSize = 1 * 1024 * 1024; // 1 MB + const int32 dataAlignment = info.NumChannels * bytesPerSample; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes) + const int32 chunkSize = Math::Max(minChunkSize, (int32)Math::AlignUp(bufferSize / ASSET_FILE_DATA_CHUNKS, dataAlignment)); const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize); ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS); From a698095bdd647867b571fea566010ad05454fce8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Oct 2023 14:34:08 +0200 Subject: [PATCH 80/80] Fix spatial audio playback when clip is set after the audio source was enabled #1622 --- Source/Engine/Audio/AudioSource.cpp | 8 ++++++++ Source/Engine/Audio/AudioSource.h | 1 + 2 files changed, 9 insertions(+) diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index 93c2e18df..afe5c4b49 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -265,6 +265,7 @@ void AudioSource::Cleanup() void AudioSource::OnClipChanged() { Stop(); + _clipChanged = true; } void AudioSource::OnClipLoaded() @@ -318,6 +319,12 @@ void AudioSource::SetNonStreamingBuffer() void AudioSource::PlayInternal() { + if (_clipChanged && SourceIDs.HasItems()) + { + // If clip was changed between source setup (OnEnable) and actual playback start then ensure to flush any runtime properties with the audio backend + _clipChanged = false; + AudioBackend::Source::SpatialSetupChanged(this); + } AudioBackend::Source::Play(this); _isActuallyPlayingSth = true; @@ -482,6 +489,7 @@ void AudioSource::OnEnable() { _prevPos = GetPosition(); _velocity = Vector3::Zero; + _clipChanged = false; Audio::OnAddSource(this); GetScene()->Ticking.Update.AddTick(this); diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index 3b6f32153..70cdb4180 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -53,6 +53,7 @@ private: bool _loop; bool _playOnStart; bool _allowSpatialization; + bool _clipChanged = false; bool _isActuallyPlayingSth = false; bool _needToUpdateStreamingBuffers = false;