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

# Conflicts:
#	Content/Editor/Camera/M_Camera.flax
#	Content/Editor/CubeTexturePreviewMaterial.flax
#	Content/Editor/DebugMaterials/DDGIDebugProbes.flax
#	Content/Editor/DebugMaterials/SingleColor/Decal.flax
#	Content/Editor/DebugMaterials/SingleColor/Particle.flax
#	Content/Editor/DebugMaterials/SingleColor/Surface.flax
#	Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
#	Content/Editor/DebugMaterials/SingleColor/Terrain.flax
#	Content/Editor/DefaultFontMaterial.flax
#	Content/Editor/Gizmo/FoliageBrushMaterial.flax
#	Content/Editor/Gizmo/Material.flax
#	Content/Editor/Gizmo/MaterialWire.flax
#	Content/Editor/Gizmo/SelectionOutlineMaterial.flax
#	Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax
#	Content/Editor/Highlight Material.flax
#	Content/Editor/Icons/IconsMaterial.flax
#	Content/Editor/IesProfilePreviewMaterial.flax
#	Content/Editor/Particles/Particle Material Color.flax
#	Content/Editor/Particles/Smoke Material.flax
#	Content/Editor/SpriteMaterial.flax
#	Content/Editor/Terrain/Circle Brush Material.flax
#	Content/Editor/Terrain/Highlight Terrain Material.flax
#	Content/Editor/TexturePreviewMaterial.flax
#	Content/Editor/Wires Debug Material.flax
#	Content/Engine/DefaultDeformableMaterial.flax
#	Content/Engine/DefaultMaterial.flax
#	Content/Engine/DefaultTerrainMaterial.flax
#	Content/Engine/SingleColorMaterial.flax
#	Content/Engine/SkyboxMaterial.flax
#	Source/Engine/Graphics/Materials/MaterialShader.h
This commit is contained in:
Wojtek Figat
2024-04-23 10:30:01 +02:00
91 changed files with 1107 additions and 830 deletions

View File

@@ -249,6 +249,7 @@ namespace FlaxEditor.Content
private ScriptMemberInfo[] _parameters;
private ScriptMemberInfo[] _methods;
private object[] _attributes;
private List<Action<ScriptType>> _disposing;
/// <summary>
/// Gets the Visual Script asset that contains this type.
@@ -310,6 +311,13 @@ namespace FlaxEditor.Content
internal void Dispose()
{
if (_disposing != null)
{
foreach (var e in _disposing)
e(new ScriptType(this));
_disposing.Clear();
_disposing = null;
}
if (_parameters != null)
{
OnAssetReloading(_asset);
@@ -510,6 +518,14 @@ namespace FlaxEditor.Content
}
return _methods;
}
/// <inheritdoc />
public void TrackLifetime(Action<ScriptType> disposing)
{
if (_disposing == null)
_disposing = new List<Action<ScriptType>>();
_disposing.Add(disposing);
}
}
/// <summary>

View File

@@ -209,16 +209,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
if (result != DragDropEffect.None || _dragHandlers == null)
return result;
return _dragHandlers.Effect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_dragHandlers.OnDragLeave();
_dragHandlers?.OnDragLeave();
base.OnDragLeave();
}

View File

@@ -585,7 +585,7 @@ namespace FlaxEditor.CustomEditors.Editors
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
if (result != DragDropEffect.None || _dragHandlers == null)
return result;
return _dragHandlers.Effect;
@@ -594,7 +594,7 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc />
public override void OnDragLeave()
{
_dragHandlers.OnDragLeave();
_dragHandlers?.OnDragLeave();
base.OnDragLeave();
}

View File

@@ -554,7 +554,7 @@ namespace FlaxEditor.GUI
// Check if has selected item
if (_selectedIndices != null && _selectedIndices.Count > 0)
{
string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values";
string text = _selectedIndices.Count == 1 ? (_selectedIndices[0] >= 0 && _selectedIndices[0] < _items.Count ? _items[_selectedIndices[0]] : "") : "Multiple Values";
// Draw text of the selected item
float textScale = Height / DefaultHeight;

View File

@@ -42,15 +42,14 @@ namespace FlaxEditor.GUI.ContextMenu
// Arrange controls
Margin margin = _menu._itemsMargin;
float y = margin.Top;
float x = margin.Left;
float y = 0;
float width = Width - margin.Width;
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is ContextMenuItem item && item.Visible)
{
var height = item.Height;
item.Bounds = new Rectangle(x, y, width, height);
item.Bounds = new Rectangle(margin.Left, y, width, height);
y += height + margin.Height;
}
}
@@ -300,7 +299,6 @@ namespace FlaxEditor.GUI.ContextMenu
if (_panel.Children[i] is ContextMenuChildMenu menu && menu.Text == text)
return menu;
}
return null;
}
@@ -319,7 +317,6 @@ namespace FlaxEditor.GUI.ContextMenu
Parent = _panel
};
}
return item;
}
@@ -396,10 +393,12 @@ namespace FlaxEditor.GUI.ContextMenu
float height = _itemsAreaMargin.Height;
int itemsLeft = MaximumItemsInViewCount;
int overflowItemCount = 0;
int itemsCount = 0;
for (int i = 0; i < _panel.Children.Count; i++)
{
if (_panel.Children[i] is ContextMenuItem item && item.Visible)
{
itemsCount++;
if (itemsLeft > 0)
{
height += item.Height + _itemsMargin.Height;
@@ -412,6 +411,8 @@ namespace FlaxEditor.GUI.ContextMenu
maxWidth = Mathf.Max(maxWidth, item.MinimumWidth);
}
}
if (itemsCount != 0)
height -= _itemsMargin.Height; // Remove item margin from top and bottom
maxWidth = Mathf.Max(maxWidth + 20, MinimumWidth);
// Move child arrows to accommodate scroll bar showing

View File

@@ -29,6 +29,15 @@ namespace FlaxEditor.GUI.ContextMenu
CloseMenuOnClick = false;
}
private void ShowChild(ContextMenu parentContextMenu)
{
// Hide parent CM popups and set itself as child
var vAlign = parentContextMenu.ItemsAreaMargin.Top;
var location = new Float2(Width, -vAlign);
location = PointToParent(parentContextMenu, location);
parentContextMenu.ShowChild(ContextMenu, location);
}
/// <inheritdoc />
public override void Draw()
{
@@ -58,14 +67,12 @@ namespace FlaxEditor.GUI.ContextMenu
var parentContextMenu = ParentContextMenu;
if (parentContextMenu == ContextMenu)
return;
if (ContextMenu.IsOpened)
return;
base.OnMouseEnter(location);
// Hide parent CM popups and set itself as child
parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0)));
ShowChild(parentContextMenu);
}
/// <inheritdoc />
@@ -78,8 +85,7 @@ namespace FlaxEditor.GUI.ContextMenu
if (ContextMenu.IsOpened)
return true;
// Hide parent CM popups and set itself as child
parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0)));
ShowChild(parentContextMenu);
return base.OnMouseUp(location, button);
}
}

View File

@@ -99,6 +99,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (Type == ScriptType.Null)
{
Editor.LogError("Missing anim event type " + _instanceTypeName);
InitMissing();
return;
}
Instance = (AnimEvent)Type.CreateInstance();
@@ -125,20 +126,37 @@ namespace FlaxEditor.GUI.Timeline.Tracks
_isRegisteredForScriptsReload = true;
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
Type.TrackLifetime(OnTypeDisposing);
}
private void OnTypeDisposing(ScriptType type)
{
if (Type == type && !IsDisposing)
{
// Turn into missing script
OnScriptsReloadBegin();
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
InitMissing();
}
}
private void InitMissing()
{
CanDelete = true;
CanSplit = false;
CanResize = false;
TooltipText = $"Missing Anim Event Type '{_instanceTypeName}'";
BackgroundColor = Color.Red;
Type = ScriptType.Null;
Instance = null;
}
internal void InitMissing(string typeName, byte[] data)
{
Type = ScriptType.Null;
IsContinuous = false;
CanDelete = true;
CanSplit = false;
CanResize = false;
TooltipText = $"Missing Anim Event Type '{typeName}'";
Instance = null;
BackgroundColor = Color.Red;
_instanceTypeName = typeName;
_instanceData = data;
InitMissing();
}
internal void Load(BinaryReader stream)

View File

@@ -16,6 +16,7 @@
#include "Engine/Engine/CommandLine.h"
#include "Engine/Renderer/ProbesRenderer.h"
#include "Engine/Animations/Graph/AnimGraph.h"
#include "Engine/Core/ObjectsRemovalService.h"
ManagedEditor::InternalOptions ManagedEditor::ManagedEditorOptions;
@@ -572,6 +573,29 @@ bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScript
return false;
}
void ManagedEditor::WipeOutLeftoverSceneObjects()
{
Array<ScriptingObject*> objects = Scripting::GetObjects();
bool removedAny = false;
for (ScriptingObject* object : objects)
{
if (EnumHasAllFlags(object->Flags, ObjectFlags::IsDuringPlay) && EnumHasNoneFlags(object->Flags, ObjectFlags::WasMarkedToDelete))
{
if (auto* sceneObject = Cast<SceneObject>(object))
{
if (sceneObject->HasParent())
continue; // Skip sub-objects
LOG(Error, "Object '{}' (ID={}, Type={}) is still in memory after play end but should be destroyed (memory leak).", sceneObject->GetNamePath(), sceneObject->GetID(), sceneObject->GetType().ToString());
sceneObject->DeleteObject();
removedAny = true;
}
}
}
if (removedAny)
ObjectsRemovalService::Flush();
}
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
{
ASSERT(!HasManagedInstance());

View File

@@ -241,6 +241,7 @@ public:
API_FUNCTION(Internal) static VisualScriptStackFrame GetVisualScriptPreviousScopeFrame();
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects();
private:
void OnEditorAssemblyLoaded(MAssembly* assembly);

View File

@@ -144,5 +144,11 @@ namespace FlaxEditor.Scripting
{
return Utils.GetEmptyArray<ScriptMemberInfo>();
}
/// <inheritdoc />
public void TrackLifetime(Action<ScriptType> disposing)
{
ElementType.TrackLifetime(disposing);
}
}
}

View File

@@ -167,6 +167,12 @@ namespace FlaxEditor.Scripting
/// <param name="bindingAttr">A bitmask comprised of one or more <see cref="T:System.Reflection.BindingFlags" /> that specify how the search is conducted.-or- Zero (<see cref="F:System.Reflection.BindingFlags.Default" />), to return an empty array.</param>
/// <returns>An array of member objects representing all methods defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no methods are defined for the current type, or if none of the defined methods match the binding constraints.</returns>
ScriptMemberInfo[] GetMethods(BindingFlags bindingAttr);
/// <summary>
/// Registers delegate to be invoked upon script type disposal (except hot-reload in Editor via <see cref="ScriptsBuilder.ScriptsReload"/>). For example, can happen when user deleted Visual Script asset.
/// </summary>
/// <param name="disposing">Event to call when script type gets disposed (eg. removed asset).</param>
void TrackLifetime(Action<ScriptType> disposing);
}
/// <summary>

View File

@@ -1395,11 +1395,21 @@ namespace FlaxEditor.Scripting
}
/// <summary>
/// Basic check to see if a type could be casted to another type
/// Registers delegate to be invoked upon script type disposal (except hot-reload in Editor via <see cref="ScriptsBuilder.ScriptsReload"/>). For example, can happen when user deleted Visual Script asset.
/// </summary>
/// <param name="disposing">Event to call when script type gets disposed (eg. removed asset).</param>
public void TrackLifetime(Action<ScriptType> disposing)
{
if (_custom != null)
_custom.TrackLifetime(disposing);
}
/// <summary>
/// Basic check to see if a type could be cast to another type
/// </summary>
/// <param name="from">Source type</param>
/// <param name="to">Target type</param>
/// <returns>True if the type can be casted</returns>
/// <returns>True if the type can be cast.</returns>
public static bool CanCast(ScriptType from, ScriptType to)
{
if (from == to)
@@ -1412,10 +1422,10 @@ namespace FlaxEditor.Scripting
}
/// <summary>
/// Basic check to see if this type could be casted to another type
/// Basic check to see if this type could be cast to another type
/// </summary>
/// <param name="to">Target type</param>
/// <returns>True if the type can be casted</returns>
/// <returns>True if the type can be cast.</returns>
public bool CanCastTo(ScriptType to)
{
return CanCast(this, to);

View File

@@ -163,6 +163,8 @@ namespace FlaxEditor.States
Editor.OnPlayBegin();
IsPlayModeStarting = false;
Profiler.EndEvent();
Time.Synchronize();
}
private void SetupEditorEnvOptions()
@@ -192,7 +194,7 @@ namespace FlaxEditor.States
// Restore editor scene
SceneRestoring?.Invoke();
_duplicateScenes.DeletedScenes();
_duplicateScenes.UnloadScenes();
PluginManager.Internal_DeinitializeGamePlugins();
Editor.Internal_SetPlayMode(false);
_duplicateScenes.RestoreSceneData();
@@ -209,6 +211,8 @@ namespace FlaxEditor.States
Editor.OnPlayEnd();
IsPlayModeEnding = false;
Profiler.EndEvent();
Time.Synchronize();
}
}
}

View File

@@ -6,6 +6,7 @@ using System.IO;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -16,14 +17,13 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
[HideInEditor]
public abstract class BlendPointsEditor : ContainerControl
public class BlendPointsEditor : ContainerControl
{
private readonly Animation.MultiBlend _node;
private readonly bool _is2D;
private Float2 _rangeX;
private Float2 _rangeY;
private readonly BlendPoint[] _blendPoints = new BlendPoint[Animation.MultiBlend.MaxAnimationsCount];
private readonly Guid[] _pointsAnims = new Guid[Animation.MultiBlend.MaxAnimationsCount];
private readonly Float2[] _pointsLocations = new Float2[Animation.MultiBlend.MaxAnimationsCount];
private Float2 _rangeX, _rangeY;
private Float2 _debugPos = Float2.Minimum;
private readonly List<BlendPoint> _blendPoints = new List<BlendPoint>();
/// <summary>
/// Represents single blend point.
@@ -31,16 +31,22 @@ namespace FlaxEditor.Surface.Archetypes
/// <seealso cref="FlaxEngine.GUI.Control" />
protected class BlendPoint : Control
{
private static Matrix3x3 _transform = Matrix3x3.RotationZ(45.0f * Mathf.DegreesToRadians) * Matrix3x3.Translation2D(4.0f, 0.5f);
private readonly BlendPointsEditor _editor;
private readonly int _index;
private bool _isMouseDown;
private Float2 _mousePosOffset;
private bool _isMouseDown, _mouseMoved;
private object[] _mouseMoveStartValues;
/// <summary>
/// The default size for the blend points.
/// </summary>
public const float DefaultSize = 8.0f;
/// <summary>
/// Blend point index.
/// </summary>
public int Index => _index;
/// <summary>
/// Initializes a new instance of the <see cref="BlendPoint"/> class.
/// </summary>
@@ -53,24 +59,45 @@ namespace FlaxEditor.Surface.Archetypes
_index = index;
}
private void EndMove()
{
_isMouseDown = false;
EndMouseCapture();
if (_mouseMoveStartValues != null)
{
// Add undo action
_editor._node.Surface.AddBatchedUndoAction(new EditNodeValuesAction(_editor._node, _mouseMoveStartValues, true));
_mouseMoveStartValues = null;
}
if (_mouseMoved)
_editor._node.Surface.MarkAsEdited();
}
/// <inheritdoc />
public override void OnGotFocus()
{
base.OnGotFocus();
_editor.SelectedIndex = _index;
_editor._node.SelectedAnimationIndex = _index;
}
/// <inheritdoc />
public override void Draw()
{
// Cache data
var isSelected = _editor.SelectedIndex == _index;
// Draw rotated rectangle
Render2D.PushTransform(ref _transform);
Render2D.FillRectangle(new Rectangle(0, 0, 5, 5), isSelected ? Color.Orange : Color.BlueViolet);
Render2D.PopTransform();
// Draw dot with outline
var style = Style.Current;
var icon = Editor.Instance.Icons.VisjectBoxClosed32;
var size = Height;
var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
var outline = Color.Black; // Shadow
if (_isMouseDown)
outline = style.SelectionBorder;
else if (IsMouseOver)
outline = style.BorderHighlighted;
else if (_editor._node.SelectedAnimationIndex == _index)
outline = style.BackgroundSelected;
Render2D.DrawSprite(icon, rect.MakeExpanded(4.0f), outline);
Render2D.DrawSprite(icon, rect, style.Foreground);
}
/// <inheritdoc />
@@ -80,6 +107,9 @@ namespace FlaxEditor.Surface.Archetypes
{
Focus();
_isMouseDown = true;
_mouseMoved = false;
_mouseMoveStartValues = null;
_mousePosOffset = -location;
StartMouseCapture();
return true;
}
@@ -92,8 +122,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (button == MouseButton.Left && _isMouseDown)
{
_isMouseDown = false;
EndMouseCapture();
EndMove();
return true;
}
@@ -103,9 +132,26 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (_isMouseDown)
if (_isMouseDown && (_mouseMoved || Float2.DistanceSquared(location, _mousePosOffset) > 16.0f))
{
_editor.SetLocation(_index, _editor.BlendPointPosToBlendSpacePos(Location + location));
if (!_mouseMoved)
{
// Capture initial state for undo
_mouseMoved = true;
_mouseMoveStartValues = _editor._node.Surface.Undo != null ? (object[])_editor._node.Values.Clone() : null;
}
var newLocation = Location + location + _mousePosOffset;
newLocation = _editor.BlendPointPosToBlendSpacePos(newLocation);
if (Root != null && Root.GetKey(KeyboardKeys.Control))
{
var data0 = (Float4)_editor._node.Values[0];
var rangeX = new Float2(data0.X, data0.Y);
var rangeY = _editor._is2D ? new Float2(data0.Z, data0.W) : Float2.One;
var grid = new Float2(Mathf.Abs(rangeX.Y - rangeX.X) * 0.01f, Mathf.Abs(rangeY.X - rangeY.Y) * 0.01f);
newLocation = Float2.SnapToGrid(newLocation, grid);
}
_editor.SetLocation(_index, newLocation);
}
base.OnMouseMove(location);
@@ -116,8 +162,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (_isMouseDown)
{
_isMouseDown = false;
EndMouseCapture();
EndMove();
}
base.OnMouseLeave();
@@ -128,8 +173,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (_isMouseDown)
{
_isMouseDown = false;
EndMouseCapture();
EndMove();
}
base.OnLostFocus();
@@ -149,40 +193,130 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
public bool Is2D => _is2D;
/// <summary>
/// Blend points count.
/// </summary>
public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point
/// <summary>
/// Initializes a new instance of the <see cref="BlendPointsEditor"/> class.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="is2D">The value indicating whether blend space is 2D, otherwise it is 1D.</param>
/// <param name="x">The X location.</param>
/// <param name="y">The Y location.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public BlendPointsEditor(bool is2D, float x, float y, float width, float height)
public BlendPointsEditor(Animation.MultiBlend node, bool is2D, float x, float y, float width, float height)
: base(x, y, width, height)
{
_node = node;
_is2D = is2D;
}
/// <summary>
/// Gets the blend space data.
/// </summary>
/// <param name="rangeX">The space range for X axis (X-width, Y-height).</param>
/// <param name="rangeY">The space range for Y axis (X-width, Y-height).</param>
/// <param name="pointsAnims">The points anims (input array to fill of size equal 14).</param>
/// <param name="pointsLocations">The points locations (input array to fill of size equal 14).</param>
public abstract void GetData(out Float2 rangeX, out Float2 rangeY, Guid[] pointsAnims, Float2[] pointsLocations);
internal void AddPoint()
{
// Add random point within range
var rand = new Float2(Mathf.Frac((float)Platform.TimeSeconds), (Platform.TimeCycles % 10000) / 10000.0f);
AddPoint(Float2.Lerp(new Float2(_rangeX.X, _rangeY.X), new Float2(_rangeX.Y, _rangeY.Y), rand));
}
private void AddPoint(Float2 location)
{
// Reuse existing animation
var count = PointsCount;
Guid id = Guid.Empty;
for (int i = 0; i < count; i++)
{
id = (Guid)_node.Values[5 + i * 2];
if (id != Guid.Empty)
break;
}
if (id == Guid.Empty)
{
// Just use the first anim from project, user will change it
var ids = FlaxEngine.Content.GetAllAssetsByType(typeof(FlaxEngine.Animation));
if (ids.Length != 0)
id = ids[0];
else
return;
}
AddPoint(id, location);
}
/// <summary>
/// Gets or sets the index of the selected blend point.
/// Sets the blend point asset.
/// </summary>
public abstract int SelectedIndex { get; set; }
/// <param name="asset">The asset.</param>
/// <param name="location">The location.</param>
public void AddPoint(Guid asset, Float2 location)
{
// Find the first free slot
var count = PointsCount;
if (count == Animation.MultiBlend.MaxAnimationsCount)
return;
var values = (object[])_node.Values.Clone();
var index = 0;
for (; index < count; index++)
{
var dataB = (Guid)_node.Values[5 + index * 2];
if (dataB == Guid.Empty)
break;
}
if (index == count)
{
// Add another blend point
Array.Resize(ref values, values.Length + 2);
}
values[4 + index * 2] = new Float4(location.X, _is2D ? location.Y : 0.0f, 0, 1.0f);
values[5 + index * 2] = asset;
_node.SetValues(values);
// Auto-select
_node.SelectedAnimationIndex = index;
}
/// <summary>
/// Sets the blend point asset.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="asset">The asset.</param>
/// <param name="withUndo">True to use undo action.</param>
public void SetAsset(int index, Guid asset, bool withUndo = true)
{
if (withUndo)
{
_node.SetValue(5 + index * 2, asset);
}
else
{
_node.Values[5 + index * 2] = asset;
_node.Surface.MarkAsEdited();
}
_node.UpdateUI();
}
/// <summary>
/// Sets the blend point location.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="location">The location.</param>
public abstract void SetLocation(int index, Float2 location);
public void SetLocation(int index, Float2 location)
{
var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
if (_is2D)
dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W);
_node.Values[4 + index * 2] = dataA;
_node.UpdateUI();
}
/// <summary>
/// Gets the blend points area.
@@ -249,10 +383,23 @@ namespace FlaxEditor.Surface.Archetypes
public override void Update(float deltaTime)
{
// Synchronize blend points collection
GetData(out _rangeX, out _rangeY, _pointsAnims, _pointsLocations);
for (int i = 0; i < Animation.MultiBlend.MaxAnimationsCount; i++)
var data0 = (Float4)_node.Values[0];
_rangeX = new Float2(data0.X, data0.Y);
_rangeY = _is2D ? new Float2(data0.Z, data0.W) : Float2.Zero;
var count = PointsCount;
while (_blendPoints.Count > count)
{
if (_pointsAnims[i] != Guid.Empty)
_blendPoints[count].Dispose();
_blendPoints.RemoveAt(count);
}
while (_blendPoints.Count < count)
_blendPoints.Add(null);
for (int i = 0; i < count; i++)
{
var animId = (Guid)_node.Values[5 + i * 2];
var dataA = (Float4)_node.Values[4 + i * 2];
var location = new Float2(Mathf.Clamp(dataA.X, _rangeX.X, _rangeX.Y), _is2D ? Mathf.Clamp(dataA.Y, _rangeY.X, _rangeY.Y) : 0.0f);
if (animId != Guid.Empty)
{
if (_blendPoints[i] == null)
{
@@ -264,7 +411,13 @@ namespace FlaxEditor.Surface.Archetypes
}
// Update blend point
_blendPoints[i].Location = BlendSpacePosToBlendPointPos(_pointsLocations[i]);
_blendPoints[i].Location = BlendSpacePosToBlendPointPos(location);
var asset = Editor.Instance.ContentDatabase.FindAsset(animId);
var tooltip = asset?.ShortName ?? string.Empty;
tooltip += "\nX: " + location.X;
if (_is2D)
tooltip += "\nY: " + location.Y;
_blendPoints[i].TooltipText = tooltip;
}
else
{
@@ -277,6 +430,26 @@ namespace FlaxEditor.Surface.Archetypes
}
}
// Debug current playback position
if (((AnimGraphSurface)_node.Surface).TryGetTraceEvent(_node, out var traceEvent))
{
if (_is2D)
{
unsafe
{
// Unpack xy from 32-bits
Half2 packed = *(Half2*)&traceEvent.Value;
_debugPos = (Float2)packed;
}
}
else
_debugPos = new Float2(traceEvent.Value, 0.0f);
}
else
{
_debugPos = Float2.Minimum;
}
base.Update(deltaTime);
}
@@ -293,14 +466,90 @@ namespace FlaxEditor.Surface.Archetypes
}
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
Focus();
return true;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
return true;
if (button == MouseButton.Right)
{
// Show context menu
var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu();
var b = menu.AddButton("Add point", OnAddPoint);
b.Tag = location;
b.Enabled = PointsCount < Animation.MultiBlend.MaxAnimationsCount;
if (GetChildAt(location) is BlendPoint blendPoint)
{
b = menu.AddButton("Remove point", OnRemovePoint);
b.Tag = blendPoint.Index;
b.TooltipText = blendPoint.TooltipText;
}
menu.Show(this, location);
}
return true;
}
private void OnAddPoint(FlaxEditor.GUI.ContextMenu.ContextMenuButton b)
{
AddPoint(BlendPointPosToBlendSpacePos((Float2)b.Tag));
}
private void OnRemovePoint(FlaxEditor.GUI.ContextMenu.ContextMenuButton b)
{
SetAsset((int)b.Tag, Guid.Empty);
}
private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
{
// Draw line
Render2D.DrawLine(start, end, gridColor);
// Draw label
var labelWidth = 50.0f;
var labelHeight = 10.0f;
var labelMargin = 2.0f;
string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
var hAlign = TextAlignment.Near;
Rectangle labelRect;
if (vertical)
{
labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
if (isLast)
return; // Don't overlap with the first horizontal label
}
else
{
labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
if (isLast)
{
labelRect.X = start.X - labelMargin - labelRect.Width;
hAlign = TextAlignment.Far;
}
}
Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
/// <inheritdoc />
public override void Draw()
{
// Cache data
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
var containsFocus = ContainsFocus;
GetPointsArea(out var pointsArea);
var data0 = (Float4)_node.Values[0];
var rangeX = new Float2(data0.X, data0.Y);
// Background
Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground);
@@ -309,19 +558,27 @@ namespace FlaxEditor.Surface.Archetypes
// Grid
int splits = 10;
var gridColor = style.TextBoxBackgroundSelected * 1.1f;
var labelColor = style.ForegroundDisabled;
var labelFont = style.FontSmall;
//var blendArea = BlendAreaRect;
var blendArea = pointsArea;
for (int i = 0; i < splits; i++)
for (int i = 0; i <= splits; i++)
{
float x = blendArea.Left + blendArea.Width * i / splits;
Render2D.DrawLine(new Float2(x, 1), new Float2(x, rect.Height - 2), gridColor);
float alpha = (float)i / splits;
float x = blendArea.Left + blendArea.Width * alpha;
float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
if (_is2D)
{
for (int i = 0; i < splits; i++)
var rangeY = new Float2(data0.Z, data0.W);
for (int i = 0; i <= splits; i++)
{
float y = blendArea.Top + blendArea.Height * i / splits;
Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
float alpha = (float)i / splits;
float y = blendArea.Top + blendArea.Height * alpha;
float value = Mathf.Lerp(rangeY.X, rangeY.Y, alpha);
DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
}
else
@@ -330,11 +587,24 @@ namespace FlaxEditor.Surface.Archetypes
Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
}
// Base
base.Draw();
// Draw debug position
if (_debugPos.X > float.MinValue)
{
// Draw dot with outline
var icon = Editor.Instance.Icons.VisjectBoxOpen32;
var size = BlendPoint.DefaultSize;
var debugPos = BlendSpacePosToBlendPointPos(_debugPos);
var debugRect = new Rectangle(debugPos + new Float2(size * -0.5f) + size * 0.5f, new Float2(size));
var outline = Color.Black; // Shadow
Render2D.DrawSprite(icon, debugRect.MakeExpanded(2.0f), outline);
Render2D.DrawSprite(icon, debugRect, style.ProgressNormal);
}
// Frame
Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), containsFocus ? style.ProgressNormal : style.BackgroundSelected);
var frameColor = containsFocus ? style.BackgroundSelected : (IsMouseOver ? style.ForegroundGrey : style.ForegroundDisabled);
Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), frameColor);
}
}
@@ -346,6 +616,14 @@ namespace FlaxEditor.Surface.Archetypes
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public abstract class MultiBlend : SurfaceNode
{
private Button _addButton;
private Button _removeButton;
/// <summary>
/// The blend space editor.
/// </summary>
protected BlendPointsEditor _editor;
/// <summary>
/// The selected animation label.
/// </summary>
@@ -379,7 +657,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary>
/// The maximum animations amount to blend per node.
/// </summary>
public const int MaxAnimationsCount = 14;
public const int MaxAnimationsCount = 255;
/// <summary>
/// Gets or sets the index of the selected animation.
@@ -387,7 +665,12 @@ namespace FlaxEditor.Surface.Archetypes
public int SelectedAnimationIndex
{
get => _selectedAnimation.SelectedIndex;
set => _selectedAnimation.SelectedIndex = value;
set
{
OnSelectedAnimationPopupShowing(_selectedAnimation);
_selectedAnimation.SelectedIndex = value;
UpdateUI();
}
}
/// <inheritdoc />
@@ -402,20 +685,14 @@ namespace FlaxEditor.Surface.Archetypes
Text = "Selected Animation:",
Parent = this
};
_selectedAnimation = new ComboBox(_selectedAnimationLabel.X, 4 * layoutOffsetY, _selectedAnimationLabel.Width)
{
TooltipText = "Select blend point to view and edit it",
Parent = this
};
_selectedAnimation.PopupShowing += OnSelectedAnimationPopupShowing;
_selectedAnimation.SelectedIndexChanged += OnSelectedAnimationChanged;
var items = new List<string>(MaxAnimationsCount);
while (items.Count < MaxAnimationsCount)
items.Add(string.Empty);
_selectedAnimation.Items = items;
_animationPicker = new AssetPicker(new ScriptType(typeof(FlaxEngine.Animation)), new Float2(_selectedAnimation.Left, _selectedAnimation.Bottom + 4))
{
Parent = this
@@ -428,20 +705,36 @@ namespace FlaxEditor.Surface.Archetypes
Text = "Speed:",
Parent = this
};
_animationSpeed = new FloatValueBox(1.0f, _animationSpeedLabel.Right + 4, _animationSpeedLabel.Y, _selectedAnimation.Right - _animationSpeedLabel.Right - 4)
{
SlideSpeed = 0.01f,
Parent = this
};
_animationSpeed.ValueChanged += OnAnimationSpeedValueChanged;
var buttonsSize = 12;
_addButton = new Button(_selectedAnimation.Right - buttonsSize, _selectedAnimation.Bottom + 4, buttonsSize, buttonsSize)
{
Text = "+",
TooltipText = "Add a new blend point",
Parent = this
};
_addButton.Clicked += OnAddButtonClicked;
_removeButton = new Button(_addButton.Left - buttonsSize - 4, _addButton.Y, buttonsSize, buttonsSize)
{
Text = "-",
TooltipText = "Remove selected blend point",
Parent = this
};
_removeButton.Clicked += OnRemoveButtonClicked;
}
private void OnSelectedAnimationPopupShowing(ComboBox comboBox)
{
var items = comboBox.Items;
items.Clear();
for (var i = 0; i < MaxAnimationsCount; i++)
var count = _editor.PointsCount;
for (var i = 0; i < count; i++)
{
var animId = (Guid)Values[5 + i * 2];
var path = string.Empty;
@@ -484,6 +777,16 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private void OnAddButtonClicked()
{
_editor.AddPoint();
}
private void OnRemoveButtonClicked()
{
_editor.SetAsset(SelectedAnimationIndex, Guid.Empty);
}
/// <summary>
/// Updates the editor UI.
/// </summary>
@@ -491,7 +794,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <param name="isValid">if set to <c>true</c> is selection valid.</param>
/// <param name="data0">The packed data 0.</param>
/// <param name="data1">The packed data 1.</param>
protected virtual void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
public virtual void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
{
if (isValid)
{
@@ -511,19 +814,21 @@ namespace FlaxEditor.Surface.Archetypes
_animationPicker.Enabled = isValid;
_animationSpeedLabel.Enabled = isValid;
_animationSpeed.Enabled = isValid;
_addButton.Enabled = _editor.PointsCount < MaxAnimationsCount;
_removeButton.Enabled = isValid && data1 != Guid.Empty;
}
/// <summary>
/// Updates the editor UI.
/// </summary>
protected void UpdateUI()
public void UpdateUI()
{
if (_isUpdatingUI)
return;
_isUpdatingUI = true;
var selectedIndex = _selectedAnimation.SelectedIndex;
var isValid = selectedIndex != -1;
var isValid = selectedIndex >= 0 && selectedIndex < _editor.PointsCount;
Float4 data0;
Guid data1;
if (isValid)
@@ -549,6 +854,16 @@ namespace FlaxEditor.Surface.Archetypes
UpdateUI();
}
/// <inheritdoc />
public override void OnSpawned(SurfaceNodeActions action)
{
base.OnSpawned(action);
// Select the first animation to make setup easier
OnSelectedAnimationPopupShowing(_selectedAnimation);
_selectedAnimation.SelectedIndex = 0;
}
/// <inheritdoc />
public override void OnValuesChanged()
{
@@ -566,67 +881,6 @@ namespace FlaxEditor.Surface.Archetypes
{
private readonly Label _animationXLabel;
private readonly FloatValueBox _animationX;
private readonly Editor _editor;
/// <summary>
/// The Multi Blend 1D blend space editor.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.Archetypes.BlendPointsEditor" />
protected class Editor : BlendPointsEditor
{
private MultiBlend1D _node;
/// <summary>
/// Initializes a new instance of the <see cref="Editor"/> class.
/// </summary>
/// <param name="node">The parent Visject Node node.</param>
/// <param name="x">The X location.</param>
/// <param name="y">The Y location.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Editor(MultiBlend1D node, float x, float y, float width, float height)
: base(false, x, y, width, height)
{
_node = node;
}
/// <inheritdoc />
public override void GetData(out Float2 rangeX, out Float2 rangeY, Guid[] pointsAnims, Float2[] pointsLocations)
{
var data0 = (Float4)_node.Values[0];
rangeX = new Float2(data0.X, data0.Y);
rangeY = Float2.Zero;
for (int i = 0; i < MaxAnimationsCount; i++)
{
var dataA = (Float4)_node.Values[4 + i * 2];
var dataB = (Guid)_node.Values[5 + i * 2];
pointsAnims[i] = dataB;
pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), 0.0f);
}
}
/// <inheritdoc />
public override int SelectedIndex
{
get => _node.SelectedAnimationIndex;
set => _node.SelectedAnimationIndex = value;
}
/// <inheritdoc />
public override void SetLocation(int index, Float2 location)
{
var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
_node.Values[4 + index * 2] = dataA;
_node.Surface.MarkAsEdited();
_node.UpdateUI();
}
}
/// <inheritdoc />
public MultiBlend1D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
@@ -646,11 +900,8 @@ namespace FlaxEditor.Surface.Archetypes
};
_animationX.ValueChanged += OnAnimationXChanged;
_editor = new Editor(this,
FlaxEditor.Surface.Constants.NodeMarginX,
_animationX.Bottom + 4.0f,
Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f,
120.0f);
var size = Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f;
_editor = new BlendPointsEditor(this, false, FlaxEditor.Surface.Constants.NodeMarginX, _animationX.Bottom + 4.0f, size, 120.0f);
_editor.Parent = this;
}
@@ -670,7 +921,7 @@ namespace FlaxEditor.Surface.Archetypes
}
/// <inheritdoc />
protected override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
{
base.UpdateUI(selectedIndex, isValid, ref data0, ref data1);
@@ -700,68 +951,6 @@ namespace FlaxEditor.Surface.Archetypes
private readonly FloatValueBox _animationX;
private readonly Label _animationYLabel;
private readonly FloatValueBox _animationY;
private readonly Editor _editor;
/// <summary>
/// The Multi Blend 2D blend space editor.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.Archetypes.BlendPointsEditor" />
protected class Editor : BlendPointsEditor
{
private MultiBlend2D _node;
/// <summary>
/// Initializes a new instance of the <see cref="Editor"/> class.
/// </summary>
/// <param name="node">The parent Visject Node node.</param>
/// <param name="x">The X location.</param>
/// <param name="y">The Y location.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Editor(MultiBlend2D node, float x, float y, float width, float height)
: base(true, x, y, width, height)
{
_node = node;
}
/// <inheritdoc />
public override void GetData(out Float2 rangeX, out Float2 rangeY, Guid[] pointsAnims, Float2[] pointsLocations)
{
var data0 = (Float4)_node.Values[0];
rangeX = new Float2(data0.X, data0.Y);
rangeY = new Float2(data0.Z, data0.W);
for (int i = 0; i < MaxAnimationsCount; i++)
{
var dataA = (Float4)_node.Values[4 + i * 2];
var dataB = (Guid)_node.Values[5 + i * 2];
pointsAnims[i] = dataB;
pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), Mathf.Clamp(dataA.Y, rangeY.X, rangeY.Y));
}
}
/// <inheritdoc />
public override int SelectedIndex
{
get => _node.SelectedAnimationIndex;
set => _node.SelectedAnimationIndex = value;
}
/// <inheritdoc />
public override void SetLocation(int index, Float2 location)
{
var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W);
_node.Values[4 + index * 2] = dataA;
_node.Surface.MarkAsEdited();
_node.UpdateUI();
}
}
/// <inheritdoc />
public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
@@ -795,11 +984,8 @@ namespace FlaxEditor.Surface.Archetypes
};
_animationY.ValueChanged += OnAnimationYChanged;
_editor = new Editor(this,
FlaxEditor.Surface.Constants.NodeMarginX,
_animationY.Bottom + 4.0f,
Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f,
120.0f);
var size = Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f;
_editor = new BlendPointsEditor(this, true, FlaxEditor.Surface.Constants.NodeMarginX, _animationY.Bottom + 4.0f, size, size);
_editor.Parent = this;
}
@@ -834,7 +1020,7 @@ namespace FlaxEditor.Surface.Archetypes
}
/// <inheritdoc />
protected override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
{
base.UpdateUI(selectedIndex, isValid, ref data0, ref data1);

View File

@@ -621,7 +621,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new MultiBlend1D(id, context, arch, groupArch),
Title = "Multi Blend 1D",
Description = "Animation blending in 1D",
Flags = NodeFlags.AnimGraph,
Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize,
Size = new Float2(420, 300),
DefaultValues = new object[]
{
@@ -633,19 +633,6 @@ namespace FlaxEditor.Surface.Archetypes
// Per blend sample data
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
},
Elements = new[]
{
@@ -658,10 +645,10 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 3, 3),
// Axis X
NodeElementArchetype.Factory.Input(4, "X", true, typeof(float), 4),
NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
NodeElementArchetype.Factory.Float(60, 4 * Surface.Constants.LayoutOffsetY, 0, 0),
NodeElementArchetype.Factory.Float(145, 4 * Surface.Constants.LayoutOffsetY, 0, 1),
NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4),
NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
NodeElementArchetype.Factory.Float(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 0),
NodeElementArchetype.Factory.Float(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 1),
}
},
new NodeArchetype
@@ -670,8 +657,8 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new MultiBlend2D(id, context, arch, groupArch),
Title = "Multi Blend 2D",
Description = "Animation blending in 2D",
Flags = NodeFlags.AnimGraph,
Size = new Float2(420, 320),
Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize,
Size = new Float2(420, 620),
DefaultValues = new object[]
{
// Node data
@@ -682,19 +669,6 @@ namespace FlaxEditor.Surface.Archetypes
// Per blend sample data
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
new Float4(0, 0, 0, 1.0f), Guid.Empty,
},
Elements = new[]
{
@@ -707,16 +681,16 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 3, 3),
// Axis X
NodeElementArchetype.Factory.Input(4, "X", true, typeof(float), 4),
NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
NodeElementArchetype.Factory.Float(60, 4 * Surface.Constants.LayoutOffsetY, 0, 0),
NodeElementArchetype.Factory.Float(145, 4 * Surface.Constants.LayoutOffsetY, 0, 1),
NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4),
NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
NodeElementArchetype.Factory.Float(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 0),
NodeElementArchetype.Factory.Float(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 1),
// Axis Y
NodeElementArchetype.Factory.Input(5, "Y", true, typeof(float), 5),
NodeElementArchetype.Factory.Text(30, 5 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
NodeElementArchetype.Factory.Float(60, 5 * Surface.Constants.LayoutOffsetY, 0, 2),
NodeElementArchetype.Factory.Float(145, 5 * Surface.Constants.LayoutOffsetY, 0, 3),
NodeElementArchetype.Factory.Input(4, "Y", true, typeof(float), 5),
NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
NodeElementArchetype.Factory.Float(60, 4 * Surface.Constants.LayoutOffsetY + 2, 0, 2),
NodeElementArchetype.Factory.Float(145, 4 * Surface.Constants.LayoutOffsetY + 2, 0, 3),
}
},
new NodeArchetype

View File

@@ -104,6 +104,16 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private void OnTypeDisposing(ScriptType type)
{
if (_type == type && !IsDisposing)
{
// Turn into missing script
_type = ScriptType.Null;
Instance = null;
}
}
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded(action);
@@ -113,6 +123,7 @@ namespace FlaxEditor.Surface.Archetypes
_type = TypeUtils.GetType(typeName);
if (_type != null)
{
_type.TrackLifetime(OnTypeDisposing);
TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type);
try
{

View File

@@ -73,6 +73,11 @@ namespace FlaxEditor.Surface
/// </summary>
BehaviorTreeGraph = 1024,
/// <summary>
/// Node can have different amount of items in values array.
/// </summary>
VariableValuesSize = 2048,
/// <summary>
/// Node can be used in the all visual graphs.
/// </summary>

View File

@@ -951,15 +951,20 @@ namespace FlaxEditor.Surface
{
if (_isDuringValuesEditing || !Surface.CanEdit)
return;
if (values == null || Values == null || values.Length != Values.Length)
if (values == null || Values == null)
throw new ArgumentException();
bool resize = values.Length != Values.Length;
if (resize && (Archetype.Flags & NodeFlags.VariableValuesSize) == 0)
throw new ArgumentException();
_isDuringValuesEditing = true;
var before = Surface.Undo != null ? (object[])Values.Clone() : null;
Array.Copy(values, Values, values.Length);
if (resize)
Values = (object[])values.Clone();
else
Array.Copy(values, Values, values.Length);
OnValuesChanged();
Surface.MarkAsEdited(graphEdited);
@@ -1057,6 +1062,20 @@ namespace FlaxEditor.Surface
}
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
return true;
if (button == MouseButton.Right)
return true;
return false;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
@@ -1064,13 +1083,10 @@ namespace FlaxEditor.Surface
return true;
// Close
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0)
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
{
if (_closeButtonRect.Contains(ref location))
{
Surface.Delete(this);
return true;
}
Surface.Delete(this);
return true;
}
// Secondary Context Menu

View File

@@ -14,6 +14,7 @@ namespace FlaxEditor.Surface.Undo
private ContextHandle _context;
private readonly uint _nodeId;
private readonly bool _graphEdited;
private readonly bool _resize;
private object[] _before;
private object[] _after;
@@ -23,7 +24,8 @@ namespace FlaxEditor.Surface.Undo
throw new ArgumentNullException(nameof(before));
if (node?.Values == null)
throw new ArgumentNullException(nameof(node));
if (before.Length != node.Values.Length)
_resize = before.Length != node.Values.Length;
if (_resize && (node.Archetype.Flags & NodeFlags.VariableValuesSize) == 0)
throw new ArgumentException(nameof(before));
_surface = node.Surface;
@@ -48,7 +50,10 @@ namespace FlaxEditor.Surface.Undo
throw new Exception("Missing node.");
node.SetIsDuringValuesEditing(true);
Array.Copy(_after, node.Values, _after.Length);
if (_resize)
node.Values = (object[])_after.Clone();
else
Array.Copy(_after, node.Values, _after.Length);
node.OnValuesChanged();
context.MarkAsModified(_graphEdited);
node.SetIsDuringValuesEditing(false);
@@ -65,7 +70,10 @@ namespace FlaxEditor.Surface.Undo
throw new Exception("Missing node.");
node.SetIsDuringValuesEditing(true);
Array.Copy(_before, node.Values, _before.Length);
if (_resize)
node.Values = (object[])_before.Clone();
else
Array.Copy(_before, node.Values, _before.Length);
node.OnValuesChanged();
context.MarkAsModified(_graphEdited);
node.SetIsDuringValuesEditing(false);

View File

@@ -630,6 +630,15 @@ namespace FlaxEditor.Surface
stream.ReadCommonValue(ref node.Values[j]);
}
}
else if ((node.Archetype.Flags & NodeFlags.VariableValuesSize) != 0)
{
node.Values = new object[valuesCnt];
for (int j = firstValueReadIdx; j < valuesCnt; j++)
{
// ReSharper disable once PossibleNullReferenceException
stream.ReadCommonValue(ref node.Values[j]);
}
}
else
{
Editor.LogWarning(string.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID));
@@ -795,6 +804,12 @@ namespace FlaxEditor.Surface
for (int j = firstValueReadIdx; j < valuesCnt; j++)
node.Values[j] = stream.ReadVariant();
}
else if ((node.Archetype.Flags & NodeFlags.VariableValuesSize) != 0)
{
node.Values = new object[valuesCnt];
for (int j = firstValueReadIdx; j < valuesCnt; j++)
node.Values[j] = stream.ReadVariant();
}
else
{
Editor.LogWarning(string.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID));

View File

@@ -120,9 +120,9 @@ namespace FlaxEditor.Utilities
/// <summary>
/// Deletes the creates scenes for the simulation.
/// </summary>
public void DeletedScenes()
public void UnloadScenes()
{
Profiler.BeginEvent("DuplicateScenes.DeletedScenes");
Profiler.BeginEvent("DuplicateScenes.UnloadScenes");
Editor.Log("Restoring scene data");
// TODO: here we can keep changes for actors marked to keep their state after simulation
@@ -134,6 +134,8 @@ namespace FlaxEditor.Utilities
throw new Exception("Failed to unload scenes.");
}
FlaxEngine.Scripting.FlushRemovedObjects();
Editor.WipeOutLeftoverSceneObjects();
Profiler.EndEvent();
}

View File

@@ -210,12 +210,17 @@ namespace FlaxEditor.Utilities
/// <returns>The duplicated value.</returns>
internal static object CloneValue(object value)
{
// For object references just clone it
if (value is FlaxEngine.Object)
return value;
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass))
{
var json = JsonSerializer.Serialize(value);
value = JsonSerializer.Deserialize(json, value.GetType());
}
return value;
}

View File

@@ -115,7 +115,12 @@ namespace FlaxEditor.Viewport.Previews
_hasUILinked = true;
}
else if (_uiControlLinked != null)
{
if (_uiControlLinked.Control != null &&
_uiControlLinked.Control.Parent == null)
_uiControlLinked.Control.Parent = _uiParentLink;
_hasUILinked = true;
}
}
private void LinkCanvas(Actor actor)