From fccd43502bb3153632cdf3fbc6d0ad331074c952 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sun, 4 May 2025 21:14:10 +0200 Subject: [PATCH 01/90] 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 02/90] 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 03/90] 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 f588d6da51c218a70e2f3c8d9b11cca6b74242f6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 2 Sep 2025 20:33:41 -0500 Subject: [PATCH 04/90] 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 05/90] 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 fb5cedc575bc9a6b1fbb80cbe5421a5024cbd863 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 19 Sep 2025 16:32:59 +0200 Subject: [PATCH 06/90] 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 0917a743cd8d91ca56eadf710f538dc81b146f34 Mon Sep 17 00:00:00 2001 From: Saas Date: Sun, 21 Sep 2025 17:07:13 +0200 Subject: [PATCH 07/90] 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 18a0e8d6122303dba3a75af1e6e7adf2d2327e8e Mon Sep 17 00:00:00 2001 From: frank Date: Sat, 4 Oct 2025 22:37:37 +0800 Subject: [PATCH 08/90] 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