diff --git a/Content/Editor/Scripting/CSharpEmptyTemplate.cs b/Content/Editor/Scripting/CSharpEmptyTemplate.cs
new file mode 100644
index 000000000..b221b83d9
--- /dev/null
+++ b/Content/Editor/Scripting/CSharpEmptyTemplate.cs
@@ -0,0 +1,5 @@
+%copyright%using System;
+using System.Collections.Generic;
+using FlaxEngine;
+
+namespace %namespace%;
diff --git a/Content/Editor/Scripting/CppAssetTemplate.h b/Content/Editor/Scripting/CppAssetTemplate.h
index f6b1421dd..ad452bc62 100644
--- a/Content/Editor/Scripting/CppAssetTemplate.h
+++ b/Content/Editor/Scripting/CppAssetTemplate.h
@@ -13,10 +13,10 @@ API_CLASS() class %module%%class% : public ISerializable
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_NO_SPAWN(%class%);
public:
- // Custom float value.
+ // Custom float value.
API_FIELD(Attributes = "Range(0, 20), EditorOrder(0), EditorDisplay(\"Data\")")
float FloatValue = 20.0f;
- // Custom vector data.
+ // Custom vector data.
API_FIELD(Attributes = "EditorOrder(1), EditorDisplay(\"Data\")")
Vector3 Vector3Value = Vector3(0.1f);
};
diff --git a/Content/Editor/Scripting/ShaderTemplate.shader b/Content/Editor/Scripting/ShaderTemplate.shader
index 5e7034adc..869a32219 100644
--- a/Content/Editor/Scripting/ShaderTemplate.shader
+++ b/Content/Editor/Scripting/ShaderTemplate.shader
@@ -7,6 +7,6 @@ META_CB_END
META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Fullscreen(Quad_VS2PS input) : SV_Target
{
- // Solid color fill from the constant buffer passed from code
- return Color;
+ // Solid color fill from the constant buffer passed from code
+ return Color;
}
diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax
index c797e5b26..92f2b2794 100644
--- a/Content/Shaders/ColorGrading.flax
+++ b/Content/Shaders/ColorGrading.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e378171320ebc1c7516832a15576254d403508e1ae1e14c9af5cf30c75f231cc
-size 10629
+oid sha256:2c45e8483ac28e494958d96e5965fc54871e8de88b2e52c79b58e27d4a0636cb
+size 12321
diff --git a/Content/Shaders/Editor/LightmapUVsDensity.flax b/Content/Shaders/Editor/LightmapUVsDensity.flax
index 2aa2cc60f..d7f265055 100644
--- a/Content/Shaders/Editor/LightmapUVsDensity.flax
+++ b/Content/Shaders/Editor/LightmapUVsDensity.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a58cc65b8291c10e4fafa81df67dc30e9e1b80fd903ae2e4d173f3c586faf59f
-size 4391
+oid sha256:a0ed25e40158253b2fdd565e7ad0e745308eadc5091a89502cbc1fa22288039f
+size 4515
diff --git a/Source/Editor/Content/Create/PrefabCreateEntry.cs b/Source/Editor/Content/Create/PrefabCreateEntry.cs
new file mode 100644
index 000000000..06d0c94d4
--- /dev/null
+++ b/Source/Editor/Content/Create/PrefabCreateEntry.cs
@@ -0,0 +1,173 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using Object = FlaxEngine.Object;
+
+namespace FlaxEditor.Content.Create
+{
+ ///
+ /// Prefab asset creating handler. Allows to specify base actor to use as the root.
+ ///
+ ///
+ public class PrefabCreateEntry : CreateFileEntry
+ {
+ ///
+ /// The create options.
+ ///
+ public class Options
+ {
+ ///
+ /// The root actor.
+ ///
+ [TypeReference(typeof(FlaxEngine.Actor), nameof(IsValid))]
+ [Tooltip("The actor type of the root of the new Prefab.")]
+ public Type RootActorType = typeof(EmptyActor);
+
+ private static bool IsValid(Type type)
+ {
+ return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
+ }
+ }
+
+ private readonly Options _options = new Options();
+
+ ///
+ public override object Settings => _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The result file url.
+ public PrefabCreateEntry(string resultUrl)
+ : base("Settings", resultUrl)
+ {
+ }
+
+ ///
+ public override bool Create()
+ {
+ var actorType = new ScriptType(_options.RootActorType ?? typeof(EmptyActor));
+ Actor actor;
+ try
+ {
+ actor = actorType.CreateInstance() as Actor;
+ Object.Destroy(actor, 20.0f);
+ }
+ catch (Exception ex)
+ {
+ Editor.LogError("Failed to create prefab with root actor type: " + actorType.Name);
+ Editor.LogWarning(ex);
+ return true;
+ }
+
+ return PrefabManager.CreatePrefab(actor, ResultUrl, true);
+ }
+ }
+
+ ///
+ /// Widget asset creating handler. Allows to specify base UIControl to use as the root.
+ ///
+ ///
+ public class WidgetCreateEntry : CreateFileEntry
+ {
+ ///
+ /// The create options.
+ ///
+ public class Options
+ {
+ ///
+ /// Which mode is used to initialize this widget.
+ ///
+ public enum WidgetMode
+ {
+ ///
+ /// Initialize the widget with a UICanvas.
+ ///
+ Canvas,
+
+ ///
+ /// Initialize the widget with a UIControl.
+ ///
+ Control
+ }
+
+ ///
+ /// The mode used to initialize the widget.
+ ///
+ [Tooltip("Whether to initialize the widget with a canvas or a control.")]
+ public WidgetMode WidgetInitializationMode = WidgetMode.Control;
+
+ bool ShowRoot => WidgetInitializationMode == WidgetMode.Control;
+
+ ///
+ /// The root control.
+ ///
+ [TypeReference(typeof(Control), nameof(IsValid))]
+ [Tooltip("The control type of the root of the new Widget's root control."), VisibleIf(nameof(ShowRoot))]
+ public Type RootControlType = typeof(Button);
+
+ private static bool IsValid(Type type)
+ {
+ return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
+ }
+ }
+
+ private readonly Options _options = new Options();
+
+ ///
+ public override object Settings => _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The result file url.
+ public WidgetCreateEntry(string resultUrl)
+ : base("Settings", resultUrl)
+ {
+ }
+
+ ///
+ public override bool Create()
+ {
+ Actor actor = null;
+
+ if (_options.WidgetInitializationMode == Options.WidgetMode.Control)
+ {
+ var controlType = new ScriptType(_options.RootControlType ?? typeof(Control));
+ Control control;
+ try
+ {
+ control = controlType.CreateInstance() as Control;
+ }
+ catch (Exception ex)
+ {
+ Editor.LogError("Failed to create widget with root control type: " + controlType.Name);
+ Editor.LogWarning(ex);
+ return true;
+ }
+
+ actor = new UIControl
+ {
+ Control = control,
+ Name = controlType.Name
+ };
+ }
+ else if (_options.WidgetInitializationMode == Options.WidgetMode.Canvas)
+ {
+ actor = new UICanvas();
+ }
+
+ if (actor == null)
+ {
+ Editor.LogError("Failed to create widget. Final actor was null.");
+ return true;
+ }
+ Object.Destroy(actor, 20.0f);
+
+ return PrefabManager.CreatePrefab(actor, ResultUrl, true);
+ }
+ }
+}
diff --git a/Source/Editor/Content/Items/CSharpScriptItem.cs b/Source/Editor/Content/Items/CSharpScriptItem.cs
index 26e7e98ac..b5e02f9ed 100644
--- a/Source/Editor/Content/Items/CSharpScriptItem.cs
+++ b/Source/Editor/Content/Items/CSharpScriptItem.cs
@@ -5,7 +5,7 @@ using FlaxEngine;
namespace FlaxEditor.Content
{
///
- /// Content item that contains C# script file with source code.
+ /// Content item that contains C# file with source code.
///
///
public class CSharpScriptItem : ScriptItem
diff --git a/Source/Editor/Content/Items/PrefabItem.cs b/Source/Editor/Content/Items/PrefabItem.cs
index a61e07e7c..3638f274f 100644
--- a/Source/Editor/Content/Items/PrefabItem.cs
+++ b/Source/Editor/Content/Items/PrefabItem.cs
@@ -11,6 +11,8 @@ namespace FlaxEditor.Content
///
public sealed class PrefabItem : JsonAssetItem
{
+ private string _cachedTypeDescription = null;
+
///
/// Initializes a new instance of the class.
///
@@ -42,6 +44,26 @@ namespace FlaxEditor.Content
///
public override SpriteHandle DefaultThumbnail => SpriteHandle.Invalid;
+ ///
+ public override string TypeDescription
+ {
+ get
+ {
+ if (_cachedTypeDescription == null)
+ {
+ _cachedTypeDescription = "Prefab";
+ var prefab = FlaxEngine.Content.Load(ID);
+ if (prefab)
+ {
+ Actor root = prefab.GetDefaultInstance();
+ if (root is UIControl or UICanvas)
+ _cachedTypeDescription = "Widget";
+ }
+ }
+ return _cachedTypeDescription;
+ }
+ }
+
///
public override bool IsOfType(Type type)
{
diff --git a/Source/Editor/Content/Proxy/CSharpScriptProxy.cs b/Source/Editor/Content/Proxy/CSharpProxy.cs
similarity index 60%
rename from Source/Editor/Content/Proxy/CSharpScriptProxy.cs
rename to Source/Editor/Content/Proxy/CSharpProxy.cs
index afab13f5d..099ad6109 100644
--- a/Source/Editor/Content/Proxy/CSharpScriptProxy.cs
+++ b/Source/Editor/Content/Proxy/CSharpProxy.cs
@@ -9,19 +9,15 @@ using FlaxEngine;
namespace FlaxEditor.Content
{
///
- /// Context proxy object for C# script files.
+ /// Proxy object for C# files
///
- ///
- [ContentContextMenu("New/C# Script")]
- public class CSharpScriptProxy : ScriptProxy
+ /// ///
+ public abstract class CSharpProxy : ScriptProxy
{
///
/// The script files extension filter.
///
- public static readonly string ExtensionFiler = "*.cs";
-
- ///
- public override string Name => "C# Script";
+ public static readonly string ExtensionFilter = "*.cs";
///
public override bool IsProxyFor(ContentItem item)
@@ -29,6 +25,12 @@ namespace FlaxEditor.Content
return item is CSharpScriptItem;
}
+ ///
+ /// Gets the path for the C# template.
+ ///
+ /// The path to the template
+ protected abstract void GetTemplatePath(out string path);
+
///
public override ContentItem ConstructItem(string path)
{
@@ -39,7 +41,7 @@ namespace FlaxEditor.Content
public override void Create(string outputPath, object arg)
{
// Load template
- var templatePath = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cs");
+ GetTemplatePath(out var templatePath);
var scriptTemplate = File.ReadAllText(templatePath);
// Find the module that this script is being added (based on the path)
@@ -69,4 +71,38 @@ namespace FlaxEditor.Content
///
public override Color AccentColor => Color.FromRGB(0x1c9c2b);
}
+
+ ///
+ /// Context proxy object for C# script files.
+ ///
+ ///
+ [ContentContextMenu("New/C#/C# Script")]
+ public class CSharpScriptProxy : CSharpProxy
+ {
+ ///
+ public override string Name => "C# Script";
+
+ ///
+ protected override void GetTemplatePath(out string path)
+ {
+ path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cs");
+ }
+ }
+
+ ///
+ /// Context proxy object for empty C# files.
+ ///
+ ///
+ [ContentContextMenu("New/C#/C# Empty File")]
+ public class CSharpEmptyProxy : CSharpProxy
+ {
+ ///
+ public override string Name => "C# Empty File";
+
+ ///
+ protected override void GetTemplatePath(out string path)
+ {
+ path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CSharpEmptyTemplate.cs");
+ }
+ }
}
diff --git a/Source/Editor/Content/Proxy/CppProxy.cs b/Source/Editor/Content/Proxy/CppProxy.cs
index 0a32ccec6..af5f93421 100644
--- a/Source/Editor/Content/Proxy/CppProxy.cs
+++ b/Source/Editor/Content/Proxy/CppProxy.cs
@@ -11,7 +11,7 @@ namespace FlaxEditor.Content
///
/// Context proxy object for C++ files.
///
- ///
+ ///
public abstract class CppProxy : ScriptProxy
{
///
@@ -74,7 +74,7 @@ namespace FlaxEditor.Content
///
/// Context proxy object for C++ script files.
///
- ///
+ ///
[ContentContextMenu("New/C++/C++ Script")]
public class CppScriptProxy : CppProxy
{
@@ -104,7 +104,7 @@ namespace FlaxEditor.Content
///
/// Context proxy object for C++ Json Asset files.
///
- ///
+ ///
[ContentContextMenu("New/C++/C++ Function Library")]
public class CppStaticClassProxy : CppProxy
{
@@ -122,7 +122,7 @@ namespace FlaxEditor.Content
///
/// Context proxy object for C++ Json Asset files.
///
- ///
+ ///
[ContentContextMenu("New/C++/C++ Json Asset")]
public class CppAssetProxy : CppProxy
{
diff --git a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs
index 8c1204128..be2530efa 100644
--- a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs
@@ -3,6 +3,9 @@
using System;
using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Timeline;
+using FlaxEditor.GUI.Timeline.Tracks;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -48,6 +51,63 @@ namespace FlaxEditor.Content
Editor.Instance.ContentImporting.Create(new ParticleEmitterCreateEntry(outputPath));
}
+ ///
+ public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
+ {
+ base.OnContentWindowContextMenu(menu, item);
+
+ if (item is BinaryAssetItem binaryAssetItem)
+ {
+ var button = menu.AddButton("Create Particle System", CreateParticleSystemClicked);
+ button.Tag = binaryAssetItem;
+ }
+ }
+
+ private void CreateParticleSystemClicked(ContextMenuButton obj)
+ {
+ var binaryAssetItem = (BinaryAssetItem)obj.Tag;
+ CreateParticleSystem(binaryAssetItem);
+ }
+
+ ///
+ /// Creates the particle system from the given particle emitter.
+ ///
+ /// The particle emitter item to use as a base for the particle system.
+ public static void CreateParticleSystem(BinaryAssetItem emitterItem)
+ {
+ var particleSystemName = emitterItem.ShortName + " Particle System";
+ var particleSystemProxy = Editor.Instance.ContentDatabase.GetProxy();
+ Editor.Instance.Windows.ContentWin.NewItem(particleSystemProxy, null, item => OnParticleSystemCreated(item, emitterItem), particleSystemName);
+ }
+
+ private static void OnParticleSystemCreated(ContentItem item, BinaryAssetItem particleItem)
+ {
+ var assetItem = (AssetItem)item;
+ var particleSystem = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ if (particleSystem == null || particleSystem.WaitForLoaded())
+ {
+ Editor.LogError("Failed to load created particle system.");
+ return;
+ }
+
+ ParticleEmitter emitter = FlaxEngine.Content.LoadAsync(particleItem.ID);
+ if (emitter == null || emitter.WaitForLoaded())
+ {
+ Editor.LogError("Failed to load base particle emitter.");
+ }
+
+ ParticleSystemPreview tempPreview = new ParticleSystemPreview(false);
+ ParticleSystemTimeline timeline = new ParticleSystemTimeline(tempPreview);
+ timeline.Load(particleSystem);
+
+ var track = (ParticleEmitterTrack)timeline.NewTrack(ParticleEmitterTrack.GetArchetype());
+ track.Asset = emitter;
+ track.TrackMedia.DurationFrames = timeline.DurationFrames;
+ track.Rename(particleItem.ShortName);
+ timeline.AddTrack(track);
+ timeline.Save(particleSystem);
+ }
+
///
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs
index d2971f296..27bece29c 100644
--- a/Source/Editor/Content/Proxy/PrefabProxy.cs
+++ b/Source/Editor/Content/Proxy/PrefabProxy.cs
@@ -1,14 +1,13 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
-using System.IO;
+using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
-using Object = FlaxEngine.Object;
namespace FlaxEditor.Content
{
@@ -90,14 +89,8 @@ namespace FlaxEditor.Content
var transform = Transform.Identity;
if (!(arg is Actor actor))
{
- // Create default prefab root object
- actor = new EmptyActor
- {
- Name = "Root"
- };
-
- // Cleanup it after usage
- Object.Destroy(actor, 20.0f);
+ Editor.Instance.ContentImporting.Create(new PrefabCreateEntry(outputPath));
+ return;
}
else if (actor.HasScene)
{
@@ -251,18 +244,8 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- // Create prefab with UI Control
- var actor = new UIControl
- {
- Name = Path.GetFileNameWithoutExtension(outputPath),
- StaticFlags = StaticFlags.None,
- };
- actor.Control = new Button
- {
- Text = "Button",
- };
- PrefabManager.CreatePrefab(actor, outputPath, false);
- Object.Destroy(actor, 20.0f);
+ Editor.Instance.ContentImporting.Create(new WidgetCreateEntry(outputPath));
+ return;
}
}
}
diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
index e05ca80d9..551dd1beb 100644
--- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
+++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.Content.Thumbnails;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -39,6 +40,59 @@ namespace FlaxEditor.Content
///
public override Type AssetType => typeof(SkinnedModel);
+ ///
+ public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
+ {
+ base.OnContentWindowContextMenu(menu, item);
+
+ if (item is BinaryAssetItem binaryAssetItem)
+ {
+ var button = menu.AddButton("Create Animation Graph", CreateAnimationGraphClicked);
+ button.Tag = binaryAssetItem;
+ }
+ }
+
+ private void CreateAnimationGraphClicked(ContextMenuButton obj)
+ {
+ var binaryAssetItem = (BinaryAssetItem)obj.Tag;
+ CreateAnimationGraph(binaryAssetItem);
+ }
+
+ ///
+ /// Creates the animation graph from the given particle emitter.
+ ///
+ /// The skinned model item to use as the base model for the animation graph.
+ public static void CreateAnimationGraph(BinaryAssetItem skinnedModelItem)
+ {
+ var animationGraphName = skinnedModelItem.ShortName + " Graph";
+ var animationGraphProxy = Editor.Instance.ContentDatabase.GetProxy();
+ Editor.Instance.Windows.ContentWin.NewItem(animationGraphProxy, null, item => OnAnimationGraphCreated(item, skinnedModelItem), animationGraphName);
+ }
+
+ private static void OnAnimationGraphCreated(ContentItem item, BinaryAssetItem skinnedModelItem)
+ {
+ var skinnedModel = FlaxEngine.Content.Load(skinnedModelItem.ID);
+ if (skinnedModel == null)
+ {
+ Editor.LogError("Failed to load base skinned model.");
+ return;
+ }
+
+ // Hack the animation graph window to modify the base model of the animation graph.
+ // TODO: implement it without window logic (load AnimGraphSurface and set AnimationGraphWindow.BaseModelId to model)
+ AnimationGraphWindow win = new AnimationGraphWindow(Editor.Instance, item as AssetItem);
+ win.Show();
+
+ // Ensure the window knows the asset is loaded so we can save it later.
+ win.Asset.WaitForLoaded();
+ win.Update(0); // Call Update() to refresh the loaded flag.
+
+ win.SetBaseModel(skinnedModel);
+ win.Surface.MarkAsEdited();
+ win.Save();
+ win.Close();
+ }
+
///
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index fefe48b97..b1a3201cd 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -289,6 +289,17 @@ bool DeployDataStep::Perform(CookingData& data)
}
}
+ // Remove any leftover files copied from .NET SDK that are not needed by the engine runtime
+ {
+ Array files;
+ FileSystem::DirectoryGetFiles(files, dstDotnet, TEXT("*.exe"));
+ for (const String& file : files)
+ {
+ LOG(Info, "Removing '{}'", FileSystem::ConvertAbsolutePathToRelative(dstDotnet, file));
+ FileSystem::DeleteFile(file);
+ }
+ }
+
// Optimize deployed C# class library (remove DLLs unused by scripts)
if (aotMode == DotNetAOTModes::None && buildSettings.SkipUnusedDotnetLibsPackaging)
{
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 852fd99c5..d2aa85269 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -261,7 +261,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (!file.Contains("GameProjectTarget"))
continue; // Skip
- if (file.Contains("Modules.Add(\"Game\")"))
+ if (file.Contains("Modules.Add(\"Game\")") || file.Contains("Modules.Add(nameof(Game))"))
{
// Assume Game represents the main game module
moduleName = "Game";
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index e341d4d4f..1d65ec71c 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -123,10 +123,10 @@ namespace FlaxEditor.CustomEditors.Editors
_linkButton.Clicked += ToggleLink;
ToggleEnabled();
SetLinkStyle();
- var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
- _linkButton.LocalX += textSize.X + 10;
if (LinkedLabel != null)
{
+ var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
+ _linkButton.LocalX += textSize.X + 10;
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
diff --git a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs
index f8323bb49..91c508a33 100644
--- a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs
@@ -1,3 +1,4 @@
+using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -39,20 +40,27 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
Window = layout.Control.RootWindow.Window;
+ var panelElement = layout.CustomContainer();
+ var panel = panelElement.ContainerControl as Panel;
- var panel = layout.CustomContainer();
- panel.CustomControl.SlotsHorizontally = 2;
- panel.CustomControl.SlotsVertically = 1;
-
- var button = panel.Button("Listen", "Press to listen for input events");
+ var button = panelElement.Button("Listen", "Press to listen for input events");
_button = button.Button;
+ _button.Width = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText("Listening...").X + 8;
+ _button.Height = ComboBox.DefaultHeight;
_button.Clicked += OnButtonClicked;
ResetButton();
- var padding = panel.CustomControl.SlotPadding;
- panel.CustomControl.Height = ComboBox.DefaultHeight + padding.Height;
+ panel.Height = ComboBox.DefaultHeight;
- base.Initialize(panel);
+ base.Initialize(panelElement);
+
+ if (panelElement.Children.Find(x => x is EnumElement) is EnumElement comboBoxElement)
+ {
+ var comboBox = comboBoxElement.ComboBox;
+ comboBox.AnchorPreset = AnchorPresets.StretchAll;
+ comboBox.Offsets = new Margin(0, _button.Width + 2, 0, 0);
+ comboBox.LocalX += _button.Width + 2;
+ }
}
///
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 7c0670010..1f06b2704 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -2,14 +2,18 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Linq;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.Input;
using FlaxEditor.Content;
+using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
+using FlaxEditor.Windows;
+using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -53,7 +57,7 @@ namespace FlaxEditor.CustomEditors.Editors
Index = index;
SetupContextMenu += OnSetupContextMenu;
- _arrangeButtonRect = new Rectangle(2, 3, 12, 12);
+ _arrangeButtonRect = new Rectangle(2, 4, 12, 12);
// Extend margin of the label to support a dragging handle.
Margin m = Margin;
@@ -75,7 +79,7 @@ namespace FlaxEditor.CustomEditors.Editors
b = menu.AddButton("Move down", OnMoveDownClicked);
b.Enabled = Index + 1 < Editor.Count && !Editor._readOnly;
-
+
b = menu.AddButton("Remove", OnRemoveClicked);
b.Enabled = !Editor._readOnly;
}
@@ -88,13 +92,12 @@ namespace FlaxEditor.CustomEditors.Editors
_arrangeButtonInUse = false;
}
-
///
public override void Draw()
{
base.Draw();
- var style = FlaxEngine.GUI.Style.Current;
+ var style = FlaxEngine.GUI.Style.Current;
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
@@ -104,6 +107,14 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
+ ///
+ protected override void OnSizeChanged()
+ {
+ base.OnSizeChanged();
+
+ _arrangeButtonRect.Y = (Height - _arrangeButtonRect.Height) * 0.5f;
+ }
+
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{
var child = Editor.ChildrenEditors[0];
@@ -228,7 +239,38 @@ namespace FlaxEditor.CustomEditors.Editors
ArrowImageClosed = new SpriteBrush(icons.ArrowRight12);
ArrowImageOpened = new SpriteBrush(icons.ArrowDown12);
HeaderText = $"Element {index}";
- IsClosed = false;
+
+ string saveName = string.Empty;
+ if (editor.Presenter?.Owner is PropertiesWindow propertiesWindow)
+ {
+ var selection = FlaxEditor.Editor.Instance.SceneEditing.Selection[0];
+ if (selection != null)
+ {
+ saveName += $"{selection.ID},";
+ }
+ }
+ else if (editor.Presenter?.Owner is PrefabWindow prefabWindow)
+ {
+ var selection = prefabWindow.Selection[0];
+ if (selection != null)
+ {
+ saveName += $"{selection.ID},";
+ }
+ }
+ if (editor.ParentEditor?.Layout.ContainerControl is DropPanel pdp)
+ {
+ saveName += $"{pdp.HeaderText},";
+ }
+ if (editor.Layout.ContainerControl is DropPanel mainGroup)
+ {
+ saveName += $"{mainGroup.HeaderText}";
+ IsClosed = FlaxEditor.Editor.Instance.ProjectCache.IsGroupToggled($"{saveName}:{index}");
+ }
+ else
+ {
+ IsClosed = false;
+ }
+
Editor = editor;
Index = index;
Offsets = new Margin(7, 7, 0, 0);
@@ -239,6 +281,37 @@ namespace FlaxEditor.CustomEditors.Editors
HeaderTextMargin = new Margin(18, 0, 0, 0);
_arrangeButtonRect = new Rectangle(16, 3, 12, 12);
}
+ IsClosedChanged += OnIsClosedChanged;
+ }
+
+ private void OnIsClosedChanged(DropPanel panel)
+ {
+ string saveName = string.Empty;
+ if (Editor.Presenter?.Owner is PropertiesWindow pw)
+ {
+ var selection = FlaxEditor.Editor.Instance.SceneEditing.Selection[0];
+ if (selection != null)
+ {
+ saveName += $"{selection.ID},";
+ }
+ }
+ else if (Editor.Presenter?.Owner is PrefabWindow prefabWindow)
+ {
+ var selection = prefabWindow.Selection[0];
+ if (selection != null)
+ {
+ saveName += $"{selection.ID},";
+ }
+ }
+ if (Editor.ParentEditor?.Layout.ContainerControl is DropPanel pdp)
+ {
+ saveName += $"{pdp.HeaderText},";
+ }
+ if (Editor.Layout.ContainerControl is DropPanel mainGroup)
+ {
+ saveName += $"{mainGroup.HeaderText}";
+ FlaxEditor.Editor.Instance.ProjectCache.SetGroupToggle($"{saveName}:{Index}", panel.IsClosed);
+ }
}
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
@@ -278,10 +351,10 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Draw()
{
base.Draw();
+
if (_canReorder)
{
var style = FlaxEngine.GUI.Style.Current;
-
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
@@ -377,6 +450,7 @@ namespace FlaxEditor.CustomEditors.Editors
private bool _canResize;
private bool _canReorderItems;
private CollectionAttribute.DisplayType _displayType;
+ private List _cachedDropPanels = new List();
///
/// Gets the length of the collection.
@@ -519,6 +593,10 @@ namespace FlaxEditor.CustomEditors.Editors
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
elementType.Equals(new ScriptType(typeof(SettingsBase)));
+ if (_cachedDropPanels == null)
+ _cachedDropPanels = new List();
+ else
+ _cachedDropPanels.Clear();
for (int i = 0; i < size; i++)
{
// Apply spacing
@@ -542,6 +620,7 @@ namespace FlaxEditor.CustomEditors.Editors
else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single))
{
var cdp = panel.CustomContainer();
+ _cachedDropPanels.Add(cdp.CustomControl);
cdp.CustomControl.Setup(this, i, _canReorderItems);
var itemLayout = cdp.VerticalPanel();
cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
@@ -549,6 +628,12 @@ namespace FlaxEditor.CustomEditors.Editors
GenericEditor.OnReadOnlyProperty(itemLayout);
}
}
+
+ if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single))
+ {
+ if (Layout is GroupElement g)
+ g.SetupContextMenu += OnSetupContextMenu;
+ }
}
_elementsCount = size;
@@ -583,6 +668,28 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
+ private void OnSetupContextMenu(ContextMenu menu, DropPanel panel)
+ {
+ if (menu.Items.Any(x => x is ContextMenuButton b && b.Text.Equals("Open All", StringComparison.Ordinal)))
+ return;
+
+ menu.AddSeparator();
+ menu.AddButton("Open All", () =>
+ {
+ foreach (var cachedPanel in _cachedDropPanels)
+ {
+ cachedPanel.IsClosed = false;
+ }
+ });
+ menu.AddButton("Close All", () =>
+ {
+ foreach (var cachedPanel in _cachedDropPanels)
+ {
+ cachedPanel.IsClosed = true;
+ }
+ });
+ }
+
///
protected override void Deinitialize()
{
@@ -663,7 +770,7 @@ namespace FlaxEditor.CustomEditors.Editors
cloned[i] = tmp;
}
}
-
+
SetValue(cloned);
}
diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
index bc31cd10b..fd367d21f 100644
--- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
@@ -208,7 +208,9 @@ namespace FlaxEditor.CustomEditors.Editors
else
{
// Draw info
+ Render2D.PushClip(nameRect);
Render2D.DrawText(style.FontMedium, Type != null ? $"None ({Utilities.Utils.GetPropertyNameUI(Type.ToString())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center);
+ Render2D.PopClip();
}
// Draw picker button
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index 78521361a..bfcd8d046 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -569,19 +569,19 @@ namespace FlaxEditor.CustomEditors.Editors
internal static void OnReadOnlyProperty(LayoutElementsContainer itemLayout, int labelIndex = -1)
{
- PropertiesListElement list = null;
+ PropertiesList list = null;
int firstChildControlIndex = 0;
bool disableSingle = true;
var control = itemLayout.Children[itemLayout.Children.Count - 1];
if (control is GroupElement group && group.Children.Count > 0)
{
- list = group.Children[0] as PropertiesListElement;
+ list = (group.Children[0] as PropertiesListElement)?.Properties;
disableSingle = false; // Disable all nested editors
}
else if (control is PropertiesListElement list1 && labelIndex != -1)
{
- list = list1;
- firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
+ list = list1.Labels[labelIndex].FirstChildControlContainer ?? list1.Properties;
+ firstChildControlIndex = list1.Labels[labelIndex].FirstChildControlIndex;
}
else if (control?.Control != null)
{
@@ -591,10 +591,10 @@ namespace FlaxEditor.CustomEditors.Editors
if (list != null)
{
// Disable controls added to the editor
- var count = list.Properties.Children.Count;
+ var count = list.Children.Count;
for (int j = firstChildControlIndex; j < count; j++)
{
- var child = list.Properties.Children[j];
+ var child = list.Children[j];
if (disableSingle && child is PropertyNameLabel)
break;
diff --git a/Source/Editor/CustomEditors/Editors/IntegerEditor.cs b/Source/Editor/CustomEditors/Editors/IntegerEditor.cs
index 4f6c2a9e7..16bcde242 100644
--- a/Source/Editor/CustomEditors/Editors/IntegerEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/IntegerEditor.cs
@@ -50,7 +50,6 @@ namespace FlaxEditor.CustomEditors.Editors
return;
}
}
- if (_element == null)
{
// Use int value editor
var element = layout.IntegerValue();
diff --git a/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs
index 5fe374337..efa6581f5 100644
--- a/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs
@@ -33,10 +33,10 @@ namespace FlaxEditor.CustomEditors.Editors
if (layout.Children.Count == 0)
return;
var propList = layout.Children[layout.Children.Count - 1] as PropertiesListElement;
- if (propList == null || propList.Children.Count != 2)
+ if (propList == null || propList.Children.Count < 2)
return;
- var idElement = propList.Children[0] as TextBoxElement;
- var valueElement = propList.Children[1] as TextBoxElement;
+ var idElement = propList.Children[propList.Children.Count - 2] as TextBoxElement;
+ var valueElement = propList.Children[propList.Children.Count - 1] as TextBoxElement;
if (idElement == null || valueElement == null)
return;
_idElement = idElement;
diff --git a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
index 58f36737c..972f64ba6 100644
--- a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
+++ b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
@@ -1,5 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+using System;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -24,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Elements
EnableContainmentLines = true,
};
+ ///
+ /// Event is fired if the group can setup a context menu and the context menu is being setup.
+ ///
+ public Action SetupContextMenu;
+
///
public override ContainerControl ContainerControl => Panel;
diff --git a/Source/Editor/CustomEditors/Elements/Container/PropertiesListElement.cs b/Source/Editor/CustomEditors/Elements/Container/PropertiesListElement.cs
index 0862200da..c7705b336 100644
--- a/Source/Editor/CustomEditors/Elements/Container/PropertiesListElement.cs
+++ b/Source/Editor/CustomEditors/Elements/Container/PropertiesListElement.cs
@@ -53,13 +53,7 @@ namespace FlaxEditor.CustomEditors.Elements
internal void OnAddProperty(string name, string tooltip)
{
- var label = new PropertyNameLabel(name)
- {
- Parent = Properties,
- TooltipText = tooltip,
- FirstChildControlIndex = Properties.Children.Count
- };
- Labels.Add(label);
+ OnAddProperty(new PropertyNameLabel(name), tooltip);
}
internal void OnAddProperty(PropertyNameLabel label, string tooltip)
@@ -88,6 +82,7 @@ namespace FlaxEditor.CustomEditors.Elements
public override void ClearLayout()
{
base.ClearLayout();
+
Labels.Clear();
}
}
diff --git a/Source/Editor/CustomEditors/GUI/CheckablePropertyNameLabel.cs b/Source/Editor/CustomEditors/GUI/CheckablePropertyNameLabel.cs
index 3cd8b57c1..0ce5f441e 100644
--- a/Source/Editor/CustomEditors/GUI/CheckablePropertyNameLabel.cs
+++ b/Source/Editor/CustomEditors/GUI/CheckablePropertyNameLabel.cs
@@ -58,6 +58,8 @@ namespace FlaxEditor.CustomEditors.GUI
// Update child controls enabled state
if (FirstChildControlIndex >= 0 && Parent is PropertiesList propertiesList)
{
+ if (FirstChildControlContainer != null)
+ propertiesList = FirstChildControlContainer;
var controls = propertiesList.Children;
var labels = propertiesList.Element.Labels;
var thisIndex = labels.IndexOf(this);
diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
index 208dc64e6..49eef13d2 100644
--- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs
+++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
@@ -245,16 +245,25 @@ namespace FlaxEditor.CustomEditors.GUI
for (int i = 0; i < count; i++)
{
var label = _element.Labels[i];
+ var container = label.FirstChildControlContainer ?? this;
if (label.FirstChildControlIndex < 0)
yStarts[i] = 0;
- else if (_children.Count <= label.FirstChildControlIndex)
+ else if (container.ChildrenCount <= label.FirstChildControlIndex)
yStarts[i] = y;
+ else if (label.FirstChildControlContainer != null)
+ {
+ var firstChild = label.FirstChildControlContainer.Children[label.FirstChildControlIndex];
+ yStarts[i] = firstChild.PointToParent(this, Float2.Zero).Y;
+ if (i == count - 1)
+ yStarts[i + 1] = firstChild.Parent.Bottom;
+ }
else
{
- yStarts[i] = _children[label.FirstChildControlIndex].Top;
+ var firstChild = _children[label.FirstChildControlIndex];
+ yStarts[i] = firstChild.Top;
if (i == count - 1)
- yStarts[i + 1] = _children[label.FirstChildControlIndex].Bottom;
+ yStarts[i + 1] = firstChild.Bottom;
}
}
diff --git a/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs b/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs
index 035c90fe9..1301be318 100644
--- a/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs
+++ b/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs
@@ -30,6 +30,11 @@ namespace FlaxEditor.CustomEditors.GUI
///
internal int FirstChildControlIndex;
+ ///
+ /// Helper value used by the to draw property names in a proper area.
+ ///
+ internal PropertiesList FirstChildControlContainer;
+
///
/// The linked custom editor (shows the label property).
///
@@ -154,8 +159,16 @@ namespace FlaxEditor.CustomEditors.GUI
public override void OnDestroy()
{
SetupContextMenu = null;
+ LinkedEditor = null;
+ FirstChildControlContainer = null;
base.OnDestroy();
}
+
+ ///
+ public override string ToString()
+ {
+ return Text.ToString();
+ }
}
}
diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
index 0ec88238e..5b0e2f742 100644
--- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs
+++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
@@ -74,11 +74,11 @@ namespace FlaxEditor.CustomEditors
{
var element = Group(title, useTransparentHeader);
element.Panel.Tag = linkedEditor;
- element.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
+ element.Panel.MouseButtonRightClicked += (panel, location) => OnGroupPanelMouseButtonRightClicked(element, panel, location);
return element;
}
- private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Float2 location)
+ private void OnGroupPanelMouseButtonRightClicked(GroupElement element, DropPanel groupPanel, Float2 location)
{
var linkedEditor = (CustomEditor)groupPanel.Tag;
var menu = new ContextMenu();
@@ -91,6 +91,7 @@ namespace FlaxEditor.CustomEditors
menu.AddButton("Copy", linkedEditor.Copy);
var paste = menu.AddButton("Paste", linkedEditor.Paste);
paste.Enabled = linkedEditor.CanPaste;
+ element.SetupContextMenu?.Invoke(menu, groupPanel);
menu.Show(groupPanel, location);
}
@@ -666,7 +667,20 @@ namespace FlaxEditor.CustomEditors
}
var property = AddPropertyItem(name, tooltip);
- return property.Object(values, editor);
+ int start = property.Properties.Children.Count;
+ var result = property.Object(values, editor);
+
+ // Special case when properties list is nested into another properties list (eg. array of structures or LocalizedString editor)
+ if (this is PropertiesListElement thisPropertiesList &&
+ editor.ParentEditor != null &&
+ editor.ParentEditor.LinkedLabel != null &&
+ editor.ParentEditor.LinkedLabel.FirstChildControlContainer == null)
+ {
+ editor.ParentEditor.LinkedLabel.FirstChildControlIndex = start;
+ editor.ParentEditor.LinkedLabel.FirstChildControlContainer = property.Properties;
+ }
+
+ return result;
}
///
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index 1c065cdd8..e76cb5dfe 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -40,7 +40,6 @@ public class Editor : EditorModule
options.ScriptingAPI.SystemReferences.Add("System.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
- options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile");
// Enable optimizations for Editor, disable this for debugging the editor
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index 4da379fac..09253851c 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -226,7 +226,7 @@ bool Editor::CheckProjectUpgrade()
" base.Init();\n"
"\n"
" // Reference the modules for game\n"
- " Modules.Add(\"{0}\");\n"
+ " Modules.Add(nameof({0}));\n"
" }}\n"
"}}\n"
), codeName), Encoding::UTF8);
@@ -242,7 +242,7 @@ bool Editor::CheckProjectUpgrade()
" base.Init();\n"
"\n"
" // Reference the modules for editor\n"
- " Modules.Add(\"{0}\");\n"
+ " Modules.Add(nameof({0}));\n"
"{1}"
" }}\n"
"}}\n"
@@ -476,7 +476,7 @@ int32 Editor::LoadProduct()
" base.Init();\n"
"\n"
" // Reference the modules for game\n"
- " Modules.Add(\"Game\");\n"
+ " Modules.Add(nameof(Game));\n"
" }\n"
"}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT(
@@ -490,7 +490,7 @@ int32 Editor::LoadProduct()
" base.Init();\n"
"\n"
" // Reference the modules for editor\n"
- " Modules.Add(\"Game\");\n"
+ " Modules.Add(nameof(Game));\n"
" }\n"
"}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT(
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
index cb197e141..80a2d7494 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
@@ -337,14 +337,12 @@ namespace FlaxEditor.GUI.ContextMenu
///
/// Adds the separator.
///
- /// Created context menu item control.
- public ContextMenuSeparator AddSeparator()
+ public void AddSeparator()
{
var item = new ContextMenuSeparator(this)
{
Parent = _panel
};
- return item;
}
///
diff --git a/Source/Editor/GUI/EnumComboBox.cs b/Source/Editor/GUI/EnumComboBox.cs
index d332bb8d0..94c6c50df 100644
--- a/Source/Editor/GUI/EnumComboBox.cs
+++ b/Source/Editor/GUI/EnumComboBox.cs
@@ -7,6 +7,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEngine;
@@ -277,6 +278,14 @@ namespace FlaxEditor.GUI
}
}
+ ///
+ protected override void OnLayoutMenuButton(ContextMenuButton button, int index, bool construct = false)
+ {
+ base.OnLayoutMenuButton(button, index, construct);
+ if (IsFlags)
+ button.CloseMenuOnClick = false;
+ }
+
///
protected override void OnItemClicked(int index)
{
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index d10a1e51c..9619f82a0 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -195,7 +195,7 @@ namespace FlaxEditor.GUI.Input
var style = Style.Current;
// Draw sliding UI
- Render2D.DrawSprite(style.Scalar, SlideRect, style.Foreground);
+ Render2D.DrawSprite(style.Scalar, SlideRect, EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled);
// Check if is sliding
if (_isSliding)
diff --git a/Source/Editor/GUI/Timeline/AnimationTimeline.cs b/Source/Editor/GUI/Timeline/AnimationTimeline.cs
index 331cb6f44..63329bfc0 100644
--- a/Source/Editor/GUI/Timeline/AnimationTimeline.cs
+++ b/Source/Editor/GUI/Timeline/AnimationTimeline.cs
@@ -130,9 +130,9 @@ namespace FlaxEditor.GUI.Timeline
public override void OnPlay()
{
var time = CurrentTime;
- _preview.Play();
if (_preview != null)
{
+ _preview.Play();
Editor.Internal_SetAnimationTime(Object.GetUnmanagedPtr(_preview.PreviewActor), time);
}
diff --git a/Source/Editor/GUI/Timeline/Undo/EditTrackAction.cs b/Source/Editor/GUI/Timeline/Undo/EditTrackAction.cs
index 0714b02e2..b03dc3d76 100644
--- a/Source/Editor/GUI/Timeline/Undo/EditTrackAction.cs
+++ b/Source/Editor/GUI/Timeline/Undo/EditTrackAction.cs
@@ -34,6 +34,8 @@ namespace FlaxEditor.GUI.Timeline.Undo
private void Set(byte[] data)
{
+ if (_timeline == null)
+ return;
var track = _timeline.FindTrack(_name);
using (var memory = new MemoryStream(data))
using (var stream = new BinaryReader(memory))
@@ -42,11 +44,8 @@ namespace FlaxEditor.GUI.Timeline.Undo
track.Flags = (TrackFlags)stream.ReadByte();
track.Archetype.Load(Timeline.FormatVersion, track, stream);
}
- if (_timeline != null)
- {
- _timeline.ArrangeTracks();
- _timeline.MarkAsEdited();
- }
+ _timeline.ArrangeTracks();
+ _timeline.MarkAsEdited();
track.OnUndo();
}
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index 31295253f..d8ea556c8 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -272,11 +272,13 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetShaderAssetSourceCode(BinaryAss
obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data);
auto source = data.Get();
auto sourceLength = data.Length();
+ if (sourceLength <= 0)
+ return MCore::String::GetEmpty();
Encryption::DecryptBytes(data.Get(), data.Length());
source[sourceLength - 1] = 0;
// Get source and encrypt it back
- const StringAnsiView srcData((const char*)data.Get(), data.Length());
+ const StringAnsiView srcData(source, sourceLength);
const auto str = MUtils::ToString(srcData);
Encryption::EncryptBytes(data.Get(), data.Length());
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index eb877d84a..2cbd9cd85 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -686,6 +686,10 @@ namespace FlaxEditor.Modules
}
else
{
+ // Try to remove module if build.cs file is being deleted
+ if (item.Path.Contains(".Build.cs", StringComparison.Ordinal) && item.ItemType == ContentItemType.Script)
+ Editor.Instance.CodeEditing.RemoveModule(item.Path);
+
// Check if it's an asset
if (item.IsAsset)
{
@@ -1077,6 +1081,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new ParticleSystemProxy());
Proxy.Add(new SceneAnimationProxy());
Proxy.Add(new CSharpScriptProxy());
+ Proxy.Add(new CSharpEmptyProxy());
Proxy.Add(new CppAssetProxy());
Proxy.Add(new CppStaticClassProxy());
Proxy.Add(new CppScriptProxy());
diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs
index fecfcfdd6..6f54ab10d 100644
--- a/Source/Editor/Modules/SimulationModule.cs
+++ b/Source/Editor/Modules/SimulationModule.cs
@@ -145,6 +145,10 @@ namespace FlaxEditor.Modules
return;
}
+ // Save any modified scenes to prevent loosing local changes
+ if (Editor.Scene.IsEdited())
+ Level.SaveAllScenes();
+
// Load scenes after entering the play mode
_scenesToReload = new Guid[Level.ScenesCount];
for (int i = 0; i < _scenesToReload.Length; i++)
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
index 93296b766..e298288a4 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
@@ -382,7 +382,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
// Get editor target and target files and add module
var files = Directory.GetFiles(path);
- var targetModuleText = $"Modules.Add(\"{moduleName}\");\n ";
+ var targetModuleText = $"Modules.Add(nameof({moduleName}));\n ";
foreach (var file in files)
{
if (!file.Contains(".Build.cs", StringComparison.OrdinalIgnoreCase))
@@ -404,6 +404,93 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
}
+ internal void RemoveModule(string path)
+ {
+ if (!File.Exists(path))
+ return;
+
+ // Read text, figure out if it is an editor module or game module
+ var editorModule = false;
+ var moduleTextIndex = -1;
+ var fileText = File.ReadAllText(path);
+ if (fileText.Contains("GameModule", StringComparison.Ordinal))
+ {
+ moduleTextIndex = fileText.IndexOf("GameModule", StringComparison.Ordinal);
+ }
+ else if (fileText.Contains("ThirdPartyModule", StringComparison.Ordinal))
+ {
+ moduleTextIndex = fileText.IndexOf("ThirdPartyModule", StringComparison.Ordinal);
+ }
+ else if (fileText.Contains("DepsModule", StringComparison.Ordinal))
+ {
+ moduleTextIndex = fileText.IndexOf("DepsModule", StringComparison.Ordinal);
+ }
+ else if (fileText.Contains("GameEditorModule", StringComparison.Ordinal))
+ {
+ moduleTextIndex = fileText.IndexOf("GameEditorModule", StringComparison.Ordinal);
+ editorModule = true;
+ }
+ else
+ {
+ // If it does not contain a module, then this could be target file and not a module file
+ return;
+ }
+
+ // Get module name
+ var classTextIndex = fileText.IndexOf("class ", StringComparison.Ordinal);
+ var className = fileText.Substring(classTextIndex, moduleTextIndex - classTextIndex).Replace("class ", "").Replace(":", "").Trim();
+ Editor.Log($"Removing Module: {className}");
+
+ // Find target files
+ // Assume Target files are in the source directory that is up 2 levels
+ var sourceDirectoryInfo = Directory.GetParent(path)?.Parent;
+ if (sourceDirectoryInfo != null)
+ {
+ var sourceFiles = Directory.GetFiles(sourceDirectoryInfo.FullName);
+ // Search target files for module name and remove it
+ foreach (var file in sourceFiles)
+ {
+ string fileName = Path.GetFileName(file);
+ if (file.Contains(".Build.cs", StringComparison.OrdinalIgnoreCase))
+ {
+ var targetText = File.ReadAllText(file);
+
+ // Skip game project if it is suppose to be an editor module
+ if (editorModule && targetText.Contains("GameProjectTarget", StringComparison.Ordinal))
+ continue;
+
+ var newText = targetText;
+ bool removedModuleText = false;
+ if (targetText.Contains($"Modules.Add(\"{className}\")", StringComparison.Ordinal))
+ {
+ newText = newText.Replace($"Modules.Add(\"{className}\");\n", "", StringComparison.Ordinal).Replace($"Modules.Add(\"{className}\");", "", StringComparison.Ordinal);
+ removedModuleText = true;
+ }
+
+ if (targetText.Contains($"Modules.Add(nameof({className}))", StringComparison.Ordinal))
+ {
+ newText = newText.Replace($"Modules.Add(nameof({className}));\n", "", StringComparison.Ordinal).Replace($"Modules.Add(nameof({className}));", "", StringComparison.Ordinal);
+ removedModuleText = true;
+ }
+ if (removedModuleText)
+ {
+ File.WriteAllText(file, newText);
+ Editor.Log($"Removed Module: {className} from {file}");
+ }
+ }
+ // Remove Generated module files
+ else if (fileName.Equals($"{className}.csproj", StringComparison.Ordinal) ||
+ fileName.Equals($"{className}.Gen.cs", StringComparison.Ordinal) ||
+ fileName.Equals($"{className}.Gen.cpp", StringComparison.Ordinal) ||
+ fileName.Equals($"{className}.Gen.h", StringComparison.Ordinal))
+ {
+ File.Delete(file);
+ Editor.Log($"Deleted generated modules file for module: {className}. File path {file}");
+ }
+ }
+ }
+ }
+
///
public override void OnUpdate()
{
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index 8f786362d..3417f72a0 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -376,11 +376,25 @@ namespace FlaxEditor.Options
public float ConnectionCurvature { get; set; } = 1.0f;
///
- /// Gets or sets the visject connection curvature.
+ /// Gets or sets a value that indicates wether the context menu description panel is shown or not.
///
[DefaultValue(true)]
- [EditorDisplay("Visject"), EditorOrder(550), Tooltip("Shows/hides the description panel in the visual scripting context menu.")]
- public bool VisualScriptingDescriptionPanel { get; set; } = true;
+ [EditorDisplay("Visject"), EditorOrder(550), Tooltip("Shows/hides the description panel in visual scripting context menu.")]
+ public bool NodeDescriptionPanel { get; set; } = true;
+
+ ///
+ /// Gets or sets the surface grid snapping option.
+ ///
+ [DefaultValue(false)]
+ [EditorDisplay("Visject", "Grid Snapping"), EditorOrder(551), Tooltip("Toggles grid snapping when moving nodes.")]
+ public bool SurfaceGridSnapping { get; set; } = false;
+
+ ///
+ /// Gets or sets the surface grid snapping option.
+ ///
+ [DefaultValue(20.0f)]
+ [EditorDisplay("Visject", "Grid Snapping Size"), EditorOrder(551), Tooltip("Defines the size of the grid for nodes snapping."), VisibleIf(nameof(SurfaceGridSnapping))]
+ public float SurfaceGridSnappingSize { get; set; } = 20.0f;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont);
diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs
index 6d9db8ad6..ec3775b95 100644
--- a/Source/Editor/Scripting/ScriptType.cs
+++ b/Source/Editor/Scripting/ScriptType.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
@@ -671,6 +672,18 @@ namespace FlaxEditor.Scripting
/// The new member value.
public void SetValue(object obj, object value)
{
+ // Perform automatic conversion if type supports it
+ var type = ValueType.Type;
+ var valueType = value?.GetType();
+ if (valueType != null && type != null && valueType != type)
+ {
+ var converter = TypeDescriptor.GetConverter(type);
+ if (converter.CanConvertTo(type))
+ value = converter.ConvertTo(value, type);
+ else if (converter.CanConvertFrom(valueType))
+ value = converter.ConvertFrom(null, null, value);
+ }
+
if (_managed is PropertyInfo propertyInfo)
propertyInfo.SetValue(obj, value);
else if (_managed is FieldInfo fieldInfo)
diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs
index 93495f750..bc7984af9 100644
--- a/Source/Editor/States/LoadingState.cs
+++ b/Source/Editor/States/LoadingState.cs
@@ -57,8 +57,9 @@ namespace FlaxEditor.States
{
// Generate project files when Cache is missing or was cleared previously
var projectFolderPath = Editor.GameProject?.ProjectFolderPath;
- if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
- !Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects")))
+ if (!string.IsNullOrEmpty(projectFolderPath) &&
+ (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
+ !Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects"))))
{
var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs;
ScriptsBuilder.GenerateProject(customArgs);
diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
index c3bf7d0a5..f76faa794 100644
--- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
@@ -23,6 +23,7 @@ namespace FlaxEditor.Surface.Archetypes
private readonly bool _is2D;
private Float2 _rangeX, _rangeY;
private Float2 _debugPos = Float2.Minimum;
+ private float _debugScale = 1.0f;
private readonly List _blendPoints = new List();
///
@@ -445,6 +446,7 @@ namespace FlaxEditor.Surface.Archetypes
// Debug current playback position
if (((AnimGraphSurface)_node.Surface).TryGetTraceEvent(_node, out var traceEvent))
{
+ var prev = _debugPos;
if (_is2D)
{
unsafe
@@ -456,10 +458,17 @@ namespace FlaxEditor.Surface.Archetypes
}
else
_debugPos = new Float2(traceEvent.Value, 0.0f);
+
+ // Scale debug pointer when it moves to make it more visible when investigating blending
+ const float debugMaxSize = 2.0f;
+ float debugScale = Mathf.Saturate(Float2.Distance(ref _debugPos, ref prev) / new Float2(_rangeX.Absolute.ValuesSum, _rangeY.Absolute.ValuesSum).Length * 100.0f) * debugMaxSize + 1.0f;
+ float debugBlendSpeed = _debugScale <= debugScale ? 4.0f : 1.0f;
+ _debugScale = Mathf.Lerp(_debugScale, debugScale, deltaTime * debugBlendSpeed);
}
else
{
_debugPos = Float2.Minimum;
+ _debugScale = 1.0f;
}
base.Update(deltaTime);
@@ -606,7 +615,7 @@ namespace FlaxEditor.Surface.Archetypes
{
// Draw dot with outline
var icon = Editor.Instance.Icons.VisjectBoxOpen32;
- var size = BlendPoint.DefaultSize;
+ var size = BlendPoint.DefaultSize * _debugScale;
var debugPos = BlendSpacePosToBlendPointPos(_debugPos);
var debugRect = new Rectangle(debugPos + new Float2(size * -0.5f) + size * 0.5f, new Float2(size));
var outline = Color.Black; // Shadow
diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
index 526c24fae..f31f1245a 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
@@ -809,7 +809,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!_useDescriptionPanel)
return;
- if (archetype == null || !Editor.Instance.Options.Options.Interface.VisualScriptingDescriptionPanel)
+ if (archetype == null || !Editor.Instance.Options.Options.Interface.NodeDescriptionPanel)
{
HideDescriptionPanel();
return;
@@ -875,7 +875,7 @@ namespace FlaxEditor.Surface.ContextMenu
AddInputOutputElement(archetype, output.Type, true, $"{output.Name} ({output.Type.Name})");
}
}
- else
+ else if (archetype.Elements != null) // Skip if no Elements (ex: Comment node)
{
foreach (var element in archetype.Elements)
{
diff --git a/Source/Editor/Surface/MaterialSurface.cs b/Source/Editor/Surface/MaterialSurface.cs
index 0d6e437b8..077e59fc0 100644
--- a/Source/Editor/Surface/MaterialSurface.cs
+++ b/Source/Editor/Surface/MaterialSurface.cs
@@ -17,7 +17,7 @@ namespace FlaxEditor.Surface
public class MaterialSurface : VisjectSurface
{
///
- public MaterialSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo)
+ public MaterialSurface(IVisjectSurfaceOwner owner, Action onSave = null, FlaxEditor.Undo undo = null)
: base(owner, onSave, undo)
{
}
diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs
index 86b68da4e..54c438fe3 100644
--- a/Source/Editor/Surface/SurfaceComment.cs
+++ b/Source/Editor/Surface/SurfaceComment.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
-using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
@@ -86,7 +85,10 @@ namespace FlaxEditor.Surface
// Read node data
Title = TitleValue;
Color = ColorValue;
- Size = SizeValue;
+ var size = SizeValue;
+ if (Surface.GridSnappingEnabled)
+ size = Surface.SnapToGrid(size, true);
+ Size = size;
// Order
// Backwards compatibility - When opening with an older version send the old comments to the back
@@ -299,7 +301,10 @@ namespace FlaxEditor.Surface
if (_isResizing)
{
// Update size
- Size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom));
+ var size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom));
+ if (Surface.GridSnappingEnabled)
+ size = Surface.SnapToGrid(size, true);
+ Size = size;
}
else
{
diff --git a/Source/Editor/Surface/SurfaceControl.cs b/Source/Editor/Surface/SurfaceControl.cs
index 9774d76ba..29d7c1768 100644
--- a/Source/Editor/Surface/SurfaceControl.cs
+++ b/Source/Editor/Surface/SurfaceControl.cs
@@ -131,6 +131,15 @@ namespace FlaxEditor.Surface
///
public virtual void OnSurfaceLoaded(SurfaceNodeActions action)
{
+ // Snap bounds (with ceil) when using grid snapping
+ if (Surface.GridSnappingEnabled)
+ {
+ var bounds = Bounds;
+ bounds.Location = Surface.SnapToGrid(bounds.Location, false);
+ bounds.Size = Surface.SnapToGrid(bounds.Size, true);
+ Bounds = bounds;
+ }
+
UpdateRectangles();
}
diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs
index 10f079579..c5c011db0 100644
--- a/Source/Editor/Surface/SurfaceNode.cs
+++ b/Source/Editor/Surface/SurfaceNode.cs
@@ -167,6 +167,15 @@ namespace FlaxEditor.Surface
if (Surface == null)
return;
+ // Snap bounds (with ceil) when using grid snapping
+ if (Surface.GridSnappingEnabled)
+ {
+ var size = Surface.SnapToGrid(new Float2(width, height), true);
+ width = size.X;
+ height = size.Y;
+ }
+
+ // Arrange output boxes on the right edge
for (int i = 0; i < Elements.Count; i++)
{
if (Elements[i] is OutputBox box)
@@ -175,6 +184,7 @@ namespace FlaxEditor.Surface
}
}
+ // Resize
Size = CalculateNodeSize(width, height);
}
@@ -976,10 +986,12 @@ namespace FlaxEditor.Surface
else
Array.Copy(values, Values, values.Length);
OnValuesChanged();
- Surface.MarkAsEdited(graphEdited);
if (Surface != null)
+ {
+ Surface.MarkAsEdited(graphEdited);
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
+ }
_isDuringValuesEditing = false;
}
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index 8a37fb6c6..123168950 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -10,9 +9,9 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Options;
using FlaxEditor.Scripting;
-using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
using FlaxEngine;
+using FlaxEditor.GUI;
namespace FlaxEditor.Surface
{
@@ -73,9 +72,8 @@ namespace FlaxEditor.Surface
// By name
if (Editor.Instance.Options.Options.General.ScriptMembersOrder == GeneralOptions.MembersOrder.Alphabetical)
- {
return string.Compare(x.DisplayName, y.DisplayName, StringComparison.InvariantCulture);
- }
+
// Keep same order
return 0;
}
@@ -111,6 +109,79 @@ namespace FlaxEditor.Surface
return CustomEditors.Editors.GenericEditor.OnGroup(layout, "Parameters");
}
+ private sealed class DummyMaterialSurfaceOwner : IVisjectSurfaceOwner
+ {
+ public Asset SurfaceAsset => null;
+ public string SurfaceName => null;
+ public FlaxEditor.Undo Undo => null;
+ public byte[] SurfaceData { get; set; }
+ public VisjectSurfaceContext ParentContext => null;
+
+ public void OnContextCreated(VisjectSurfaceContext context)
+ {
+ }
+
+ public void OnSurfaceEditedChanged()
+ {
+ }
+
+ public void OnSurfaceGraphEdited()
+ {
+ }
+
+ public void OnSurfaceClose()
+ {
+ }
+ }
+
+ private static void FindGraphParameters(Material material, List surfaceParameters)
+ {
+ if (material == null || material.WaitForLoaded())
+ return;
+ var surfaceData = material.LoadSurface(false);
+ if (surfaceData != null && surfaceData.Length > 0)
+ {
+ var surfaceOwner = new DummyMaterialSurfaceOwner { SurfaceData = surfaceData };
+ var surface = new MaterialSurface(surfaceOwner);
+ if (!surface.Load())
+ {
+ surfaceParameters.AddRange(surface.Parameters);
+
+ // Search for any nested parameters (eg. via Sample Layer)
+ foreach (var node in surface.Nodes)
+ {
+ if (node.GroupArchetype.GroupID == 8 && node.Archetype.TypeID == 1) // Sample Layer
+ {
+ if (node.Values != null && node.Values.Length > 0 && node.Values[0] is Guid layerId)
+ {
+ var layer = FlaxEngine.Content.Load(layerId);
+ if (layer)
+ {
+ FindGraphParameters(layer, surfaceParameters);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void FindGraphParameters(MaterialBase materialBase, List surfaceParameters)
+ {
+ while (materialBase != null && !materialBase.WaitForLoaded())
+ {
+ if (materialBase is MaterialInstance materialInstance)
+ {
+ materialBase = materialInstance.BaseMaterial;
+ }
+ else if (materialBase is Material material)
+ {
+ FindGraphParameters(material, surfaceParameters);
+ break;
+ }
+ }
+ }
+
internal static GraphParameterData[] InitGraphParameters(IEnumerable parameters, Material material)
{
int count = parameters.Count();
@@ -118,128 +189,11 @@ namespace FlaxEditor.Surface
int i = 0;
// Load material surface parameters meta to use it for material instance parameters editing
- SurfaceParameter[] surfaceParameters = null;
+ var surfaceParameters = new List();
try
{
Profiler.BeginEvent("Init Material Parameters UI Data");
-
- if (material != null && !material.WaitForLoaded())
- {
- var surfaceData = material.LoadSurface(false);
- if (surfaceData != null && surfaceData.Length > 0)
- {
- using (var memoryStream = new MemoryStream(surfaceData))
- using (var stream = new BinaryReader(memoryStream))
- {
- // IMPORTANT! This must match C++ Graph format
-
- // Magic Code
- int tmp = stream.ReadInt32();
- if (tmp != 1963542358)
- {
- // Error
- throw new Exception("Invalid Graph format version");
- }
-
- // Version
- var version = stream.ReadUInt32();
- var guidBytes = new byte[16];
- if (version < 7000)
- {
- // Time saved (not used anymore to prevent binary diffs after saving unmodified surface)
- stream.ReadInt64();
-
- // Nodes count
- int nodesCount = stream.ReadInt32();
-
- // Parameters count
- int parametersCount = stream.ReadInt32();
-
- // For each node
- for (int j = 0; j < nodesCount; j++)
- {
- // ID
- stream.ReadUInt32();
-
- // Type
- stream.ReadUInt16();
- stream.ReadUInt16();
- }
-
- // For each param
- surfaceParameters = new SurfaceParameter[parametersCount];
- for (int j = 0; j < parametersCount; j++)
- {
- // Create param
- var param = new SurfaceParameter();
- surfaceParameters[j] = param;
-
- // Properties
- param.Type = new ScriptType(VisjectSurfaceContext.GetGraphParameterValueType((VisjectSurfaceContext.GraphParamType_Deprecated)stream.ReadByte()));
- stream.Read(guidBytes, 0, 16);
- param.ID = new Guid(guidBytes);
- param.Name = stream.ReadStr(97);
- param.IsPublic = stream.ReadByte() != 0;
- var isStatic = stream.ReadByte() != 0;
- var isUIVisible = stream.ReadByte() != 0;
- var isUIEditable = stream.ReadByte() != 0;
-
- // References [Deprecated]
- int refsCount = stream.ReadInt32();
- for (int k = 0; k < refsCount; k++)
- stream.ReadUInt32();
-
- // Value
- stream.ReadCommonValue(ref param.Value);
-
- // Meta
- param.Meta.Load(stream);
- }
- }
- else if (version == 7000)
- {
- // Nodes count
- int nodesCount = stream.ReadInt32();
-
- // Parameters count
- int parametersCount = stream.ReadInt32();
-
- // For each node
- for (int j = 0; j < nodesCount; j++)
- {
- // ID
- stream.ReadUInt32();
-
- // Type
- stream.ReadUInt16();
- stream.ReadUInt16();
- }
-
- // For each param
- surfaceParameters = new SurfaceParameter[parametersCount];
- for (int j = 0; j < parametersCount; j++)
- {
- // Create param
- var param = new SurfaceParameter();
- surfaceParameters[j] = param;
-
- // Properties
- param.Type = stream.ReadVariantScriptType();
- stream.Read(guidBytes, 0, 16);
- param.ID = new Guid(guidBytes);
- param.Name = stream.ReadStr(97);
- param.IsPublic = stream.ReadByte() != 0;
-
- // Value
- param.Value = stream.ReadVariant();
-
- // Meta
- param.Meta.Load(stream);
- }
- }
- }
- }
- }
+ FindGraphParameters(material, surfaceParameters);
}
catch (Exception ex)
{
@@ -253,7 +207,26 @@ namespace FlaxEditor.Surface
foreach (var parameter in parameters)
{
- var surfaceParameter = surfaceParameters?.FirstOrDefault(x => x.ID == parameter.ParameterID);
+ var parameterId = parameter.ParameterID;
+ var surfaceParameter = surfaceParameters.FirstOrDefault(x => x.ID == parameterId);
+ if (surfaceParameter == null)
+ {
+ // Permutate original parameter ID to reflect logic in MaterialGenerator::prepareLayer used for nested layers
+ unsafe
+ {
+ var raw = parameterId;
+ var interop = *(FlaxEngine.Json.JsonSerializer.GuidInterop*)&raw;
+ interop.A -= (uint)(i * 17 + 13);
+ parameterId = *(Guid*)&interop;
+ }
+ surfaceParameter = surfaceParameters.FirstOrDefault(x => x.ID == parameterId);
+ }
+ if (surfaceParameter != null)
+ {
+ // Reorder so it won't be picked by other parameter that uses the same ID (eg. params from duplicated materials used as layers in other material)
+ surfaceParameters.Remove(surfaceParameter);
+ surfaceParameters.Add(surfaceParameter);
+ }
var attributes = surfaceParameter?.Meta.GetAttributes() ?? FlaxEngine.Utils.GetEmptyArray();
data[i] = new GraphParameterData(null, parameter.Name, parameter.IsPublic, ToType(parameter.ParameterType), attributes, parameter);
i++;
@@ -587,5 +560,33 @@ namespace FlaxEditor.Surface
return true;
return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left);
}
+
+ internal static void PerformCommonSetup(Windows.Assets.AssetEditorWindow window, ToolStrip toolStrip, VisjectSurface surface,
+ out ToolStripButton saveButton, out ToolStripButton undoButton, out ToolStripButton redoButton)
+ {
+ var editor = window.Editor;
+ var interfaceOptions = editor.Options.Options.Interface;
+ var inputOptions = editor.Options.Options.Input;
+ var undo = surface.Undo;
+
+ // Toolstrip
+ saveButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save");
+ toolStrip.AddSeparator();
+ undoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ redoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
+ toolStrip.AddSeparator();
+ toolStrip.AddButton(editor.Icons.Search64, editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({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;
+ gridSnapButton.Checked = surface.GridSnappingEnabled = interfaceOptions.SurfaceGridSnapping;
+ surface.GridSnappingSize = interfaceOptions.SurfaceGridSnappingSize;
+
+ // Setup input actions
+ window.InputActions.Add(options => options.Undo, undo.PerformUndo);
+ window.InputActions.Add(options => options.Redo, undo.PerformRedo);
+ window.InputActions.Add(options => options.Search, editor.ContentFinding.ShowSearch);
+ }
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
index 604394769..d086aa851 100644
--- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
+++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
@@ -326,7 +326,7 @@ namespace FlaxEditor.Surface
_cmCopyButton = menu.AddButton("Copy", Copy);
menu.AddButton("Paste", Paste).Enabled = CanEdit && CanPaste();
_cmDuplicateButton = menu.AddButton("Duplicate", Duplicate);
- _cmDuplicateButton.Enabled = CanEdit;
+ _cmDuplicateButton.Enabled = CanEdit && selection.Any(node => (node.Archetype.Flags & NodeFlags.NoSpawnViaPaste) == 0);
var canRemove = CanEdit && selection.All(node => (node.Archetype.Flags & NodeFlags.NoRemove) == 0);
menu.AddButton("Cut", Cut).Enabled = canRemove;
menu.AddButton("Delete", Delete).Enabled = canRemove;
diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs
index 9b6f86bcb..416f0152e 100644
--- a/Source/Editor/Surface/VisjectSurface.Input.cs
+++ b/Source/Editor/Surface/VisjectSurface.Input.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Options;
@@ -24,6 +25,7 @@ namespace FlaxEditor.Surface
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
+ private Float2 _gridRoundingDelta;
private HashSet _movingNodes;
private readonly Stack _inputBrackets = new Stack();
@@ -189,6 +191,22 @@ namespace FlaxEditor.Surface
}
}
+ ///
+ /// Snaps a coordinate point to the grid.
+ ///
+ /// The point to be rounded.
+ /// Round to ceiling instead?
+ /// Rounded coordinate.
+ public Float2 SnapToGrid(Float2 point, bool ceil = false)
+ {
+ float gridSize = GridSnappingSize;
+ Float2 snapped = point.Absolute / gridSize;
+ snapped = ceil ? Float2.Ceil(snapped) : Float2.Floor(snapped);
+ snapped.X = (float)Math.CopySign(snapped.X * gridSize, point.X);
+ snapped.Y = (float)Math.CopySign(snapped.Y * gridSize, point.Y);
+ return snapped;
+ }
+
///
public override void OnMouseEnter(Float2 location)
{
@@ -256,18 +274,39 @@ namespace FlaxEditor.Surface
// Moving
else if (_isMovingSelection)
{
+ var gridSnap = GridSnappingEnabled;
+ if (!gridSnap)
+ _gridRoundingDelta = Float2.Zero; // Reset in case user toggled option between frames.
+
// Calculate delta (apply view offset)
var viewDelta = _rootControl.Location - _movingSelectionViewPos;
_movingSelectionViewPos = _rootControl.Location;
- var delta = location - _leftMouseDownPos - viewDelta;
- if (delta.LengthSquared > 0.01f)
+ var delta = location - _leftMouseDownPos - viewDelta + _gridRoundingDelta;
+ var deltaLengthSquared = delta.LengthSquared;
+
+ delta /= _targetScale;
+ if ((!gridSnap || Mathf.Abs(delta.X) >= GridSnappingSize || (Mathf.Abs(delta.Y) >= GridSnappingSize))
+ && deltaLengthSquared > 0.01f)
{
- // Move selected nodes
- delta /= _targetScale;
+ if (gridSnap)
+ {
+ Float2 unroundedDelta = delta;
+ delta = SnapToGrid(unroundedDelta);
+ _gridRoundingDelta = (unroundedDelta - delta) * _targetScale; // Standardize unit of the rounding delta, in case user zooms between node movements.
+ }
+
foreach (var node in _movingNodes)
+ {
+ if (gridSnap)
+ {
+ Float2 unroundedLocation = node.Location;
+ node.Location = SnapToGrid(unroundedLocation);
+ }
node.Location += delta;
+ }
+
_leftMouseDownPos = location;
- _movingNodesDelta += delta;
+ _movingNodesDelta += delta; // TODO: Figure out how to handle undo for differing values of _gridRoundingDelta between selected nodes. For now it will be a small error in undo.
if (_movingNodes.Count > 0)
{
Cursor = CursorType.SizeAll;
diff --git a/Source/Editor/Surface/VisjectSurface.Serialization.cs b/Source/Editor/Surface/VisjectSurface.Serialization.cs
index cbfcf1b20..646e1add5 100644
--- a/Source/Editor/Surface/VisjectSurface.Serialization.cs
+++ b/Source/Editor/Surface/VisjectSurface.Serialization.cs
@@ -62,7 +62,8 @@ namespace FlaxEditor.Surface
///
/// The method calls the setter to assign the result bytes. Sets null value if failed.
///
- public virtual void Save()
+ /// True if failed, otherwise false.
+ public virtual bool Save()
{
var wasEdited = IsEdited;
@@ -71,19 +72,16 @@ namespace FlaxEditor.Surface
_context.CachedSurfaceMeta.Scale = ViewScale;
// Save context (and every modified child context)
- bool failed = RootContext.Save();
-
- if (failed)
- {
- // Error
- return;
- }
+ if (RootContext.Save())
+ return true;
// Clear flag
if (wasEdited)
{
Owner.OnSurfaceEditedChanged();
}
+
+ return false;
}
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs
index b3869f304..28ec17d53 100644
--- a/Source/Editor/Surface/VisjectSurface.cs
+++ b/Source/Editor/Surface/VisjectSurface.cs
@@ -31,11 +31,29 @@ namespace FlaxEditor.Surface
///
protected SurfaceRootControl _rootControl;
+ ///
+ /// Is grid snapping enabled for this surface?
+ ///
+ public bool GridSnappingEnabled
+ {
+ get => _gridSnappingEnabled;
+ set
+ {
+ _gridSnappingEnabled = value;
+ _gridRoundingDelta = Float2.Zero;
+ }
+ }
+
+ ///
+ /// The size of the snapping grid.
+ ///
+ public float GridSnappingSize = 20f;
+
private float _targetScale = 1.0f;
private float _moveViewWithMouseDragSpeed = 1.0f;
private bool _canEdit = true;
private readonly bool _supportsDebugging;
- private bool _isReleasing;
+ private bool _isReleasing, _gridSnappingEnabled;
private VisjectCM _activeVisjectCM;
private GroupArchetype _customNodesGroup;
private List _customNodes;
@@ -632,6 +650,11 @@ namespace FlaxEditor.Surface
SelectionChanged?.Invoke();
}
+ internal void ToggleGridSnapping()
+ {
+ GridSnappingEnabled = !GridSnappingEnabled;
+ }
+
///
/// Selects all the nodes.
///
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 7da0d9707..16e4f303b 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -891,9 +891,21 @@ namespace FlaxEditor.Surface
///
protected Tabs _tabs;
- private readonly ToolStripButton _saveButton;
- private readonly ToolStripButton _undoButton;
- private readonly ToolStripButton _redoButton;
+ ///
+ /// Save button on a toolstrip.
+ ///
+ protected ToolStripButton _saveButton;
+
+ ///
+ /// Undo button on a toolstrip.
+ ///
+ protected ToolStripButton _undoButton;
+
+ ///
+ /// Redo button on a toolstrip.
+ ///
+ protected ToolStripButton _redoButton;
+
private bool _showWholeGraphOnLoad = true;
///
@@ -950,8 +962,6 @@ namespace FlaxEditor.Surface
protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false)
: base(editor, item)
{
- var inputOptions = Editor.Options.Options.Input;
-
// Undo
_undo = new FlaxEditor.Undo();
_undo.UndoDone += OnUndoRedo;
@@ -998,20 +1008,6 @@ namespace FlaxEditor.Surface
_propertiesEditor.Panel.Parent = _split2.Panel2;
}
_propertiesEditor.Modified += OnPropertyEdited;
-
- // Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
- _toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
-
- // Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
}
private void OnUndoRedo(IUndoAction action)
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 81014acf4..edb3e0e39 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -1427,6 +1427,26 @@ namespace FlaxEditor.Viewport
return new Ray(nearPoint + viewOrigin, Vector3.Normalize(farPoint - nearPoint));
}
+ ///
+ /// Projects the point from 3D world-space to viewport coordinates.
+ ///
+ /// The input world-space location (XYZ in world).
+ /// The output viewport window coordinates (XY in screen pixels).
+ public void ProjectPoint(Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation)
+ {
+ viewportSpaceLocation = Float2.Minimum;
+ var viewport = new FlaxEngine.Viewport(0, 0, Width, Height);
+ if (viewport.Width < Mathf.Epsilon || viewport.Height < Mathf.Epsilon)
+ return;
+ Vector3 viewOrigin = Task.View.Origin;
+ Float3 position = ViewPosition - viewOrigin;
+ CreateProjectionMatrix(out var p);
+ CreateViewMatrix(position, out var v);
+ Matrix.Multiply(ref v, ref p, out var vp);
+ viewport.Project(ref worldSpaceLocation, ref vp, out var projected);
+ viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y);
+ }
+
///
/// Called when mouse control begins.
///
diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
index 9d65e5f7b..1cf03f062 100644
--- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
+++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
@@ -206,6 +206,7 @@ namespace FlaxEditor.Windows.Assets
_surface.ContextChanged += OnSurfaceContextChanged;
// Toolstrip
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more");
@@ -295,6 +296,15 @@ namespace FlaxEditor.Windows.Assets
base.SetParameter(index, value);
}
+ ///
+ /// Sets the base model of the animation graph this window is editing.
+ ///
+ /// The new base model.
+ public void SetBaseModel(SkinnedModel baseModel)
+ {
+ _properties.BaseModel = baseModel;
+ }
+
///
protected override void UnlinkItem()
{
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index d443fc166..f4e7a4f6a 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -172,13 +172,8 @@ namespace FlaxEditor.Windows.Assets
_knowledgePropertiesEditor.Panel.Parent = _split2.Panel2;
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/ai/behavior-trees/index.html")).LinkTooltip("See documentation to learn more");
// Debug behavior picker
@@ -206,11 +201,6 @@ namespace FlaxEditor.Windows.Assets
_behaviorPicker.CheckValid = OnBehaviorPickerCheckValid;
_behaviorPicker.ValueChanged += OnBehaviorPickerValueChanged;
- // Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
-
SetCanEdit(!Editor.IsPlayMode);
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
@@ -430,8 +420,7 @@ namespace FlaxEditor.Windows.Assets
private bool SaveSurface()
{
- _surface.Save();
- return false;
+ return _surface.Save();
}
private void SetCanEdit(bool canEdit)
diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
index 43b34a8c6..c1ce59f75 100644
--- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs
+++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
@@ -242,8 +242,11 @@ namespace FlaxEditor.Windows.Assets
private void OpenOptionsContextMenu()
{
- if (_optionsCM != null && _optionsCM.ContainsFocus)
- return;
+ if (_optionsCM != null)
+ {
+ _optionsCM.Hide();
+ _optionsCM.Dispose();
+ }
_optionsCM = new ContextMenu();
_optionsCM.AddButton("Copy type name", () => Clipboard.Text = Asset.DataTypeName);
diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
index 480b39ff6..63471a446 100644
--- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
@@ -9,10 +9,12 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Surface;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Windows.Assets
{
@@ -247,21 +249,9 @@ namespace FlaxEditor.Windows.Assets
if (parameters.Length == 0)
return;
- // Utility buttons
- {
- var buttons = layout.CustomContainer();
- var gridControl = buttons.CustomControl;
- gridControl.ClipChildren = false;
- gridControl.Height = Button.DefaultHeight;
- gridControl.SlotsHorizontally = 2;
- gridControl.SlotsVertically = 1;
- var rebuildButton = buttons.Button("Remove overrides", "Unchecks all overrides for parameters.").Button;
- rebuildButton.Clicked += OnRemoveOverrides;
- var removeButton = buttons.Button("Override all", "Checks all parameters overrides.").Button;
- removeButton.Clicked += OnOverrideAll;
- }
-
var parametersGroup = SurfaceUtils.InitGraphParametersGroup(layout);
+ var settingButton = parametersGroup.AddSettingsButton();
+ settingButton.Clicked += (image, button) => OnSettingsButtonClicked(image, button, proxy.Window);
var baseMaterial = materialInstance.BaseMaterial;
var material = baseMaterial;
if (material)
@@ -323,6 +313,19 @@ namespace FlaxEditor.Windows.Assets
itemLayout.Property(label, valueContainer, null, e.Tooltip?.Text);
});
}
+
+ private void OnSettingsButtonClicked(Image image, MouseButton mouseButton, MaterialInstanceWindow window)
+ {
+ if (mouseButton != MouseButton.Left)
+ return;
+
+ var cm = new ContextMenu();
+ if (window != null)
+ cm.AddButton("Revert All Parameters", window.OnRevertAllParameters).TooltipText = "Reverts all the overridden parameters to the default values.";
+ cm.AddButton("Override All Parameters", OnOverrideAll).TooltipText = "Checks all parameters overrides.";
+ cm.AddButton("Remove Parameter Overrides", OnRemoveOverrides).TooltipText = "Unchecks all overrides for parameters.";
+ cm.Show(image, image.Size);
+ }
private void OnRemoveOverrides()
{
@@ -389,8 +392,6 @@ namespace FlaxEditor.Windows.Assets
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Rotate64, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values");
- _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/instanced-materials/index.html")).LinkTooltip("See documentation to learn more");
// Split Panel
diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs
index ef37fbe67..ad72c92d9 100644
--- a/Source/Editor/Windows/Assets/MaterialWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialWindow.cs
@@ -257,8 +257,9 @@ namespace FlaxEditor.Windows.Assets
};
// Toolstrip
- _toolstrip.AddSeparator();
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code");
+ _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more");
}
diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
index 93dce7034..c373b4cdc 100644
--- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
@@ -141,8 +141,9 @@ namespace FlaxEditor.Windows.Assets
};
// Toolstrip
- _toolstrip.AddSeparator();
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code");
+ _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
}
diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs
index 5a3db6a9d..1b60f4257 100644
--- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs
+++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs
@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
@@ -133,10 +134,21 @@ namespace FlaxEditor.Windows.Assets
group.Panel.Tag = i;
group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
group.Object(new ListValueContainer(elementType, i, Values));
+
+ var stringNameElement = group.Editors[0].ChildrenEditors.Find(x => x is StringEditor).Layout.Children.Find(x => x is TextBoxElement) as TextBoxElement;
+ if (stringNameElement != null)
+ {
+ stringNameElement.TextBox.TextBoxEditEnd += (textbox) => OnNameChanged(group.Panel, (TextBox)textbox);
+ }
}
}
}
+ private void OnNameChanged(DropPanel panel, TextBox textbox)
+ {
+ panel.HeaderText = textbox.Text;
+ }
+
private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Float2 location)
{
var menu = new ContextMenu();
diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
index 9618b30eb..24b3e3f9b 100644
--- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
+++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
@@ -70,13 +70,7 @@ namespace FlaxEditor.Windows.Assets
_undo.ActionDone += OnUndoRedo;
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
- _toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
// Panel
_panel = new Panel(ScrollBars.None)
@@ -85,11 +79,6 @@ namespace FlaxEditor.Windows.Assets
Offsets = new Margin(0, 0, _toolstrip.Bottom, 0),
Parent = this
};
-
- // Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
}
private void OnUndoRedo(IUndoAction action)
diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
index 4a2cb74cf..b424ecffc 100644
--- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs
+++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
@@ -597,13 +597,7 @@ namespace FlaxEditor.Windows.Assets
_propertiesEditor.Select(_properties);
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
- _toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more");
_debugToolstripControls = new[]
@@ -643,9 +637,6 @@ namespace FlaxEditor.Windows.Assets
debugObjectPickerContainer.Parent = _toolstrip;
// Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
InputActions.Add(options => options.DebuggerContinue, OnDebuggerContinue);
InputActions.Add(options => options.DebuggerStepOver, OnDebuggerStepOver);
InputActions.Add(options => options.DebuggerStepOut, OnDebuggerStepOut);
@@ -1202,7 +1193,8 @@ namespace FlaxEditor.Windows.Assets
private bool SaveSurface()
{
- _surface.Save();
+ if (_surface.Save())
+ return true;
// Reselect actors to prevent issues after Visual Script properties were modified
Editor.Windows.PropertiesWin.Presenter.BuildLayoutOnUpdate();
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 831561d35..2103bb8cd 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -1421,6 +1421,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto cData = node->Values[4 + c * 2].AsFloat4();
// Get triangle coords
+ byte anims[3] = { a, b, c };
Float2 points[3] = {
Float2(aData.X, aData.Y),
Float2(bData.X, bData.Y),
@@ -1534,18 +1535,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bestPoint = closest;
hasBest = true;
- float d = Float2::Distance(s[0], s[1]);
- if (Math::IsZero(d))
- {
- bestWeight = 0;
- }
- else
- {
- bestWeight = Float2::Distance(s[0], closest) / d;
- }
-
- bestAnims[0] = j;
- bestAnims[1] = (j + 1) % 3;
+ const float d = Float2::Distance(s[0], s[1]);
+ bestWeight = d < ANIM_GRAPH_BLEND_THRESHOLD ? 0 : Float2::Distance(s[0], closest) / d;
+
+ bestAnims[0] = anims[j];
+ bestAnims[1] = anims[(j + 1) % 3];
}
}
}
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 616fbe91a..4fd1718ab 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -1471,7 +1471,8 @@ Asset::LoadResult VisualScript::load()
for (int32 i = 0; i < count; i++)
{
const int32 oldIndex = _oldParamsLayout.Find(Graph.Parameters[i].Identifier);
- instanceParams[i] = oldIndex != -1 ? valuesCache[oldIndex] : Graph.Parameters[i].Value;
+ const bool useOldValue = oldIndex != -1 && valuesCache[oldIndex] != _oldParamsValues[i];
+ instanceParams[i] = useOldValue ? valuesCache[oldIndex] : Graph.Parameters[i].Value;
}
}
}
@@ -1486,6 +1487,8 @@ Asset::LoadResult VisualScript::load()
instanceParams[i] = Graph.Parameters[i].Value;
}
}
+ _oldParamsLayout.Clear();
+ _oldParamsValues.Clear();
}
#endif
@@ -1499,15 +1502,18 @@ void VisualScript::unload(bool isReloading)
{
// Cache existing instanced parameters IDs to restore values after asset reload (params order might be changed but the IDs are stable)
_oldParamsLayout.Resize(Graph.Parameters.Count());
+ _oldParamsValues.Resize(Graph.Parameters.Count());
for (int32 i = 0; i < Graph.Parameters.Count(); i++)
{
auto& param = Graph.Parameters[i];
_oldParamsLayout[i] = param.Identifier;
+ _oldParamsValues[i] = param.Value;
}
}
else
{
_oldParamsLayout.Clear();
+ _oldParamsValues.Clear();
}
#else
_instances.Clear();
diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h
index 934e6917e..1cd8e9749 100644
--- a/Source/Engine/Content/Assets/VisualScript.h
+++ b/Source/Engine/Content/Assets/VisualScript.h
@@ -154,6 +154,7 @@ private:
Array> _fields;
#if USE_EDITOR
Array _oldParamsLayout;
+ Array _oldParamsValues;
#endif
public:
diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs
index 59cf55cc1..f78b23b73 100644
--- a/Source/Engine/Content/JsonAssetReference.cs
+++ b/Source/Engine/Content/JsonAssetReference.cs
@@ -1,6 +1,10 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
+#if FLAX_EDITOR
+using System.ComponentModel;
+using System.Globalization;
+#endif
using System.Runtime.CompilerServices;
namespace FlaxEngine
@@ -11,6 +15,7 @@ namespace FlaxEngine
/// Type of the asset instance type.
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
+ [TypeConverter(typeof(TypeConverters.JsonAssetReferenceConverter))]
#endif
[Newtonsoft.Json.JsonConverter(typeof(Json.JsonAssetReferenceConverter))]
public struct JsonAssetReference : IComparable, IComparable>, IEquatable>
@@ -125,7 +130,7 @@ namespace FlaxEngine
///
public override string ToString()
{
- return Asset?.ToString();
+ return Asset?.ToString() ?? "null";
}
///
@@ -141,3 +146,33 @@ namespace FlaxEngine
}
}
}
+
+#if FLAX_EDITOR
+namespace FlaxEngine.TypeConverters
+{
+ internal class JsonAssetReferenceConverter : TypeConverter
+ {
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (value is string valueStr)
+ {
+ var result = Activator.CreateInstance(destinationType);
+ Json.JsonSerializer.ParseID(valueStr, out var id);
+ var asset = Content.LoadAsync(id);
+ destinationType.GetField("Asset").SetValue(result, asset);
+ return result;
+ }
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType.Name.StartsWith("JsonAssetReference", StringComparison.Ordinal))
+ return true;
+ return base.CanConvertTo(context, destinationType);
+ }
+ }
+}
+#endif
diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp
index d057bae0c..cc7805e6b 100644
--- a/Source/Engine/ContentImporters/ImportModel.cpp
+++ b/Source/Engine/ContentImporters/ImportModel.cpp
@@ -639,7 +639,17 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
// Save animation data
MemoryWriteStream stream(8182);
- const int32 animIndex = options && options->ObjectIndex != -1 ? options->ObjectIndex : 0; // Single animation per asset
+ int32 animIndex = options ? options->ObjectIndex : -1; // Single animation per asset
+ if (animIndex == -1)
+ {
+ // Pick the longest animation by default (eg. to skip ref pose anim if exported as the first one)
+ animIndex = 0;
+ for (int32 i = 1; i < modelData.Animations.Count(); i++)
+ {
+ if (modelData.Animations[i].GetLength() > modelData.Animations[animIndex].GetLength())
+ animIndex = i;
+ }
+ }
if (modelData.Pack2AnimationHeader(&stream, animIndex))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
diff --git a/Source/Engine/Core/Math/Float2.cs b/Source/Engine/Core/Math/Float2.cs
index adbe61508..92247d865 100644
--- a/Source/Engine/Core/Math/Float2.cs
+++ b/Source/Engine/Core/Math/Float2.cs
@@ -865,6 +865,16 @@ namespace FlaxEngine
return new Float2(Mathf.Ceil(v.X), Mathf.Ceil(v.Y));
}
+ ///
+ /// Returns the vector with components containing the smallest integer smaller to or equal to the original value.
+ ///
+ /// The value.
+ /// The result.
+ public static Float2 Floor(Float2 v)
+ {
+ return new Float2(Mathf.Floor(v.X), Mathf.Floor(v.Y));
+ }
+
///
/// Breaks the components of the vector into an integral and a fractional part. Returns vector made of fractional parts.
///
diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp
index ab92426d7..46240d4b8 100644
--- a/Source/Engine/Core/Math/Quaternion.cpp
+++ b/Source/Engine/Core/Math/Quaternion.cpp
@@ -7,6 +7,7 @@
#include "Matrix3x3.h"
#include "Math.h"
#include "../Types/String.h"
+#include "Engine/Core/Math/Transform.h"
Quaternion Quaternion::Zero(0, 0, 0, 0);
Quaternion Quaternion::One(1, 1, 1, 1);
@@ -537,3 +538,14 @@ void Quaternion::RotationYawPitchRoll(float yaw, float pitch, float roll, Quater
result.Y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
result.Z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
}
+
+Quaternion Quaternion::GetRotationFromNormal(const Vector3& normal, const Transform& reference)
+{
+ Float3 up = reference.GetUp();
+ const float dot = Vector3::Dot(normal, up);
+ if (Math::NearEqual(Math::Abs(dot), 1))
+ {
+ up = reference.GetRight();
+ }
+ return Quaternion::LookRotation(normal, up);
+}
diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs
index e50795221..b6b9518c3 100644
--- a/Source/Engine/Core/Math/Quaternion.cs
+++ b/Source/Engine/Core/Math/Quaternion.cs
@@ -1483,6 +1483,45 @@ namespace FlaxEngine
return results;
}
+ ///
+ /// Gets rotation from a normal in relation to a transform.
+ /// This function is especially useful for axis aligned faces,
+ /// and with .
+ ///
+ /// Example code:
+ ///
+ /// GetRotationFromNormalExample :
+ /// RayOrigin;
+ /// SomeObject;
+ ///
+ /// {
+ /// (.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out hit)
+ /// {
+ /// position = hit.Collider.Position;
+ /// transform = hit.Collider.Transform;
+ /// point = hit.Point;
+ /// normal = hit.Normal;
+ /// rot = .GetRotationFromNormal(normal,transform);
+ /// SomeObject.Position = point;
+ /// SomeObject.Orientation = rot;
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// The normal vector.
+ /// The reference transform.
+ /// The rotation from the normal vector.
+ public static Quaternion GetRotationFromNormal(Vector3 normal, Transform reference)
+ {
+ Float3 up = reference.Up;
+ var dot = Vector3.Dot(normal, up);
+ if (Mathf.NearEqual(Math.Abs(dot), 1))
+ up = reference.Right;
+ return LookRotation(normal, up);
+ }
+
///
/// Adds two quaternions.
///
diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h
index 45605a674..b2b57886f 100644
--- a/Source/Engine/Core/Math/Quaternion.h
+++ b/Source/Engine/Core/Math/Quaternion.h
@@ -660,6 +660,14 @@ public:
// @param roll The roll of rotation (in radians)
// @param result When the method completes, contains the newly created quaternion
static void RotationYawPitchRoll(float yaw, float pitch, float roll, Quaternion& result);
+
+ ///
+ /// Gets rotation from a normal in relation to a transform. This function is especially useful for axis aligned faces, and with .
+ ///
+ /// The normal vector.
+ /// The reference transform.
+ /// The rotation from the normal vector.
+ static Quaternion GetRotationFromNormal(const Vector3& normal, const Transform& reference);
};
///
diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp
index 6195dad04..0106be2f9 100644
--- a/Source/Engine/Core/Math/Transform.cpp
+++ b/Source/Engine/Core/Math/Transform.cpp
@@ -252,3 +252,9 @@ void Transform::Lerp(const Transform& t1, const Transform& t2, float amount, Tra
Quaternion::Slerp(t1.Orientation, t2.Orientation, amount, result.Orientation);
Float3::Lerp(t1.Scale, t2.Scale, amount, result.Scale);
}
+
+Transform Transform::AlignRotationToNormalAndSnapToGrid(const Vector3& point, const Vector3& normal, const Vector3& normalOffset, const Transform& relativeTo, const Vector3& gridSize, const Float3& scale)
+{
+ Quaternion rot = Quaternion::GetRotationFromNormal(normal, relativeTo);
+ return Transform(Vector3::SnapToGrid(point, gridSize, rot, relativeTo.Translation, normalOffset), rot, scale);
+}
diff --git a/Source/Engine/Core/Math/Transform.cs b/Source/Engine/Core/Math/Transform.cs
index e00265f6d..7387bab83 100644
--- a/Source/Engine/Core/Math/Transform.cs
+++ b/Source/Engine/Core/Math/Transform.cs
@@ -478,6 +478,96 @@ namespace FlaxEngine
Float3.Lerp(ref start.Scale, ref end.Scale, amount, out result.Scale);
}
+ ///
+ /// Combines the functions:
+ /// ,
+ /// .
+ /// Example code:
+ ///
+ /// AlignRotationToObjectAndSnapToGridExample :
+ /// Offset = new Vector3(0, 0, 50f);
+ /// GridSize = * 20.0f;
+ /// RayOrigin;
+ /// SomeObject;
+ ///
+ /// {
+ /// (.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out hit)
+ /// {
+ /// transform = hit.Collider.Transform;
+ /// point = hit.Point;
+ /// normal = hit.Normal;
+ /// SomeObject.Transform = .AlignRotationToNormalAndSnapToGrid
+ /// (
+ /// point,
+ /// normal,
+ /// Offset,
+ /// transform,
+ /// SomeObject.Scale,
+ /// GridSize,
+ /// Float3.One
+ /// );
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// The position to snap.
+ /// The size of the grid.
+ /// The local grid offset to apply after snapping.
+ /// The normal vector.
+ /// The relative transform.
+ /// The scale to apply to the transform.
+ /// The rotated and snapped transform.
+ public static Transform AlignRotationToNormalAndSnapToGrid(Vector3 point, Vector3 normal, Vector3 normalOffset, Transform relativeTo, Vector3 gridSize, Float3 scale)
+ {
+ Quaternion rot = Quaternion.GetRotationFromNormal(normal, relativeTo);
+ return new Transform(Vector3.SnapToGrid(point, gridSize, rot, relativeTo.Translation, normalOffset), rot, scale);
+ }
+
+ ///
+ /// Combines the functions:
+ /// ,
+ /// .
+ /// Example code:
+ ///
+ /// AlignRotationToObjectAndSnapToGridExample :
+ /// Offset = new Vector3(0, 0, 50f);
+ /// GridSize = * 20.0f;
+ /// RayOrigin;
+ /// SomeObject;
+ ///
+ /// {
+ /// (.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out hit)
+ /// {
+ /// transform = hit.Collider.Transform;
+ /// point = hit.Point;
+ /// normal = hit.Normal;
+ /// SomeObject.Transform = .AlignRotationToNormalAndSnapToGrid
+ /// (
+ /// point,
+ /// normal,
+ /// Offset,
+ /// transform,
+ /// GridSize
+ /// );
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// The position to snap.
+ /// The size of the grid.
+ /// The local grid offset to apply after snapping.
+ /// The normal vector.
+ /// The relative transform.
+ /// The rotated and snapped transform with scale .
+ public static Transform AlignRotationToNormalAndSnapToGrid(Vector3 point, Vector3 normal, Vector3 normalOffset, Transform relativeTo, Vector3 gridSize)
+ {
+ return AlignRotationToNormalAndSnapToGrid(point, normal, normalOffset, relativeTo, gridSize, Float3.One);
+ }
+
///
/// Tests for equality between two objects.
///
diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h
index 2c51bfa4a..8e9e07e0a 100644
--- a/Source/Engine/Core/Math/Transform.h
+++ b/Source/Engine/Core/Math/Transform.h
@@ -291,6 +291,20 @@ public:
return result;
}
+ ///
+ /// Combines the functions:
+ /// ,
+ /// .
+ ///
+ /// The position to snap.
+ /// The size of the grid.
+ /// The local grid offset to apply after snapping.
+ /// The normal vector.
+ /// The relative transform.
+ /// The scale to apply to the transform.
+ /// The rotated and snapped transform.
+ static Transform AlignRotationToNormalAndSnapToGrid(const Vector3& point, const Vector3& normal, const Vector3& normalOffset, const Transform& relativeTo, const Vector3& gridSize, const Float3& scale = Float3::One);
+
public:
FORCE_INLINE Transform operator*(const Transform& other) const
{
diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp
index 3ffb2cdc9..5a26f2309 100644
--- a/Source/Engine/Core/Math/Vector3.cpp
+++ b/Source/Engine/Core/Math/Vector3.cpp
@@ -324,6 +324,20 @@ float Float3::Angle(const Float3& from, const Float3& to)
return Math::Acos(dot);
}
+template<>
+Float3 Float3::SnapToGrid(const Float3& pos, const Float3& gridSize)
+{
+ return Float3(Math::Ceil((pos.X - (gridSize.X * 0.5f)) / gridSize.X) * gridSize.X,
+ Math::Ceil((pos.Y - (gridSize.Y * 0.5f)) / gridSize.Y) * gridSize.Y,
+ Math::Ceil((pos.Z - (gridSize.Z * 0.5f)) / gridSize.Z) * gridSize.Z);
+}
+
+template<>
+Float3 Float3::SnapToGrid(const Float3& point, const Float3& gridSize, const Quaternion& gridOrientation, const Float3& gridOrigin, const Float3& offset)
+{
+ return (gridOrientation * (gridOrientation.Conjugated() * SnapToGrid(point - gridOrigin, gridSize) + offset)) + gridOrigin;
+}
+
// Double
static_assert(sizeof(Double3) == 24, "Invalid Double3 type size.");
@@ -638,6 +652,20 @@ double Double3::Angle(const Double3& from, const Double3& to)
return Math::Acos(dot);
}
+template<>
+Double3 Double3::SnapToGrid(const Double3& pos, const Double3& gridSize)
+{
+ return Double3(Math::Ceil((pos.X - (gridSize.X * 0.5)) / gridSize.X) * gridSize.X,
+ Math::Ceil((pos.Y - (gridSize.Y * 0.5)) / gridSize.Y) * gridSize.Y,
+ Math::Ceil((pos.Z - (gridSize.Z * 0.5)) / gridSize.Z) * gridSize.Z);
+}
+
+template<>
+Double3 Double3::SnapToGrid(const Double3& point, const Double3& gridSize, const Quaternion& gridOrientation, const Double3& gridOrigin, const Double3& offset)
+{
+ return (gridOrientation * (gridOrientation.Conjugated() * SnapToGrid(point - gridOrigin, gridSize) + offset)) + gridOrigin;
+}
+
// Int
static_assert(sizeof(Int3) == 12, "Invalid Int3 type size.");
@@ -852,3 +880,17 @@ int32 Int3::Angle(const Int3& from, const Int3& to)
{
return 0;
}
+
+template<>
+Int3 Int3::SnapToGrid(const Int3& pos, const Int3& gridSize)
+{
+ return Int3(((pos.X - (gridSize.X / 2)) / gridSize.X) * gridSize.X,
+ ((pos.Y - (gridSize.Y / 2)) / gridSize.Y) * gridSize.Y,
+ ((pos.Z - (gridSize.Z / 2)) / gridSize.Z) * gridSize.Z);
+}
+
+template<>
+Int3 Int3::SnapToGrid(const Int3& point, const Int3& gridSize, const Quaternion& gridOrientation, const Int3& gridOrigin, const Int3& offset)
+{
+ return (gridOrientation * (gridOrientation.Conjugated() * SnapToGrid(point - gridOrigin, gridSize) + offset)) + gridOrigin;
+}
diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs
index 738a9bad8..5505c5053 100644
--- a/Source/Engine/Core/Math/Vector3.cs
+++ b/Source/Engine/Core/Math/Vector3.cs
@@ -1672,7 +1672,7 @@ namespace FlaxEngine
}
///
- /// Snaps the input position into the grid.
+ /// Snaps the input position onto the grid.
///
/// The position to snap.
/// The size of the grid.
@@ -1685,6 +1685,44 @@ namespace FlaxEngine
return pos;
}
+ ///
+ /// Snaps the onto the rotated grid.
+ /// For world aligned grid snapping use instead.
+ /// Example code:
+ ///
+ /// SnapToGridExample :
+ /// GridSize = * 20.0f;
+ /// RayOrigin;
+ /// SomeObject;
+ ///
+ /// {
+ /// (.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out hit)
+ /// {
+ /// position = hit.Collider.Position;
+ /// transform = hit.Collider.Transform;
+ /// point = hit.Point;
+ /// normal = hit.Normal;
+ /// //Get rotation from normal relative to collider transform
+ /// rot = .GetRotationFromNormal(normal, transform);
+ /// point = .SnapToGrid(point, GridSize, rot, position);
+ /// SomeObject.Position = point;
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// The position to snap.
+ /// The size of the grid.
+ /// The rotation of the grid.
+ /// The center point of the grid.
+ /// The local position offset applied to the snapped position before grid rotation.
+ /// The position snapped to the grid.
+ public static Vector3 SnapToGrid(Vector3 point, Vector3 gridSize, Quaternion gridOrientation, Vector3 gridOrigin, Vector3 offset)
+ {
+ return ((SnapToGrid(point - gridOrigin, gridSize) * gridOrientation.Conjugated() + offset) * gridOrientation) + gridOrigin;
+ }
+
///
/// Adds two vectors.
///
diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h
index 619fcfc12..2e5d3d478 100644
--- a/Source/Engine/Core/Math/Vector3.h
+++ b/Source/Engine/Core/Math/Vector3.h
@@ -927,6 +927,25 @@ public:
/// The second vector.
/// The angle (in radians).
static FLAXENGINE_API T Angle(const Vector3Base& from, const Vector3Base& to);
+
+ ///
+ /// Snaps the input position onto the grid.
+ ///
+ /// The position to snap.
+ /// The size of the grid.
+ /// The position snapped to the grid.
+ static FLAXENGINE_API Vector3Base SnapToGrid(const Vector3Base& pos, const Vector3Base& gridSize);
+
+ ///
+ /// Snaps the onto the rotated grid. For world aligned grid snapping use instead.
+ ///
+ /// The position to snap.
+ /// The size of the grid.
+ /// The center point of the grid.
+ /// The rotation of the grid.
+ /// The local position offset applied to the snapped position before grid rotation.
+ /// The position snapped to the grid.
+ static FLAXENGINE_API Vector3Base SnapToGrid(const Vector3Base& point, const Vector3Base& gridSize, const Quaternion& gridOrientation, const Vector3Base& gridOrigin = Zero, const Vector3Base& offset = Zero);
};
template
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index c3e395992..09a5b9108 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -479,6 +479,10 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
batch.DrawCall.InstanceCount = 1;
auto& firstInstance = batch.Instances[0];
firstInstance.Load(batch.DrawCall);
+#if USE_EDITOR
+ if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
+ batch.DrawCall.Surface.LODDitherFactor = type.ScaleInLightmap; // See LightmapUVsDensityMaterialShader
+#endif
if (EnumHasAnyFlags(drawModes, DrawPass::Forward))
{
diff --git a/Source/Engine/Graphics/GPUPipelineStatePermutations.h b/Source/Engine/Graphics/GPUPipelineStatePermutations.h
index e3306fa92..44214056a 100644
--- a/Source/Engine/Graphics/GPUPipelineStatePermutations.h
+++ b/Source/Engine/Graphics/GPUPipelineStatePermutations.h
@@ -39,11 +39,13 @@ public:
FORCE_INLINE GPUPipelineState* Get(int index) const
{
+ ASSERT_LOW_LAYER(index >= 0 && index < Size);
return States[index];
}
FORCE_INLINE GPUPipelineState*& operator[](int32 index)
{
+ ASSERT_LOW_LAYER(index >= 0 && index < Size);
return States[index];
}
@@ -129,6 +131,7 @@ public:
public:
FORCE_INLINE GPUShaderProgramCS* Get(const int index) const
{
+ ASSERT_LOW_LAYER(index >= 0 && index < Size);
return Shaders[index];
}
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index 766e08a39..77c2089e1 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -480,6 +480,8 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
const ViewMode viewMode = renderContext.View.Mode;
if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview)
GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]);
+ if (viewMode == ViewMode::LightmapUVsDensity)
+ drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader
#endif
// Push draw call to the render list
@@ -541,6 +543,8 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
const ViewMode viewMode = renderContextBatch.GetMainContext().View.Mode;
if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview)
GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]);
+ if (viewMode == ViewMode::LightmapUVsDensity)
+ drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader
#endif
// Push draw call to the render lists
diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h
index 467a88ea3..3ada38b34 100644
--- a/Source/Engine/Graphics/Models/MeshBase.h
+++ b/Source/Engine/Graphics/Models/MeshBase.h
@@ -241,5 +241,9 @@ public:
/// The object sorting key.
///
int8 SortOrder;
+
+#if USE_EDITOR
+ float LightmapScale = -1.0f;
+#endif
};
};
diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h
index 5feb5dd9a..dc3895954 100644
--- a/Source/Engine/Graphics/PostProcessSettings.h
+++ b/Source/Engine/Graphics/PostProcessSettings.h
@@ -52,6 +52,11 @@ API_ENUM() enum class ToneMappingMode
/// The ACES Filmic reference tonemapper (approximation).
///
ACES = 2,
+
+ ///
+ /// The AGX tonemapper.
+ ///
+ AGX = 3,
};
///
diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
index 14874abf1..2e1aaf457 100644
--- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp
+++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
@@ -213,13 +213,13 @@ protected:
const int32 dstMips = dstTexture->MipLevels();
GPUTexture* srcTexture = _streamingTexture->GetTexture();
const int32 srcMips = srcTexture->MipLevels();
+ const int32 srcMissingMips = srcMips - srcTexture->ResidentMipLevels();
const int32 mipCount = Math::Min(dstMips, srcMips);
- ASSERT(mipCount > 0);
- for (int32 mipIndex = 0; mipIndex < mipCount; mipIndex++)
+ for (int32 mipIndex = srcMissingMips; mipIndex < mipCount; mipIndex++)
{
context->GPU->CopySubresource(dstTexture, dstMips - mipIndex - 1, srcTexture, srcMips - mipIndex - 1);
}
- _uploadedMipCount = mipCount;
+ _uploadedMipCount = mipCount - srcMissingMips;
return Result::Ok;
}
@@ -238,10 +238,10 @@ protected:
void OnSync() override
{
+ _newTexture->SetResidentMipLevels(_uploadedMipCount);
Swap(_streamingTexture->_texture, _newTexture);
- _streamingTexture->GetTexture()->SetResidentMipLevels(_uploadedMipCount);
- _streamingTexture->ResidencyChanged();
SAFE_DELETE_GPU_RESOURCE(_newTexture);
+ _streamingTexture->ResidencyChanged();
// Base
GPUTask::OnSync();
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 7d267f21b..d1ccd367e 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -27,32 +27,18 @@ struct AxisEvaluation
struct ActionData
{
- bool Active;
- uint64 FrameIndex;
- InputActionState State;
-
- ActionData()
- {
- Active = false;
- FrameIndex = 0;
- State = InputActionState::Waiting;
- }
+ bool Active = false;
+ uint64 FrameIndex = 0;
+ InputActionState State = InputActionState::Waiting;
};
struct AxisData
{
- float Value;
- float ValueRaw;
- float PrevKeyValue;
- uint64 FrameIndex;
-
- AxisData()
- {
- Value = 0.0f;
- ValueRaw = 0.0f;
- PrevKeyValue = 0.0f;
- FrameIndex = 0;
- }
+ float Value = 0.0f;
+ float ValueRaw = 0.0f;
+ float PrevValue = 0.0f;
+ float PrevKeyValue = 0.0f;
+ uint64 FrameIndex = 0;
};
namespace InputImpl
@@ -990,6 +976,7 @@ void InputService::Update()
// Setup axis data
data.PrevKeyValue = e.PrevKeyValue;
+ data.PrevValue = data.Value;
data.ValueRaw = e.RawValue;
data.Value = e.Value;
@@ -1025,7 +1012,7 @@ void InputService::Update()
{
for (auto i = Axes.Begin(); i.IsNotEnd(); ++i)
{
- if (Math::NotNearEqual(i->Value.Value, i->Value.PrevKeyValue))
+ if (Math::NotNearEqual(i->Value.Value, i->Value.PrevValue))
{
Input::AxisValueChanged(i->Key);
}
diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp
index 29b980cbe..f6aa00c78 100644
--- a/Source/Engine/Level/Actors/Camera.cpp
+++ b/Source/Engine/Level/Actors/Camera.cpp
@@ -237,7 +237,10 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp
viewport.Unproject(nearPoint, ivp, nearPoint);
viewport.Unproject(farPoint, ivp, farPoint);
- return Ray(nearPoint, Vector3::Normalize(farPoint - nearPoint));
+ Vector3 dir = Vector3::Normalize(farPoint - nearPoint);
+ if (dir.IsZero())
+ return Ray::Identity;
+ return Ray(nearPoint, dir);
}
Viewport Camera::GetViewport() const
@@ -303,6 +306,8 @@ void Camera::GetMatrices(Matrix& view, Matrix& projection, const Viewport& viewp
void Camera::OnPreviewModelLoaded()
{
_previewModelBuffer.Setup(_previewModel.Get());
+ if (_previewModelBuffer.Count() > 0)
+ _previewModelBuffer.At(0).ReceiveDecals = false;
UpdateCache();
}
diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp
index 01a4cde78..01b4248ec 100644
--- a/Source/Engine/Level/Actors/Spline.cpp
+++ b/Source/Engine/Level/Actors/Spline.cpp
@@ -150,35 +150,30 @@ float Spline::GetSplineLength() const
{
float sum = 0.0f;
constexpr int32 slices = 20;
- constexpr float step = 1.0f / (float)slices;
- Vector3 prevPoint = Vector3::Zero;
- if (Curve.GetKeyframes().Count() != 0)
- {
- const auto& a = Curve[0];
- prevPoint = a.Value.Translation * _transform.Scale;
- }
+ constexpr float step = 1.0f / (float)(slices - 1);
+ const Vector3 scale = _transform.Scale;
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{
const auto& a = Curve[i - 1];
const auto& b = Curve[i];
+ Vector3 prevPoint = a.Value.Translation * scale;
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
- // TODO: implement sth more analytical than brute-force solution
- for (int32 slice = 0; slice < slices; slice++)
+ for (int32 slice = 1; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
- pos *= _transform.Scale;
- sum += (float)Vector3::DistanceSquared(pos, prevPoint);
+ pos *= scale;
+ sum += (float)Vector3::Distance(pos, prevPoint);
prevPoint = pos;
}
}
- return Math::Sqrt(sum);
+ return sum;
}
float Spline::GetSplineSegmentLength(int32 index) const
@@ -188,28 +183,28 @@ float Spline::GetSplineSegmentLength(int32 index) const
CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f);
float sum = 0.0f;
constexpr int32 slices = 20;
- constexpr float step = 1.0f / (float)slices;
+ constexpr float step = 1.0f / (float)(slices - 1);
const auto& a = Curve[index - 1];
const auto& b = Curve[index];
- Vector3 startPoint = a.Value.Translation * _transform.Scale;
+ const Vector3 scale = _transform.Scale;
+ Vector3 prevPoint = a.Value.Translation * scale;
{
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
- // TODO: implement sth more analytical than brute-force solution
- for (int32 slice = 0; slice < slices; slice++)
+ for (int32 slice = 1; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
- pos *= _transform.Scale;
- sum += (float)Vector3::DistanceSquared(pos, startPoint);
- startPoint = pos;
+ pos *= scale;
+ sum += (float)Vector3::Distance(pos, prevPoint);
+ prevPoint = pos;
}
}
- return Math::Sqrt(sum);
+ return sum;
}
float Spline::GetSplineTime(int32 index) const
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index c6d130357..81f30df0c 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -357,6 +357,10 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
+#if USE_EDITOR
+ if (HasStaticFlag(StaticFlags::Lightmap))
+ draw.LightmapScale = _scaleInLightmap;
+#endif
Model->Draw(renderContext, draw);
@@ -391,6 +395,10 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch)
draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
+#if USE_EDITOR
+ if (HasStaticFlag(StaticFlags::Lightmap))
+ draw.LightmapScale = _scaleInLightmap;
+#endif
Model->Draw(renderContextBatch, draw);
diff --git a/Source/Engine/Level/Tags.cs b/Source/Engine/Level/Tags.cs
index 3a25a8efa..3c1bb2047 100644
--- a/Source/Engine/Level/Tags.cs
+++ b/Source/Engine/Level/Tags.cs
@@ -7,9 +7,7 @@ using System.Runtime.CompilerServices;
namespace FlaxEngine
{
-#if FLAX_EDITOR
[TypeConverter(typeof(TypeConverters.TagConverter))]
-#endif
partial struct Tag : IEquatable, IEquatable, IComparable, IComparable, IComparable
{
///
@@ -254,7 +252,6 @@ namespace FlaxEngine
}
}
-#if FLAX_EDITOR
namespace FlaxEngine.TypeConverters
{
internal class TagConverter : TypeConverter
@@ -291,4 +288,3 @@ namespace FlaxEngine.TypeConverters
}
}
}
-#endif
diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp
index 8446f9ac3..2bb0d22b6 100644
--- a/Source/Engine/Networking/NetworkReplicator.cpp
+++ b/Source/Engine/Networking/NetworkReplicator.cpp
@@ -1503,6 +1503,8 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds)
{
+ if (targetIds.IsValid() && targetIds.Length() == 0)
+ return true; // Target list is provided, but it's empty so nobody will get this RPC
Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline())
diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
index 2a728170b..dbd7c0d02 100644
--- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
+++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
@@ -66,12 +66,14 @@ bool GPUParticles::Init(ParticleEmitter* owner, MemoryReadStream& shaderCacheStr
LOG(Warning, "Missing valid GPU particles constant buffer.");
return true;
}
- if (cb0->GetSize() < sizeof(GPUParticlesData))
+ const int32 cbSize = cb0->GetSize();
+ if (cbSize < sizeof(GPUParticlesData))
{
- LOG(Warning, "Invalid size GPU particles constant buffer. required {0} bytes but got {1}", sizeof(GPUParticlesData), cb0->GetSize());
+ LOG(Warning, "Invalid size GPU particles constant buffer. required {0} bytes but got {1}", sizeof(GPUParticlesData), cbSize);
return true;
}
- _cbData.Resize(cb0->GetSize());
+ _cbData.Resize(cbSize);
+ Platform::MemoryClear(_cbData.Get(), cbSize);
// Load material parameters
if (_params.Load(materialParamsStream))
diff --git a/Source/Engine/Renderer/ColorGradingPass.h b/Source/Engine/Renderer/ColorGradingPass.h
index 39968a4e6..7da9a20df 100644
--- a/Source/Engine/Renderer/ColorGradingPass.h
+++ b/Source/Engine/Renderer/ColorGradingPass.h
@@ -15,7 +15,7 @@ private:
bool _useVolumeTexture;
PixelFormat _lutFormat;
AssetReference _shader;
- GPUPipelineStatePermutationsPs<3> _psLut;
+ GPUPipelineStatePermutationsPs<4> _psLut;
public:
diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp
index 47fbbe3df..66121ac05 100644
--- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp
+++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp
@@ -15,7 +15,6 @@
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Foliage/Foliage.h"
#include "Engine/ShadowsOfMordor/Builder.Config.h"
-#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Actors/StaticModel.h"
@@ -70,40 +69,6 @@ DrawPass LightmapUVsDensityMaterialShader::GetDrawModes() const
return DrawPass::GBuffer;
}
-namespace
-{
- Actor* FindActorByDrawCall(Actor* actor, const DrawCall& drawCall, float& scaleInLightmap)
- {
- // TODO: large-worlds
- const auto asStaticModel = ScriptingObject::Cast(actor);
- if (asStaticModel && asStaticModel->GetPerInstanceRandom() == drawCall.PerInstanceRandom && asStaticModel->GetPosition() == drawCall.ObjectPosition)
- {
- scaleInLightmap = asStaticModel->GetScaleInLightmap();
- return asStaticModel;
- }
- const auto asFoliage = ScriptingObject::Cast(actor);
- if (asFoliage)
- {
- for (auto i = asFoliage->Instances.Begin(); i.IsNotEnd(); ++i)
- {
- auto& instance = *i;
- if (instance.Random == drawCall.PerInstanceRandom && instance.Transform.Translation == drawCall.ObjectPosition)
- {
- scaleInLightmap = asFoliage->FoliageTypes[instance.Type].ScaleInLightmap;
- return asFoliage;
- }
- }
- }
- for (Actor* child : actor->Children)
- {
- const auto other = FindActorByDrawCall(child, drawCall, scaleInLightmap);
- if (other)
- return other;
- }
- return nullptr;
- }
-}
-
void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
{
// Prepare
@@ -121,33 +86,6 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
_ps->Init(psDesc);
}
- // Find the static model that produced this draw call
- const Actor* drawCallActor = nullptr;
- float scaleInLightmap = 1.0f;
- if (params.RenderContext.Task)
- {
- // Skip this lookup as it's too slow
-
- /*if (params.RenderContext.Task->ActorsSource & ActorsSources::CustomActors)
- {
- for (auto actor : params.RenderContext.Task->CustomActors)
- {
- drawCallActor = FindActorByDrawCall(actor, drawCall, scaleInLightmap);
- if (drawCallActor)
- break;
- }
- }
- if (!drawCallActor && params.RenderContext.Task->ActorsSource & ActorsSources::Scenes)
- {
- for (auto& scene : Level::Scenes)
- {
- drawCallActor = FindActorByDrawCall(scene, drawCall, scaleInLightmap);
- if (drawCallActor)
- break;
- }
- }*/
- }
-
// Bind constants
if (cb && cb->GetSize())
{
@@ -166,19 +104,15 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
data.LightmapSize = 1024.0f;
data.LightmapArea = drawCall.Surface.LightmapUVsArea;
const ModelLOD* drawCallModelLod;
- if (GBufferPass::IndexBufferToModelLOD.TryGet(drawCall.Geometry.IndexBuffer, drawCallModelLod))
+ float scaleInLightmap = drawCall.Surface.LODDitherFactor; // Reuse field
+ if (scaleInLightmap < 0.0f)
+ data.LightmapSize = -1.0f; // Not using lightmap
+ else if (GBufferPass::IndexBufferToModelLOD.TryGet(drawCall.Geometry.IndexBuffer, drawCallModelLod))
{
// Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting)
float globalObjectsScale = 1.0f;
int32 atlasSize = 1024;
int32 chartsPadding = 3;
- const Scene* drawCallScene = drawCallActor ? drawCallActor->GetScene() : (Level::Scenes.Count() != 0 ? Level::Scenes[0] : nullptr);
- if (drawCallScene)
- {
- globalObjectsScale = drawCallScene->Info.LightmapSettings.GlobalObjectsScale;
- atlasSize = (int32)drawCallScene->Info.LightmapSettings.AtlasSize;
- chartsPadding = drawCallScene->Info.LightmapSettings.ChartsPadding;
- }
BoundingBox box = drawCallModelLod->GetBox(drawCall.World);
Float3 size = box.GetSize();
float dimensionsCoeff = size.AverageArithmetic();
diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs
index 5be6a6a53..04a20fdb4 100644
--- a/Source/Engine/Serialization/JsonConverters.cs
+++ b/Source/Engine/Serialization/JsonConverters.cs
@@ -479,7 +479,7 @@ namespace FlaxEngine.Json
///
public override bool CanConvert(Type objectType)
{
- return objectType.Name.StartsWith("JsonAssetReference");
+ return objectType.Name.StartsWith("JsonAssetReference", StringComparison.Ordinal);
}
}
diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h
index d9ef89fb5..4a9107a78 100644
--- a/Source/Engine/Terrain/Terrain.h
+++ b/Source/Engine/Terrain/Terrain.h
@@ -187,7 +187,7 @@ public:
///
/// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array).
///
- API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)")
+ API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount=8, MaxCount=8)")
FORCE_INLINE const Array, FixedAllocation<8>>& GetPhysicalMaterials() const
{
return _physicalMaterials;
@@ -199,6 +199,27 @@ public:
API_PROPERTY()
void SetPhysicalMaterials(const Array, FixedAllocation<8>>& value);
+ ///
+ /// Gets the physical material used to define the terrain collider physical properties.
+ /// [Deprecated on 16.02.2024, expires on 16.02.2026]
+ ///
+ API_PROPERTY(Attributes="HideInEditor, NoSerialize")
+ DEPRECATED("Use PhysicalMaterials instead.") FORCE_INLINE JsonAssetReference& GetPhysicalMaterial()
+ {
+ return _physicalMaterials[0];
+ }
+
+ ///
+ /// Sets the physical materials used to define the terrain collider physical properties.
+ /// [Deprecated on 16.02.2024, expires on 16.02.2026]
+ ///
+ DEPRECATED("Use PhysicalMaterials instead.") API_PROPERTY()
+ void SetPhysicalMaterial(const JsonAssetReference& value)
+ {
+ for (auto& e : _physicalMaterials)
+ e = value;
+ }
+
///
/// Gets the terrain Level Of Detail count.
///
diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp
index fa483eb39..ad353581c 100644
--- a/Source/Engine/Terrain/TerrainChunk.cpp
+++ b/Source/Engine/Terrain/TerrainChunk.cpp
@@ -124,6 +124,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
}
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
drawCall.PerInstanceRandom = _perInstanceRandom;
+#if USE_EDITOR
+ if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
+ drawCall.Surface.LODDitherFactor = 1.0f; // See LightmapUVsDensityMaterialShader
+#endif
// Add half-texel offset for heightmap sampling in vertex shader
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);
@@ -181,6 +185,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi
}
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
drawCall.PerInstanceRandom = _perInstanceRandom;
+#if USE_EDITOR
+ if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
+ drawCall.Surface.LODDitherFactor = 1.0f; // See LightmapUVsDensityMaterialShader
+#endif
// Add half-texel offset for heightmap sampling in vertex shader
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);
diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp
index ae1c30a3c..cfeb24a38 100644
--- a/Source/Engine/Tests/TestScripting.cpp
+++ b/Source/Engine/Tests/TestScripting.cpp
@@ -7,6 +7,12 @@
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include
+Foo::Foo(const SpawnParams& params)
+ : ScriptingObject(params)
+ , FooInterface(nullptr)
+{
+}
+
TestNesting::TestNesting(const SpawnParams& params)
: SerializableScriptingObject(params)
{
diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h
index 5cd61cbc7..19fd76351 100644
--- a/Source/Engine/Tests/TestScripting.h
+++ b/Source/Engine/Tests/TestScripting.h
@@ -8,6 +8,21 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/SerializableScriptingObject.h"
+// Test interface (name conflict with namespace)
+API_INTERFACE(Namespace="Foo") class FLAXENGINE_API IFoo
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(IFoo);
+};
+
+// Test class (name conflict with namespace)
+API_CLASS(Namespace="Foo") class FLAXENGINE_API Foo : public ScriptingObject
+{
+ DECLARE_SCRIPTING_TYPE(Foo);
+
+ // Test field.
+ API_FIELD() IFoo* FooInterface;
+};
+
// Test compilation with nested types.
API_CLASS() class TestNesting : public SerializableScriptingObject
{
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp
index 4b3654865..7f316dfd5 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp
@@ -192,15 +192,10 @@ void MaterialGenerator::prepareLayer(MaterialLayer* layer, bool allowVisiblePara
// For all not root layers (sub-layers) we won't to change theirs ID in order to prevent duplicated ID)
m.SrcId = param->Identifier;
- if (isRooLayer)
+ m.DstId = param->Identifier;
+ if (!isRooLayer)
{
- // Use the same ID (so we can edit it)
- m.DstId = param->Identifier;
- }
- else
- {
- // Generate new ID
- m.DstId = param->Identifier;
+ // Generate new ID (stable permutation based on the original ID)
m.DstId.A += _parameters.Count() * 17 + 13;
}
layer->ParamIdsMappings.Add(m);
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
index 7df348182..58448f5ea 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
@@ -644,20 +644,12 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
// Flipbook
case 10:
{
- // Get input values
auto uv = Value::Cast(tryGetValue(node->GetBox(0), getUVs), VariantType::Float2);
auto frame = Value::Cast(tryGetValue(node->GetBox(1), node->Values[0]), VariantType::Float);
auto framesXY = Value::Cast(tryGetValue(node->GetBox(2), node->Values[1]), VariantType::Float2);
auto invertX = Value::Cast(tryGetValue(node->GetBox(3), node->Values[2]), VariantType::Float);
auto invertY = Value::Cast(tryGetValue(node->GetBox(4), node->Values[3]), VariantType::Float);
-
- // Write operations
- auto framesCount = writeLocal(VariantType::Float, String::Format(TEXT("{0}.x * {1}.y"), framesXY.Value, framesXY.Value), node);
- frame = writeLocal(VariantType::Float, String::Format(TEXT("fmod(floor({0}), {1})"), frame.Value, framesCount.Value), node);
- auto framesXYInv = writeOperation2(node, Value::One.AsFloat2(), framesXY, '/');
- auto frameY = writeLocal(VariantType::Float, String::Format(TEXT("abs({0} * {1}.y - (floor({2} * {3}.x) + {0} * 1))"), invertY.Value, framesXY.Value, frame.Value, framesXYInv.Value), node);
- auto frameX = writeLocal(VariantType::Float, String::Format(TEXT("abs({0} * {1}.x - (({2} - {1}.x * floor({2} * {3}.x)) + {0} * 1))"), invertX.Value, framesXY.Value, frame.Value, framesXYInv.Value), node);
- value = writeLocal(VariantType::Float2, String::Format(TEXT("({3} + float2({0}, {1})) * {2}"), frameX.Value, frameY.Value, framesXYInv.Value, uv.Value), node);
+ value = writeLocal(VariantType::Float2, String::Format(TEXT("Flipbook({0}, {1}, {2}, float2({3}, {4}))"), uv.Value, frame.Value, framesXY.Value, invertX.Value, invertY.Value), node);
break;
}
// Sample Global SDF
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index abf769304..dab426190 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -1379,14 +1379,16 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations))
{
+ int32 index = 0;
for (auto& animation : data.Animations)
{
- LOG(Info, "Imported animation '{}' has {} channels, duration: {} frames, frames per second: {}", animation.Name, animation.Channels.Count(), animation.Duration, animation.FramesPerSecond);
+ LOG(Info, "Imported animation '{}' at index {} has {} channels, duration: {} frames ({} seconds), frames per second: {}", animation.Name, index, animation.Channels.Count(), animation.Duration, animation.GetLength(), animation.FramesPerSecond);
if (animation.Duration <= ZeroTolerance || animation.FramesPerSecond <= ZeroTolerance)
{
errorMsg = TEXT("Invalid animation duration.");
return true;
}
+ index++;
}
}
switch (options.Type)
diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
index 95b49850b..55f810211 100644
--- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
@@ -924,6 +924,19 @@ namespace FlaxEngine.GUI
return newLineLoc;
}
+ private int FindNextLineBegin()
+ {
+ int caretPos = CaretPosition;
+ if (caretPos + 2 > TextLength)
+ return TextLength;
+ int newLineLoc = _text.IndexOf('\n', caretPos + 2);
+ if (newLineLoc == -1)
+ newLineLoc = TextLength;
+ else
+ newLineLoc++;
+ return newLineLoc;
+ }
+
private int FindLineDownChar(int index)
{
if (!IsMultiline)
@@ -1423,6 +1436,30 @@ namespace FlaxEngine.GUI
return true;
}
+ case KeyboardKeys.PageDown:
+ {
+ if (IsScrollable && IsMultiline)
+ {
+ var location = GetCharPosition(_selectionStart, out var height);
+ var sizeHeight = Size.Y / height;
+ location.Y += height * (int)sizeHeight;
+ TargetViewOffset = Vector2.Clamp(new Float2(0, location.Y), Float2.Zero, TextSize - new Float2(0, Size.Y));
+ SetSelection(HitTestText(location));
+ }
+ return true;
+ }
+ case KeyboardKeys.PageUp:
+ {
+ if (IsScrollable && IsMultiline)
+ {
+ var location = GetCharPosition(_selectionStart, out var height);
+ var sizeHeight = Size.Y / height;
+ location.Y -= height * (int)sizeHeight;
+ TargetViewOffset = Vector2.Clamp(new Float2(0, location.Y), Float2.Zero, TextSize - new Float2(0, Size.Y));
+ SetSelection(HitTestText(location));
+ }
+ return true;
+ }
case KeyboardKeys.Delete:
{
if (IsReadOnly)
@@ -1491,8 +1528,13 @@ namespace FlaxEngine.GUI
return true;
case KeyboardKeys.End:
{
+ // Select text from the current cursor point to the beginning of a new line
+ if (shiftDown && _selectionStart != -1)
+ SetSelection(_selectionStart, FindNextLineBegin());
// Move caret after last character
- SetSelection(TextLength);
+ else
+ SetSelection(TextLength);
+
return true;
}
case KeyboardKeys.Tab:
diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs
index c53307c65..530663761 100644
--- a/Source/Engine/UI/GUI/ContainerControl.cs
+++ b/Source/Engine/UI/GUI/ContainerControl.cs
@@ -750,7 +750,7 @@ namespace FlaxEngine.GUI
{
if (base.IsTouchOver)
return true;
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
if (_children[i].IsTouchOver)
return true;
@@ -960,7 +960,7 @@ namespace FlaxEngine.GUI
public override void OnMouseLeave()
{
// Check all children collisions with mouse and fire events for them
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Visible && child.Enabled && child.IsMouseOver)
@@ -1063,7 +1063,7 @@ namespace FlaxEngine.GUI
if (base.IsTouchPointerOver(pointerId))
return true;
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
if (_children[i].IsTouchPointerOver(pointerId))
return true;
@@ -1168,7 +1168,7 @@ namespace FlaxEngine.GUI
///
public override void OnTouchLeave(int pointerId)
{
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId))
@@ -1183,7 +1183,7 @@ namespace FlaxEngine.GUI
///
public override bool OnCharInput(char c)
{
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Enabled && child.ContainsFocus)
@@ -1197,7 +1197,7 @@ namespace FlaxEngine.GUI
///
public override bool OnKeyDown(KeyboardKeys key)
{
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Enabled && child.ContainsFocus)
@@ -1211,7 +1211,7 @@ namespace FlaxEngine.GUI
///
public override void OnKeyUp(KeyboardKeys key)
{
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Enabled && child.ContainsFocus)
@@ -1294,7 +1294,7 @@ namespace FlaxEngine.GUI
base.OnDragLeave();
// Check all children collisions with mouse and fire events for them
- for (int i = 0; i < _children.Count && _children.Count > 0; i++)
+ for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.IsDragOver)
diff --git a/Source/Engine/Visject/GraphUtilities.cpp b/Source/Engine/Visject/GraphUtilities.cpp
index e32e280d5..4aaeb01fe 100644
--- a/Source/Engine/Visject/GraphUtilities.cpp
+++ b/Source/Engine/Visject/GraphUtilities.cpp
@@ -409,9 +409,24 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, Variant& b, MathO
case VariantType::Uint:
v.AsUint = (uint32)op((float)a.AsUint, (float)b.AsUint);
break;
+ case VariantType::Int64:
+ v.AsUint = (int64)op((float)a.AsInt64, (float)b.AsInt64);
+ break;
+ case VariantType::Uint64:
+ v.AsUint = (uint64)op((float)a.AsUint64, (float)b.AsUint64);
+ break;
+ case VariantType::Int16:
+ v.AsUint = (int16)op((float)a.AsInt16, (float)b.AsInt16);
+ break;
+ case VariantType::Uint16:
+ v.AsUint = (uint16)op((float)a.AsUint16, (float)b.AsUint16);
+ break;
case VariantType::Float:
v.AsFloat = op(a.AsFloat, b.AsFloat);
break;
+ case VariantType::Double:
+ v.AsDouble = op((float)a.AsDouble, (float)b.AsDouble);
+ break;
case VariantType::Float2:
{
Float2& vv = *(Float2*)v.AsData;
diff --git a/Source/Shaders/ColorGrading.shader b/Source/Shaders/ColorGrading.shader
index ae4067b18..59de00aa2 100644
--- a/Source/Shaders/ColorGrading.shader
+++ b/Source/Shaders/ColorGrading.shader
@@ -193,6 +193,57 @@ float3 TonemapACES(float3 linearColor)
#endif
+#ifdef TONE_MAPPING_MODE_AGX
+
+float3 agxAscCdl(float3 color, float3 slope, float3 offset, float3 power, float sat)
+{
+ const float3 lw = float3(0.2126, 0.7152, 0.0722);
+ float luma = dot(color, lw);
+ float3 c = pow(color * slope + offset, power);
+ return luma + sat * (c - luma);
+}
+
+float3 TonemapAGX(float3 linearColor)
+{
+ static const float3x3 AgXInsetMatrix = {
+ 0.8566, 0.1373, 0.1119,
+ 0.0951, 0.7612, 0.0768,
+ 0.0483, 0.1014, 0.8113
+ };
+ static const float3x3 AgXOutsetMatrix = {
+ 1.1271, -0.1413, -0.1413,
+ -0.1106, 1.1578, -0.1106,
+ -0.0165, -0.0165, 1.2519
+ };
+ static const float AgxMinEv = -12.47393;
+ static const float AgxMaxEv = 4.026069;
+
+ float3 color = linearColor;
+ color = mul(color, AgXInsetMatrix);
+ color = max(color, 1e-10);
+ color = clamp(log2(color), AgxMinEv, AgxMaxEv);
+ color = (color - AgxMinEv) / (AgxMaxEv - AgxMinEv);
+ color = saturate(color);
+
+ float3 x2 = color * color;
+ float3 x4 = x2 * x2;
+ color = + 15.5 * x4 * x2
+ - 40.14 * x4 * color
+ + 31.96 * x4
+ - 6.868 * x2 * color
+ + 0.4298 * x2
+ + 0.1191 * color
+ - 0.00232;
+
+ // color = agxAscCdl(color, float3(1.0, 1.0, 1.0), float3(0.0, 0.0, 0.0), float3(1.35, 1.35, 1.35), 1.4);
+ color = mul(color, AgXOutsetMatrix);
+ color = pow(max(float3(0.0, 0.0, 0.0), color), float3(2.2, 2.2, 2.2));
+ color = saturate(color);
+ return color;
+}
+
+#endif
+
// Perfoms the tonemapping on the input linear color
float3 Tonemap(float3 linearColor)
{
@@ -202,6 +253,8 @@ float3 Tonemap(float3 linearColor)
return TonemapNeutral(linearColor);
#elif defined(TONE_MAPPING_MODE_ACES)
return TonemapACES(linearColor);
+#elif defined(TONE_MAPPING_MODE_AGX)
+ return TonemapAGX(linearColor);
#else
return float3(0, 0, 0);
#endif
@@ -292,6 +345,7 @@ META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_1(TONE_MAPPING_MODE_NONE=1)
META_PERMUTATION_1(TONE_MAPPING_MODE_NEUTRAL=1)
META_PERMUTATION_1(TONE_MAPPING_MODE_ACES=1)
+META_PERMUTATION_1(TONE_MAPPING_MODE_AGX=1)
float4 PS_Lut2D(Quad_VS2PS input) : SV_Target
{
return CombineLUTs(input.TexCoord, 0);
@@ -301,6 +355,7 @@ META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_1(TONE_MAPPING_MODE_NONE=1)
META_PERMUTATION_1(TONE_MAPPING_MODE_NEUTRAL=1)
META_PERMUTATION_1(TONE_MAPPING_MODE_ACES=1)
+META_PERMUTATION_1(TONE_MAPPING_MODE_AGX=1)
float4 PS_Lut3D(Quad_GS2PS input) : SV_Target
{
return CombineLUTs(input.Vertex.TexCoord, input.LayerIndex);
diff --git a/Source/Shaders/Editor/LightmapUVsDensity.shader b/Source/Shaders/Editor/LightmapUVsDensity.shader
index b1fafd193..588332a00 100644
--- a/Source/Shaders/Editor/LightmapUVsDensity.shader
+++ b/Source/Shaders/Editor/LightmapUVsDensity.shader
@@ -98,7 +98,11 @@ void PS(in PixelInput input, out float4 Light : SV_Target0, out float4 RT0 : SV_
float3 minColor = float3(235/255.0, 52/255.0, 67/255.0);
float3 bestColor = float3(51/255.0, 235/255.0, 70/255.0);
float3 maxColor = float3(52/255.0, 149/255.0, 235/255.0);
- if (density < bestDensity)
+ if (LightmapSize < 0.0f)
+ {
+ color = float3(52/255.0, 229/255.0, 235/255.0); // No lightmap
+ }
+ else if (density < bestDensity)
{
color = lerp(minColor, bestColor, (density - minDensity) / (bestDensity - minDensity));
}
diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl
index 04e3a037e..f8fcd69f3 100644
--- a/Source/Shaders/MaterialCommon.hlsl
+++ b/Source/Shaders/MaterialCommon.hlsl
@@ -267,4 +267,12 @@ float3 AOMultiBounce(float visibility, float3 albedo)
return max(visibility, ((visibility * a + b) * visibility + c) * visibility);
}
+float2 Flipbook(float2 uv, float frame, float2 sizeXY, float2 flipXY = 0.0f)
+{
+ float2 frameXY = float2((uint)frame % (uint)sizeXY.y, (uint)frame / (uint)sizeXY.x);
+ float2 flipFrameXY = sizeXY - frameXY - float2(1, 1);
+ frameXY = lerp(frameXY, flipFrameXY, flipXY);
+ return (uv + frameXY) / sizeXY;
+}
+
#endif
diff --git a/Source/Shaders/Noise.hlsl b/Source/Shaders/Noise.hlsl
index 59c810d78..1375ecd6e 100644
--- a/Source/Shaders/Noise.hlsl
+++ b/Source/Shaders/Noise.hlsl
@@ -339,23 +339,25 @@ float3 CustomNoise3D(float3 p)
float c = CustomNoise(p + float3(0.0f, 0.0f, 0.0001f));
float3 grad = float3(o - a, o - b, o - c);
- float3 other = abs(grad.zxy);
- return normalize(cross(grad,other));
+ float3 ret = cross(grad, abs(grad.zxy));
+ if (length(ret) <= 0.0001f) return float3(0.0f, 0.0f, 0.0f);
+ return normalize(ret);
}
float3 CustomNoise3D(float3 position, int octaves, float roughness)
{
float weight = 0.0f;
- float3 noise = float3(0.0, 0.0, 0.0);
+ float3 noise = float3(0.0f, 0.0f, 0.0f);
float scale = 1.0f;
+ roughness = lerp(2.0f, 0.2f, roughness);
for (int i = 0; i < octaves; i++)
{
- float curWeight = pow((1.0 - ((float)i / octaves)), lerp(2.0, 0.2, roughness));
+ float curWeight = pow((1.0f - ((float)i / (float)octaves)), roughness);
noise += CustomNoise3D(position * scale) * curWeight;
weight += curWeight;
- scale *= 1.72531;
+ scale *= 1.72531f;
}
- return noise / weight;
+ return noise / max(weight, 0.0001f);
}
#endif
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
index 74c544ae1..3e7bc62da 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
@@ -543,7 +543,7 @@ namespace Flax.Build.Bindings
// interface
if (apiType.IsInterface)
- return string.Format("FlaxEngine.Object.GetUnmanagedInterface({{0}}, typeof({0}))", apiType.FullNameManaged);
+ return string.Format("FlaxEngine.Object.GetUnmanagedInterface({{0}}, typeof({0}))", apiType.Name);
}
// Object reference property
@@ -603,7 +603,7 @@ namespace Flax.Build.Bindings
fullReturnValueType = $"{apiType.Namespace}.Interop.{returnValueType}";
// Interfaces are not supported by NativeMarshallingAttribute, marshal the parameter
- returnMarshalType = $"MarshalUsing(typeof({fullReturnValueType}Marshaller))";
+ returnMarshalType = $"MarshalUsing(typeof({returnValueType}Marshaller))";
}
else if (functionInfo.ReturnType.Type == "MonoArray" || functionInfo.ReturnType.Type == "MArray")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
@@ -916,6 +916,30 @@ namespace Flax.Build.Bindings
if (defaultValue != null)
contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine();
}
+
+ // Check if array has fixed allocation and add in MaxCount Collection attribute if a Collection attribute does not already exist.
+ if (defaultValueType != null && (string.IsNullOrEmpty(attributes) || !attributes.Contains("Collection", StringComparison.Ordinal)))
+ {
+ // Array or Span or DataContainer
+#if USE_NETCORE
+ if ((defaultValueType.Type == "Array" || defaultValueType.Type == "Span" || defaultValueType.Type == "DataContainer" || defaultValueType.Type == "MonoArray" || defaultValueType.Type == "MArray") && defaultValueType.GenericArgs != null)
+#else
+ if ((defaultValueType.Type == "Array" || defaultValueType.Type == "Span" || defaultValueType.Type == "DataContainer") && defaultValueType.GenericArgs != null)
+#endif
+ {
+ if (defaultValueType.GenericArgs.Count > 1)
+ {
+ var allocationArg = defaultValueType.GenericArgs[1];
+ if (allocationArg.Type.Contains("FixedAllocation", StringComparison.Ordinal) && allocationArg.GenericArgs.Count > 0)
+ {
+ if (int.TryParse(allocationArg.GenericArgs[0].ToString(), out int allocation))
+ {
+ contents.Append(indent).Append($"[Collection(MaxCount={allocation.ToString()})]").AppendLine();
+ }
+ }
+ }
+ }
+ }
}
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged, string defaultValue = null, TypeInfo defaultValueType = null)
@@ -1110,6 +1134,8 @@ namespace Flax.Build.Bindings
contents.Append(indent).Append('}').AppendLine();
contents.AppendLine();
+ if (buildData.Configuration != TargetConfiguration.Release)
+ contents.Append(indent).Append("[System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)]").AppendLine();
contents.Append(indent).Append("private ");
if (eventInfo.IsStatic)
contents.Append("static ");
@@ -2211,7 +2237,7 @@ namespace Flax.Build.Bindings
contents.Append(indent).AppendLine("/// ");
if (buildData.Target != null & buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
- contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerFullName}))]");
+ contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerName}))]");
contents.Append(indent).AppendLine($"public static class {marshallerName}");
contents.Append(indent).AppendLine("{");
contents.AppendLine("#pragma warning disable 1591");
diff --git a/Source/Tools/Flax.Build/Build/EngineModule.cs b/Source/Tools/Flax.Build/Build/EngineModule.cs
index 021a31f44..7fdb9de40 100644
--- a/Source/Tools/Flax.Build/Build/EngineModule.cs
+++ b/Source/Tools/Flax.Build/Build/EngineModule.cs
@@ -43,6 +43,7 @@ namespace Flax.Build
options.ScriptingAPI.Defines.Add("FLAX");
options.ScriptingAPI.Defines.Add("FLAX_ASSERTIONS");
options.ScriptingAPI.FileReferences.Add(Utilities.RemovePathRelativeParts(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "DotNet", "Newtonsoft.Json.dll")));
+ options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
}
}
}