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"); } } }