Merge remote-tracking branch 'origin/master' into 1.9

# Conflicts:
#	Source/Editor/Modules/ContentDatabaseModule.cs
#	Source/Editor/Surface/SurfaceUtils.cs
#	Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
#	Source/Engine/Foliage/Foliage.cpp
#	Source/Engine/Graphics/Models/MeshBase.h
#	Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
This commit is contained in:
Wojtek Figat
2024-09-12 13:38:20 +02:00
111 changed files with 1717 additions and 511 deletions

View File

@@ -0,0 +1,5 @@
%copyright%using System;
using System.Collections.Generic;
using FlaxEngine;
namespace %namespace%;

View File

@@ -13,10 +13,10 @@ API_CLASS() class %module%%class% : public ISerializable
API_AUTO_SERIALIZATION(); API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_NO_SPAWN(%class%); DECLARE_SCRIPTING_TYPE_NO_SPAWN(%class%);
public: public:
// Custom float value. // Custom float value.
API_FIELD(Attributes = "Range(0, 20), EditorOrder(0), EditorDisplay(\"Data\")") API_FIELD(Attributes = "Range(0, 20), EditorOrder(0), EditorDisplay(\"Data\")")
float FloatValue = 20.0f; float FloatValue = 20.0f;
// Custom vector data. // Custom vector data.
API_FIELD(Attributes = "EditorOrder(1), EditorDisplay(\"Data\")") API_FIELD(Attributes = "EditorOrder(1), EditorDisplay(\"Data\")")
Vector3 Vector3Value = Vector3(0.1f); Vector3 Vector3Value = Vector3(0.1f);
}; };

View File

@@ -7,6 +7,6 @@ META_CB_END
META_PS(true, FEATURE_LEVEL_ES2) META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Fullscreen(Quad_VS2PS input) : SV_Target float4 PS_Fullscreen(Quad_VS2PS input) : SV_Target
{ {
// Solid color fill from the constant buffer passed from code // Solid color fill from the constant buffer passed from code
return Color; return Color;
} }

BIN
Content/Shaders/ColorGrading.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@@ -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
{
/// <summary>
/// Prefab asset creating handler. Allows to specify base actor to use as the root.
/// </summary>
/// <seealso cref="FlaxEditor.Content.Create.CreateFileEntry" />
public class PrefabCreateEntry : CreateFileEntry
{
/// <summary>
/// The create options.
/// </summary>
public class Options
{
/// <summary>
/// The root actor.
/// </summary>
[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();
/// <inheritdoc />
public override object Settings => _options;
/// <summary>
/// Initializes a new instance of the <see cref="PrefabCreateEntry"/> class.
/// </summary>
/// <param name="resultUrl">The result file url.</param>
public PrefabCreateEntry(string resultUrl)
: base("Settings", resultUrl)
{
}
/// <inheritdoc />
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);
}
}
/// <summary>
/// Widget asset creating handler. Allows to specify base UIControl to use as the root.
/// </summary>
/// <seealso cref="FlaxEditor.Content.Create.CreateFileEntry" />
public class WidgetCreateEntry : CreateFileEntry
{
/// <summary>
/// The create options.
/// </summary>
public class Options
{
/// <summary>
/// Which mode is used to initialize this widget.
/// </summary>
public enum WidgetMode
{
/// <summary>
/// Initialize the widget with a UICanvas.
/// </summary>
Canvas,
/// <summary>
/// Initialize the widget with a UIControl.
/// </summary>
Control
}
/// <summary>
/// The mode used to initialize the widget.
/// </summary>
[Tooltip("Whether to initialize the widget with a canvas or a control.")]
public WidgetMode WidgetInitializationMode = WidgetMode.Control;
bool ShowRoot => WidgetInitializationMode == WidgetMode.Control;
/// <summary>
/// The root control.
/// </summary>
[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();
/// <inheritdoc />
public override object Settings => _options;
/// <summary>
/// Initializes a new instance of the <see cref="WidgetCreateEntry"/> class.
/// </summary>
/// <param name="resultUrl">The result file url.</param>
public WidgetCreateEntry(string resultUrl)
: base("Settings", resultUrl)
{
}
/// <inheritdoc />
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);
}
}
}

View File

@@ -5,7 +5,7 @@ using FlaxEngine;
namespace FlaxEditor.Content namespace FlaxEditor.Content
{ {
/// <summary> /// <summary>
/// Content item that contains C# script file with source code. /// Content item that contains C# file with source code.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Content.ScriptItem" /> /// <seealso cref="FlaxEditor.Content.ScriptItem" />
public class CSharpScriptItem : ScriptItem public class CSharpScriptItem : ScriptItem

View File

@@ -11,6 +11,8 @@ namespace FlaxEditor.Content
/// <seealso cref="FlaxEditor.Content.JsonAssetItem" /> /// <seealso cref="FlaxEditor.Content.JsonAssetItem" />
public sealed class PrefabItem : JsonAssetItem public sealed class PrefabItem : JsonAssetItem
{ {
private string _cachedTypeDescription = null;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PrefabItem"/> class. /// Initializes a new instance of the <see cref="PrefabItem"/> class.
/// </summary> /// </summary>
@@ -42,6 +44,26 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override SpriteHandle DefaultThumbnail => SpriteHandle.Invalid; public override SpriteHandle DefaultThumbnail => SpriteHandle.Invalid;
/// <inheritdoc />
public override string TypeDescription
{
get
{
if (_cachedTypeDescription == null)
{
_cachedTypeDescription = "Prefab";
var prefab = FlaxEngine.Content.Load<Prefab>(ID);
if (prefab)
{
Actor root = prefab.GetDefaultInstance();
if (root is UIControl or UICanvas)
_cachedTypeDescription = "Widget";
}
}
return _cachedTypeDescription;
}
}
/// <inheritdoc /> /// <inheritdoc />
public override bool IsOfType(Type type) public override bool IsOfType(Type type)
{ {

View File

@@ -9,19 +9,15 @@ using FlaxEngine;
namespace FlaxEditor.Content namespace FlaxEditor.Content
{ {
/// <summary> /// <summary>
/// Context proxy object for C# script files. /// Proxy object for C# files
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpScriptProxy" /> /// /// <seealso cref="FlaxEditor.Content.ScriptProxy" />
[ContentContextMenu("New/C# Script")] public abstract class CSharpProxy : ScriptProxy
public class CSharpScriptProxy : ScriptProxy
{ {
/// <summary> /// <summary>
/// The script files extension filter. /// The script files extension filter.
/// </summary> /// </summary>
public static readonly string ExtensionFiler = "*.cs"; public static readonly string ExtensionFilter = "*.cs";
/// <inheritdoc />
public override string Name => "C# Script";
/// <inheritdoc /> /// <inheritdoc />
public override bool IsProxyFor(ContentItem item) public override bool IsProxyFor(ContentItem item)
@@ -29,6 +25,12 @@ namespace FlaxEditor.Content
return item is CSharpScriptItem; return item is CSharpScriptItem;
} }
/// <summary>
/// Gets the path for the C# template.
/// </summary>
/// <param name="path">The path to the template</param>
protected abstract void GetTemplatePath(out string path);
/// <inheritdoc /> /// <inheritdoc />
public override ContentItem ConstructItem(string path) public override ContentItem ConstructItem(string path)
{ {
@@ -39,7 +41,7 @@ namespace FlaxEditor.Content
public override void Create(string outputPath, object arg) public override void Create(string outputPath, object arg)
{ {
// Load template // Load template
var templatePath = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cs"); GetTemplatePath(out var templatePath);
var scriptTemplate = File.ReadAllText(templatePath); var scriptTemplate = File.ReadAllText(templatePath);
// Find the module that this script is being added (based on the path) // Find the module that this script is being added (based on the path)
@@ -69,4 +71,38 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override Color AccentColor => Color.FromRGB(0x1c9c2b); public override Color AccentColor => Color.FromRGB(0x1c9c2b);
} }
/// <summary>
/// Context proxy object for C# script files.
/// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpProxy" />
[ContentContextMenu("New/C#/C# Script")]
public class CSharpScriptProxy : CSharpProxy
{
/// <inheritdoc />
public override string Name => "C# Script";
/// <inheritdoc />
protected override void GetTemplatePath(out string path)
{
path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cs");
}
}
/// <summary>
/// Context proxy object for empty C# files.
/// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpProxy" />
[ContentContextMenu("New/C#/C# Empty File")]
public class CSharpEmptyProxy : CSharpProxy
{
/// <inheritdoc />
public override string Name => "C# Empty File";
/// <inheritdoc />
protected override void GetTemplatePath(out string path)
{
path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CSharpEmptyTemplate.cs");
}
}
} }

View File

@@ -11,7 +11,7 @@ namespace FlaxEditor.Content
/// <summary> /// <summary>
/// Context proxy object for C++ files. /// Context proxy object for C++ files.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpScriptProxy" /> /// <seealso cref="FlaxEditor.Content.ScriptProxy" />
public abstract class CppProxy : ScriptProxy public abstract class CppProxy : ScriptProxy
{ {
/// <summary> /// <summary>
@@ -74,7 +74,7 @@ namespace FlaxEditor.Content
/// <summary> /// <summary>
/// Context proxy object for C++ script files. /// Context proxy object for C++ script files.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpScriptProxy" /> /// <seealso cref="FlaxEditor.Content.CppProxy" />
[ContentContextMenu("New/C++/C++ Script")] [ContentContextMenu("New/C++/C++ Script")]
public class CppScriptProxy : CppProxy public class CppScriptProxy : CppProxy
{ {
@@ -104,7 +104,7 @@ namespace FlaxEditor.Content
/// <summary> /// <summary>
/// Context proxy object for C++ Json Asset files. /// Context proxy object for C++ Json Asset files.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpScriptProxy" /> /// <seealso cref="FlaxEditor.Content.CppProxy" />
[ContentContextMenu("New/C++/C++ Function Library")] [ContentContextMenu("New/C++/C++ Function Library")]
public class CppStaticClassProxy : CppProxy public class CppStaticClassProxy : CppProxy
{ {
@@ -122,7 +122,7 @@ namespace FlaxEditor.Content
/// <summary> /// <summary>
/// Context proxy object for C++ Json Asset files. /// Context proxy object for C++ Json Asset files.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpScriptProxy" /> /// <seealso cref="FlaxEditor.Content.CppProxy" />
[ContentContextMenu("New/C++/C++ Json Asset")] [ContentContextMenu("New/C++/C++ Json Asset")]
public class CppAssetProxy : CppProxy public class CppAssetProxy : CppProxy
{ {

View File

@@ -3,6 +3,9 @@
using System; using System;
using FlaxEditor.Content.Create; using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails; using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Timeline;
using FlaxEditor.GUI.Timeline.Tracks;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
@@ -48,6 +51,63 @@ namespace FlaxEditor.Content
Editor.Instance.ContentImporting.Create(new ParticleEmitterCreateEntry(outputPath)); Editor.Instance.ContentImporting.Create(new ParticleEmitterCreateEntry(outputPath));
} }
/// <inheritdoc />
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);
}
/// <summary>
/// Creates the particle system from the given particle emitter.
/// </summary>
/// <param name="emitterItem">The particle emitter item to use as a base for the particle system.</param>
public static void CreateParticleSystem(BinaryAssetItem emitterItem)
{
var particleSystemName = emitterItem.ShortName + " Particle System";
var particleSystemProxy = Editor.Instance.ContentDatabase.GetProxy<ParticleSystem>();
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<ParticleSystem>(assetItem.ID);
if (particleSystem == null || particleSystem.WaitForLoaded())
{
Editor.LogError("Failed to load created particle system.");
return;
}
ParticleEmitter emitter = FlaxEngine.Content.LoadAsync<ParticleEmitter>(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);
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request) public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{ {

View File

@@ -1,14 +1,13 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.IO; using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails; using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Content namespace FlaxEditor.Content
{ {
@@ -90,14 +89,8 @@ namespace FlaxEditor.Content
var transform = Transform.Identity; var transform = Transform.Identity;
if (!(arg is Actor actor)) if (!(arg is Actor actor))
{ {
// Create default prefab root object Editor.Instance.ContentImporting.Create(new PrefabCreateEntry(outputPath));
actor = new EmptyActor return;
{
Name = "Root"
};
// Cleanup it after usage
Object.Destroy(actor, 20.0f);
} }
else if (actor.HasScene) else if (actor.HasScene)
{ {
@@ -251,18 +244,8 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) public override void Create(string outputPath, object arg)
{ {
// Create prefab with UI Control Editor.Instance.ContentImporting.Create(new WidgetCreateEntry(outputPath));
var actor = new UIControl return;
{
Name = Path.GetFileNameWithoutExtension(outputPath),
StaticFlags = StaticFlags.None,
};
actor.Control = new Button
{
Text = "Button",
};
PrefabManager.CreatePrefab(actor, outputPath, false);
Object.Destroy(actor, 20.0f);
} }
} }
} }

View File

@@ -2,6 +2,7 @@
using System; using System;
using FlaxEditor.Content.Thumbnails; using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
@@ -39,6 +40,59 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override Type AssetType => typeof(SkinnedModel); public override Type AssetType => typeof(SkinnedModel);
/// <inheritdoc />
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);
}
/// <summary>
/// Creates the animation graph from the given particle emitter.
/// </summary>
/// <param name="skinnedModelItem">The skinned model item to use as the base model for the animation graph.</param>
public static void CreateAnimationGraph(BinaryAssetItem skinnedModelItem)
{
var animationGraphName = skinnedModelItem.ShortName + " Graph";
var animationGraphProxy = Editor.Instance.ContentDatabase.GetProxy<AnimationGraph>();
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<SkinnedModel>(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();
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request) public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{ {

View File

@@ -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<String> 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) // Optimize deployed C# class library (remove DLLs unused by scripts)
if (aotMode == DotNetAOTModes::None && buildSettings.SkipUnusedDotnetLibsPackaging) if (aotMode == DotNetAOTModes::None && buildSettings.SkipUnusedDotnetLibsPackaging)
{ {

View File

@@ -261,7 +261,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (!file.Contains("GameProjectTarget")) if (!file.Contains("GameProjectTarget"))
continue; // Skip 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 // Assume Game represents the main game module
moduleName = "Game"; moduleName = "Game";

View File

@@ -123,10 +123,10 @@ namespace FlaxEditor.CustomEditors.Editors
_linkButton.Clicked += ToggleLink; _linkButton.Clicked += ToggleLink;
ToggleEnabled(); ToggleEnabled();
SetLinkStyle(); SetLinkStyle();
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
_linkButton.LocalX += textSize.X + 10;
if (LinkedLabel != null) if (LinkedLabel != null)
{ {
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
_linkButton.LocalX += textSize.X + 10;
LinkedLabel.SetupContextMenu += (label, menu, editor) => LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{ {
menu.AddSeparator(); menu.AddSeparator();

View File

@@ -1,3 +1,4 @@
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -39,20 +40,27 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
Window = layout.Control.RootWindow.Window; Window = layout.Control.RootWindow.Window;
var panelElement = layout.CustomContainer<Panel>();
var panel = panelElement.ContainerControl as Panel;
var panel = layout.CustomContainer<UniformGridPanel>(); var button = panelElement.Button("Listen", "Press to listen for input events");
panel.CustomControl.SlotsHorizontally = 2;
panel.CustomControl.SlotsVertically = 1;
var button = panel.Button("Listen", "Press to listen for input events");
_button = button.Button; _button = button.Button;
_button.Width = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText("Listening...").X + 8;
_button.Height = ComboBox.DefaultHeight;
_button.Clicked += OnButtonClicked; _button.Clicked += OnButtonClicked;
ResetButton(); ResetButton();
var padding = panel.CustomControl.SlotPadding; panel.Height = ComboBox.DefaultHeight;
panel.CustomControl.Height = ComboBox.DefaultHeight + padding.Height;
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;
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -2,14 +2,18 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.CustomEditors.GUI; using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -53,7 +57,7 @@ namespace FlaxEditor.CustomEditors.Editors
Index = index; Index = index;
SetupContextMenu += OnSetupContextMenu; 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. // Extend margin of the label to support a dragging handle.
Margin m = Margin; Margin m = Margin;
@@ -75,7 +79,7 @@ namespace FlaxEditor.CustomEditors.Editors
b = menu.AddButton("Move down", OnMoveDownClicked); b = menu.AddButton("Move down", OnMoveDownClicked);
b.Enabled = Index + 1 < Editor.Count && !Editor._readOnly; b.Enabled = Index + 1 < Editor.Count && !Editor._readOnly;
b = menu.AddButton("Remove", OnRemoveClicked); b = menu.AddButton("Remove", OnRemoveClicked);
b.Enabled = !Editor._readOnly; b.Enabled = !Editor._readOnly;
} }
@@ -88,13 +92,12 @@ namespace FlaxEditor.CustomEditors.Editors
_arrangeButtonInUse = false; _arrangeButtonInUse = false;
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
base.Draw(); base.Draw();
var style = FlaxEngine.GUI.Style.Current;
var style = FlaxEngine.GUI.Style.Current;
var mousePosition = PointFromScreen(Input.MouseScreenPosition); var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey; var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor); Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
@@ -104,6 +107,14 @@ namespace FlaxEditor.CustomEditors.Editors
} }
} }
/// <inheritdoc />
protected override void OnSizeChanged()
{
base.OnSizeChanged();
_arrangeButtonRect.Y = (Height - _arrangeButtonRect.Height) * 0.5f;
}
private bool ArrangeAreaCheck(out int index, out Rectangle rect) private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{ {
var child = Editor.ChildrenEditors[0]; var child = Editor.ChildrenEditors[0];
@@ -228,7 +239,38 @@ namespace FlaxEditor.CustomEditors.Editors
ArrowImageClosed = new SpriteBrush(icons.ArrowRight12); ArrowImageClosed = new SpriteBrush(icons.ArrowRight12);
ArrowImageOpened = new SpriteBrush(icons.ArrowDown12); ArrowImageOpened = new SpriteBrush(icons.ArrowDown12);
HeaderText = $"Element {index}"; 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; Editor = editor;
Index = index; Index = index;
Offsets = new Margin(7, 7, 0, 0); Offsets = new Margin(7, 7, 0, 0);
@@ -239,6 +281,37 @@ namespace FlaxEditor.CustomEditors.Editors
HeaderTextMargin = new Margin(18, 0, 0, 0); HeaderTextMargin = new Margin(18, 0, 0, 0);
_arrangeButtonRect = new Rectangle(16, 3, 12, 12); _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) private bool ArrangeAreaCheck(out int index, out Rectangle rect)
@@ -278,10 +351,10 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Draw() public override void Draw()
{ {
base.Draw(); base.Draw();
if (_canReorder) if (_canReorder)
{ {
var style = FlaxEngine.GUI.Style.Current; var style = FlaxEngine.GUI.Style.Current;
var mousePosition = PointFromScreen(Input.MouseScreenPosition); var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey; var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor); 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 _canResize;
private bool _canReorderItems; private bool _canReorderItems;
private CollectionAttribute.DisplayType _displayType; private CollectionAttribute.DisplayType _displayType;
private List<CollectionDropPanel> _cachedDropPanels = new List<CollectionDropPanel>();
/// <summary> /// <summary>
/// Gets the length of the collection. /// Gets the length of the collection.
@@ -519,6 +593,10 @@ namespace FlaxEditor.CustomEditors.Editors
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) || (elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
elementType.Equals(new ScriptType(typeof(JsonAsset))) || elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
elementType.Equals(new ScriptType(typeof(SettingsBase))); elementType.Equals(new ScriptType(typeof(SettingsBase)));
if (_cachedDropPanels == null)
_cachedDropPanels = new List<CollectionDropPanel>();
else
_cachedDropPanels.Clear();
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
// Apply spacing // Apply spacing
@@ -542,6 +620,7 @@ namespace FlaxEditor.CustomEditors.Editors
else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single)) else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single))
{ {
var cdp = panel.CustomContainer<CollectionDropPanel>(); var cdp = panel.CustomContainer<CollectionDropPanel>();
_cachedDropPanels.Add(cdp.CustomControl);
cdp.CustomControl.Setup(this, i, _canReorderItems); cdp.CustomControl.Setup(this, i, _canReorderItems);
var itemLayout = cdp.VerticalPanel(); var itemLayout = cdp.VerticalPanel();
cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
@@ -549,6 +628,12 @@ namespace FlaxEditor.CustomEditors.Editors
GenericEditor.OnReadOnlyProperty(itemLayout); GenericEditor.OnReadOnlyProperty(itemLayout);
} }
} }
if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single))
{
if (Layout is GroupElement g)
g.SetupContextMenu += OnSetupContextMenu;
}
} }
_elementsCount = size; _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;
}
});
}
/// <inheritdoc /> /// <inheritdoc />
protected override void Deinitialize() protected override void Deinitialize()
{ {
@@ -663,7 +770,7 @@ namespace FlaxEditor.CustomEditors.Editors
cloned[i] = tmp; cloned[i] = tmp;
} }
} }
SetValue(cloned); SetValue(cloned);
} }

View File

@@ -208,7 +208,9 @@ namespace FlaxEditor.CustomEditors.Editors
else else
{ {
// Draw info // 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.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 // Draw picker button

View File

@@ -569,19 +569,19 @@ namespace FlaxEditor.CustomEditors.Editors
internal static void OnReadOnlyProperty(LayoutElementsContainer itemLayout, int labelIndex = -1) internal static void OnReadOnlyProperty(LayoutElementsContainer itemLayout, int labelIndex = -1)
{ {
PropertiesListElement list = null; PropertiesList list = null;
int firstChildControlIndex = 0; int firstChildControlIndex = 0;
bool disableSingle = true; bool disableSingle = true;
var control = itemLayout.Children[itemLayout.Children.Count - 1]; var control = itemLayout.Children[itemLayout.Children.Count - 1];
if (control is GroupElement group && group.Children.Count > 0) 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 disableSingle = false; // Disable all nested editors
} }
else if (control is PropertiesListElement list1 && labelIndex != -1) else if (control is PropertiesListElement list1 && labelIndex != -1)
{ {
list = list1; list = list1.Labels[labelIndex].FirstChildControlContainer ?? list1.Properties;
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex; firstChildControlIndex = list1.Labels[labelIndex].FirstChildControlIndex;
} }
else if (control?.Control != null) else if (control?.Control != null)
{ {
@@ -591,10 +591,10 @@ namespace FlaxEditor.CustomEditors.Editors
if (list != null) if (list != null)
{ {
// Disable controls added to the editor // Disable controls added to the editor
var count = list.Properties.Children.Count; var count = list.Children.Count;
for (int j = firstChildControlIndex; j < count; j++) for (int j = firstChildControlIndex; j < count; j++)
{ {
var child = list.Properties.Children[j]; var child = list.Children[j];
if (disableSingle && child is PropertyNameLabel) if (disableSingle && child is PropertyNameLabel)
break; break;

View File

@@ -50,7 +50,6 @@ namespace FlaxEditor.CustomEditors.Editors
return; return;
} }
} }
if (_element == null)
{ {
// Use int value editor // Use int value editor
var element = layout.IntegerValue(); var element = layout.IntegerValue();

View File

@@ -33,10 +33,10 @@ namespace FlaxEditor.CustomEditors.Editors
if (layout.Children.Count == 0) if (layout.Children.Count == 0)
return; return;
var propList = layout.Children[layout.Children.Count - 1] as PropertiesListElement; 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; return;
var idElement = propList.Children[0] as TextBoxElement; var idElement = propList.Children[propList.Children.Count - 2] as TextBoxElement;
var valueElement = propList.Children[1] as TextBoxElement; var valueElement = propList.Children[propList.Children.Count - 1] as TextBoxElement;
if (idElement == null || valueElement == null) if (idElement == null || valueElement == null)
return; return;
_idElement = idElement; _idElement = idElement;

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -24,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Elements
EnableContainmentLines = true, EnableContainmentLines = true,
}; };
/// <summary>
/// Event is fired if the group can setup a context menu and the context menu is being setup.
/// </summary>
public Action<ContextMenu, DropPanel> SetupContextMenu;
/// <inheritdoc /> /// <inheritdoc />
public override ContainerControl ContainerControl => Panel; public override ContainerControl ContainerControl => Panel;

View File

@@ -53,13 +53,7 @@ namespace FlaxEditor.CustomEditors.Elements
internal void OnAddProperty(string name, string tooltip) internal void OnAddProperty(string name, string tooltip)
{ {
var label = new PropertyNameLabel(name) OnAddProperty(new PropertyNameLabel(name), tooltip);
{
Parent = Properties,
TooltipText = tooltip,
FirstChildControlIndex = Properties.Children.Count
};
Labels.Add(label);
} }
internal void OnAddProperty(PropertyNameLabel label, string tooltip) internal void OnAddProperty(PropertyNameLabel label, string tooltip)
@@ -88,6 +82,7 @@ namespace FlaxEditor.CustomEditors.Elements
public override void ClearLayout() public override void ClearLayout()
{ {
base.ClearLayout(); base.ClearLayout();
Labels.Clear(); Labels.Clear();
} }
} }

View File

@@ -58,6 +58,8 @@ namespace FlaxEditor.CustomEditors.GUI
// Update child controls enabled state // Update child controls enabled state
if (FirstChildControlIndex >= 0 && Parent is PropertiesList propertiesList) if (FirstChildControlIndex >= 0 && Parent is PropertiesList propertiesList)
{ {
if (FirstChildControlContainer != null)
propertiesList = FirstChildControlContainer;
var controls = propertiesList.Children; var controls = propertiesList.Children;
var labels = propertiesList.Element.Labels; var labels = propertiesList.Element.Labels;
var thisIndex = labels.IndexOf(this); var thisIndex = labels.IndexOf(this);

View File

@@ -245,16 +245,25 @@ namespace FlaxEditor.CustomEditors.GUI
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
var label = _element.Labels[i]; var label = _element.Labels[i];
var container = label.FirstChildControlContainer ?? this;
if (label.FirstChildControlIndex < 0) if (label.FirstChildControlIndex < 0)
yStarts[i] = 0; yStarts[i] = 0;
else if (_children.Count <= label.FirstChildControlIndex) else if (container.ChildrenCount <= label.FirstChildControlIndex)
yStarts[i] = y; 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 else
{ {
yStarts[i] = _children[label.FirstChildControlIndex].Top; var firstChild = _children[label.FirstChildControlIndex];
yStarts[i] = firstChild.Top;
if (i == count - 1) if (i == count - 1)
yStarts[i + 1] = _children[label.FirstChildControlIndex].Bottom; yStarts[i + 1] = firstChild.Bottom;
} }
} }

View File

@@ -30,6 +30,11 @@ namespace FlaxEditor.CustomEditors.GUI
/// </summary> /// </summary>
internal int FirstChildControlIndex; internal int FirstChildControlIndex;
/// <summary>
/// Helper value used by the <see cref="PropertiesList"/> to draw property names in a proper area.
/// </summary>
internal PropertiesList FirstChildControlContainer;
/// <summary> /// <summary>
/// The linked custom editor (shows the label property). /// The linked custom editor (shows the label property).
/// </summary> /// </summary>
@@ -154,8 +159,16 @@ namespace FlaxEditor.CustomEditors.GUI
public override void OnDestroy() public override void OnDestroy()
{ {
SetupContextMenu = null; SetupContextMenu = null;
LinkedEditor = null;
FirstChildControlContainer = null;
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public override string ToString()
{
return Text.ToString();
}
} }
} }

View File

@@ -74,11 +74,11 @@ namespace FlaxEditor.CustomEditors
{ {
var element = Group(title, useTransparentHeader); var element = Group(title, useTransparentHeader);
element.Panel.Tag = linkedEditor; element.Panel.Tag = linkedEditor;
element.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; element.Panel.MouseButtonRightClicked += (panel, location) => OnGroupPanelMouseButtonRightClicked(element, panel, location);
return element; return element;
} }
private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Float2 location) private void OnGroupPanelMouseButtonRightClicked(GroupElement element, DropPanel groupPanel, Float2 location)
{ {
var linkedEditor = (CustomEditor)groupPanel.Tag; var linkedEditor = (CustomEditor)groupPanel.Tag;
var menu = new ContextMenu(); var menu = new ContextMenu();
@@ -91,6 +91,7 @@ namespace FlaxEditor.CustomEditors
menu.AddButton("Copy", linkedEditor.Copy); menu.AddButton("Copy", linkedEditor.Copy);
var paste = menu.AddButton("Paste", linkedEditor.Paste); var paste = menu.AddButton("Paste", linkedEditor.Paste);
paste.Enabled = linkedEditor.CanPaste; paste.Enabled = linkedEditor.CanPaste;
element.SetupContextMenu?.Invoke(menu, groupPanel);
menu.Show(groupPanel, location); menu.Show(groupPanel, location);
} }
@@ -666,7 +667,20 @@ namespace FlaxEditor.CustomEditors
} }
var property = AddPropertyItem(name, tooltip); 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;
} }
/// <summary> /// <summary>

View File

@@ -40,7 +40,6 @@ public class Editor : EditorModule
options.ScriptingAPI.SystemReferences.Add("System.Xml"); options.ScriptingAPI.SystemReferences.Add("System.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter"); options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile"); options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile");
// Enable optimizations for Editor, disable this for debugging the editor // Enable optimizations for Editor, disable this for debugging the editor

View File

@@ -226,7 +226,7 @@ bool Editor::CheckProjectUpgrade()
" base.Init();\n" " base.Init();\n"
"\n" "\n"
" // Reference the modules for game\n" " // Reference the modules for game\n"
" Modules.Add(\"{0}\");\n" " Modules.Add(nameof({0}));\n"
" }}\n" " }}\n"
"}}\n" "}}\n"
), codeName), Encoding::UTF8); ), codeName), Encoding::UTF8);
@@ -242,7 +242,7 @@ bool Editor::CheckProjectUpgrade()
" base.Init();\n" " base.Init();\n"
"\n" "\n"
" // Reference the modules for editor\n" " // Reference the modules for editor\n"
" Modules.Add(\"{0}\");\n" " Modules.Add(nameof({0}));\n"
"{1}" "{1}"
" }}\n" " }}\n"
"}}\n" "}}\n"
@@ -476,7 +476,7 @@ int32 Editor::LoadProduct()
" base.Init();\n" " base.Init();\n"
"\n" "\n"
" // Reference the modules for game\n" " // Reference the modules for game\n"
" Modules.Add(\"Game\");\n" " Modules.Add(nameof(Game));\n"
" }\n" " }\n"
"}\n"), Encoding::UTF8); "}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT( failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT(
@@ -490,7 +490,7 @@ int32 Editor::LoadProduct()
" base.Init();\n" " base.Init();\n"
"\n" "\n"
" // Reference the modules for editor\n" " // Reference the modules for editor\n"
" Modules.Add(\"Game\");\n" " Modules.Add(nameof(Game));\n"
" }\n" " }\n"
"}\n"), Encoding::UTF8); "}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT( failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT(

View File

@@ -337,14 +337,12 @@ namespace FlaxEditor.GUI.ContextMenu
/// <summary> /// <summary>
/// Adds the separator. /// Adds the separator.
/// </summary> /// </summary>
/// <returns>Created context menu item control.</returns> public void AddSeparator()
public ContextMenuSeparator AddSeparator()
{ {
var item = new ContextMenuSeparator(this) var item = new ContextMenuSeparator(this)
{ {
Parent = _panel Parent = _panel
}; };
return item;
} }
/// <summary> /// <summary>

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
@@ -277,6 +278,14 @@ namespace FlaxEditor.GUI
} }
} }
/// <inheritdoc />
protected override void OnLayoutMenuButton(ContextMenuButton button, int index, bool construct = false)
{
base.OnLayoutMenuButton(button, index, construct);
if (IsFlags)
button.CloseMenuOnClick = false;
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnItemClicked(int index) protected override void OnItemClicked(int index)
{ {

View File

@@ -195,7 +195,7 @@ namespace FlaxEditor.GUI.Input
var style = Style.Current; var style = Style.Current;
// Draw sliding UI // Draw sliding UI
Render2D.DrawSprite(style.Scalar, SlideRect, style.Foreground); Render2D.DrawSprite(style.Scalar, SlideRect, EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled);
// Check if is sliding // Check if is sliding
if (_isSliding) if (_isSliding)

View File

@@ -130,9 +130,9 @@ namespace FlaxEditor.GUI.Timeline
public override void OnPlay() public override void OnPlay()
{ {
var time = CurrentTime; var time = CurrentTime;
_preview.Play();
if (_preview != null) if (_preview != null)
{ {
_preview.Play();
Editor.Internal_SetAnimationTime(Object.GetUnmanagedPtr(_preview.PreviewActor), time); Editor.Internal_SetAnimationTime(Object.GetUnmanagedPtr(_preview.PreviewActor), time);
} }

View File

@@ -34,6 +34,8 @@ namespace FlaxEditor.GUI.Timeline.Undo
private void Set(byte[] data) private void Set(byte[] data)
{ {
if (_timeline == null)
return;
var track = _timeline.FindTrack(_name); var track = _timeline.FindTrack(_name);
using (var memory = new MemoryStream(data)) using (var memory = new MemoryStream(data))
using (var stream = new BinaryReader(memory)) using (var stream = new BinaryReader(memory))
@@ -42,11 +44,8 @@ namespace FlaxEditor.GUI.Timeline.Undo
track.Flags = (TrackFlags)stream.ReadByte(); track.Flags = (TrackFlags)stream.ReadByte();
track.Archetype.Load(Timeline.FormatVersion, track, stream); track.Archetype.Load(Timeline.FormatVersion, track, stream);
} }
if (_timeline != null) _timeline.ArrangeTracks();
{ _timeline.MarkAsEdited();
_timeline.ArrangeTracks();
_timeline.MarkAsEdited();
}
track.OnUndo(); track.OnUndo();
} }

View File

@@ -272,11 +272,13 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetShaderAssetSourceCode(BinaryAss
obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data); obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data);
auto source = data.Get<char>(); auto source = data.Get<char>();
auto sourceLength = data.Length(); auto sourceLength = data.Length();
if (sourceLength <= 0)
return MCore::String::GetEmpty();
Encryption::DecryptBytes(data.Get(), data.Length()); Encryption::DecryptBytes(data.Get(), data.Length());
source[sourceLength - 1] = 0; source[sourceLength - 1] = 0;
// Get source and encrypt it back // 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); const auto str = MUtils::ToString(srcData);
Encryption::EncryptBytes(data.Get(), data.Length()); Encryption::EncryptBytes(data.Get(), data.Length());

View File

@@ -686,6 +686,10 @@ namespace FlaxEditor.Modules
} }
else 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 // Check if it's an asset
if (item.IsAsset) if (item.IsAsset)
{ {
@@ -1077,6 +1081,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new ParticleSystemProxy()); Proxy.Add(new ParticleSystemProxy());
Proxy.Add(new SceneAnimationProxy()); Proxy.Add(new SceneAnimationProxy());
Proxy.Add(new CSharpScriptProxy()); Proxy.Add(new CSharpScriptProxy());
Proxy.Add(new CSharpEmptyProxy());
Proxy.Add(new CppAssetProxy()); Proxy.Add(new CppAssetProxy());
Proxy.Add(new CppStaticClassProxy()); Proxy.Add(new CppStaticClassProxy());
Proxy.Add(new CppScriptProxy()); Proxy.Add(new CppScriptProxy());

View File

@@ -145,6 +145,10 @@ namespace FlaxEditor.Modules
return; return;
} }
// Save any modified scenes to prevent loosing local changes
if (Editor.Scene.IsEdited())
Level.SaveAllScenes();
// Load scenes after entering the play mode // Load scenes after entering the play mode
_scenesToReload = new Guid[Level.ScenesCount]; _scenesToReload = new Guid[Level.ScenesCount];
for (int i = 0; i < _scenesToReload.Length; i++) for (int i = 0; i < _scenesToReload.Length; i++)

View File

@@ -382,7 +382,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
// Get editor target and target files and add module // Get editor target and target files and add module
var files = Directory.GetFiles(path); var files = Directory.GetFiles(path);
var targetModuleText = $"Modules.Add(\"{moduleName}\");\n "; var targetModuleText = $"Modules.Add(nameof({moduleName}));\n ";
foreach (var file in files) foreach (var file in files)
{ {
if (!file.Contains(".Build.cs", StringComparison.OrdinalIgnoreCase)) 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}");
}
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnUpdate() public override void OnUpdate()
{ {

View File

@@ -376,11 +376,25 @@ namespace FlaxEditor.Options
public float ConnectionCurvature { get; set; } = 1.0f; public float ConnectionCurvature { get; set; } = 1.0f;
/// <summary> /// <summary>
/// Gets or sets the visject connection curvature. /// Gets or sets a value that indicates wether the context menu description panel is shown or not.
/// </summary> /// </summary>
[DefaultValue(true)] [DefaultValue(true)]
[EditorDisplay("Visject"), EditorOrder(550), Tooltip("Shows/hides the description panel in the visual scripting context menu.")] [EditorDisplay("Visject"), EditorOrder(550), Tooltip("Shows/hides the description panel in visual scripting context menu.")]
public bool VisualScriptingDescriptionPanel { get; set; } = true; public bool NodeDescriptionPanel { get; set; } = true;
/// <summary>
/// Gets or sets the surface grid snapping option.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Visject", "Grid Snapping"), EditorOrder(551), Tooltip("Toggles grid snapping when moving nodes.")]
public bool SurfaceGridSnapping { get; set; } = false;
/// <summary>
/// Gets or sets the surface grid snapping option.
/// </summary>
[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<FontAsset>(EditorAssets.PrimaryFont); private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont); private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -671,6 +672,18 @@ namespace FlaxEditor.Scripting
/// <param name="value">The new member value.</param> /// <param name="value">The new member value.</param>
public void SetValue(object obj, object 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) if (_managed is PropertyInfo propertyInfo)
propertyInfo.SetValue(obj, value); propertyInfo.SetValue(obj, value);
else if (_managed is FieldInfo fieldInfo) else if (_managed is FieldInfo fieldInfo)

View File

@@ -57,8 +57,9 @@ namespace FlaxEditor.States
{ {
// Generate project files when Cache is missing or was cleared previously // Generate project files when Cache is missing or was cleared previously
var projectFolderPath = Editor.GameProject?.ProjectFolderPath; var projectFolderPath = Editor.GameProject?.ProjectFolderPath;
if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) || if (!string.IsNullOrEmpty(projectFolderPath) &&
!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects"))) (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects"))))
{ {
var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs; var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs;
ScriptsBuilder.GenerateProject(customArgs); ScriptsBuilder.GenerateProject(customArgs);

View File

@@ -23,6 +23,7 @@ namespace FlaxEditor.Surface.Archetypes
private readonly bool _is2D; private readonly bool _is2D;
private Float2 _rangeX, _rangeY; private Float2 _rangeX, _rangeY;
private Float2 _debugPos = Float2.Minimum; private Float2 _debugPos = Float2.Minimum;
private float _debugScale = 1.0f;
private readonly List<BlendPoint> _blendPoints = new List<BlendPoint>(); private readonly List<BlendPoint> _blendPoints = new List<BlendPoint>();
/// <summary> /// <summary>
@@ -445,6 +446,7 @@ namespace FlaxEditor.Surface.Archetypes
// Debug current playback position // Debug current playback position
if (((AnimGraphSurface)_node.Surface).TryGetTraceEvent(_node, out var traceEvent)) if (((AnimGraphSurface)_node.Surface).TryGetTraceEvent(_node, out var traceEvent))
{ {
var prev = _debugPos;
if (_is2D) if (_is2D)
{ {
unsafe unsafe
@@ -456,10 +458,17 @@ namespace FlaxEditor.Surface.Archetypes
} }
else else
_debugPos = new Float2(traceEvent.Value, 0.0f); _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 else
{ {
_debugPos = Float2.Minimum; _debugPos = Float2.Minimum;
_debugScale = 1.0f;
} }
base.Update(deltaTime); base.Update(deltaTime);
@@ -606,7 +615,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
// Draw dot with outline // Draw dot with outline
var icon = Editor.Instance.Icons.VisjectBoxOpen32; var icon = Editor.Instance.Icons.VisjectBoxOpen32;
var size = BlendPoint.DefaultSize; var size = BlendPoint.DefaultSize * _debugScale;
var debugPos = BlendSpacePosToBlendPointPos(_debugPos); var debugPos = BlendSpacePosToBlendPointPos(_debugPos);
var debugRect = new Rectangle(debugPos + new Float2(size * -0.5f) + size * 0.5f, new Float2(size)); var debugRect = new Rectangle(debugPos + new Float2(size * -0.5f) + size * 0.5f, new Float2(size));
var outline = Color.Black; // Shadow var outline = Color.Black; // Shadow

View File

@@ -809,7 +809,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!_useDescriptionPanel) if (!_useDescriptionPanel)
return; return;
if (archetype == null || !Editor.Instance.Options.Options.Interface.VisualScriptingDescriptionPanel) if (archetype == null || !Editor.Instance.Options.Options.Interface.NodeDescriptionPanel)
{ {
HideDescriptionPanel(); HideDescriptionPanel();
return; return;
@@ -875,7 +875,7 @@ namespace FlaxEditor.Surface.ContextMenu
AddInputOutputElement(archetype, output.Type, true, $"{output.Name} ({output.Type.Name})"); 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) foreach (var element in archetype.Elements)
{ {

View File

@@ -17,7 +17,7 @@ namespace FlaxEditor.Surface
public class MaterialSurface : VisjectSurface public class MaterialSurface : VisjectSurface
{ {
/// <inheritdoc /> /// <inheritdoc />
public MaterialSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) public MaterialSurface(IVisjectSurfaceOwner owner, Action onSave = null, FlaxEditor.Undo undo = null)
: base(owner, onSave, undo) : base(owner, onSave, undo)
{ {
} }

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEngine; using FlaxEngine;
@@ -86,7 +85,10 @@ namespace FlaxEditor.Surface
// Read node data // Read node data
Title = TitleValue; Title = TitleValue;
Color = ColorValue; Color = ColorValue;
Size = SizeValue; var size = SizeValue;
if (Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Size = size;
// Order // Order
// Backwards compatibility - When opening with an older version send the old comments to the back // Backwards compatibility - When opening with an older version send the old comments to the back
@@ -299,7 +301,10 @@ namespace FlaxEditor.Surface
if (_isResizing) if (_isResizing)
{ {
// Update size // 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 else
{ {

View File

@@ -131,6 +131,15 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
public virtual void OnSurfaceLoaded(SurfaceNodeActions action) 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(); UpdateRectangles();
} }

View File

@@ -167,6 +167,15 @@ namespace FlaxEditor.Surface
if (Surface == null) if (Surface == null)
return; 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++) for (int i = 0; i < Elements.Count; i++)
{ {
if (Elements[i] is OutputBox box) if (Elements[i] is OutputBox box)
@@ -175,6 +184,7 @@ namespace FlaxEditor.Surface
} }
} }
// Resize
Size = CalculateNodeSize(width, height); Size = CalculateNodeSize(width, height);
} }
@@ -976,10 +986,12 @@ namespace FlaxEditor.Surface
else else
Array.Copy(values, Values, values.Length); Array.Copy(values, Values, values.Length);
OnValuesChanged(); OnValuesChanged();
Surface.MarkAsEdited(graphEdited);
if (Surface != null) if (Surface != null)
{
Surface.MarkAsEdited(graphEdited);
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited)); Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
}
_isDuringValuesEditing = false; _isDuringValuesEditing = false;
} }

View File

@@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@@ -10,9 +9,9 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Options; using FlaxEditor.Options;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
using FlaxEngine; using FlaxEngine;
using FlaxEditor.GUI;
namespace FlaxEditor.Surface namespace FlaxEditor.Surface
{ {
@@ -73,9 +72,8 @@ namespace FlaxEditor.Surface
// By name // By name
if (Editor.Instance.Options.Options.General.ScriptMembersOrder == GeneralOptions.MembersOrder.Alphabetical) if (Editor.Instance.Options.Options.General.ScriptMembersOrder == GeneralOptions.MembersOrder.Alphabetical)
{
return string.Compare(x.DisplayName, y.DisplayName, StringComparison.InvariantCulture); return string.Compare(x.DisplayName, y.DisplayName, StringComparison.InvariantCulture);
}
// Keep same order // Keep same order
return 0; return 0;
} }
@@ -111,6 +109,79 @@ namespace FlaxEditor.Surface
return CustomEditors.Editors.GenericEditor.OnGroup(layout, "Parameters"); 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<SurfaceParameter> 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<MaterialBase>(layerId);
if (layer)
{
FindGraphParameters(layer, surfaceParameters);
}
}
}
}
}
}
}
private static void FindGraphParameters(MaterialBase materialBase, List<SurfaceParameter> 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<MaterialParameter> parameters, Material material) internal static GraphParameterData[] InitGraphParameters(IEnumerable<MaterialParameter> parameters, Material material)
{ {
int count = parameters.Count(); int count = parameters.Count();
@@ -118,128 +189,11 @@ namespace FlaxEditor.Surface
int i = 0; int i = 0;
// Load material surface parameters meta to use it for material instance parameters editing // Load material surface parameters meta to use it for material instance parameters editing
SurfaceParameter[] surfaceParameters = null; var surfaceParameters = new List<SurfaceParameter>();
try try
{ {
Profiler.BeginEvent("Init Material Parameters UI Data"); Profiler.BeginEvent("Init Material Parameters UI Data");
FindGraphParameters(material, surfaceParameters);
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);
}
}
}
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -253,7 +207,26 @@ namespace FlaxEditor.Surface
foreach (var parameter in parameters) 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<Attribute>(); var attributes = surfaceParameter?.Meta.GetAttributes() ?? FlaxEngine.Utils.GetEmptyArray<Attribute>();
data[i] = new GraphParameterData(null, parameter.Name, parameter.IsPublic, ToType(parameter.ParameterType), attributes, parameter); data[i] = new GraphParameterData(null, parameter.Name, parameter.IsPublic, ToType(parameter.ParameterType), attributes, parameter);
i++; i++;
@@ -587,5 +560,33 @@ namespace FlaxEditor.Surface
return true; return true;
return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left); 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);
}
} }
} }

View File

@@ -326,7 +326,7 @@ namespace FlaxEditor.Surface
_cmCopyButton = menu.AddButton("Copy", Copy); _cmCopyButton = menu.AddButton("Copy", Copy);
menu.AddButton("Paste", Paste).Enabled = CanEdit && CanPaste(); menu.AddButton("Paste", Paste).Enabled = CanEdit && CanPaste();
_cmDuplicateButton = menu.AddButton("Duplicate", Duplicate); _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); var canRemove = CanEdit && selection.All(node => (node.Archetype.Flags & NodeFlags.NoRemove) == 0);
menu.AddButton("Cut", Cut).Enabled = canRemove; menu.AddButton("Cut", Cut).Enabled = canRemove;
menu.AddButton("Delete", Delete).Enabled = canRemove; menu.AddButton("Delete", Delete).Enabled = canRemove;

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Options; using FlaxEditor.Options;
@@ -24,6 +25,7 @@ namespace FlaxEditor.Surface
private string _currentInputText = string.Empty; private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta; private Float2 _movingNodesDelta;
private Float2 _gridRoundingDelta;
private HashSet<SurfaceNode> _movingNodes; private HashSet<SurfaceNode> _movingNodes;
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>(); private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
@@ -189,6 +191,22 @@ namespace FlaxEditor.Surface
} }
} }
/// <summary>
/// Snaps a coordinate point to the grid.
/// </summary>
/// <param name="point">The point to be rounded.</param>
/// <param name="ceil">Round to ceiling instead?</param>
/// <returns>Rounded coordinate.</returns>
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;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnMouseEnter(Float2 location) public override void OnMouseEnter(Float2 location)
{ {
@@ -256,18 +274,39 @@ namespace FlaxEditor.Surface
// Moving // Moving
else if (_isMovingSelection) else if (_isMovingSelection)
{ {
var gridSnap = GridSnappingEnabled;
if (!gridSnap)
_gridRoundingDelta = Float2.Zero; // Reset in case user toggled option between frames.
// Calculate delta (apply view offset) // Calculate delta (apply view offset)
var viewDelta = _rootControl.Location - _movingSelectionViewPos; var viewDelta = _rootControl.Location - _movingSelectionViewPos;
_movingSelectionViewPos = _rootControl.Location; _movingSelectionViewPos = _rootControl.Location;
var delta = location - _leftMouseDownPos - viewDelta; var delta = location - _leftMouseDownPos - viewDelta + _gridRoundingDelta;
if (delta.LengthSquared > 0.01f) var deltaLengthSquared = delta.LengthSquared;
delta /= _targetScale;
if ((!gridSnap || Mathf.Abs(delta.X) >= GridSnappingSize || (Mathf.Abs(delta.Y) >= GridSnappingSize))
&& deltaLengthSquared > 0.01f)
{ {
// Move selected nodes if (gridSnap)
delta /= _targetScale; {
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) foreach (var node in _movingNodes)
{
if (gridSnap)
{
Float2 unroundedLocation = node.Location;
node.Location = SnapToGrid(unroundedLocation);
}
node.Location += delta; node.Location += delta;
}
_leftMouseDownPos = location; _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) if (_movingNodes.Count > 0)
{ {
Cursor = CursorType.SizeAll; Cursor = CursorType.SizeAll;

View File

@@ -62,7 +62,8 @@ namespace FlaxEditor.Surface
/// <remarks> /// <remarks>
/// The method calls the <see cref="ISurfaceContext.SurfaceData"/> setter to assign the result bytes. Sets null value if failed. /// The method calls the <see cref="ISurfaceContext.SurfaceData"/> setter to assign the result bytes. Sets null value if failed.
/// </remarks> /// </remarks>
public virtual void Save() /// <returns>True if failed, otherwise false.</returns>
public virtual bool Save()
{ {
var wasEdited = IsEdited; var wasEdited = IsEdited;
@@ -71,19 +72,16 @@ namespace FlaxEditor.Surface
_context.CachedSurfaceMeta.Scale = ViewScale; _context.CachedSurfaceMeta.Scale = ViewScale;
// Save context (and every modified child context) // Save context (and every modified child context)
bool failed = RootContext.Save(); if (RootContext.Save())
return true;
if (failed)
{
// Error
return;
}
// Clear flag // Clear flag
if (wasEdited) if (wasEdited)
{ {
Owner.OnSurfaceEditedChanged(); Owner.OnSurfaceEditedChanged();
} }
return false;
} }
} }
} }

View File

@@ -31,11 +31,29 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
protected SurfaceRootControl _rootControl; protected SurfaceRootControl _rootControl;
/// <summary>
/// Is grid snapping enabled for this surface?
/// </summary>
public bool GridSnappingEnabled
{
get => _gridSnappingEnabled;
set
{
_gridSnappingEnabled = value;
_gridRoundingDelta = Float2.Zero;
}
}
/// <summary>
/// The size of the snapping grid.
/// </summary>
public float GridSnappingSize = 20f;
private float _targetScale = 1.0f; private float _targetScale = 1.0f;
private float _moveViewWithMouseDragSpeed = 1.0f; private float _moveViewWithMouseDragSpeed = 1.0f;
private bool _canEdit = true; private bool _canEdit = true;
private readonly bool _supportsDebugging; private readonly bool _supportsDebugging;
private bool _isReleasing; private bool _isReleasing, _gridSnappingEnabled;
private VisjectCM _activeVisjectCM; private VisjectCM _activeVisjectCM;
private GroupArchetype _customNodesGroup; private GroupArchetype _customNodesGroup;
private List<NodeArchetype> _customNodes; private List<NodeArchetype> _customNodes;
@@ -632,6 +650,11 @@ namespace FlaxEditor.Surface
SelectionChanged?.Invoke(); SelectionChanged?.Invoke();
} }
internal void ToggleGridSnapping()
{
GridSnappingEnabled = !GridSnappingEnabled;
}
/// <summary> /// <summary>
/// Selects all the nodes. /// Selects all the nodes.
/// </summary> /// </summary>

View File

@@ -891,9 +891,21 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
protected Tabs _tabs; protected Tabs _tabs;
private readonly ToolStripButton _saveButton; /// <summary>
private readonly ToolStripButton _undoButton; /// Save button on a toolstrip.
private readonly ToolStripButton _redoButton; /// </summary>
protected ToolStripButton _saveButton;
/// <summary>
/// Undo button on a toolstrip.
/// </summary>
protected ToolStripButton _undoButton;
/// <summary>
/// Redo button on a toolstrip.
/// </summary>
protected ToolStripButton _redoButton;
private bool _showWholeGraphOnLoad = true; private bool _showWholeGraphOnLoad = true;
/// <summary> /// <summary>
@@ -950,8 +962,6 @@ namespace FlaxEditor.Surface
protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false) protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false)
: base(editor, item) : base(editor, item)
{ {
var inputOptions = Editor.Options.Options.Input;
// Undo // Undo
_undo = new FlaxEditor.Undo(); _undo = new FlaxEditor.Undo();
_undo.UndoDone += OnUndoRedo; _undo.UndoDone += OnUndoRedo;
@@ -998,20 +1008,6 @@ namespace FlaxEditor.Surface
_propertiesEditor.Panel.Parent = _split2.Panel2; _propertiesEditor.Panel.Parent = _split2.Panel2;
} }
_propertiesEditor.Modified += OnPropertyEdited; _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) private void OnUndoRedo(IUndoAction action)

View File

@@ -1427,6 +1427,26 @@ namespace FlaxEditor.Viewport
return new Ray(nearPoint + viewOrigin, Vector3.Normalize(farPoint - nearPoint)); return new Ray(nearPoint + viewOrigin, Vector3.Normalize(farPoint - nearPoint));
} }
/// <summary>
/// Projects the point from 3D world-space to viewport coordinates.
/// </summary>
/// <param name="worldSpaceLocation">The input world-space location (XYZ in world).</param>
/// <param name="viewportSpaceLocation">The output viewport window coordinates (XY in screen pixels).</param>
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);
}
/// <summary> /// <summary>
/// Called when mouse control begins. /// Called when mouse control begins.
/// </summary> /// </summary>

View File

@@ -206,6 +206,7 @@ namespace FlaxEditor.Windows.Assets
_surface.ContextChanged += OnSurfaceContextChanged; _surface.ContextChanged += OnSurfaceContextChanged;
// Toolstrip // 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"); _showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view");
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more"); _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); base.SetParameter(index, value);
} }
/// <summary>
/// Sets the base model of the animation graph this window is editing.
/// </summary>
/// <param name="baseModel">The new base model.</param>
public void SetBaseModel(SkinnedModel baseModel)
{
_properties.BaseModel = baseModel;
}
/// <inheritdoc /> /// <inheritdoc />
protected override void UnlinkItem() protected override void UnlinkItem()
{ {

View File

@@ -172,13 +172,8 @@ namespace FlaxEditor.Windows.Assets
_knowledgePropertiesEditor.Panel.Parent = _split2.Panel2; _knowledgePropertiesEditor.Panel.Parent = _split2.Panel2;
// Toolstrip // Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator(); _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"); _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 // Debug behavior picker
@@ -206,11 +201,6 @@ namespace FlaxEditor.Windows.Assets
_behaviorPicker.CheckValid = OnBehaviorPickerCheckValid; _behaviorPicker.CheckValid = OnBehaviorPickerCheckValid;
_behaviorPicker.ValueChanged += OnBehaviorPickerValueChanged; _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); SetCanEdit(!Editor.IsPlayMode);
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
} }
@@ -430,8 +420,7 @@ namespace FlaxEditor.Windows.Assets
private bool SaveSurface() private bool SaveSurface()
{ {
_surface.Save(); return _surface.Save();
return false;
} }
private void SetCanEdit(bool canEdit) private void SetCanEdit(bool canEdit)

View File

@@ -242,8 +242,11 @@ namespace FlaxEditor.Windows.Assets
private void OpenOptionsContextMenu() private void OpenOptionsContextMenu()
{ {
if (_optionsCM != null && _optionsCM.ContainsFocus) if (_optionsCM != null)
return; {
_optionsCM.Hide();
_optionsCM.Dispose();
}
_optionsCM = new ContextMenu(); _optionsCM = new ContextMenu();
_optionsCM.AddButton("Copy type name", () => Clipboard.Text = Asset.DataTypeName); _optionsCM.AddButton("Copy type name", () => Clipboard.Text = Asset.DataTypeName);

View File

@@ -9,10 +9,12 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.GUI; using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Surface; using FlaxEditor.Surface;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.Windows.Assets namespace FlaxEditor.Windows.Assets
{ {
@@ -247,21 +249,9 @@ namespace FlaxEditor.Windows.Assets
if (parameters.Length == 0) if (parameters.Length == 0)
return; return;
// Utility buttons
{
var buttons = layout.CustomContainer<UniformGridPanel>();
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 parametersGroup = SurfaceUtils.InitGraphParametersGroup(layout);
var settingButton = parametersGroup.AddSettingsButton();
settingButton.Clicked += (image, button) => OnSettingsButtonClicked(image, button, proxy.Window);
var baseMaterial = materialInstance.BaseMaterial; var baseMaterial = materialInstance.BaseMaterial;
var material = baseMaterial; var material = baseMaterial;
if (material) if (material)
@@ -323,6 +313,19 @@ namespace FlaxEditor.Windows.Assets
itemLayout.Property(label, valueContainer, null, e.Tooltip?.Text); 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() private void OnRemoveOverrides()
{ {
@@ -389,8 +392,6 @@ namespace FlaxEditor.Windows.Assets
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); _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})"); _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator(); _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"); _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 // Split Panel

View File

@@ -257,8 +257,9 @@ namespace FlaxEditor.Windows.Assets
}; };
// Toolstrip // 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.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"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more");
} }

View File

@@ -141,8 +141,9 @@ namespace FlaxEditor.Windows.Assets
}; };
// Toolstrip // 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.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"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
} }

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.Content.Import; using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
@@ -133,10 +134,21 @@ namespace FlaxEditor.Windows.Assets
group.Panel.Tag = i; group.Panel.Tag = i;
group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
group.Object(new ListValueContainer(elementType, i, Values)); 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) private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Float2 location)
{ {
var menu = new ContextMenu(); var menu = new ContextMenu();

View File

@@ -70,13 +70,7 @@ namespace FlaxEditor.Windows.Assets
_undo.ActionDone += OnUndoRedo; _undo.ActionDone += OnUndoRedo;
// Toolstrip // 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, ShowWholeGraph).LinkTooltip("Show whole graph");
// Panel // Panel
_panel = new Panel(ScrollBars.None) _panel = new Panel(ScrollBars.None)
@@ -85,11 +79,6 @@ namespace FlaxEditor.Windows.Assets
Offsets = new Margin(0, 0, _toolstrip.Bottom, 0), Offsets = new Margin(0, 0, _toolstrip.Bottom, 0),
Parent = this 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) private void OnUndoRedo(IUndoAction action)

View File

@@ -597,13 +597,7 @@ namespace FlaxEditor.Windows.Assets
_propertiesEditor.Select(_properties); _propertiesEditor.Select(_properties);
// Toolstrip // 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, ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more");
_debugToolstripControls = new[] _debugToolstripControls = new[]
@@ -643,9 +637,6 @@ namespace FlaxEditor.Windows.Assets
debugObjectPickerContainer.Parent = _toolstrip; debugObjectPickerContainer.Parent = _toolstrip;
// Setup input actions // 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.DebuggerContinue, OnDebuggerContinue);
InputActions.Add(options => options.DebuggerStepOver, OnDebuggerStepOver); InputActions.Add(options => options.DebuggerStepOver, OnDebuggerStepOver);
InputActions.Add(options => options.DebuggerStepOut, OnDebuggerStepOut); InputActions.Add(options => options.DebuggerStepOut, OnDebuggerStepOut);
@@ -1202,7 +1193,8 @@ namespace FlaxEditor.Windows.Assets
private bool SaveSurface() private bool SaveSurface()
{ {
_surface.Save(); if (_surface.Save())
return true;
// Reselect actors to prevent issues after Visual Script properties were modified // Reselect actors to prevent issues after Visual Script properties were modified
Editor.Windows.PropertiesWin.Presenter.BuildLayoutOnUpdate(); Editor.Windows.PropertiesWin.Presenter.BuildLayoutOnUpdate();

View File

@@ -1421,6 +1421,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto cData = node->Values[4 + c * 2].AsFloat4(); const auto cData = node->Values[4 + c * 2].AsFloat4();
// Get triangle coords // Get triangle coords
byte anims[3] = { a, b, c };
Float2 points[3] = { Float2 points[3] = {
Float2(aData.X, aData.Y), Float2(aData.X, aData.Y),
Float2(bData.X, bData.Y), Float2(bData.X, bData.Y),
@@ -1534,18 +1535,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bestPoint = closest; bestPoint = closest;
hasBest = true; hasBest = true;
float d = Float2::Distance(s[0], s[1]); const float d = Float2::Distance(s[0], s[1]);
if (Math::IsZero(d)) bestWeight = d < ANIM_GRAPH_BLEND_THRESHOLD ? 0 : Float2::Distance(s[0], closest) / d;
{
bestWeight = 0; bestAnims[0] = anims[j];
} bestAnims[1] = anims[(j + 1) % 3];
else
{
bestWeight = Float2::Distance(s[0], closest) / d;
}
bestAnims[0] = j;
bestAnims[1] = (j + 1) % 3;
} }
} }
} }

View File

@@ -1471,7 +1471,8 @@ Asset::LoadResult VisualScript::load()
for (int32 i = 0; i < count; i++) for (int32 i = 0; i < count; i++)
{ {
const int32 oldIndex = _oldParamsLayout.Find(Graph.Parameters[i].Identifier); 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; instanceParams[i] = Graph.Parameters[i].Value;
} }
} }
_oldParamsLayout.Clear();
_oldParamsValues.Clear();
} }
#endif #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) // 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()); _oldParamsLayout.Resize(Graph.Parameters.Count());
_oldParamsValues.Resize(Graph.Parameters.Count());
for (int32 i = 0; i < Graph.Parameters.Count(); i++) for (int32 i = 0; i < Graph.Parameters.Count(); i++)
{ {
auto& param = Graph.Parameters[i]; auto& param = Graph.Parameters[i];
_oldParamsLayout[i] = param.Identifier; _oldParamsLayout[i] = param.Identifier;
_oldParamsValues[i] = param.Value;
} }
} }
else else
{ {
_oldParamsLayout.Clear(); _oldParamsLayout.Clear();
_oldParamsValues.Clear();
} }
#else #else
_instances.Clear(); _instances.Clear();

View File

@@ -154,6 +154,7 @@ private:
Array<Field, InlinedAllocation<32>> _fields; Array<Field, InlinedAllocation<32>> _fields;
#if USE_EDITOR #if USE_EDITOR
Array<Guid> _oldParamsLayout; Array<Guid> _oldParamsLayout;
Array<Variant> _oldParamsValues;
#endif #endif
public: public:

View File

@@ -1,6 +1,10 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
#if FLAX_EDITOR
using System.ComponentModel;
using System.Globalization;
#endif
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace FlaxEngine namespace FlaxEngine
@@ -11,6 +15,7 @@ namespace FlaxEngine
/// <typeparam name="T">Type of the asset instance type.</typeparam> /// <typeparam name="T">Type of the asset instance type.</typeparam>
#if FLAX_EDITOR #if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))] [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
[TypeConverter(typeof(TypeConverters.JsonAssetReferenceConverter))]
#endif #endif
[Newtonsoft.Json.JsonConverter(typeof(Json.JsonAssetReferenceConverter))] [Newtonsoft.Json.JsonConverter(typeof(Json.JsonAssetReferenceConverter))]
public struct JsonAssetReference<T> : IComparable, IComparable<JsonAssetReference<T>>, IEquatable<JsonAssetReference<T>> public struct JsonAssetReference<T> : IComparable, IComparable<JsonAssetReference<T>>, IEquatable<JsonAssetReference<T>>
@@ -125,7 +130,7 @@ namespace FlaxEngine
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return Asset?.ToString(); return Asset?.ToString() ?? "null";
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -141,3 +146,33 @@ namespace FlaxEngine
} }
} }
} }
#if FLAX_EDITOR
namespace FlaxEngine.TypeConverters
{
internal class JsonAssetReferenceConverter : TypeConverter
{
/// <inheritdoc />
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<JsonAsset>(id);
destinationType.GetField("Asset").SetValue(result, asset);
return result;
}
return base.ConvertTo(context, culture, value, destinationType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType.Name.StartsWith("JsonAssetReference", StringComparison.Ordinal))
return true;
return base.CanConvertTo(context, destinationType);
}
}
}
#endif

View File

@@ -639,7 +639,17 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
// Save animation data // Save animation data
MemoryWriteStream stream(8182); 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)) if (modelData.Pack2AnimationHeader(&stream, animIndex))
return CreateAssetResult::Error; return CreateAssetResult::Error;
if (context.AllocateChunk(0)) if (context.AllocateChunk(0))

View File

@@ -865,6 +865,16 @@ namespace FlaxEngine
return new Float2(Mathf.Ceil(v.X), Mathf.Ceil(v.Y)); return new Float2(Mathf.Ceil(v.X), Mathf.Ceil(v.Y));
} }
/// <summary>
/// Returns the vector with components containing the smallest integer smaller to or equal to the original value.
/// </summary>
/// <param name="v">The value.</param>
/// <returns>The result.</returns>
public static Float2 Floor(Float2 v)
{
return new Float2(Mathf.Floor(v.X), Mathf.Floor(v.Y));
}
/// <summary> /// <summary>
/// Breaks the components of the vector into an integral and a fractional part. Returns vector made of fractional parts. /// Breaks the components of the vector into an integral and a fractional part. Returns vector made of fractional parts.
/// </summary> /// </summary>

View File

@@ -7,6 +7,7 @@
#include "Matrix3x3.h" #include "Matrix3x3.h"
#include "Math.h" #include "Math.h"
#include "../Types/String.h" #include "../Types/String.h"
#include "Engine/Core/Math/Transform.h"
Quaternion Quaternion::Zero(0, 0, 0, 0); Quaternion Quaternion::Zero(0, 0, 0, 0);
Quaternion Quaternion::One(1, 1, 1, 1); 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.Y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
result.Z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2; 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);
}

View File

@@ -1483,6 +1483,45 @@ namespace FlaxEngine
return results; return results;
} }
/// <summary>
/// Gets rotation from a normal in relation to a transform.<br/>
/// This function is especially useful for axis aligned faces,
/// and with <seealso cref="Physics.RayCast(Vector3, Vector3, out RayCastHit, float, uint, bool)"/>.
///
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> GetRotationFromNormalExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Vector3"/> position = hit.Collider.Position;
/// <see cref="Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// <see cref="Quaternion"/> rot = <see cref="Quaternion"/>.GetRotationFromNormal(normal,transform);
/// SomeObject.Position = point;
/// SomeObject.Orientation = rot;
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="normal">The normal vector.</param>
/// <param name="reference">The reference transform.</param>
/// <returns>The rotation from the normal vector.</returns>
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);
}
/// <summary> /// <summary>
/// Adds two quaternions. /// Adds two quaternions.
/// </summary> /// </summary>

View File

@@ -660,6 +660,14 @@ public:
// @param roll The roll of rotation (in radians) // @param roll The roll of rotation (in radians)
// @param result When the method completes, contains the newly created quaternion // @param result When the method completes, contains the newly created quaternion
static void RotationYawPitchRoll(float yaw, float pitch, float roll, Quaternion& result); static void RotationYawPitchRoll(float yaw, float pitch, float roll, Quaternion& result);
/// <summary>
/// Gets rotation from a normal in relation to a transform. This function is especially useful for axis aligned faces, and with <seealso cref="Physics::RayCast"/>.
/// </summary>
/// <param name="normal">The normal vector.</param>
/// <param name="reference">The reference transform.</param>
/// <returns>The rotation from the normal vector.</returns>
static Quaternion GetRotationFromNormal(const Vector3& normal, const Transform& reference);
}; };
/// <summary> /// <summary>

View File

@@ -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); Quaternion::Slerp(t1.Orientation, t2.Orientation, amount, result.Orientation);
Float3::Lerp(t1.Scale, t2.Scale, amount, result.Scale); 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);
}

View File

@@ -478,6 +478,96 @@ namespace FlaxEngine
Float3.Lerp(ref start.Scale, ref end.Scale, amount, out result.Scale); Float3.Lerp(ref start.Scale, ref end.Scale, amount, out result.Scale);
} }
/// <summary>
/// Combines the functions:<br/>
/// <see cref="Vector3.SnapToGrid(FlaxEngine.Vector3,FlaxEngine.Vector3)"/>,<br/>
/// <see cref="Quaternion.GetRotationFromNormal"/>.
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> AlignRotationToObjectAndSnapToGridExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Vector3"/> Offset = new Vector3(0, 0, 50f);<br/>
/// <see langword="public" /> <see cref="Vector3"/> GridSize = <see cref="Vector3.One"/> * 20.0f;<br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// SomeObject.Transform = <see cref="Transform"/>.AlignRotationToNormalAndSnapToGrid
/// (
/// point,
/// normal,
/// Offset,
/// transform,
/// SomeObject.Scale,
/// GridSize,
/// Float3.One
/// );
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="normalOffset">The local grid offset to apply after snapping.</param>
/// <param name="normal">The normal vector.</param>
/// <param name="relativeTo">The relative transform.</param>
/// <param name="scale">The scale to apply to the transform.</param>
/// <returns>The rotated and snapped transform.</returns>
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);
}
/// <summary>
/// Combines the functions:<br/>
/// <see cref="Vector3.SnapToGrid(FlaxEngine.Vector3,FlaxEngine.Vector3)"/>,<br/>
/// <see cref="Quaternion.GetRotationFromNormal"/>.
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> AlignRotationToObjectAndSnapToGridExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Vector3"/> Offset = new Vector3(0, 0, 50f);<br/>
/// <see langword="public" /> <see cref="Vector3"/> GridSize = <see cref="Vector3.One"/> * 20.0f;<br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// SomeObject.Transform = <see cref="Transform"/>.AlignRotationToNormalAndSnapToGrid
/// (
/// point,
/// normal,
/// Offset,
/// transform,
/// GridSize
/// );
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="normalOffset">The local grid offset to apply after snapping.</param>
/// <param name="normal">The normal vector.</param>
/// <param name="relativeTo">The relative transform.</param>
/// <returns>The rotated and snapped transform with scale <see cref="Float3.One"/>.</returns>
public static Transform AlignRotationToNormalAndSnapToGrid(Vector3 point, Vector3 normal, Vector3 normalOffset, Transform relativeTo, Vector3 gridSize)
{
return AlignRotationToNormalAndSnapToGrid(point, normal, normalOffset, relativeTo, gridSize, Float3.One);
}
/// <summary> /// <summary>
/// Tests for equality between two objects. /// Tests for equality between two objects.
/// </summary> /// </summary>

View File

@@ -291,6 +291,20 @@ public:
return result; return result;
} }
/// <summary>
/// Combines the functions: <br/>
/// <see cref="SnapToGrid"/>,<br/>
/// <see cref="GetRotationFromNormal"/>.
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="normalOffset">The local grid offset to apply after snapping.</param>
/// <param name="normal">The normal vector.</param>
/// <param name="relativeTo">The relative transform.</param>
/// <param name="scale">The scale to apply to the transform.</param>
/// <returns>The rotated and snapped transform.</returns>
static Transform AlignRotationToNormalAndSnapToGrid(const Vector3& point, const Vector3& normal, const Vector3& normalOffset, const Transform& relativeTo, const Vector3& gridSize, const Float3& scale = Float3::One);
public: public:
FORCE_INLINE Transform operator*(const Transform& other) const FORCE_INLINE Transform operator*(const Transform& other) const
{ {

View File

@@ -324,6 +324,20 @@ float Float3::Angle(const Float3& from, const Float3& to)
return Math::Acos(dot); 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 // Double
static_assert(sizeof(Double3) == 24, "Invalid Double3 type size."); 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); 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 // Int
static_assert(sizeof(Int3) == 12, "Invalid Int3 type size."); static_assert(sizeof(Int3) == 12, "Invalid Int3 type size.");
@@ -852,3 +880,17 @@ int32 Int3::Angle(const Int3& from, const Int3& to)
{ {
return 0; 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;
}

View File

@@ -1672,7 +1672,7 @@ namespace FlaxEngine
} }
/// <summary> /// <summary>
/// Snaps the input position into the grid. /// Snaps the input position onto the grid.
/// </summary> /// </summary>
/// <param name="pos">The position to snap.</param> /// <param name="pos">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param> /// <param name="gridSize">The size of the grid.</param>
@@ -1685,6 +1685,44 @@ namespace FlaxEngine
return pos; return pos;
} }
/// <summary>
/// Snaps the <paramref name="point"/> onto the rotated grid.<br/>
/// For world aligned grid snapping use <b><see cref="SnapToGrid(FlaxEngine.Vector3,FlaxEngine.Vector3)"/></b> instead.
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> SnapToGridExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Vector3"/> GridSize = <see cref="Vector3.One"/> * 20.0f;<br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Vector3"/> position = hit.Collider.Position;
/// <see cref="FlaxEngine.Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// //Get rotation from normal relative to collider transform
/// <see cref="Quaternion"/> rot = <see cref="Quaternion"/>.GetRotationFromNormal(normal, transform);
/// point = <see cref="Vector3"/>.SnapToGrid(point, GridSize, rot, position);
/// SomeObject.Position = point;
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="gridOrientation">The rotation of the grid.</param>
/// <param name="gridOrigin">The center point of the grid.</param>
/// <param name="offset">The local position offset applied to the snapped position before grid rotation.</param>
/// <returns>The position snapped to the grid.</returns>
public static Vector3 SnapToGrid(Vector3 point, Vector3 gridSize, Quaternion gridOrientation, Vector3 gridOrigin, Vector3 offset)
{
return ((SnapToGrid(point - gridOrigin, gridSize) * gridOrientation.Conjugated() + offset) * gridOrientation) + gridOrigin;
}
/// <summary> /// <summary>
/// Adds two vectors. /// Adds two vectors.
/// </summary> /// </summary>

View File

@@ -927,6 +927,25 @@ public:
/// <param name="to">The second vector.</param> /// <param name="to">The second vector.</param>
/// <returns>The angle (in radians).</returns> /// <returns>The angle (in radians).</returns>
static FLAXENGINE_API T Angle(const Vector3Base& from, const Vector3Base& to); static FLAXENGINE_API T Angle(const Vector3Base& from, const Vector3Base& to);
/// <summary>
/// Snaps the input position onto the grid.
/// </summary>
/// <param name="pos">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <returns>The position snapped to the grid.</returns>
static FLAXENGINE_API Vector3Base SnapToGrid(const Vector3Base& pos, const Vector3Base& gridSize);
/// <summary>
/// Snaps the <paramref name="point"/> onto the rotated grid. For world aligned grid snapping use <b><see cref="SnapToGrid"/></b> instead.
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="gridOrigin">The center point of the grid.</param>
/// <param name="gridOrientation">The rotation of the grid.</param>
/// <param name="offset">The local position offset applied to the snapped position before grid rotation.</param>
/// <returns>The position snapped to the grid.</returns>
static FLAXENGINE_API Vector3Base SnapToGrid(const Vector3Base& point, const Vector3Base& gridSize, const Quaternion& gridOrientation, const Vector3Base& gridOrigin = Zero, const Vector3Base& offset = Zero);
}; };
template<typename T> template<typename T>

View File

@@ -479,6 +479,10 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
batch.DrawCall.InstanceCount = 1; batch.DrawCall.InstanceCount = 1;
auto& firstInstance = batch.Instances[0]; auto& firstInstance = batch.Instances[0];
firstInstance.Load(batch.DrawCall); 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)) if (EnumHasAnyFlags(drawModes, DrawPass::Forward))
{ {

View File

@@ -39,11 +39,13 @@ public:
FORCE_INLINE GPUPipelineState* Get(int index) const FORCE_INLINE GPUPipelineState* Get(int index) const
{ {
ASSERT_LOW_LAYER(index >= 0 && index < Size);
return States[index]; return States[index];
} }
FORCE_INLINE GPUPipelineState*& operator[](int32 index) FORCE_INLINE GPUPipelineState*& operator[](int32 index)
{ {
ASSERT_LOW_LAYER(index >= 0 && index < Size);
return States[index]; return States[index];
} }
@@ -129,6 +131,7 @@ public:
public: public:
FORCE_INLINE GPUShaderProgramCS* Get(const int index) const FORCE_INLINE GPUShaderProgramCS* Get(const int index) const
{ {
ASSERT_LOW_LAYER(index >= 0 && index < Size);
return Shaders[index]; return Shaders[index];
} }

View File

@@ -480,6 +480,8 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
const ViewMode viewMode = renderContext.View.Mode; const ViewMode viewMode = renderContext.View.Mode;
if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview)
GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]);
if (viewMode == ViewMode::LightmapUVsDensity)
drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader
#endif #endif
// Push draw call to the render list // 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; const ViewMode viewMode = renderContextBatch.GetMainContext().View.Mode;
if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview)
GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]);
if (viewMode == ViewMode::LightmapUVsDensity)
drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader
#endif #endif
// Push draw call to the render lists // Push draw call to the render lists

View File

@@ -241,5 +241,9 @@ public:
/// The object sorting key. /// The object sorting key.
/// </summary> /// </summary>
int8 SortOrder; int8 SortOrder;
#if USE_EDITOR
float LightmapScale = -1.0f;
#endif
}; };
}; };

View File

@@ -52,6 +52,11 @@ API_ENUM() enum class ToneMappingMode
/// The ACES Filmic reference tonemapper (approximation). /// The ACES Filmic reference tonemapper (approximation).
/// </summary> /// </summary>
ACES = 2, ACES = 2,
/// <summary>
/// The AGX tonemapper.
/// </summary>
AGX = 3,
}; };
/// <summary> /// <summary>

View File

@@ -213,13 +213,13 @@ protected:
const int32 dstMips = dstTexture->MipLevels(); const int32 dstMips = dstTexture->MipLevels();
GPUTexture* srcTexture = _streamingTexture->GetTexture(); GPUTexture* srcTexture = _streamingTexture->GetTexture();
const int32 srcMips = srcTexture->MipLevels(); const int32 srcMips = srcTexture->MipLevels();
const int32 srcMissingMips = srcMips - srcTexture->ResidentMipLevels();
const int32 mipCount = Math::Min(dstMips, srcMips); const int32 mipCount = Math::Min(dstMips, srcMips);
ASSERT(mipCount > 0); for (int32 mipIndex = srcMissingMips; mipIndex < mipCount; mipIndex++)
for (int32 mipIndex = 0; mipIndex < mipCount; mipIndex++)
{ {
context->GPU->CopySubresource(dstTexture, dstMips - mipIndex - 1, srcTexture, srcMips - mipIndex - 1); context->GPU->CopySubresource(dstTexture, dstMips - mipIndex - 1, srcTexture, srcMips - mipIndex - 1);
} }
_uploadedMipCount = mipCount; _uploadedMipCount = mipCount - srcMissingMips;
return Result::Ok; return Result::Ok;
} }
@@ -238,10 +238,10 @@ protected:
void OnSync() override void OnSync() override
{ {
_newTexture->SetResidentMipLevels(_uploadedMipCount);
Swap(_streamingTexture->_texture, _newTexture); Swap(_streamingTexture->_texture, _newTexture);
_streamingTexture->GetTexture()->SetResidentMipLevels(_uploadedMipCount);
_streamingTexture->ResidencyChanged();
SAFE_DELETE_GPU_RESOURCE(_newTexture); SAFE_DELETE_GPU_RESOURCE(_newTexture);
_streamingTexture->ResidencyChanged();
// Base // Base
GPUTask::OnSync(); GPUTask::OnSync();

View File

@@ -27,32 +27,18 @@ struct AxisEvaluation
struct ActionData struct ActionData
{ {
bool Active; bool Active = false;
uint64 FrameIndex; uint64 FrameIndex = 0;
InputActionState State; InputActionState State = InputActionState::Waiting;
ActionData()
{
Active = false;
FrameIndex = 0;
State = InputActionState::Waiting;
}
}; };
struct AxisData struct AxisData
{ {
float Value; float Value = 0.0f;
float ValueRaw; float ValueRaw = 0.0f;
float PrevKeyValue; float PrevValue = 0.0f;
uint64 FrameIndex; float PrevKeyValue = 0.0f;
uint64 FrameIndex = 0;
AxisData()
{
Value = 0.0f;
ValueRaw = 0.0f;
PrevKeyValue = 0.0f;
FrameIndex = 0;
}
}; };
namespace InputImpl namespace InputImpl
@@ -990,6 +976,7 @@ void InputService::Update()
// Setup axis data // Setup axis data
data.PrevKeyValue = e.PrevKeyValue; data.PrevKeyValue = e.PrevKeyValue;
data.PrevValue = data.Value;
data.ValueRaw = e.RawValue; data.ValueRaw = e.RawValue;
data.Value = e.Value; data.Value = e.Value;
@@ -1025,7 +1012,7 @@ void InputService::Update()
{ {
for (auto i = Axes.Begin(); i.IsNotEnd(); ++i) 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); Input::AxisValueChanged(i->Key);
} }

View File

@@ -237,7 +237,10 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp
viewport.Unproject(nearPoint, ivp, nearPoint); viewport.Unproject(nearPoint, ivp, nearPoint);
viewport.Unproject(farPoint, ivp, farPoint); 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 Viewport Camera::GetViewport() const
@@ -303,6 +306,8 @@ void Camera::GetMatrices(Matrix& view, Matrix& projection, const Viewport& viewp
void Camera::OnPreviewModelLoaded() void Camera::OnPreviewModelLoaded()
{ {
_previewModelBuffer.Setup(_previewModel.Get()); _previewModelBuffer.Setup(_previewModel.Get());
if (_previewModelBuffer.Count() > 0)
_previewModelBuffer.At(0).ReceiveDecals = false;
UpdateCache(); UpdateCache();
} }

View File

@@ -150,35 +150,30 @@ float Spline::GetSplineLength() const
{ {
float sum = 0.0f; float sum = 0.0f;
constexpr int32 slices = 20; constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices; constexpr float step = 1.0f / (float)(slices - 1);
Vector3 prevPoint = Vector3::Zero; const Vector3 scale = _transform.Scale;
if (Curve.GetKeyframes().Count() != 0)
{
const auto& a = Curve[0];
prevPoint = a.Value.Translation * _transform.Scale;
}
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++) for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{ {
const auto& a = Curve[i - 1]; const auto& a = Curve[i - 1];
const auto& b = Curve[i]; const auto& b = Curve[i];
Vector3 prevPoint = a.Value.Translation * scale;
const float length = Math::Abs(b.Time - a.Time); const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent; Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution for (int32 slice = 1; slice < slices; slice++)
for (int32 slice = 0; slice < slices; slice++)
{ {
const float t = (float)slice * step; const float t = (float)slice * step;
Vector3 pos; Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos); AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale; pos *= scale;
sum += (float)Vector3::DistanceSquared(pos, prevPoint); sum += (float)Vector3::Distance(pos, prevPoint);
prevPoint = pos; prevPoint = pos;
} }
} }
return Math::Sqrt(sum); return sum;
} }
float Spline::GetSplineSegmentLength(int32 index) const float Spline::GetSplineSegmentLength(int32 index) const
@@ -188,28 +183,28 @@ float Spline::GetSplineSegmentLength(int32 index) const
CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f); CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f);
float sum = 0.0f; float sum = 0.0f;
constexpr int32 slices = 20; 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& a = Curve[index - 1];
const auto& b = Curve[index]; 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); const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent; Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution for (int32 slice = 1; slice < slices; slice++)
for (int32 slice = 0; slice < slices; slice++)
{ {
const float t = (float)slice * step; const float t = (float)slice * step;
Vector3 pos; Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos); AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale; pos *= scale;
sum += (float)Vector3::DistanceSquared(pos, startPoint); sum += (float)Vector3::Distance(pos, prevPoint);
startPoint = pos; prevPoint = pos;
} }
} }
return Math::Sqrt(sum); return sum;
} }
float Spline::GetSplineTime(int32 index) const float Spline::GetSplineTime(int32 index) const

View File

@@ -357,6 +357,10 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.ForcedLOD = _forcedLod; draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder; draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr; draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
#if USE_EDITOR
if (HasStaticFlag(StaticFlags::Lightmap))
draw.LightmapScale = _scaleInLightmap;
#endif
Model->Draw(renderContext, draw); Model->Draw(renderContext, draw);
@@ -391,6 +395,10 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch)
draw.ForcedLOD = _forcedLod; draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder; draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr; draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
#if USE_EDITOR
if (HasStaticFlag(StaticFlags::Lightmap))
draw.LightmapScale = _scaleInLightmap;
#endif
Model->Draw(renderContextBatch, draw); Model->Draw(renderContextBatch, draw);

View File

@@ -7,9 +7,7 @@ using System.Runtime.CompilerServices;
namespace FlaxEngine namespace FlaxEngine
{ {
#if FLAX_EDITOR
[TypeConverter(typeof(TypeConverters.TagConverter))] [TypeConverter(typeof(TypeConverters.TagConverter))]
#endif
partial struct Tag : IEquatable<Tag>, IEquatable<string>, IComparable, IComparable<Tag>, IComparable<string> partial struct Tag : IEquatable<Tag>, IEquatable<string>, IComparable, IComparable<Tag>, IComparable<string>
{ {
/// <summary> /// <summary>
@@ -254,7 +252,6 @@ namespace FlaxEngine
} }
} }
#if FLAX_EDITOR
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class TagConverter : TypeConverter internal class TagConverter : TypeConverter
@@ -291,4 +288,3 @@ namespace FlaxEngine.TypeConverters
} }
} }
} }
#endif

View File

@@ -1503,6 +1503,8 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds) bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> 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); Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline()) if (!info || !obj || NetworkManager::IsOffline())

View File

@@ -66,12 +66,14 @@ bool GPUParticles::Init(ParticleEmitter* owner, MemoryReadStream& shaderCacheStr
LOG(Warning, "Missing valid GPU particles constant buffer."); LOG(Warning, "Missing valid GPU particles constant buffer.");
return true; 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; return true;
} }
_cbData.Resize(cb0->GetSize()); _cbData.Resize(cbSize);
Platform::MemoryClear(_cbData.Get(), cbSize);
// Load material parameters // Load material parameters
if (_params.Load(materialParamsStream)) if (_params.Load(materialParamsStream))

View File

@@ -15,7 +15,7 @@ private:
bool _useVolumeTexture; bool _useVolumeTexture;
PixelFormat _lutFormat; PixelFormat _lutFormat;
AssetReference<Shader> _shader; AssetReference<Shader> _shader;
GPUPipelineStatePermutationsPs<3> _psLut; GPUPipelineStatePermutationsPs<4> _psLut;
public: public:

View File

@@ -15,7 +15,6 @@
#include "Engine/Renderer/DrawCall.h" #include "Engine/Renderer/DrawCall.h"
#include "Engine/Foliage/Foliage.h" #include "Engine/Foliage/Foliage.h"
#include "Engine/ShadowsOfMordor/Builder.Config.h" #include "Engine/ShadowsOfMordor/Builder.Config.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/Actors/StaticModel.h"
@@ -70,40 +69,6 @@ DrawPass LightmapUVsDensityMaterialShader::GetDrawModes() const
return DrawPass::GBuffer; return DrawPass::GBuffer;
} }
namespace
{
Actor* FindActorByDrawCall(Actor* actor, const DrawCall& drawCall, float& scaleInLightmap)
{
// TODO: large-worlds
const auto asStaticModel = ScriptingObject::Cast<StaticModel>(actor);
if (asStaticModel && asStaticModel->GetPerInstanceRandom() == drawCall.PerInstanceRandom && asStaticModel->GetPosition() == drawCall.ObjectPosition)
{
scaleInLightmap = asStaticModel->GetScaleInLightmap();
return asStaticModel;
}
const auto asFoliage = ScriptingObject::Cast<Foliage>(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) void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
{ {
// Prepare // Prepare
@@ -121,33 +86,6 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
_ps->Init(psDesc); _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 // Bind constants
if (cb && cb->GetSize()) if (cb && cb->GetSize())
{ {
@@ -166,19 +104,15 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
data.LightmapSize = 1024.0f; data.LightmapSize = 1024.0f;
data.LightmapArea = drawCall.Surface.LightmapUVsArea; data.LightmapArea = drawCall.Surface.LightmapUVsArea;
const ModelLOD* drawCallModelLod; 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) // Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting)
float globalObjectsScale = 1.0f; float globalObjectsScale = 1.0f;
int32 atlasSize = 1024; int32 atlasSize = 1024;
int32 chartsPadding = 3; 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); BoundingBox box = drawCallModelLod->GetBox(drawCall.World);
Float3 size = box.GetSize(); Float3 size = box.GetSize();
float dimensionsCoeff = size.AverageArithmetic(); float dimensionsCoeff = size.AverageArithmetic();

View File

@@ -479,7 +479,7 @@ namespace FlaxEngine.Json
/// <inheritdoc /> /// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType.Name.StartsWith("JsonAssetReference"); return objectType.Name.StartsWith("JsonAssetReference", StringComparison.Ordinal);
} }
} }

View File

@@ -187,7 +187,7 @@ public:
/// <summary> /// <summary>
/// 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). /// 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).
/// </summary> /// </summary>
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<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& GetPhysicalMaterials() const FORCE_INLINE const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& GetPhysicalMaterials() const
{ {
return _physicalMaterials; return _physicalMaterials;
@@ -199,6 +199,27 @@ public:
API_PROPERTY() API_PROPERTY()
void SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& value); void SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& value);
/// <summary>
/// Gets the physical material used to define the terrain collider physical properties.
/// [Deprecated on 16.02.2024, expires on 16.02.2026]
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize")
DEPRECATED("Use PhysicalMaterials instead.") FORCE_INLINE JsonAssetReference<PhysicalMaterial>& GetPhysicalMaterial()
{
return _physicalMaterials[0];
}
/// <summary>
/// Sets the physical materials used to define the terrain collider physical properties.
/// [Deprecated on 16.02.2024, expires on 16.02.2026]
/// </summary>
DEPRECATED("Use PhysicalMaterials instead.") API_PROPERTY()
void SetPhysicalMaterial(const JsonAssetReference<PhysicalMaterial>& value)
{
for (auto& e : _physicalMaterials)
e = value;
}
/// <summary> /// <summary>
/// Gets the terrain Level Of Detail count. /// Gets the terrain Level Of Detail count.
/// </summary> /// </summary>

View File

@@ -124,6 +124,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
} }
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World); drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
drawCall.PerInstanceRandom = _perInstanceRandom; 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 // Add half-texel offset for heightmap sampling in vertex shader
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); //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.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
drawCall.PerInstanceRandom = _perInstanceRandom; 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 // Add half-texel offset for heightmap sampling in vertex shader
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);

View File

@@ -7,6 +7,12 @@
#include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Scripting/ManagedCLR/MUtils.h"
#include <ThirdParty/catch2/catch.hpp> #include <ThirdParty/catch2/catch.hpp>
Foo::Foo(const SpawnParams& params)
: ScriptingObject(params)
, FooInterface(nullptr)
{
}
TestNesting::TestNesting(const SpawnParams& params) TestNesting::TestNesting(const SpawnParams& params)
: SerializableScriptingObject(params) : SerializableScriptingObject(params)
{ {

View File

@@ -8,6 +8,21 @@
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/SerializableScriptingObject.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. // Test compilation with nested types.
API_CLASS() class TestNesting : public SerializableScriptingObject API_CLASS() class TestNesting : public SerializableScriptingObject
{ {

View File

@@ -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) // For all not root layers (sub-layers) we won't to change theirs ID in order to prevent duplicated ID)
m.SrcId = param->Identifier; m.SrcId = param->Identifier;
if (isRooLayer) m.DstId = param->Identifier;
if (!isRooLayer)
{ {
// Use the same ID (so we can edit it) // Generate new ID (stable permutation based on the original ID)
m.DstId = param->Identifier;
}
else
{
// Generate new ID
m.DstId = param->Identifier;
m.DstId.A += _parameters.Count() * 17 + 13; m.DstId.A += _parameters.Count() * 17 + 13;
} }
layer->ParamIdsMappings.Add(m); layer->ParamIdsMappings.Add(m);

Some files were not shown because too many files have changed in this diff Show More