From d2dba124df294a3e221ba2775fea2f11c2a24d87 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Oct 2024 13:12:48 +0200 Subject: [PATCH 001/203] add model import option to only create material slots but not import actual materials --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 6 +++++- Source/Engine/Tools/ModelTool/ModelTool.h | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 408fcb887..212ccdd4f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1430,7 +1430,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option auto& texture = data.Textures[i]; // Auto-import textures - if (autoImportOutput.IsEmpty() || EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures) || texture.FilePath.IsEmpty()) + if (autoImportOutput.IsEmpty() || EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures) || texture.FilePath.IsEmpty() || options.CreateEmptyMaterialSlots) continue; String assetPath = GetAdditionalImportPath(autoImportOutput, importedFileNames, StringUtils::GetFileNameWithoutExtension(texture.FilePath)); #if COMPILE_WITH_ASSETS_IMPORTER @@ -1486,6 +1486,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } + // The rest of the steps this function performs become irrelevant when we're only creating slots. + if (options.CreateEmptyMaterialSlots) + continue; + if (options.ImportMaterialsAsInstances) { // Create material instance diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 708b94342..2ea76383c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -273,16 +273,19 @@ public: public: // Materials // 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))") + API_FIELD(Attributes="EditorOrder(399), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportMaterials = true; + // If checked, the importer will create empty material slots for every material. + API_FIELD(Attributes = "EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") + bool CreateEmptyMaterialSlots; // 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)), VisibleIf(nameof(ShowGeometry))") + API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)") bool ImportMaterialsAsInstances = false; // The material used as the base material that will be instanced as the imported model's material. - API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))") + API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)") 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))") + API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)") bool ImportTextures = true; // If checked, the importer will try to keep the model's current overridden material slots, instead of importing materials from the source file. API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))") From fccd43502bb3153632cdf3fbc6d0ad331074c952 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sun, 4 May 2025 21:14:10 +0200 Subject: [PATCH 002/203] add create file Content Panel toolstrip button --- .../Windows/ContentWindow.ContextMenu.cs | 86 +++++++++++++------ Source/Editor/Windows/ContentWindow.cs | 26 +++++- 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 1ad225997..40dd9131b 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -39,8 +39,7 @@ namespace FlaxEditor.Windows folder = CurrentViewFolder; } Assert.IsNotNull(folder); - bool isRootFolder = CurrentViewFolder == _root.Folder; - + // Create context menu ContextMenuButton b; ContextMenu cm = new ContextMenu @@ -78,7 +77,7 @@ namespace FlaxEditor.Windows cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path))); if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text)) - { + { cm.AddButton("Show in Content Panel", () => { Editor.Instance.Windows.ContentWin.ClearItemsSearch(); @@ -178,19 +177,61 @@ namespace FlaxEditor.Windows cm.AddSeparator(); + CreateNewModuleMenu(cm, folder); + CreateNewFolderMenu(cm, folder, false, item); + CreateNewContentItemMenu(cm, folder); + + if (folder.CanHaveAssets) + { + cm.AddButton("Import file", () => + { + _view.ClearSelection(); + Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder); + }); + } + + // Remove any leftover separator + if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator) + cm.ItemsContainer.Children.Last().Dispose(); + + // Show it + cm.Show(this, location); + } + + private void CreateNewModuleMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false) + { // Check if is source folder to add new module if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source) { - var button = cm.AddButton("New module"); + var button = menu.AddButton("New module"); button.CloseMenuOnClick = false; button.Clicked += () => NewModule(button, parentFolderNode.Source.Path); } - - if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) + else if (disableUncreatable) { - cm.AddButton("New folder", NewFolder); + var button = menu.AddButton("New module"); + button.Enabled = false; } + } + private bool CanCreateFolder(ContentItem item = null) + { + bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode); + return canCreateFolder; + } + + private void CreateNewFolderMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false, ContentItem item = null) + { + bool canCreateFolder = CanCreateFolder(item); + if (canCreateFolder || disableUncreatable) + { + var b = menu.AddButton("New folder", NewFolder); + b.Enabled = canCreateFolder; + } + } + + private void CreateNewContentItemMenu(ContextMenu menu, ContentFolder folder, bool showNew = true, bool disableUncreatable = false) + { // Loop through each proxy and user defined json type and add them to the context menu var actorType = new ScriptType(typeof(Actor)); var scriptType = new ScriptType(typeof(Script)); @@ -230,7 +271,8 @@ namespace FlaxEditor.Windows if (p == null) continue; - if (p.CanCreate(folder)) + bool canCreate = p.CanCreate(folder); + if (canCreate || disableUncreatable) { var parts = attribute.Path.Split('/'); ContextMenuChildMenu childCM = null; @@ -238,16 +280,20 @@ namespace FlaxEditor.Windows for (int i = 0; i < parts?.Length; i++) { var part = parts[i].Trim(); + if (part == "New" && !showNew) + continue; if (i == parts.Length - 1) { if (mainCM) { - cm.AddButton(part, () => NewItem(p)); + var b = menu.AddButton(part, () => NewItem(p)); + b.Enabled = canCreate; mainCM = false; } else if (childCM != null) { - childCM.ContextMenu.AddButton(part, () => NewItem(p)); + var b = childCM.ContextMenu.AddButton(part, () => NewItem(p)); + b.Enabled = canCreate; childCM.ContextMenu.AutoSort = true; } } @@ -255,35 +301,21 @@ namespace FlaxEditor.Windows { if (mainCM) { - childCM = cm.GetOrAddChildMenu(part); + childCM = menu.GetOrAddChildMenu(part); childCM.ContextMenu.AutoSort = true; + childCM.Enabled = canCreate; mainCM = false; } else if (childCM != null) { childCM = childCM.ContextMenu.GetOrAddChildMenu(part); childCM.ContextMenu.AutoSort = true; + childCM.Enabled = canCreate; } } } } } - - if (folder.CanHaveAssets) - { - cm.AddButton("Import file", () => - { - _view.ClearSelection(); - Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder); - }); - } - - // Remove any leftover separator - if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator) - cm.ItemsContainer.Children.Last().Dispose(); - - // Show it - cm.Show(this, location); } private void OnExpandAllClicked(ContextMenuButton button) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index f63db04bd..c87550b26 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -37,6 +37,7 @@ namespace FlaxEditor.Windows private readonly ToolStrip _toolStrip; private readonly ToolStripButton _importButton; + private readonly ToolStripButton _createNewButton; private readonly ToolStripButton _navigateBackwardButton; private readonly ToolStripButton _navigateForwardButton; private readonly ToolStripButton _navigateUpButton; @@ -153,11 +154,12 @@ namespace FlaxEditor.Windows { Parent = this, }; - _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content"); + _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content."); + _createNewButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Add64, OnCreateNewItemButtonClicked).LinkTooltip("Create a new asset."); _toolStrip.AddSeparator(); - _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward"); - _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward"); - _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up"); + _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward."); + _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward."); + _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up."); _toolStrip.AddSeparator(); // Navigation bar @@ -267,6 +269,22 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus()); } + private void OnCreateNewItemButtonClicked() + { + if (Input.GetKey(KeyboardKeys.Shift) && CanCreateFolder()) + { + NewFolder(); + return; + } + + var menu = new ContextMenu(); + CreateNewFolderMenu(menu, CurrentViewFolder, true); + CreateNewModuleMenu(menu, CurrentViewFolder, true); + menu.AddSeparator(); + CreateNewContentItemMenu(menu, CurrentViewFolder, false, true); + menu.Show(this, new Float2(1, _createNewButton.Bottom)); + } + private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) { var menu = new ContextMenu(); From c45cba18a6321cd0783e26299019de481386f6e0 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sun, 4 May 2025 21:18:00 +0200 Subject: [PATCH 003/203] fix menu positioning --- 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 c87550b26..318bf5231 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -282,7 +282,7 @@ namespace FlaxEditor.Windows CreateNewModuleMenu(menu, CurrentViewFolder, true); menu.AddSeparator(); CreateNewContentItemMenu(menu, CurrentViewFolder, false, true); - menu.Show(this, new Float2(1, _createNewButton.Bottom)); + menu.Show(this, _createNewButton.UpperLeft, ContextMenuDirection.RightUp); } private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) From 2901e3898c2878bc0e0df10c8d02f5a8b002c7cd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 29 Aug 2025 16:03:44 -0500 Subject: [PATCH 004/203] Add `IncludeInheritedTypes` to `RequireActor` --- .../Editor/CustomEditors/Dedicated/ScriptsEditor.cs | 13 +++++++------ .../Attributes/Editor/RequireActorAttribute.cs | 9 ++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 123c7252a..685bf9dfc 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -102,11 +102,12 @@ namespace FlaxEditor.CustomEditors.Dedicated var actors = ScriptsEditor.ParentEditor.Values; foreach (var a in actors) { - if (a.GetType() != requireActor.RequiredType) - { - item.Enabled = false; - break; - } + if (a.GetType() == requireActor.RequiredType) + continue; + if (requireActor.IncludeInheritedTypes && a.GetType().IsSubclassOf(requireActor.RequiredType)) + continue; + item.Enabled = false; + break; } } cm.AddItem(item); @@ -827,7 +828,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (attribute != null) { var actor = script.Actor; - if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType)) + if (actor.GetType() != attribute.RequiredType && (attribute.IncludeInheritedTypes && !actor.GetType().IsSubclassOf(attribute.RequiredType))) { Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`."); hasAllRequirements = false; diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs index 6e08217bf..3e5ad7f73 100644 --- a/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs @@ -13,13 +13,20 @@ public class RequireActorAttribute : Attribute /// The required type. /// public Type RequiredType; + + /// + /// Whether to include inherited types. + /// + public bool IncludeInheritedTypes; /// /// Initializes a new instance of the class. /// /// The required type. - public RequireActorAttribute(Type type) + /// Whether to include inherited types. + public RequireActorAttribute(Type type, bool includeInheritedTypes = false) { RequiredType = type; + IncludeInheritedTypes = includeInheritedTypes; } } From 9a363e2882b2cca89702ea77fea477200733e575 Mon Sep 17 00:00:00 2001 From: alsed Date: Sat, 30 Aug 2025 08:51:21 -0400 Subject: [PATCH 005/203] Implement prefab detection of skinned models --- .../Engine/ContentImporters/ImportModel.cpp | 40 ++++++++++++++----- Source/Engine/Tools/ModelTool/ModelTool.cpp | 23 +++++++---- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index be71236ab..a007a740d 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -19,6 +19,7 @@ #include "Engine/Animations/AnimEvent.h" #include "Engine/Level/Actors/EmptyActor.h" #include "Engine/Level/Actors/StaticModel.h" +#include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scripts/ModelPrefab.h" @@ -84,6 +85,7 @@ struct PrefabObject int32 NodeIndex; String Name; String AssetPath; + bool IsSkinned = false; }; void RepackMeshLightmapUVs(ModelData& data) @@ -320,8 +322,10 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions); }; + auto splitOptions = options; LOG(Info, "Splitting imported {0} meshes", meshesByName.Count()); + PrefabObject prefabObject; for (int32 groupIndex = 0; groupIndex < meshesByName.Count(); groupIndex++) { @@ -331,7 +335,17 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) prefabObject.NodeIndex = group.First()->NodeIndex; prefabObject.Name = group.First()->Name; + // Defaul value for ModelType splitOptions.Type = ModelTool::ModelType::Model; + + // Search for Skinned Model + if (group.First()->BlendShapes.HasItems()) + { + LOG(Info, "Mesh {0} is Skinned", prefabObject.Name); + splitOptions.Type = ModelTool::ModelType::SkinnedModel; + prefabObject.IsSkinned = true; + } + splitOptions.ObjectIndex = groupIndex; if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath, group.First())) { @@ -594,8 +608,8 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context) CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); - IMPORT_SETUP(Model, Model::SerializedVersion); - static_assert(Model::SerializedVersion == 30, "Update code."); + IMPORT_SETUP(Model, Model::SerializedVersion); + static_assert(Model::SerializedVersion == 30, "Update code."); // Save model header MemoryWriteStream stream(4096); @@ -722,22 +736,30 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M { if (e.NodeIndex == nodeIndex) { - auto* actor = New(); - actor->SetName(e.Name); - if (auto* model = Content::LoadAsync(e.AssetPath)) + if(e.IsSkinned) { - actor->Model = model; + LOG(Info,"Model {0} is Animated", e.Name); + auto* actor = New(); + actor->SetName(e.Name); + if (auto* skinnedModel = Content::LoadAsync(e.AssetPath)) + actor->SkinnedModel = skinnedModel; + nodeActors.Add(actor); + } + else + { + auto* actor = New(); + actor->SetName(e.Name); + if (auto* model = Content::LoadAsync(e.AssetPath)) + actor->Model = model; + nodeActors.Add(actor); } - nodeActors.Add(actor); } } Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New(); if (nodeActors.Count() > 1) { for (Actor* e : nodeActors) - { e->SetParent(nodeActor); - } } if (nodeActors.Count() != 1) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index fe268c350..cc483f333 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1131,7 +1131,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option options.ImportTypes |= ImportDataTypes::Skeleton; break; case ModelType::Prefab: - options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations; + options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Skeleton | ImportDataTypes::Animations; if (options.ImportMaterials) options.ImportTypes |= ImportDataTypes::Materials; if (options.ImportTextures) @@ -1157,6 +1157,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option { for (auto& mesh : lod.Meshes) { + if (mesh->BlendShapes.IsEmpty()) + continue; + for (int32 blendShapeIndex = mesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--) { auto& blendShape = mesh->BlendShapes[blendShapeIndex]; @@ -2111,12 +2114,13 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option #undef REMAP_VERTEX_BUFFER // Remap blend shapes - dstMesh->BlendShapes.Resize(srcMesh->BlendShapes.Count()); + dstMesh->BlendShapes.Clear(); + dstMesh->BlendShapes.EnsureCapacity(srcMesh->BlendShapes.Count(), false); for (int32 blendShapeIndex = 0; blendShapeIndex < srcMesh->BlendShapes.Count(); blendShapeIndex++) { const auto& srcBlendShape = srcMesh->BlendShapes[blendShapeIndex]; - auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex]; - + //auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex]; + BlendShape dstBlendShape; dstBlendShape.Name = srcBlendShape.Name; dstBlendShape.Weight = srcBlendShape.Weight; dstBlendShape.Vertices.EnsureCapacity(srcBlendShape.Vertices.Count()); @@ -2125,19 +2129,21 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option auto v = srcBlendShape.Vertices[i]; v.VertexIndex = remap[v.VertexIndex]; if (v.VertexIndex != ~0u) - { dstBlendShape.Vertices.Add(v); - } } + + if (dstBlendShape.Vertices.HasItems()) + dstMesh->BlendShapes.Add(dstBlendShape); } + /* // Remove empty blend shapes for (int32 blendShapeIndex = dstMesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--) { if (dstMesh->BlendShapes[blendShapeIndex].Vertices.IsEmpty()) dstMesh->BlendShapes.RemoveAt(blendShapeIndex); } - + */ // Optimize generated LOD meshopt_optimizeVertexCache(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, dstMeshVertexCount); meshopt_optimizeOverdraw(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, (const float*)dstMesh->Positions.Get(), dstMeshVertexCount, sizeof(Float3), 1.05f); @@ -2182,6 +2188,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option { for (auto& mesh : lod.Meshes) { + if (mesh->BlendShapes.IsEmpty()) + continue; + for (auto& blendShape : mesh->BlendShapes) { // Compute min/max for used vertex indices From f588d6da51c218a70e2f3c8d9b11cca6b74242f6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 2 Sep 2025 20:33:41 -0500 Subject: [PATCH 006/203] Add ability to loop root node in behavior trees. --- Source/Engine/AI/Behavior.cpp | 14 +++++++++++++- Source/Engine/AI/BehaviorTreeNodes.h | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index e70ca50a4..f0c4acee3 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -106,7 +106,19 @@ void Behavior::UpdateAsync() const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) _result = result; - if (_result != BehaviorUpdateResult::Running) + if (_result != BehaviorUpdateResult::Running && tree->Graph.Root->Loop) + { + // Ensure to have tree loaded on play + CHECK(Tree && !Tree->WaitForLoaded()); + BehaviorTree* tree = Tree.Get(); + CHECK(tree->Graph.Root); + + // Setup state + _result = BehaviorUpdateResult::Running; + _accumulatedTime = 0.0f; + _totalTime = 0; + } + else if (_result != BehaviorUpdateResult::Running) { Finished(); } diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 7aaab5f3c..05a472da7 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -96,6 +96,10 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTre // The target amount of the behavior logic updates per second. API_FIELD(Attributes="EditorOrder(100)") float UpdateFPS = 10.0f; + + // Whether to loop the root node. + API_FIELD(Attributes="EditorOrder(200)") + bool Loop = true; }; /// From cd0878f81064791f4f94c9faee8c47c6e1af4ead Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 2 Sep 2025 20:47:04 -0500 Subject: [PATCH 007/203] Simplify code --- Source/Engine/AI/Behavior.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index f0c4acee3..d727bdef1 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -108,12 +108,7 @@ void Behavior::UpdateAsync() _result = result; if (_result != BehaviorUpdateResult::Running && tree->Graph.Root->Loop) { - // Ensure to have tree loaded on play - CHECK(Tree && !Tree->WaitForLoaded()); - BehaviorTree* tree = Tree.Get(); - CHECK(tree->Graph.Root); - - // Setup state + // Reset State _result = BehaviorUpdateResult::Running; _accumulatedTime = 0.0f; _totalTime = 0; From a8768f918eb28c0d4e2d3a6da1fe0b57861c2490 Mon Sep 17 00:00:00 2001 From: alsed Date: Sun, 7 Sep 2025 13:45:21 -0300 Subject: [PATCH 008/203] Add more conditions for skeleton import and add blendshapes for prefab recognition --- Source/Engine/ContentImporters/ImportModel.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index a007a740d..f1d83daa7 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -339,7 +339,7 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) splitOptions.Type = ModelTool::ModelType::Model; // Search for Skinned Model - if (group.First()->BlendShapes.HasItems()) + if (group.First()->BlendWeights.HasItems() || group.First()->BlendShapes.HasItems() ) { LOG(Info, "Mesh {0} is Skinned", prefabObject.Name); splitOptions.Type = ModelTool::ModelType::SkinnedModel; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index f5b043994..a61f19668 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -765,7 +765,7 @@ bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& o } // Import skeleton - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton)) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && context.Bones.HasItems()) { data.Skeleton.Nodes.Resize(context.Nodes.Count(), false); for (int32 i = 0; i < context.Nodes.Count(); i++) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 81ce46d6c..e530e7b7c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -1404,7 +1404,7 @@ bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& } // Import skeleton - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton)) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && context.Bones.HasItems()) { data.Skeleton.Nodes.Resize(context.Nodes.Count(), false); for (int32 i = 0; i < context.Nodes.Count(); i++) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index cc483f333..103797781 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1184,7 +1184,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton)) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && data.Skeleton.Bones.HasItems()) { LOG(Info, "Imported skeleton has {0} bones and {1} nodes", data.Skeleton.Bones.Count(), data.Nodes.Count()); @@ -1324,7 +1324,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option for (int32 i = 0; i < meshesCount; i++) { const auto mesh = data.LODs[0].Meshes[i]; - if (mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) + if ((mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) && data.Skeleton.Bones.HasItems()) { auto indices = Int4::Zero; auto weights = Float4::UnitX; From 52ee8b395328cf6094ca0566f7d97dc0176dc780 Mon Sep 17 00:00:00 2001 From: alsed Date: Sun, 7 Sep 2025 20:40:28 -0300 Subject: [PATCH 009/203] better comment for animated prefab and remove unused code in remap blendshapes --- Source/Engine/ContentImporters/ImportModel.cpp | 6 +++--- Source/Engine/Tools/ModelTool/ModelTool.cpp | 11 +---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index f1d83daa7..5a3a34735 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -608,8 +608,8 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context) CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); - IMPORT_SETUP(Model, Model::SerializedVersion); - static_assert(Model::SerializedVersion == 30, "Update code."); + IMPORT_SETUP(Model, Model::SerializedVersion); + static_assert(Model::SerializedVersion == 30, "Update code."); // Save model header MemoryWriteStream stream(4096); @@ -738,7 +738,7 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M { if(e.IsSkinned) { - LOG(Info,"Model {0} is Animated", e.Name); + LOG(Info,"Creating animated model prefab {0}.", e.Name); auto* actor = New(); actor->SetName(e.Name); if (auto* skinnedModel = Content::LoadAsync(e.AssetPath)) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 103797781..ebd19e169 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -2114,12 +2114,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option #undef REMAP_VERTEX_BUFFER // Remap blend shapes - dstMesh->BlendShapes.Clear(); dstMesh->BlendShapes.EnsureCapacity(srcMesh->BlendShapes.Count(), false); for (int32 blendShapeIndex = 0; blendShapeIndex < srcMesh->BlendShapes.Count(); blendShapeIndex++) { const auto& srcBlendShape = srcMesh->BlendShapes[blendShapeIndex]; - //auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex]; BlendShape dstBlendShape; dstBlendShape.Name = srcBlendShape.Name; dstBlendShape.Weight = srcBlendShape.Weight; @@ -2132,18 +2130,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option dstBlendShape.Vertices.Add(v); } + // Add only valid blend shapes if (dstBlendShape.Vertices.HasItems()) dstMesh->BlendShapes.Add(dstBlendShape); } - /* - // Remove empty blend shapes - for (int32 blendShapeIndex = dstMesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--) - { - if (dstMesh->BlendShapes[blendShapeIndex].Vertices.IsEmpty()) - dstMesh->BlendShapes.RemoveAt(blendShapeIndex); - } - */ // Optimize generated LOD meshopt_optimizeVertexCache(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, dstMeshVertexCount); meshopt_optimizeOverdraw(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, (const float*)dstMesh->Positions.Get(), dstMeshVertexCount, sizeof(Float3), 1.05f); From 05f08db66eacad3b28bc4d0764ecbb29e0e424b3 Mon Sep 17 00:00:00 2001 From: alsed Date: Mon, 8 Sep 2025 01:28:49 -0300 Subject: [PATCH 010/203] Fixed a crash when model with no Bones was imported as Skinned Model --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index ebd19e169..6820532bb 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1119,6 +1119,12 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option options.ImportTypes |= ImportDataTypes::Textures; break; case ModelType::SkinnedModel: + if (!data.Skeleton.Bones.HasItems()) + { + LOG(Warning, "Model is not Skinned, it will be imported as Static"); + options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes; + options.Type = ModelType::Model; + } options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Skeleton; if (options.ImportMaterials) options.ImportTypes |= ImportDataTypes::Materials; @@ -1184,7 +1190,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && data.Skeleton.Bones.HasItems()) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) + && (data.Skeleton.Bones.HasItems() || data.LODs[0].Meshes[0]->BlendShapes.HasItems())) { LOG(Info, "Imported skeleton has {0} bones and {1} nodes", data.Skeleton.Bones.Count(), data.Nodes.Count()); From fb5cedc575bc9a6b1fbb80cbe5421a5024cbd863 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 19 Sep 2025 16:32:59 +0200 Subject: [PATCH 011/203] make particle age/ lifetime nodes more clear --- Source/Editor/Surface/Archetypes/Particles.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 5b8c05381..3747e2a60 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -436,10 +436,11 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 102, - Title = "Particle Lifetime", - Description = "Particle lifetime (in seconds).", + Title = "Total Lifetime", + Description = "Total particle lifetime (in seconds), assigned when the particle is created. Always the same, no matter the particles age.", + AlternativeTitles = new[] { "Age" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(200, 30), + Size = new Float2(180, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), @@ -449,9 +450,10 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 103, Title = "Particle Age", - Description = "Particle age (in seconds).", + Description = "Particle age (in seconds). How long the particle has been alive since it was created.", + AlternativeTitles = new[] { "Lifetime" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(200, 30), + Size = new Float2(170, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), @@ -533,9 +535,10 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 110, Title = "Particle Normalized Age", - Description = "Particle normalized age to range 0-1 (age divided by lifetime).", + Description = "Particle normalized age represented as from 0 (max lifetime) - 1 (max age) (age divided by lifetime).", + AlternativeTitles = new[] { "Lifetime" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(230, 30), + Size = new Float2(250, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), From 07c1dfc61329a92fe8a52420da539bdcf9b20d5b Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 19 Sep 2025 20:23:47 +0200 Subject: [PATCH 012/203] add focus selected visject controls functionality --- Source/Editor/Options/InputOptions.cs | 4 +++ Source/Editor/Surface/VisjectSurface.Input.cs | 4 ++- Source/Editor/Surface/VisjectSurface.cs | 30 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index af919c1f3..624daf890 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -694,6 +694,10 @@ namespace FlaxEditor.Options [EditorDisplay("Node Editors"), EditorOrder(4580)] public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt); + [DefaultValue(typeof(InputBinding), "Shift+F")] + [EditorDisplay("Node Editors"), EditorOrder(4590)] + public InputBinding FocusSelectedNodes = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift); + #endregion } } diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 09df195eb..72594eb5f 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.Surface private HashSet _movingNodes; private HashSet _temporarySelectedNodes; private readonly Stack _inputBrackets = new Stack(); + private InputBinding _focusSelectedNodeBinding; private class InputBracket { @@ -844,7 +845,8 @@ namespace FlaxEditor.Surface private void CurrentInputTextChanged(string currentInputText) { - if (string.IsNullOrEmpty(currentInputText)) + // Check if focus selected nodes binding is being pressed to prevent it triggering primary menu + if (string.IsNullOrEmpty(currentInputText) || _focusSelectedNodeBinding.Process(RootWindow)) return; if (IsPrimaryMenuOpened || !CanEdit) { diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 8cbcb4a21..fb0d1f576 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -425,6 +425,7 @@ namespace FlaxEditor.Surface new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }), new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }), new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }), + new InputActionsContainer.Binding(options => options.FocusSelectedNodes, () => { ShowSelection(); }), }); Context.ControlSpawned += OnSurfaceControlSpawned; @@ -436,7 +437,10 @@ namespace FlaxEditor.Surface DragHandlers.Add(_dragAssets = new DragAssets(ValidateDragItem)); DragHandlers.Add(_dragParameters = new DragNames(SurfaceParameter.DragPrefix, ValidateDragParameter)); + OnEditorOptionsChanged(Editor.Instance.Options.Options); + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged; } private void OnScriptsReloadBegin() @@ -446,6 +450,11 @@ namespace FlaxEditor.Surface _cmPrimaryMenu = null; } + private void OnEditorOptionsChanged(EditorOptions options) + { + _focusSelectedNodeBinding = options.Input.FocusSelectedNodes; + } + /// /// Gets the display name of the connection type used in the surface. /// @@ -643,6 +652,26 @@ namespace FlaxEditor.Surface ViewCenterPosition = areaRect.Center; } + /// + /// Shows the selected controls by changing the view scale and the position. + /// + public void ShowSelection() + { + var selection = SelectedControls; + if (selection.Count == 0) + return; + + // Calculate the bounds of all selected controls + Rectangle bounds = selection[0].Bounds; + for (int i = 1; i < selection.Count; i++) + bounds = Rectangle.Union(bounds, selection[i].Bounds); + + // Add margin + bounds = bounds.MakeExpanded(250.0f); + + ShowArea(bounds); + } + /// /// Shows the given surface node by changing the view scale and the position and focuses the node. /// @@ -1066,6 +1095,7 @@ namespace FlaxEditor.Surface _cmPrimaryMenu?.Dispose(); ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged; base.OnDestroy(); } From 1dd96ae9cbc219749f445ab551414150fbed25a7 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 19 Sep 2025 23:04:43 +0200 Subject: [PATCH 013/203] add undo to MoveSelectedNodes (do TODO) --- Source/Editor/Surface/SurfaceUtils.cs | 10 +++++----- Source/Editor/Surface/VisjectSurface.Input.cs | 10 +++++++++- Source/Editor/Windows/Assets/AssetEditorWindow.cs | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index d75efb5a0..bf70ca2e9 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -573,13 +573,13 @@ namespace FlaxEditor.Surface var showSearch = () => editor.ContentFinding.ShowSearch(window); // Toolstrip - saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save", ref inputOptions.Save); + saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save.", ref inputOptions.Save); toolStrip.AddSeparator(); - undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo); - redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo); + undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo.", ref inputOptions.Undo); + redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo.", ref inputOptions.Redo); toolStrip.AddSeparator(); - toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search); - toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph"); + toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool.", ref inputOptions.Search); + toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph."); var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping); gridSnapButton.LinkTooltip("Toggle grid snapping for nodes."); gridSnapButton.AutoCheck = true; diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 72594eb5f..1ebd4fdee 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -702,13 +702,21 @@ namespace FlaxEditor.Surface private void MoveSelectedNodes(Float2 delta) { - // TODO: undo + List undoActions = new List(); + delta /= _targetScale; OnGetNodesToMove(); foreach (var node in _movingNodes) + { node.Location += delta; + if (Undo != null) + undoActions.Add(new MoveNodesAction(Context, new[] { node.ID }, delta)); + } _isMovingSelection = false; MarkAsEdited(false); + + if (undoActions.Count > 0) + Undo?.AddAction(new MultiUndoAction(undoActions, "Moved ")); } /// diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index 646112441..eeac84b60 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Windows.Assets { Parent = this }; - _toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window"); + _toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window."); InputActions.Add(options => options.Save, Save); From 0917a743cd8d91ca56eadf710f538dc81b146f34 Mon Sep 17 00:00:00 2001 From: Saas Date: Sun, 21 Sep 2025 17:07:13 +0200 Subject: [PATCH 014/203] Re- add particle prefix and improve some descriptions --- Source/Editor/Surface/Archetypes/Particles.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 3747e2a60..59af7af5e 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -436,11 +436,11 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 102, - Title = "Total Lifetime", - Description = "Total particle lifetime (in seconds), assigned when the particle is created. Always the same, no matter the particles age.", + Title = "Particle Total Lifetime", + Description = "Total particle lifetime (in seconds) at the time when the particle was created. Always the same, no matter the particles age.", AlternativeTitles = new[] { "Age" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(180, 30), + Size = new Float2(250, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), @@ -535,7 +535,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 110, Title = "Particle Normalized Age", - Description = "Particle normalized age represented as from 0 (max lifetime) - 1 (max age) (age divided by lifetime).", + Description = "The normalized age of the particle, represented as 0 (max lifetime) to 1 (max age). (Same as age divided by lifetime.)", AlternativeTitles = new[] { "Lifetime" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, Size = new Float2(250, 30), From 2516820e4a32c6f6d06637134ad7b935145879f6 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 3 Oct 2025 18:46:18 +0200 Subject: [PATCH 015/203] fix reloading scripts expands all folders --- Source/Editor/Windows/ContentWindow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index d0cf84251..390658add 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -1086,7 +1086,8 @@ namespace FlaxEditor.Windows _tree.Select(folder.Node); } - OnFoldersSearchBoxTextChanged(); + if (!string.IsNullOrWhiteSpace(_foldersSearchBox.Text)) + OnFoldersSearchBoxTextChanged(); } private void Refresh() From 7ba01a413fc198f68334cf6c5d1449d957973507 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 3 Oct 2025 18:50:41 +0200 Subject: [PATCH 016/203] Revert "fix reloading scripts expands all folders" This reverts commit 2516820e4a32c6f6d06637134ad7b935145879f6. --- Source/Editor/Windows/ContentWindow.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 390658add..d0cf84251 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -1086,8 +1086,7 @@ namespace FlaxEditor.Windows _tree.Select(folder.Node); } - if (!string.IsNullOrWhiteSpace(_foldersSearchBox.Text)) - OnFoldersSearchBoxTextChanged(); + OnFoldersSearchBoxTextChanged(); } private void Refresh() From 2d4843b1f41a6e5b2b2f5d5ca342e1ab8dc2a5e1 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 3 Oct 2025 20:41:52 +0200 Subject: [PATCH 017/203] fix reloading scripts expanding scene tree #3701 --- Source/Editor/Content/Tree/ContentTreeNode.cs | 14 ++++++-------- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 11 +++++------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 8629296fc..ca629000d 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -175,15 +175,13 @@ namespace FlaxEditor.Content } } - bool isExpanded = isAnyChildVisible; - - if (isExpanded) + if (!noFilter) { - Expand(true); - } - else - { - Collapse(true); + bool isExpanded = isAnyChildVisible; + if (isExpanded) + Expand(true); + else + Collapse(true); } Visible = isThisVisible | isAnyChildVisible; diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 5f56e918a..4607a4f63 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -324,13 +324,12 @@ namespace FlaxEditor.SceneGraph.GUI isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id); } - if (isExpanded) + if (!noFilter) { - Expand(true); - } - else - { - Collapse(true); + if (isExpanded) + Expand(true); + else + Collapse(true); } Visible = isThisVisible | isAnyChildVisible; From ec08a6ca7275bcc463a4a659767f960ce1b95e5d Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 3 Oct 2025 20:44:35 +0200 Subject: [PATCH 018/203] Reapply "Merge branch 'NoClearSearchboxesWhenStuffHappens' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-NoClearSearchboxesWhenStuffHappens" This reverts commit 537d8b57ca2457496322754fdc3e630d16fe28a3. --- Source/Editor/Windows/ContentWindow.Search.cs | 1 + Source/Editor/Windows/ContentWindow.cs | 2 ++ Source/Editor/Windows/SceneTreeWindow.cs | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index f28dc4834..5a0ed63aa 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -115,6 +115,7 @@ namespace FlaxEditor.Windows var root = _root; root.LockChildrenRecursive(); + PerformLayout(); // Update tree var query = _foldersSearchBox.Text; diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 3d2ec4a66..d0cf84251 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -1085,6 +1085,8 @@ namespace FlaxEditor.Windows if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder) _tree.Select(folder.Node); } + + OnFoldersSearchBoxTextChanged(); } private void Refresh() diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 75a3723cf..ab6d2e262 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -68,6 +68,7 @@ namespace FlaxEditor.Windows TooltipText = "Search the scene tree.\n\nYou can prefix your search with different search operators:\ns: -> Actor with script of type\na: -> Actor type\nc: -> Control type", }; _searchBox.TextChanged += OnSearchBoxTextChanged; + ScriptsBuilder.ScriptsReloadEnd += OnSearchBoxTextChanged; // Scene tree panel _sceneTreePanel = new Panel @@ -112,7 +113,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection()); InputActions.Add(options => options.Rename, RenameSelection); } - + /// public override void OnPlayBeginning() { @@ -125,6 +126,7 @@ namespace FlaxEditor.Windows { base.OnPlayBegin(); _blockSceneTreeScroll = false; + OnSearchBoxTextChanged(); } /// @@ -139,6 +141,7 @@ namespace FlaxEditor.Windows { base.OnPlayEnd(); _blockSceneTreeScroll = true; + OnSearchBoxTextChanged(); } /// @@ -174,6 +177,7 @@ namespace FlaxEditor.Windows return; _tree.LockChildrenRecursive(); + PerformLayout(); // Update tree var query = _searchBox.Text; @@ -599,6 +603,7 @@ namespace FlaxEditor.Windows _dragHandlers = null; _tree = null; _searchBox = null; + ScriptsBuilder.ScriptsReloadEnd -= OnSearchBoxTextChanged; base.OnDestroy(); } From 18a0e8d6122303dba3a75af1e6e7adf2d2327e8e Mon Sep 17 00:00:00 2001 From: frank Date: Sat, 4 Oct 2025 22:37:37 +0800 Subject: [PATCH 019/203] add msdfgen for windows --- Source/Engine/Render2D/Render2D.Build.cs | 1 + Source/ThirdParty/msdfgen/LICENSE.txt | 21 +++ Source/ThirdParty/msdfgen/msdfgen.Build.cs | 45 +++++ Source/ThirdParty/msdfgen/msdfgen.h | 4 + .../ThirdParty/msdfgen/msdfgen/core/Bitmap.h | 50 ++++++ .../msdfgen/msdfgen/core/Bitmap.hpp | 117 ++++++++++++ .../msdfgen/msdfgen/core/BitmapRef.hpp | 41 +++++ .../ThirdParty/msdfgen/msdfgen/core/Contour.h | 34 ++++ .../msdfgen/msdfgen/core/DistanceMapping.h | 36 ++++ .../msdfgen/msdfgen/core/EdgeColor.h | 20 +++ .../msdfgen/msdfgen/core/EdgeHolder.h | 41 +++++ .../msdfgen/core/MSDFErrorCorrection.h | 55 ++++++ .../msdfgen/msdfgen/core/Projection.h | 37 ++++ .../ThirdParty/msdfgen/msdfgen/core/Range.hpp | 46 +++++ .../msdfgen/msdfgen/core/SDFTransformation.h | 24 +++ .../msdfgen/msdfgen/core/Scanline.h | 56 ++++++ .../ThirdParty/msdfgen/msdfgen/core/Shape.h | 53 ++++++ .../msdfgen/core/ShapeDistanceFinder.h | 37 ++++ .../msdfgen/core/ShapeDistanceFinder.hpp | 60 +++++++ .../msdfgen/msdfgen/core/SignedDistance.hpp | 38 ++++ .../msdfgen/msdfgen/core/Vector2.hpp | 167 ++++++++++++++++++ .../msdfgen/msdfgen/core/arithmetics.hpp | 63 +++++++ Source/ThirdParty/msdfgen/msdfgen/core/base.h | 16 ++ .../msdfgen/core/bitmap-interpolation.hpp | 25 +++ .../msdfgen/msdfgen/core/contour-combiners.h | 47 +++++ .../msdfgen/core/convergent-curve-ordering.h | 11 ++ .../msdfgen/msdfgen/core/edge-coloring.h | 29 +++ .../msdfgen/msdfgen/core/edge-segments.h | 146 +++++++++++++++ .../msdfgen/msdfgen/core/edge-selectors.h | 117 ++++++++++++ .../msdfgen/msdfgen/core/equation-solver.h | 14 ++ .../msdfgen/msdfgen/core/export-svg.h | 11 ++ .../msdfgen/msdfgen/core/generator-config.h | 66 +++++++ .../msdfgen/core/msdf-error-correction.h | 40 +++++ .../msdfgen/msdfgen/core/pixel-conversion.hpp | 16 ++ .../msdfgen/msdfgen/core/rasterization.h | 25 +++ .../msdfgen/msdfgen/core/render-sdf.h | 23 +++ .../msdfgen/msdfgen/core/save-bmp.h | 16 ++ .../msdfgen/msdfgen/core/save-fl32.h | 12 ++ .../msdfgen/msdfgen/core/save-rgba.h | 16 ++ .../msdfgen/msdfgen/core/save-tiff.h | 13 ++ .../msdfgen/core/sdf-error-estimation.h | 30 ++++ .../msdfgen/msdfgen/core/shape-description.h | 15 ++ .../msdfgen/msdfgen/msdfgen-config.h | 13 ++ Source/ThirdParty/msdfgen/msdfgen/msdfgen.h | 78 ++++++++ .../msdfgen/msdfgen/msdfgen/msdfgen-config.h | 13 ++ .../Flax.Build/Deps/Dependencies/msdfgen.cs | 126 +++++++++++++ 46 files changed, 1964 insertions(+) create mode 100644 Source/ThirdParty/msdfgen/LICENSE.txt create mode 100644 Source/ThirdParty/msdfgen/msdfgen.Build.cs create mode 100644 Source/ThirdParty/msdfgen/msdfgen.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Contour.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Projection.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Range.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/SDFTransformation.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Scanline.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Shape.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/ShapeDistanceFinder.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/ShapeDistanceFinder.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/SignedDistance.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/Vector2.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/arithmetics.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/base.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/bitmap-interpolation.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/contour-combiners.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/convergent-curve-ordering.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/edge-coloring.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/edge-segments.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/edge-selectors.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/equation-solver.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/export-svg.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/generator-config.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/msdf-error-correction.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/pixel-conversion.hpp create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/rasterization.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/render-sdf.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/save-bmp.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/save-fl32.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/save-rgba.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/save-tiff.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/sdf-error-estimation.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/core/shape-description.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/msdfgen-config.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/msdfgen.h create mode 100644 Source/ThirdParty/msdfgen/msdfgen/msdfgen/msdfgen-config.h create mode 100644 Source/Tools/Flax.Build/Deps/Dependencies/msdfgen.cs diff --git a/Source/Engine/Render2D/Render2D.Build.cs b/Source/Engine/Render2D/Render2D.Build.cs index f0be43983..a8f09270c 100644 --- a/Source/Engine/Render2D/Render2D.Build.cs +++ b/Source/Engine/Render2D/Render2D.Build.cs @@ -14,6 +14,7 @@ public class Render2D : EngineModule base.Setup(options); options.PrivateDependencies.Add("freetype"); + options.PrivateDependencies.Add("msdfgen"); options.PrivateDefinitions.Add("RENDER2D_USE_LINE_AA"); } diff --git a/Source/ThirdParty/msdfgen/LICENSE.txt b/Source/ThirdParty/msdfgen/LICENSE.txt new file mode 100644 index 000000000..fbea359b2 --- /dev/null +++ b/Source/ThirdParty/msdfgen/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2014 - 2025 Viktor Chlumsky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Source/ThirdParty/msdfgen/msdfgen.Build.cs b/Source/ThirdParty/msdfgen/msdfgen.Build.cs new file mode 100644 index 000000000..4d4262960 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen.Build.cs @@ -0,0 +1,45 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System.IO; +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// https://github.com/Chlumsky/msdfgen +/// +public class msdfgen : DepsModule +{ + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.MIT; + LicenseFilePath = "LICENSE.TXT"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + var depsRoot = options.DepsFolder; + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + options.OutputFiles.Add(Path.Combine(depsRoot, "msdfgen-core.lib")); + break; + case TargetPlatform.Linux: + case TargetPlatform.Mac: + // todo + //options.OutputFiles.Add(Path.Combine(depsRoot, "msdfgen-core.a")); + break; + default: throw new InvalidPlatformException(options.Platform.Target); + } + + options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, @"Source\ThirdParty\msdfgen")); + } +} diff --git a/Source/ThirdParty/msdfgen/msdfgen.h b/Source/ThirdParty/msdfgen/msdfgen.h new file mode 100644 index 000000000..a0d69e2a5 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen.h @@ -0,0 +1,4 @@ + +#pragma once + +#include "msdfgen/msdfgen.h" diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h new file mode 100644 index 000000000..9a08749f4 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h @@ -0,0 +1,50 @@ + +#pragma once + +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class. +template +class Bitmap { + +public: + Bitmap(); + Bitmap(int width, int height); + Bitmap(const BitmapConstRef &orig); + Bitmap(const Bitmap &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap(Bitmap &&orig); +#endif + ~Bitmap(); + Bitmap &operator=(const BitmapConstRef &orig); + Bitmap &operator=(const Bitmap &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap &operator=(Bitmap &&orig); +#endif + /// Bitmap width in pixels. + int width() const; + /// Bitmap height in pixels. + int height() const; + T *operator()(int x, int y); + const T *operator()(int x, int y) const; +#ifdef MSDFGEN_USE_CPP11 + explicit operator T *(); + explicit operator const T *() const; +#else + operator T *(); + operator const T *() const; +#endif + operator BitmapRef(); + operator BitmapConstRef() const; + +private: + T *pixels; + int w, h; + +}; + +} + +#include "Bitmap.hpp" diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp new file mode 100644 index 000000000..940435778 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp @@ -0,0 +1,117 @@ + +#include "Bitmap.h" + +#include +#include + +namespace msdfgen { + +template +Bitmap::Bitmap() : pixels(NULL), w(0), h(0) { } + +template +Bitmap::Bitmap(int width, int height) : w(width), h(height) { + pixels = new T[N*w*h]; +} + +template +Bitmap::Bitmap(const BitmapConstRef &orig) : w(orig.width), h(orig.height) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +template +Bitmap::Bitmap(const Bitmap &orig) : w(orig.w), h(orig.h) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap::Bitmap(Bitmap &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h) { + orig.pixels = NULL; + orig.w = 0, orig.h = 0; +} +#endif + +template +Bitmap::~Bitmap() { + delete [] pixels; +} + +template +Bitmap &Bitmap::operator=(const BitmapConstRef &orig) { + if (pixels != orig.pixels) { + delete [] pixels; + w = orig.width, h = orig.height; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +template +Bitmap &Bitmap::operator=(const Bitmap &orig) { + if (this != &orig) { + delete [] pixels; + w = orig.w, h = orig.h; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap &Bitmap::operator=(Bitmap &&orig) { + if (this != &orig) { + delete [] pixels; + pixels = orig.pixels; + w = orig.w, h = orig.h; + orig.pixels = NULL; + } + return *this; +} +#endif + +template +int Bitmap::width() const { + return w; +} + +template +int Bitmap::height() const { + return h; +} + +template +T *Bitmap::operator()(int x, int y) { + return pixels+N*(w*y+x); +} + +template +const T *Bitmap::operator()(int x, int y) const { + return pixels+N*(w*y+x); +} + +template +Bitmap::operator T *() { + return pixels; +} + +template +Bitmap::operator const T *() const { + return pixels; +} + +template +Bitmap::operator BitmapRef() { + return BitmapRef(pixels, w, h); +} + +template +Bitmap::operator BitmapConstRef() const { + return BitmapConstRef(pixels, w, h); +} + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp b/Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp new file mode 100644 index 000000000..cb17f95dc --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp @@ -0,0 +1,41 @@ + +#pragma once + +#include "base.h" + +namespace msdfgen { + +/// Reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapRef { + + T *pixels; + int width, height; + + inline BitmapRef() : pixels(NULL), width(0), height(0) { } + inline BitmapRef(T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + + inline T *operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +/// Constant reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapConstRef { + + const T *pixels; + int width, height; + + inline BitmapConstRef() : pixels(NULL), width(0), height(0) { } + inline BitmapConstRef(const T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + inline BitmapConstRef(const BitmapRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height) { } + + inline const T *operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/Contour.h b/Source/ThirdParty/msdfgen/msdfgen/core/Contour.h new file mode 100644 index 000000000..4cae48fad --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/Contour.h @@ -0,0 +1,34 @@ + +#pragma once + +#include +#include "EdgeHolder.h" + +namespace msdfgen { + +/// A single closed contour of a shape. +class Contour { + +public: + /// The sequence of edges that make up the contour. + std::vector edges; + + /// Adds an edge to the contour. + void addEdge(const EdgeHolder &edge); +#ifdef MSDFGEN_USE_CPP11 + void addEdge(EdgeHolder &&edge); +#endif + /// Creates a new edge in the contour and returns its reference. + EdgeHolder &addEdge(); + /// Adjusts the bounding box to fit the contour. + void bound(double &l, double &b, double &r, double &t) const; + /// Adjusts the bounding box to fit the contour border's mitered corners. + void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const; + /// Computes the winding of the contour. Returns 1 if positive, -1 if negative. + int winding() const; + /// Reverses the sequence of edges on the contour. + void reverse(); + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h b/Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h new file mode 100644 index 000000000..fadbefa54 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h @@ -0,0 +1,36 @@ + +#pragma once + +#include "Range.hpp" + +namespace msdfgen { + +/// Linear transformation of signed distance values. +class DistanceMapping { + +public: + /// Explicitly designates value as distance delta rather than an absolute distance. + class Delta { + public: + double value; + inline explicit Delta(double distanceDelta) : value(distanceDelta) { } + inline operator double() const { return value; } + }; + + static DistanceMapping inverse(Range range); + + DistanceMapping(); + DistanceMapping(Range range); + double operator()(double d) const; + double operator()(Delta d) const; + DistanceMapping inverse() const; + +private: + double scale; + double translate; + + inline DistanceMapping(double scale, double translate) : scale(scale), translate(translate) { } + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h new file mode 100644 index 000000000..5d3730c9a --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h @@ -0,0 +1,20 @@ + +#pragma once + +#include "base.h" + +namespace msdfgen { + +/// Edge color specifies which color channels an edge belongs to. +enum EdgeColor { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7 +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h new file mode 100644 index 000000000..5ae2dc231 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h @@ -0,0 +1,41 @@ + +#pragma once + +#include "edge-segments.h" + +namespace msdfgen { + +/// Container for a single edge of dynamic type. +class EdgeHolder { + +public: + /// Swaps the edges held by a and b. + static void swap(EdgeHolder &a, EdgeHolder &b); + + inline EdgeHolder() : edgeSegment() { } + inline EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { } + inline EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, edgeColor)) { } + inline EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, p2, edgeColor)) { } + inline EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, p2, p3, edgeColor)) { } + EdgeHolder(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder(EdgeHolder &&orig); +#endif + ~EdgeHolder(); + EdgeHolder &operator=(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder &operator=(EdgeHolder &&orig); +#endif + EdgeSegment &operator*(); + const EdgeSegment &operator*() const; + EdgeSegment *operator->(); + const EdgeSegment *operator->() const; + operator EdgeSegment *(); + operator const EdgeSegment *() const; + +private: + EdgeSegment *edgeSegment; + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h b/Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h new file mode 100644 index 000000000..9cd14eccf --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h @@ -0,0 +1,55 @@ + +#pragma once + +#include "SDFTransformation.h" +#include "Shape.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Performs error correction on a computed MSDF to eliminate interpolation artifacts. This is a low-level class, you may want to use the API in msdf-error-correction.h instead. +class MSDFErrorCorrection { + +public: + /// Stencil flags. + enum Flags { + /// Texel marked as potentially causing interpolation errors. + ERROR = 1, + /// Texel marked as protected. Protected texels are only given the error flag if they cause inversion artifacts. + PROTECTED = 2 + }; + + MSDFErrorCorrection(); + explicit MSDFErrorCorrection(const BitmapRef &stencil, const SDFTransformation &transformation); + /// Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error. + void setMinDeviationRatio(double minDeviationRatio); + /// Sets the minimum ratio between the pre-correction distance error and the post-correction distance error. + void setMinImproveRatio(double minImproveRatio); + /// Flags all texels that are interpolated at corners as protected. + void protectCorners(const Shape &shape); + /// Flags all texels that contribute to edges as protected. + template + void protectEdges(const BitmapConstRef &sdf); + /// Flags all texels as protected. + void protectAll(); + /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF only. + template + void findErrors(const BitmapConstRef &sdf); + /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF and comparison with the exact shape distance. + template