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

# 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/MaterialTemplates/Features/ForwardShading.hlsl
#	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/DefaultRadialMenu.flax
#	Content/Engine/DefaultTerrainMaterial.flax
#	Content/Engine/SingleColorMaterial.flax
#	Content/Engine/SkyboxMaterial.flax
#	Flax.flaxproj
#	Source/Engine/Graphics/Materials/MaterialShader.h
#	Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp
#	Source/Engine/Renderer/RenderList.h
#	Source/Shaders/Reflections.shader
#	Source/Shaders/ReflectionsCommon.hlsl
#	Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
This commit is contained in:
2026-02-11 00:20:38 +01:00
160 changed files with 3486 additions and 1273 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"Major": 1, "Major": 1,
"Minor": 12, "Minor": 12,
"Revision": 0, "Revision": 0,
"Build": 6903 "Build": 6904
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
@@ -0,0 +1,38 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="NavMeshBoundsVolume"/>.
/// </summary>
/// <seealso cref="ActorEditor" />
[CustomEditor(typeof(NavMeshBoundsVolume)), DefaultEditor]
internal class NavMeshBoundsVolumeEditor : ActorEditor
{
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (Values.HasDifferentTypes == false)
{
var button = layout.Button("Build");
button.Button.Clicked += OnBuildClicked;
}
}
private void OnBuildClicked()
{
foreach (var value in Values)
{
if (value is NavMeshBoundsVolume volume)
{
Navigation.BuildNavMesh(volume.Box, volume.Scene);
Editor.Instance.Scene.MarkSceneEdited(volume.Scene);
}
}
}
}
}
@@ -593,11 +593,12 @@ namespace FlaxEditor.CustomEditors.Editors
panel.Panel.Offsets = new Margin(7, 7, 0, 0); panel.Panel.Offsets = new Margin(7, 7, 0, 0);
panel.Panel.BackgroundColor = _background; panel.Panel.BackgroundColor = _background;
var elementType = ElementType; var elementType = ElementType;
var bindingAttr = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public;
bool single = elementType.IsPrimitive || bool single = elementType.IsPrimitive ||
elementType.Equals(new ScriptType(typeof(string))) || elementType.Equals(new ScriptType(typeof(string))) ||
elementType.IsEnum || elementType.IsEnum ||
(elementType.GetFields().Length == 1 && elementType.GetProperties().Length == 0) || (elementType.GetFields(bindingAttr).Length == 1 && elementType.GetProperties(bindingAttr).Length == 0) ||
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) || (elementType.GetProperties(bindingAttr).Length == 1 && elementType.GetFields(bindingAttr).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) if (_cachedDropPanels == null)
+3 -2
View File
@@ -23,6 +23,7 @@ using FlaxEngine.Assertions;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Interop; using FlaxEngine.Interop;
using FlaxEngine.Json; using FlaxEngine.Json;
using FlaxEngine.Utilities;
#pragma warning disable CS1591 #pragma warning disable CS1591
@@ -1370,7 +1371,7 @@ namespace FlaxEditor
public void BuildCSG() public void BuildCSG()
{ {
var scenes = Level.Scenes; var scenes = Level.Scenes;
scenes.ToList().ForEach(x => x.BuildCSG(0)); scenes.ForEach(x => x.BuildCSG(0));
Scene.MarkSceneEdited(scenes); Scene.MarkSceneEdited(scenes);
} }
@@ -1380,7 +1381,7 @@ namespace FlaxEditor
public void BuildNavMesh() public void BuildNavMesh()
{ {
var scenes = Level.Scenes; var scenes = Level.Scenes;
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); Navigation.BuildNavMesh();
Scene.MarkSceneEdited(scenes); Scene.MarkSceneEdited(scenes);
} }
@@ -502,6 +502,7 @@ namespace FlaxEditor.GUI.ContextMenu
if (base.OnKeyDown(key)) if (base.OnKeyDown(key))
return true; return true;
// Keyboard navigation around the menu
switch (key) switch (key)
{ {
case KeyboardKeys.ArrowDown: case KeyboardKeys.ArrowDown:
@@ -526,6 +527,20 @@ namespace FlaxEditor.GUI.ContextMenu
} }
} }
break; break;
case KeyboardKeys.ArrowRight:
for (int i = 0; i < _panel.Children.Count; i++)
{
if (_panel.Children[i] is ContextMenuChildMenu item && item.Visible && item.IsFocused && !item.ContextMenu.IsOpened)
{
item.ShowChild(this);
item.ContextMenu._panel.Children.FirstOrDefault(x => x is ContextMenuButton && x.Visible)?.Focus();
break;
}
}
break;
case KeyboardKeys.ArrowLeft:
ParentCM?.RootWindow.Focus();
break;
} }
return false; return false;
@@ -72,6 +72,11 @@ namespace FlaxEditor.GUI.ContextMenu
/// </summary> /// </summary>
public bool HasChildCMOpened => _childCM != null; public bool HasChildCMOpened => _childCM != null;
/// <summary>
/// Gets the parent context menu (if exists).
/// </summary>
public ContextMenuBase ParentCM => _parentCM;
/// <summary> /// <summary>
/// Gets the topmost context menu. /// Gets the topmost context menu.
/// </summary> /// </summary>
@@ -81,9 +86,7 @@ namespace FlaxEditor.GUI.ContextMenu
{ {
var cm = this; var cm = this;
while (cm._parentCM != null && cm._isSubMenu) while (cm._parentCM != null && cm._isSubMenu)
{
cm = cm._parentCM; cm = cm._parentCM;
}
return cm; return cm;
} }
} }
@@ -108,6 +111,11 @@ namespace FlaxEditor.GUI.ContextMenu
/// </summary> /// </summary>
public bool UseInput = true; public bool UseInput = true;
/// <summary>
/// Optional flag that can disable UI navigation (tab/enter).
/// </summary>
public bool UseNavigation = true;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class. /// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
/// </summary> /// </summary>
@@ -594,6 +602,21 @@ namespace FlaxEditor.GUI.ContextMenu
case KeyboardKeys.Escape: case KeyboardKeys.Escape:
Hide(); Hide();
return true; return true;
case KeyboardKeys.Return:
if (UseNavigation && Root?.FocusedControl != null)
{
Root.SubmitFocused();
return true;
}
break;
case KeyboardKeys.Tab:
if (UseNavigation && Root != null)
{
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
return true;
}
break;
} }
return false; return false;
} }
@@ -29,7 +29,7 @@ namespace FlaxEditor.GUI.ContextMenu
CloseMenuOnClick = false; CloseMenuOnClick = false;
} }
private void ShowChild(ContextMenu parentContextMenu) internal void ShowChild(ContextMenu parentContextMenu)
{ {
// Hide parent CM popups and set itself as child // Hide parent CM popups and set itself as child
var vAlign = parentContextMenu.ItemsAreaMargin.Top; var vAlign = parentContextMenu.ItemsAreaMargin.Top;
+37
View File
@@ -522,6 +522,16 @@ namespace FlaxEditor.GUI
cm.AddButton("Show whole curve", _editor.ShowWholeCurve); cm.AddButton("Show whole curve", _editor.ShowWholeCurve);
cm.AddButton("Reset view", _editor.ResetView); cm.AddButton("Reset view", _editor.ResetView);
} }
cm.AddSeparator();
var presetCm = cm.AddChildMenu("Apply preset");
foreach (var value in Enum.GetValues(typeof(CurvePreset)))
{
CurvePreset preset = (CurvePreset)value;
string name = Utilities.Utils.GetPropertyNameUI(preset.ToString());
var b = presetCm.ContextMenu.AddButton(name, () => _editor.ApplyPreset(preset));
b.Enabled = !(_editor is LinearCurveEditor<T> && (preset != CurvePreset.Constant && preset != CurvePreset.Linear));
}
_editor.OnShowContextMenu(cm, selectionCount); _editor.OnShowContextMenu(cm, selectionCount);
cm.Show(this, location); cm.Show(this, location);
} }
@@ -619,6 +629,33 @@ namespace FlaxEditor.GUI
} }
} }
/// <summary>
/// A list of avaliable curve presets for the <see cref="CurveEditor{T}"/>.
/// </summary>
public enum CurvePreset
{
/// <summary>
/// A curve where every point has the same value.
/// </summary>
Constant,
/// <summary>
/// A curve linear curve.
/// </summary>
Linear,
/// <summary>
/// A curve that starts a slowly and then accelerates until the end.
/// </summary>
EaseIn,
/// <summary>
/// A curve that starts a steep and then flattens until the end.
/// </summary>
EaseOut,
/// <summary>
/// A combination of the <see cref="CurvePreset.EaseIn"/> and <see cref="CurvePreset.EaseOut"/> preset.
/// </summary>
Smoothstep
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnKeyframesDeselect(IKeyframesEditor editor) public override void OnKeyframesDeselect(IKeyframesEditor editor)
{ {
+202 -4
View File
@@ -19,6 +19,48 @@ namespace FlaxEditor.GUI
/// <seealso cref="CurveEditorBase" /> /// <seealso cref="CurveEditorBase" />
public abstract partial class CurveEditor<T> : CurveEditorBase where T : new() public abstract partial class CurveEditor<T> : CurveEditorBase where T : new()
{ {
/// <summary>
/// Represents a single point in a <see cref="CurveEditorPreset"/>.
/// </summary>
protected struct CurvePresetPoint
{
/// <summary>
/// The time.
/// </summary>
public float Time;
/// <summary>
/// The value.
/// </summary>
public float Value;
/// <summary>
/// The in tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
/// </summary>
public float TangentIn;
/// <summary>
/// The out tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
/// </summary>
public float TangentOut;
}
/// <summary>
/// A curve preset.
/// </summary>
protected struct CurveEditorPreset()
{
/// <summary>
/// If the tangents will be linear or smooth.
/// </summary>
public bool LinearTangents;
/// <summary>
/// The points of the preset.
/// </summary>
public List<CurvePresetPoint> Points;
}
private class Popup : ContextMenuBase private class Popup : ContextMenuBase
{ {
private CustomEditorPresenter _presenter; private CustomEditorPresenter _presenter;
@@ -26,11 +68,12 @@ namespace FlaxEditor.GUI
private List<int> _keyframeIndices; private List<int> _keyframeIndices;
private bool _isDirty; private bool _isDirty;
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float height = 140.0f) public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float maxHeight = 140.0f)
: this(editor, height) : this(editor, maxHeight)
{ {
_presenter.Select(selection); _presenter.Select(selection);
_presenter.OpenAllGroups(); _presenter.OpenAllGroups();
Size = new Float2(Size.X, Mathf.Min(_presenter.ContainerControl.Size.Y, maxHeight));
_keyframeIndices = keyframeIndices; _keyframeIndices = keyframeIndices;
if (keyframeIndices != null && selection.Length != keyframeIndices.Count) if (keyframeIndices != null && selection.Length != keyframeIndices.Count)
throw new Exception(); throw new Exception();
@@ -169,7 +212,7 @@ namespace FlaxEditor.GUI
if (IsSelected) if (IsSelected)
color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f); color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f);
if (IsMouseOver) if (IsMouseOver)
color *= 1.1f; color *= 1.5f;
Render2D.FillRectangle(rect, color); Render2D.FillRectangle(rect, color);
} }
@@ -285,7 +328,7 @@ namespace FlaxEditor.GUI
/// <summary> /// <summary>
/// The keyframes size. /// The keyframes size.
/// </summary> /// </summary>
protected static readonly Float2 KeyframesSize = new Float2(7.0f); protected static readonly Float2 KeyframesSize = new Float2(8.0f);
/// <summary> /// <summary>
/// The colors for the keyframe points. /// The colors for the keyframe points.
@@ -326,6 +369,63 @@ namespace FlaxEditor.GUI
private Color _labelsColor; private Color _labelsColor;
private Font _labelsFont; private Font _labelsFont;
/// <summary>
/// Preset values for <see cref="CurvePreset"/> to be applied to a <see cref="CurveEditor{T}"/>.
/// </summary>
protected Dictionary<CurvePreset, CurveEditorPreset> Presets = new Dictionary<CurvePreset, CurveEditorPreset>
{
{ CurvePreset.Constant, new CurveEditorPreset
{
LinearTangents = true,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
}
}
},
{ CurvePreset.EaseIn, new CurveEditorPreset
{
LinearTangents = false,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = -1.4f, TangentOut = 0f },
}
}
},
{ CurvePreset.EaseOut, new CurveEditorPreset
{
LinearTangents = false,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 1.4f },
}
}
},
{ CurvePreset.Linear, new CurveEditorPreset
{
LinearTangents = true,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
}
}
},
{ CurvePreset.Smoothstep, new CurveEditorPreset
{
LinearTangents = false,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
}
}
},
};
/// <summary> /// <summary>
/// The keyframe UI points. /// The keyframe UI points.
/// </summary> /// </summary>
@@ -568,6 +668,28 @@ namespace FlaxEditor.GUI
/// <param name="indicesToRemove">The list of indices of the keyframes to remove.</param> /// <param name="indicesToRemove">The list of indices of the keyframes to remove.</param>
protected abstract void RemoveKeyframesInternal(HashSet<int> indicesToRemove); protected abstract void RemoveKeyframesInternal(HashSet<int> indicesToRemove);
/// <summary>
/// Tries to convert a float to the type of the type wildcard of the curve editor.
/// </summary>
/// <param name="value">The float.</param>
/// <returns>The converted value.</returns>
public static object ConvertCurvePresetValueToCurveEditorType(float value)
{
if (typeof(T) == typeof(Float2))
return new Float2(value);
if (typeof(T) == typeof(Float3))
return new Float3(value);
if (typeof(T) == typeof(Float4))
return new Float4(value);
if (typeof(T) == typeof(Vector2))
return new Vector2(value);
if (typeof(T) == typeof(Vector3))
return new Vector3(value);
if (typeof(T) == typeof(Vector4))
return new Vector4(value);
return value;
}
/// <summary> /// <summary>
/// Called when showing a context menu. Can be used to add custom buttons with actions. /// Called when showing a context menu. Can be used to add custom buttons with actions.
/// </summary> /// </summary>
@@ -752,6 +874,17 @@ namespace FlaxEditor.GUI
ShowCurve(false); ShowCurve(false);
} }
/// <summary>
/// Applies a <see cref="CurvePreset"/> to the curve editor.
/// </summary>
/// <param name="preset">The preset.</param>
public virtual void ApplyPreset(CurvePreset preset)
{
// Remove existing keyframes
SelectAll();
RemoveKeyframes();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Evaluate(out object result, float time, bool loop = false) public override void Evaluate(out object result, float time, bool loop = false)
{ {
@@ -1028,6 +1161,31 @@ namespace FlaxEditor.GUI
return true; return true;
} }
bool left = key == KeyboardKeys.ArrowLeft;
bool right = key == KeyboardKeys.ArrowRight;
bool up = key == KeyboardKeys.ArrowUp;
bool down = key == KeyboardKeys.ArrowDown;
if (left || right || up || down)
{
bool shift = Root.GetKey(KeyboardKeys.Shift);
bool alt = Root.GetKey(KeyboardKeys.Alt);
float deltaValue = 10f;
if (shift || alt)
deltaValue = shift ? 2.5f : 5f;
Float2 moveDelta = Float2.Zero;
if (left || right)
moveDelta.X = left ? -deltaValue : deltaValue;
if (up || down)
moveDelta.Y = up ? -deltaValue : deltaValue;
_contents.OnMoveStart(Float2.Zero);
_contents.OnMove(moveDelta);
_contents.OnMoveEnd(Float2.Zero);
return true;
}
return false; return false;
} }
@@ -1526,6 +1684,22 @@ namespace FlaxEditor.GUI
_tangents[i].Visible = false; _tangents[i].Visible = false;
} }
/// <inheritdoc />
public override void ApplyPreset(CurvePreset preset)
{
base.ApplyPreset(preset);
CurveEditorPreset data = Presets[preset];
foreach (var point in data.Points)
{
float time = point.Time;
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
AddKeyframe(time, value);
}
ShowWholeCurve();
}
/// <inheritdoc /> /// <inheritdoc />
protected override void DrawCurve(ref Rectangle viewRect) protected override void DrawCurve(ref Rectangle viewRect)
{ {
@@ -2312,6 +2486,30 @@ namespace FlaxEditor.GUI
} }
} }
/// <inheritdoc />
public override void ApplyPreset(CurvePreset preset)
{
base.ApplyPreset(preset);
CurveEditorPreset data = Presets[preset];
foreach (var point in data.Points)
{
float time = point.Time;
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)point.TangentIn);
object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)point.TangentOut);
AddKeyframe(time, value, tangentIn, tangentOut);
}
SelectAll();
if (data.LinearTangents)
SetTangentsLinear();
ShowWholeCurve();
}
/// <inheritdoc /> /// <inheritdoc />
protected override void SetScaleInternal(ref Float2 scale) protected override void SetScaleInternal(ref Float2 scale)
{ {
+49 -2
View File
@@ -469,7 +469,7 @@ namespace FlaxEditor.GUI.Docking
var childPanels = _childPanels.ToArray(); var childPanels = _childPanels.ToArray();
if (childPanels.Length != 0) if (childPanels.Length != 0)
{ {
// Move tabs from child panels into this one // Fallback: move tabs from child panels into this one.
DockWindow selectedTab = null; DockWindow selectedTab = null;
foreach (var childPanel in childPanels) foreach (var childPanel in childPanels)
{ {
@@ -490,7 +490,8 @@ namespace FlaxEditor.GUI.Docking
{ {
// Unlink splitter // Unlink splitter
var splitterParent = splitter.Parent; var splitterParent = splitter.Parent;
Assert.IsNotNull(splitterParent); if (splitterParent == null)
return;
splitter.Parent = null; splitter.Parent = null;
// Move controls from second split panel to the split panel parent // Move controls from second split panel to the split panel parent
@@ -507,17 +508,63 @@ namespace FlaxEditor.GUI.Docking
splitter.Dispose(); splitter.Dispose();
} }
} }
else if (IsMaster && _childPanels.Count != 0)
{
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
return;
}
else if (!IsMaster) else if (!IsMaster)
{ {
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
} }
else if (_childPanels.Count != 0)
{
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
return;
}
else if (!IsMaster) else if (!IsMaster)
{ {
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
} }
internal bool CollapseEmptyTabsProxy()
{
if (TabsCount == 0 && ChildPanelsCount > 0)
{
return TryCollapseSplitter(_tabsProxy?.Parent as Panel);
}
return false;
}
private bool TryCollapseSplitter(Panel removedPanelParent)
{
if (removedPanelParent == null)
return false;
if (!(removedPanelParent.Parent is SplitPanel tabsSplitter))
return false;
var splitterParent = tabsSplitter.Parent;
if (splitterParent == null)
return false;
tabsSplitter.Parent = null;
var scrPanel = removedPanelParent == tabsSplitter.Panel2 ? tabsSplitter.Panel1 : tabsSplitter.Panel2;
var srcPanelChildrenCount = scrPanel.ChildrenCount;
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
{
scrPanel.GetChild(i).Parent = splitterParent;
}
Assert.IsTrue(scrPanel.ChildrenCount == 0);
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
tabsSplitter.Dispose();
if (_tabsProxy != null && _tabsProxy.Parent == removedPanelParent)
_tabsProxy = null;
return true;
}
internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null) internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
{ {
DockWindow(state, window, autoSelect, splitterValue); DockWindow(state, window, autoSelect, splitterValue);
+8 -2
View File
@@ -1140,8 +1140,11 @@ namespace FlaxEditor.GUI.Tree
ParentTree.DraggedOverNode = this; ParentTree.DraggedOverNode = this;
// Expand node if mouse goes over arrow // Expand node if mouse goes over arrow
if (ArrowRect.Contains(location) && HasAnyVisibleChild) if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
{
Expand(true); Expand(true);
ParentTree?.FlushPendingPerformLayout();
}
result = OnDragEnterHeader(data); result = OnDragEnterHeader(data);
} }
@@ -1172,8 +1175,11 @@ namespace FlaxEditor.GUI.Tree
ParentTree.DraggedOverNode = this; ParentTree.DraggedOverNode = this;
// Expand node if mouse goes over arrow // Expand node if mouse goes over arrow
if (ArrowRect.Contains(location) && HasAnyVisibleChild) if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
{
Expand(true); Expand(true);
ParentTree?.FlushPendingPerformLayout();
}
if (!_isDragOverHeader) if (!_isDragOverHeader)
result = OnDragEnterHeader(data); result = OnDragEnterHeader(data);
+6
View File
@@ -13,6 +13,7 @@ namespace FlaxEditor.Gizmo
public abstract class GizmoBase public abstract class GizmoBase
{ {
private IGizmoOwner _owner; private IGizmoOwner _owner;
private bool _visible = true;
/// <summary> /// <summary>
/// Gets the gizmo owner. /// Gets the gizmo owner.
@@ -34,6 +35,11 @@ namespace FlaxEditor.Gizmo
/// </summary> /// </summary>
public virtual BoundingSphere FocusBounds => BoundingSphere.Empty; public virtual BoundingSphere FocusBounds => BoundingSphere.Empty;
/// <summary>
/// Gets or sets a value indicating whether this gizmo is visible.
/// </summary>
public bool Visible { get { return _visible; } set { _visible = value; } }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GizmoBase"/> class. /// Initializes a new instance of the <see cref="GizmoBase"/> class.
/// </summary> /// </summary>
@@ -36,11 +36,12 @@ public sealed class ViewportRubberBandSelector
/// Triggers the start of a rubber band selection. /// Triggers the start of a rubber band selection.
/// </summary> /// </summary>
/// <returns>True if selection started, otherwise false.</returns> /// <returns>True if selection started, otherwise false.</returns>
public bool TryStartingRubberBandSelection() public bool TryStartingRubberBandSelection(Float2 mousePosition)
{ {
if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{ {
_tryStartRubberBand = true; _tryStartRubberBand = true;
_cachedStartingMousePosition = mousePosition;
return true; return true;
} }
return false; return false;
@@ -82,12 +83,15 @@ public sealed class ViewportRubberBandSelector
return; return;
} }
if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart) if (_tryStartRubberBand && canStart)
{ {
_isRubberBandSpanning = true; var delta = mousePosition - _cachedStartingMousePosition;
_cachedStartingMousePosition = mousePosition; if (Mathf.Abs(delta.X) > 0.1f || Mathf.Abs(delta.Y) > 0.1f)
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); {
_tryStartRubberBand = false; _isRubberBandSpanning = true;
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
_tryStartRubberBand = false;
}
} }
else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{ {
+1 -1
View File
@@ -245,7 +245,7 @@ namespace FlaxEditor.Modules
if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren) if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren)
{ {
var bounds = actor.BoxWithChildren; var bounds = actor.BoxWithChildren;
Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
} }
} }
+5
View File
@@ -491,10 +491,15 @@ namespace FlaxEditor.Modules
Editor.LogWarning("Empty panel inside layout."); Editor.LogWarning("Empty panel inside layout.");
p.RemoveIt(); p.RemoveIt();
} }
else
{
p.CollapseEmptyTabsProxy();
}
} }
} }
panel.SelectTab(selectedTab); panel.SelectTab(selectedTab);
panel.CollapseEmptyTabsProxy();
} }
private static void SaveBounds(XmlWriter writer, Window win) private static void SaveBounds(XmlWriter writer, Window win)
+8
View File
@@ -387,6 +387,14 @@ namespace FlaxEditor.Options
[EditorDisplay("Viewport"), EditorOrder(1760)] [EditorDisplay("Viewport"), EditorOrder(1760)]
public InputBinding ToggleOrthographic = new InputBinding(KeyboardKeys.NumpadDecimal); public InputBinding ToggleOrthographic = new InputBinding(KeyboardKeys.NumpadDecimal);
[DefaultValue(typeof(InputBinding), "G")]
[EditorDisplay("Viewport"), EditorOrder(1770)]
public InputBinding ToggleGameView = new InputBinding(KeyboardKeys.G);
[DefaultValue(typeof(InputBinding), "P")]
[EditorDisplay("Viewport"), EditorOrder(1770)]
public InputBinding ToggleNavMeshVisibility = new InputBinding(KeyboardKeys.P);
#endregion #endregion
#region Debug Views #region Debug Views
@@ -555,7 +555,7 @@ namespace FlaxEditor.SceneGraph.Actors
var options = Editor.Instance.Options.Options.General; var options = Editor.Instance.Options.Options.General;
if (options.AutoRebuildNavMesh) if (options.AutoRebuildNavMesh)
{ {
Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(collider.Box, options.AutoRebuildNavMeshTimeoutMs);
} }
} }
} }
@@ -47,6 +47,11 @@ namespace FlaxEditor.SceneGraph.Actors
} }
} }
/// <summary>
/// Gets the model used by this actor.
/// </summary>
public Model Model => ((StaticModel)Actor).Model;
/// <inheritdoc /> /// <inheritdoc />
public StaticModelNode(Actor actor) public StaticModelNode(Actor actor)
: base(actor) : base(actor)
@@ -120,12 +125,12 @@ namespace FlaxEditor.SceneGraph.Actors
{ {
base.OnContextMenu(contextMenu, window); base.OnContextMenu(contextMenu, window);
// Check if every selected node is a primitive // Check if every selected node is a primitive or has collision asset
var selection = GetSelection(window); var selection = GetSelection(window);
bool autoOptionEnabled = true; bool autoOptionEnabled = true;
foreach (var node in selection) foreach (var node in selection)
{ {
if (node is StaticModelNode staticModelNode && !staticModelNode.IsPrimitive) if (node is StaticModelNode staticModelNode && (!staticModelNode.IsPrimitive && GetCollisionData(staticModelNode.Model) == null))
{ {
autoOptionEnabled = false; autoOptionEnabled = false;
break; break;
@@ -201,6 +206,54 @@ namespace FlaxEditor.SceneGraph.Actors
return Array.Empty<SceneGraphNode>(); return Array.Empty<SceneGraphNode>();
} }
private static bool TryCollisionData(Model model, BinaryAssetItem assetItem, out CollisionData collisionData)
{
collisionData = FlaxEngine.Content.Load<CollisionData>(assetItem.ID);
if (collisionData)
{
var options = collisionData.Options;
if (options.Model == model.ID || options.Model == Guid.Empty)
return true;
}
return false;
}
private CollisionData GetCollisionData(Model model)
{
if (model == null)
return null;
// Check if there already is collision data for that model to reuse
var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
if (modelItem?.ParentFolder != null)
{
foreach (var child in modelItem.ParentFolder.Children)
{
// Check if there is collision that was made with this model
if (child is BinaryAssetItem b && b.IsOfType<CollisionData>())
{
if (TryCollisionData(model, b, out var collisionData))
return collisionData;
}
// Check if there is an auto-imported collision
if (child is ContentFolder childFolder && childFolder.ShortName == modelItem.ShortName)
{
foreach (var childFolderChild in childFolder.Children)
{
if (childFolderChild is BinaryAssetItem c && c.IsOfType<CollisionData>())
{
if (TryCollisionData(model, c, out var collisionData))
return collisionData;
}
}
}
}
}
return null;
}
private void CreateAuto(StaticModel actor, Spawner spawner, bool singleNode) private void CreateAuto(StaticModel actor, Spawner spawner, bool singleNode)
{ {
// Special case for in-built Editor models that can use analytical collision // Special case for in-built Editor models that can use analytical collision
@@ -243,6 +296,15 @@ namespace FlaxEditor.SceneGraph.Actors
collider.LocalPosition = new Vector3(0, 50.0f, 0); collider.LocalPosition = new Vector3(0, 50.0f, 0);
collider.LocalOrientation = Quaternion.Euler(0, 0, 90.0f); collider.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
} }
else
{
var collider = new MeshCollider
{
Transform = actor.Transform,
CollisionData = GetCollisionData(model),
};
spawner(collider);
}
} }
private void CreateBox(StaticModel actor, Spawner spawner, bool singleNode) private void CreateBox(StaticModel actor, Spawner spawner, bool singleNode)
+2 -120
View File
@@ -304,25 +304,14 @@ namespace FlaxEditor.Surface.Archetypes
} }
} }
internal sealed class CustomCodeNode : SurfaceNode internal sealed class CustomCodeNode : ResizableSurfaceNode
{ {
private Rectangle _resizeButtonRect;
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
private bool _isResizing;
private CustomCodeTextBox _textBox; private CustomCodeTextBox _textBox;
private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
private Float2 SizeValue
{
get => (Float2)Values[SizeValueIndex];
set => SetValue(SizeValueIndex, value, false);
}
public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{ {
_sizeValueIndex = Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size; Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
if (nodeArch.TypeID == 8) if (nodeArch.TypeID == 8)
{ {
@@ -345,126 +334,19 @@ namespace FlaxEditor.Surface.Archetypes
_textBox.EditEnd += () => SetValue(0, _textBox.Text); _textBox.EditEnd += () => SetValue(0, _textBox.Text);
} }
public override bool CanSelect(ref Float2 location)
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
public override void OnSurfaceLoaded(SurfaceNodeActions action) public override void OnSurfaceLoaded(SurfaceNodeActions action)
{ {
base.OnSurfaceLoaded(action); base.OnSurfaceLoaded(action);
_textBox.Text = (string)Values[0]; _textBox.Text = (string)Values[0];
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
} }
public override void OnValuesChanged() public override void OnValuesChanged()
{ {
base.OnValuesChanged(); base.OnValuesChanged();
var size = SizeValue;
Resize(size.X, size.Y);
_textBox.Text = (string)Values[0]; _textBox.Text = (string)Values[0];
} }
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
public override void Draw()
{
base.Draw();
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
}
public override void OnLostFocus()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start sliding
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, new Float2(240, 160));
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
}
} }
internal enum MaterialTemplateInputsMapping internal enum MaterialTemplateInputsMapping
@@ -1100,6 +1100,27 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116) NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116)
} }
}, },
new NodeArchetype
{
TypeID = 5,
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch),
Title = "Set Parameter",
Description = "Parameter value setter invoked when the animation pose is evaluated (output pose comes from input)",
Flags = NodeFlags.AnimGraph,
Size = new Float2(140, 40),
DefaultValues = new object[]
{
Guid.Empty,
null
},
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 2),
NodeElementArchetype.Factory.Input(1, string.Empty, true, ScriptType.Null, 1, 1),
NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116)
}
},
}; };
} }
} }
+57 -18
View File
@@ -23,11 +23,14 @@ namespace FlaxEditor.Surface.Archetypes
TextureGroup = 4, TextureGroup = 4,
} }
internal class SampleTextureNode : SurfaceNode internal class TextureSamplerNode : SurfaceNode
{ {
private ComboBox _textureGroupPicker; private ComboBox _textureGroupPicker;
protected int _samplerTypeValueIndex = -1;
protected int _textureGroupValueIndex = -1;
protected int _level = 5;
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) protected TextureSamplerNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{ {
} }
@@ -48,13 +51,13 @@ namespace FlaxEditor.Surface.Archetypes
private void UpdateUI() private void UpdateUI()
{ {
if ((int)Values[0] == (int)CommonSamplerType.TextureGroup) if ((int)Values[_samplerTypeValueIndex] == (int)CommonSamplerType.TextureGroup)
{ {
if (_textureGroupPicker == null) if (_textureGroupPicker == null)
{ {
_textureGroupPicker = new ComboBox _textureGroupPicker = new ComboBox
{ {
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * 5), Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
Width = 100, Width = 100,
Parent = this, Parent = this,
}; };
@@ -71,7 +74,7 @@ namespace FlaxEditor.Surface.Archetypes
_textureGroupPicker.Visible = true; _textureGroupPicker.Visible = true;
} }
_textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged; _textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged;
_textureGroupPicker.SelectedIndex = (int)Values[2]; _textureGroupPicker.SelectedIndex = (int)Values[_textureGroupValueIndex];
_textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged; _textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged;
} }
else if (_textureGroupPicker != null) else if (_textureGroupPicker != null)
@@ -83,7 +86,39 @@ namespace FlaxEditor.Surface.Archetypes
private void OnSelectedTextureGroupChanged(ComboBox comboBox) private void OnSelectedTextureGroupChanged(ComboBox comboBox)
{ {
SetValue(2, _textureGroupPicker.SelectedIndex); SetValue(_textureGroupValueIndex, _textureGroupPicker.SelectedIndex);
}
}
internal class SampleTextureNode : TextureSamplerNode
{
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_samplerTypeValueIndex = 0;
_textureGroupValueIndex = 2;
}
}
internal class TriplanarSampleTextureNode : TextureSamplerNode
{
public TriplanarSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_samplerTypeValueIndex = 3;
_textureGroupValueIndex = 5;
_level = 5;
}
}
internal class ProceduralSampleTextureNode : TextureSamplerNode
{
public ProceduralSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_samplerTypeValueIndex = 0;
_textureGroupValueIndex = 2;
_level = 4;
} }
} }
@@ -280,9 +315,9 @@ namespace FlaxEditor.Surface.Archetypes
ConnectionsHints = ConnectionsHint.Vector, ConnectionsHints = ConnectionsHint.Vector,
DefaultValues = new object[] DefaultValues = new object[]
{ {
0, (int)CommonSamplerType.LinearClamp, // Sampler
-1.0f, -1.0f, // Level
0, 0, // Texture Group
}, },
Elements = new[] Elements = new[]
{ {
@@ -402,6 +437,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype new NodeArchetype
{ {
TypeID = 16, TypeID = 16,
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
Title = "Triplanar Texture", Title = "Triplanar Texture",
Description = "Projects a texture using world-space coordinates with triplanar mapping.", Description = "Projects a texture using world-space coordinates with triplanar mapping.",
Flags = NodeFlags.MaterialGraph, Flags = NodeFlags.MaterialGraph,
@@ -411,8 +447,9 @@ namespace FlaxEditor.Surface.Archetypes
Float3.One, // Scale Float3.One, // Scale
1.0f, // Blend 1.0f, // Blend
Float2.Zero, // Offset Float2.Zero, // Offset
2, // Sampler (int)CommonSamplerType.LinearWrap, // Sampler
false, // Local false, // Local
0, // Texture Group
}, },
Elements = new[] Elements = new[]
{ {
@@ -430,17 +467,17 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype new NodeArchetype
{ {
TypeID = 17, TypeID = 17,
Create = (id, context, arch, groupArch) => new SampleTextureNode(id, context, arch, groupArch), Create = (id, context, arch, groupArch) => new ProceduralSampleTextureNode(id, context, arch, groupArch),
Title = "Procedural Sample Texture", Title = "Procedural Sample Texture",
Description = "Samples a texture to create a more natural look with less obvious tiling.", Description = "Samples a texture to create a more natural look with less obvious tiling.",
Flags = NodeFlags.MaterialGraph, Flags = NodeFlags.MaterialGraph,
Size = new Float2(240, 110), Size = new Float2(240, 130),
ConnectionsHints = ConnectionsHint.Vector, ConnectionsHints = ConnectionsHint.Vector,
DefaultValues = new object[] DefaultValues = new object[]
{ {
2, (int)CommonSamplerType.LinearWrap, // Sampler
-1.0f, -1.0f, // Level
0, 0, // Texture Group
}, },
Elements = new[] Elements = new[]
{ {
@@ -448,8 +485,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(1, "UVs", true, null, 1), NodeElementArchetype.Factory.Input(1, "UVs", true, null, 1),
NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Float2), 3), NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Float2), 3),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 4), NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 4),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"), NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 3, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4, 100, 0, typeof(CommonSamplerType)) NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 3, 100, 0, typeof(CommonSamplerType))
} }
}, },
new NodeArchetype new NodeArchetype
@@ -469,6 +506,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 23, TypeID = 23,
Title = "Triplanar Normal Map", Title = "Triplanar Normal Map",
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.", Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.",
Flags = NodeFlags.MaterialGraph, Flags = NodeFlags.MaterialGraph,
Size = new Float2(280, 100), Size = new Float2(280, 100),
@@ -477,8 +515,9 @@ namespace FlaxEditor.Surface.Archetypes
Float3.One, // Scale Float3.One, // Scale
1.0f, // Blend 1.0f, // Blend
Float2.Zero, // Offset Float2.Zero, // Offset
2, // Sampler (int)CommonSamplerType.LinearWrap, // Sampler
false, // Local false, // Local
0, // Texture Group
}, },
Elements = new[] Elements = new[]
{ {
+30 -17
View File
@@ -453,7 +453,7 @@ namespace FlaxEditor.Surface.Archetypes
} }
} }
private class CurveNode<T> : SurfaceNode where T : struct private class CurveNode<T> : ResizableSurfaceNode where T : struct
{ {
private BezierCurveEditor<T> _curve; private BezierCurveEditor<T> _curve;
private bool _isSavingCurve; private bool _isSavingCurve;
@@ -467,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new CurveNode<T>(id, context, arch, groupArch), Create = (id, context, arch, groupArch) => new CurveNode<T>(id, context, arch, groupArch),
Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.", Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.",
Flags = NodeFlags.AllGraphs, Flags = NodeFlags.AllGraphs,
Size = new Float2(400, 180.0f), Size = new Float2(400, 180),
DefaultValues = new object[] DefaultValues = new object[]
{ {
// Keyframes count // Keyframes count
@@ -491,6 +491,8 @@ namespace FlaxEditor.Surface.Archetypes
0.0f, zero, zero, zero, 0.0f, zero, zero, zero,
0.0f, zero, zero, zero, 0.0f, zero, zero, zero,
0.0f, zero, zero, zero, 0.0f, zero, zero, zero,
new Float2(400, 180),
}, },
Elements = new[] Elements = new[]
{ {
@@ -504,30 +506,52 @@ namespace FlaxEditor.Surface.Archetypes
public CurveNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public CurveNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{ {
_sizeValueIndex = 29; // Index of the Size stored in Values array
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action) public override void OnLoaded(SurfaceNodeActions action)
{ {
base.OnLoaded(action); base.OnLoaded(action);
// Create curve editor
var upperLeft = GetBox(0).BottomLeft; var upperLeft = GetBox(0).BottomLeft;
var upperRight = GetBox(1).BottomRight; var upperRight = GetBox(1).BottomRight;
float curveMargin = 20.0f; float curveMargin = 20.0f;
_curve = new BezierCurveEditor<T> _curve = new BezierCurveEditor<T>
{ {
MaxKeyframes = 7, MaxKeyframes = 7,
Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f), Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f),
Parent = this Parent = this,
AnchorMax = Float2.One,
}; };
_curve.Edited += OnCurveEdited; _curve.Edited += OnCurveEdited;
_curve.UnlockChildrenRecursive(); _curve.UnlockChildrenRecursive();
_curve.PerformLayout(); _curve.PerformLayout();
// Sync keyframes
UpdateCurveKeyframes(); UpdateCurveKeyframes();
} }
/// <inheritdoc />
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
// Ensure the whole curve is shown
_curve.ShowWholeCurve();
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
if (!_isSavingCurve)
{
UpdateCurveKeyframes();
}
}
private void OnCurveEdited() private void OnCurveEdited()
{ {
if (_isSavingCurve) if (_isSavingCurve)
@@ -553,17 +577,6 @@ namespace FlaxEditor.Surface.Archetypes
_isSavingCurve = false; _isSavingCurve = false;
} }
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
if (!_isSavingCurve)
{
UpdateCurveKeyframes();
}
}
private void UpdateCurveKeyframes() private void UpdateCurveKeyframes()
{ {
var count = (int)Values[0]; var count = (int)Values[0];
@@ -1575,7 +1588,7 @@ namespace FlaxEditor.Surface.Archetypes
DefaultValues = new object[] DefaultValues = new object[]
{ {
Guid.Empty, Guid.Empty,
string.Empty string.Empty,
}, },
Elements = new[] Elements = new[]
{ {
@@ -0,0 +1,182 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Surface
{
/// <summary>
/// Visject Surface node control that cna be resized.
/// </summary>
/// <seealso cref="SurfaceNode" />
[HideInEditor]
public class ResizableSurfaceNode : SurfaceNode
{
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
/// <summary>
/// Indicates whether the node is currently being resized.
/// </summary>
protected bool _isResizing;
/// <summary>
/// Index of the Float2 value in the node values list to store node size.
/// </summary>
protected int _sizeValueIndex = -1;
/// <summary>
/// Minimum node size.
/// </summary>
protected Float2 _sizeMin = new Float2(240, 160);
/// <summary>
/// Node resizing rectangle bounds.
/// </summary>
protected Rectangle _resizeButtonRect;
private Float2 SizeValue
{
get => (Float2)Values[_sizeValueIndex];
set => SetValue(_sizeValueIndex, value, false);
}
/// <inheritdoc />
public ResizableSurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
/// <inheritdoc />
public override bool CanSelect(ref Float2 location)
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
/// <inheritdoc />
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
// Reapply the curve node size
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
base.OnSurfaceLoaded(action);
}
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
var size = SizeValue;
Resize(size.X, size.Y);
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
if (Surface.CanEdit)
{
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
}
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start resizing
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, _sizeMin);
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
}
}
}
+9 -112
View File
@@ -14,18 +14,11 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
/// <seealso cref="SurfaceNode" /> /// <seealso cref="SurfaceNode" />
[HideInEditor] [HideInEditor]
public class SurfaceComment : SurfaceNode public class SurfaceComment : ResizableSurfaceNode
{ {
private Rectangle _colorButtonRect; private Rectangle _colorButtonRect;
private Rectangle _resizeButtonRect;
private Float2 _startResizingSize;
private readonly TextBox _renameTextBox; private readonly TextBox _renameTextBox;
/// <summary>
/// True if sizing tool is in use.
/// </summary>
protected bool _isResizing;
/// <summary> /// <summary>
/// True if rename textbox is active in order to rename comment /// True if rename textbox is active in order to rename comment
/// </summary> /// </summary>
@@ -52,12 +45,6 @@ namespace FlaxEditor.Surface
set => SetValue(1, value, false); set => SetValue(1, value, false);
} }
private Float2 SizeValue
{
get => (Float2)Values[2];
set => SetValue(2, value, false);
}
private int OrderValue private int OrderValue
{ {
get => (int)Values[3]; get => (int)Values[3];
@@ -68,6 +55,8 @@ namespace FlaxEditor.Surface
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{ {
_sizeValueIndex = 2; // Index of the Size stored in Values array
_sizeMin = new Float2(140.0f, Constants.NodeHeaderSize);
_renameTextBox = new TextBox(false, 0, 0, Width) _renameTextBox = new TextBox(false, 0, 0, Width)
{ {
Height = Constants.NodeHeaderSize, Height = Constants.NodeHeaderSize,
@@ -86,10 +75,6 @@ namespace FlaxEditor.Surface
// Read node data // Read node data
Title = TitleValue; Title = TitleValue;
Color = ColorValue; Color = ColorValue;
var size = SizeValue;
if (Surface != null && 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
@@ -126,27 +111,6 @@ namespace FlaxEditor.Surface
// Read node data // Read node data
Title = TitleValue; Title = TitleValue;
Color = ColorValue; Color = ColorValue;
Size = SizeValue;
}
private void EndResizing()
{
// Clear state
_isResizing = false;
if (_startResizingSize != Size)
{
SizeValue = Size;
Surface.MarkAsEdited(false);
}
EndMouseCapture();
}
/// <inheritdoc />
public override bool CanSelect(ref Float2 location)
{
return _headerRect.MakeOffsetted(Location).Contains(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -158,6 +122,8 @@ namespace FlaxEditor.Surface
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateRectangles() protected override void UpdateRectangles()
{ {
base.UpdateRectangles();
const float headerSize = Constants.NodeHeaderSize; const float headerSize = Constants.NodeHeaderSize;
const float buttonMargin = Constants.NodeCloseButtonMargin; const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize; const float buttonSize = Constants.NodeCloseButtonSize;
@@ -222,16 +188,13 @@ namespace FlaxEditor.Surface
// Color button // Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Check if is resizing // Resize button
if (_isResizing) if (_isResizing)
{ {
// Draw overlay
Render2D.FillRectangle(_resizeButtonRect, style.Selection); Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder); Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
} }
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
// Resize button
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
} }
// Selection outline // Selection outline
@@ -247,88 +210,28 @@ namespace FlaxEditor.Surface
/// <inheritdoc /> /// <inheritdoc />
protected override Float2 CalculateNodeSize(float width, float height) protected override Float2 CalculateNodeSize(float width, float height)
{ {
return Size; // No margins or headers
return new Float2(width, height);
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnLostFocus() public override void OnLostFocus()
{ {
// Check if was resizing
if (_isResizing)
{
EndResizing();
}
// Check if was renaming
if (_isRenaming) if (_isRenaming)
{ {
Rename(_renameTextBox.Text); Rename(_renameTextBox.Text);
StopRenaming(); StopRenaming();
} }
// Base
base.OnLostFocus(); base.OnLostFocus();
} }
/// <inheritdoc />
public override void OnEndMouseCapture()
{
// Check if was resizing
if (_isResizing)
{
EndResizing();
}
else
{
base.OnEndMouseCapture();
}
}
/// <inheritdoc /> /// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise) public override bool ContainsPoint(ref Float2 location, bool precise)
{ {
return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location); return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location);
} }
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
// Check if can start resizing
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start sliding
_isResizing = true;
_startResizingSize = Size;
StartMouseCapture();
return true;
}
return false;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
// Check if is resizing
if (_isResizing)
{
// Update size
var size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom));
if (Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Size = size;
}
else
{
// Base
base.OnMouseMove(location);
}
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button) public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{ {
@@ -394,12 +297,6 @@ namespace FlaxEditor.Surface
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
if (base.OnMouseUp(location, button)) if (base.OnMouseUp(location, button))
return true; return true;
+16 -18
View File
@@ -724,7 +724,12 @@ namespace FlaxEditor.Surface
if (HasNodesSelection) if (HasNodesSelection)
{ {
var keyMoveRange = 50; var keyMoveDelta = 50;
bool altDown = RootWindow.GetKey(KeyboardKeys.Alt);
bool shiftDown = RootWindow.GetKey(KeyboardKeys.Shift);
if (altDown || shiftDown)
keyMoveDelta = shiftDown ? 10 : 25;
switch (key) switch (key)
{ {
case KeyboardKeys.Backspace: case KeyboardKeys.Backspace:
@@ -752,17 +757,18 @@ namespace FlaxEditor.Surface
Box selectedBox = GetSelectedBox(SelectedNodes); Box selectedBox = GetSelectedBox(SelectedNodes);
if (selectedBox != null) if (selectedBox != null)
{ {
Box toSelect = (key == KeyboardKeys.ArrowUp) ? selectedBox?.ParentNode.GetPreviousBox(selectedBox) : selectedBox?.ParentNode.GetNextBox(selectedBox); int delta = key == KeyboardKeys.ArrowDown ? 1 : -1;
if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput) List<Box> boxes = selectedBox.ParentNode.GetBoxes().FindAll(b => b.IsOutput == selectedBox.IsOutput);
{ int selectedIndex = boxes.IndexOf(selectedBox);
Select(toSelect.ParentNode); Box toSelect = boxes[Mathf.Wrap(selectedIndex + delta, 0, boxes.Count - 1)];
toSelect.ParentNode.SelectBox(toSelect);
} Select(toSelect.ParentNode);
toSelect.ParentNode.SelectBox(toSelect);
} }
else if (!IsMovingSelection && CanEdit) else if (!IsMovingSelection && CanEdit)
{ {
// Move selected nodes // Move selected nodes
var delta = new Float2(0, key == KeyboardKeys.ArrowUp ? -keyMoveRange : keyMoveRange); var delta = new Float2(0, key == KeyboardKeys.ArrowUp ? -keyMoveDelta : keyMoveDelta);
MoveSelectedNodes(delta); MoveSelectedNodes(delta);
} }
return true; return true;
@@ -775,12 +781,8 @@ namespace FlaxEditor.Surface
if (selectedBox != null) if (selectedBox != null)
{ {
Box toSelect = null; Box toSelect = null;
if ((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput)) if (((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput)) && selectedBox.HasAnyConnection)
{ {
if (_selectedConnectionIndex < 0 || _selectedConnectionIndex >= selectedBox.Connections.Count)
{
_selectedConnectionIndex = 0;
}
toSelect = selectedBox.Connections[_selectedConnectionIndex]; toSelect = selectedBox.Connections[_selectedConnectionIndex];
} }
else else
@@ -808,7 +810,7 @@ namespace FlaxEditor.Surface
else if (!IsMovingSelection && CanEdit) else if (!IsMovingSelection && CanEdit)
{ {
// Move selected nodes // Move selected nodes
var delta = new Float2(key == KeyboardKeys.ArrowLeft ? -keyMoveRange : keyMoveRange, 0); var delta = new Float2(key == KeyboardKeys.ArrowLeft ? -keyMoveDelta : keyMoveDelta, 0);
MoveSelectedNodes(delta); MoveSelectedNodes(delta);
} }
return true; return true;
@@ -824,13 +826,9 @@ namespace FlaxEditor.Surface
return true; return true;
if (Root.GetKey(KeyboardKeys.Shift)) if (Root.GetKey(KeyboardKeys.Shift))
{
_selectedConnectionIndex = ((_selectedConnectionIndex - 1) % connectionCount + connectionCount) % connectionCount; _selectedConnectionIndex = ((_selectedConnectionIndex - 1) % connectionCount + connectionCount) % connectionCount;
}
else else
{
_selectedConnectionIndex = (_selectedConnectionIndex + 1) % connectionCount; _selectedConnectionIndex = (_selectedConnectionIndex + 1) % connectionCount;
}
return true; return true;
} }
} }
+1 -1
View File
@@ -192,7 +192,7 @@ namespace FlaxEditor.Tools.Terrain
{ {
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation)) if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{ {
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
} }
} }
} }
@@ -209,7 +209,7 @@ namespace FlaxEditor.Tools.Terrain
{ {
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation)) if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{ {
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
} }
} }
} }
@@ -172,7 +172,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
if (_navmeshBoundsModifications != null) if (_navmeshBoundsModifications != null)
{ {
foreach (var bounds in _navmeshBoundsModifications) foreach (var bounds in _navmeshBoundsModifications)
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs); Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
} }
Editor.Instance.Scene.MarkSceneEdited(scene); Editor.Instance.Scene.MarkSceneEdited(scene);
@@ -217,11 +217,10 @@ namespace FlaxEditor.Tools.Terrain.Undo
} }
// Update navmesh // Update navmesh
var scene = Terrain.Scene;
if (_navmeshBoundsModifications != null) if (_navmeshBoundsModifications != null)
{ {
foreach (var bounds in _navmeshBoundsModifications) foreach (var bounds in _navmeshBoundsModifications)
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs); Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
} }
Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene); Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene);
@@ -303,7 +303,7 @@ namespace FlaxEditor.Actions
if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren) if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren)
{ {
var bounds = node.Actor.BoxWithChildren; var bounds = node.Actor.BoxWithChildren;
Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
} }
} }
} }
@@ -134,7 +134,8 @@ namespace FlaxEditor.Actions
var obj = Object.Find<SceneObject>(ref item.ID); var obj = Object.Find<SceneObject>(ref item.ID);
if (obj != null) if (obj != null)
{ {
scenes.Add(obj.Parent.Scene); if (obj.Parent != null)
scenes.Add(obj.Parent.Scene);
if (obj is Actor actor) if (obj is Actor actor)
actor.SetParent(newParent, _worldPositionsStays, true); actor.SetParent(newParent, _worldPositionsStays, true);
else else
@@ -121,12 +121,12 @@ namespace FlaxEditor
// Handle simple case where objects were moved just a little and use one navmesh build request to improve performance // Handle simple case where objects were moved just a little and use one navmesh build request to improve performance
if (data.BeforeBounds.Intersects(ref data.AfterBounds)) if (data.BeforeBounds.Intersects(ref data.AfterBounds))
{ {
Navigation.BuildNavMesh(data.Scene, BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
} }
else else
{ {
Navigation.BuildNavMesh(data.Scene, data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(data.Scene, data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
} }
} }
} }
+11 -7
View File
@@ -584,7 +584,7 @@ namespace FlaxEditor.Viewport
_cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth) _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
{ {
Tag = this, Tag = this,
TooltipText = "Camera Settings", TooltipText = "Camera Settings.",
Parent = _cameraWidget Parent = _cameraWidget
}; };
_cameraWidget.Parent = this; _cameraWidget.Parent = this;
@@ -593,7 +593,7 @@ namespace FlaxEditor.Viewport
_orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true) _orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true)
{ {
Checked = !_isOrtho, Checked = !_isOrtho,
TooltipText = "Toggle Orthographic/Perspective Mode", TooltipText = "Toggle Orthographic/Perspective Mode.",
Parent = _cameraWidget Parent = _cameraWidget
}; };
_orthographicModeButton.Toggled += OnOrthographicModeToggled; _orthographicModeButton.Toggled += OnOrthographicModeToggled;
@@ -832,7 +832,7 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu = new ContextMenu(); ViewWidgetButtonMenu = new ContextMenu();
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu) var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
{ {
TooltipText = "View properties", TooltipText = "View properties.",
Parent = viewMode Parent = viewMode
}; };
viewMode.Parent = this; viewMode.Parent = this;
@@ -863,8 +863,10 @@ namespace FlaxEditor.Viewport
{ {
} }
}); });
viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = _editor.Icons.Rotate32; viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = Editor.Instance.Icons.Rotate32;
viewLayers.AddButton("Disable layers", () => Task.ViewLayersMask = new LayersMask(0)); viewLayers.AddSeparator();
viewLayers.AddButton("Enable all", () => Task.ViewLayersMask = new LayersMask(-1)).Icon = Editor.Instance.Icons.CheckBoxTick12;
viewLayers.AddButton("Disable all", () => Task.ViewLayersMask = new LayersMask(0)).Icon = Editor.Instance.Icons.Cross12;
viewLayers.AddSeparator(); viewLayers.AddSeparator();
var layers = LayersAndTagsSettings.GetCurrentLayers(); var layers = LayersAndTagsSettings.GetCurrentLayers();
if (layers != null && layers.Length > 0) if (layers != null && layers.Length > 0)
@@ -904,8 +906,10 @@ namespace FlaxEditor.Viewport
{ {
} }
}); });
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = _editor.Icons.Rotate32; viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None); viewFlags.AddSeparator();
viewFlags.AddButton("Enable all", () => Task.ViewFlags = ViewFlags.All).Icon = Editor.Instance.Icons.CheckBoxTick12;
viewFlags.AddButton("Disable all", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Cross12;
viewFlags.AddSeparator(); viewFlags.AddSeparator();
for (int i = 0; i < ViewFlagsValues.Length; i++) for (int i = 0; i < ViewFlagsValues.Length; i++)
{ {
@@ -25,6 +25,7 @@ namespace FlaxEditor.Viewport
private readonly Editor _editor; private readonly Editor _editor;
private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton; private readonly ContextMenuButton _showNavigationButton;
private readonly ContextMenuButton _toggleGameViewButton;
private SelectionOutline _customSelectionOutline; private SelectionOutline _customSelectionOutline;
/// <summary> /// <summary>
@@ -108,6 +109,13 @@ namespace FlaxEditor.Viewport
private EditorSpritesRenderer _editorSpritesRenderer; private EditorSpritesRenderer _editorSpritesRenderer;
private ViewportRubberBandSelector _rubberBandSelector; private ViewportRubberBandSelector _rubberBandSelector;
private bool _gameViewActive;
private ViewFlags _preGameViewFlags;
private ViewMode _preGameViewViewMode;
private bool _gameViewWasGridShown;
private bool _gameViewWasFpsCounterShown;
private bool _gameViewWasNagivationShown;
/// <summary> /// <summary>
/// Drag and drop handlers /// Drag and drop handlers
/// </summary> /// </summary>
@@ -185,6 +193,7 @@ namespace FlaxEditor.Viewport
: base(Object.New<SceneRenderTask>(), editor.Undo, editor.Scene.Root) : base(Object.New<SceneRenderTask>(), editor.Undo, editor.Scene.Root)
{ {
_editor = editor; _editor = editor;
var inputOptions = _editor.Options.Options.Input;
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
// Prepare rendering task // Prepare rendering task
@@ -232,9 +241,14 @@ namespace FlaxEditor.Viewport
_showGridButton.CloseMenuOnClick = false; _showGridButton.CloseMenuOnClick = false;
// Show navigation widget // Show navigation widget
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", () => ShowNavigation = !ShowNavigation); _showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", inputOptions.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
_showNavigationButton.CloseMenuOnClick = false; _showNavigationButton.CloseMenuOnClick = false;
// Game View
ViewWidgetButtonMenu.AddSeparator();
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
_toggleGameViewButton.CloseMenuOnClick = false;
// Create camera widget // Create camera widget
ViewWidgetButtonMenu.AddSeparator(); ViewWidgetButtonMenu.AddSeparator();
ViewWidgetButtonMenu.AddButton("Create camera here", CreateCameraAtView); ViewWidgetButtonMenu.AddButton("Create camera here", CreateCameraAtView);
@@ -259,6 +273,10 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection);
InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.RotateSelection, RotateSelection);
InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete); InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete);
InputActions.Add(options => options.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
// Game View
InputActions.Add(options => options.ToggleGameView, ToggleGameView);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -373,9 +391,12 @@ namespace FlaxEditor.Viewport
public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth)
{ {
// Draw gizmos // Draw gizmos
for (int i = 0; i < Gizmos.Count; i++) foreach (var gizmo in Gizmos)
{ {
Gizmos[i].Draw(ref renderContext); if (gizmo.Visible)
{
gizmo.Draw(ref renderContext);
}
} }
// Draw selected objects debug shapes and visuals // Draw selected objects debug shapes and visuals
@@ -481,6 +502,36 @@ namespace FlaxEditor.Viewport
TransformGizmo.EndTransforming(); TransformGizmo.EndTransforming();
} }
/// <summary>
/// Toggles game view view mode on or off.
/// </summary>
public void ToggleGameView()
{
if (!_gameViewActive)
{
// Cache flags & values
_preGameViewFlags = Task.ViewFlags;
_preGameViewViewMode = Task.ViewMode;
_gameViewWasGridShown = Grid.Enabled;
_gameViewWasFpsCounterShown = ShowFpsCounter;
_gameViewWasNagivationShown = ShowNavigation;
}
// Set flags & values
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNagivationShown : false;
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
_gameViewActive = !_gameViewActive;
TransformGizmo.Visible = !_gameViewActive;
SelectionOutline.ShowSelectionOutline = !_gameViewActive;
_toggleGameViewButton.Icon = _gameViewActive ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnLostFocus() public override void OnLostFocus()
{ {
@@ -620,7 +671,7 @@ namespace FlaxEditor.Viewport
{ {
base.OnLeftMouseButtonDown(); base.OnLeftMouseButtonDown();
_rubberBandSelector.TryStartingRubberBandSelection(); _rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -720,9 +720,12 @@ namespace FlaxEditor.Viewport
public override void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) public override void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth)
{ {
// Draw gizmos // Draw gizmos
for (int i = 0; i < Gizmos.Count; i++) foreach (var gizmo in Gizmos)
{ {
Gizmos[i].Draw(ref renderContext); if (gizmo.Visible)
{
gizmo.Draw(ref renderContext);
}
} }
base.DrawEditorPrimitives(context, ref renderContext, target, targetDepth); base.DrawEditorPrimitives(context, ref renderContext, target, targetDepth);
@@ -99,7 +99,14 @@ namespace FlaxEditor.Windows.Assets
Window = window; Window = window;
var surfaceParam = window.Surface.GetParameter(BaseModelId); var surfaceParam = window.Surface.GetParameter(BaseModelId);
if (surfaceParam != null) if (surfaceParam != null)
BaseModel = FlaxEngine.Content.LoadAsync<SkinnedModel>((Guid)surfaceParam.Value); {
if (surfaceParam.Value is Guid asGuid)
BaseModel = FlaxEngine.Content.LoadAsync<SkinnedModel>(asGuid);
else if (surfaceParam.Value is SkinnedModel asModel)
BaseModel = asModel;
else
BaseModel = null;
}
else else
BaseModel = window.PreviewActor.GetParameterValue(BaseModelId) as SkinnedModel; BaseModel = window.PreviewActor.GetParameterValue(BaseModelId) as SkinnedModel;
} }
@@ -2,7 +2,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
@@ -25,7 +24,7 @@ namespace FlaxEditor.Windows.Assets
/// </summary> /// </summary>
/// <seealso cref="Animation" /> /// <seealso cref="Animation" />
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" /> /// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class AnimationWindow : AssetEditorWindowBase<Animation> public sealed class AnimationWindow : ClonedAssetEditorWindowBase<Animation>
{ {
private sealed class Preview : AnimationPreview private sealed class Preview : AnimationPreview
{ {
@@ -255,6 +254,7 @@ namespace FlaxEditor.Windows.Assets
private bool _isWaitingForTimelineLoad; private bool _isWaitingForTimelineLoad;
private SkinnedModel _initialPreviewModel, _initialBaseModel; private SkinnedModel _initialPreviewModel, _initialBaseModel;
private float _initialPanel2Splitter = 0.6f; private float _initialPanel2Splitter = 0.6f;
private bool _timelineIsDirty;
/// <summary> /// <summary>
/// Gets the animation timeline editor. /// Gets the animation timeline editor.
@@ -295,7 +295,7 @@ namespace FlaxEditor.Windows.Assets
Parent = _panel1.Panel1, Parent = _panel1.Panel1,
Enabled = false Enabled = false
}; };
_timeline.Modified += MarkAsEdited; _timeline.Modified += OnTimelineModified;
_timeline.SetNoTracksText("Loading..."); _timeline.SetNoTracksText("Loading...");
// Asset properties // Asset properties
@@ -321,11 +321,31 @@ namespace FlaxEditor.Windows.Assets
{ {
MarkAsEdited(); MarkAsEdited();
UpdateToolstrip(); UpdateToolstrip();
_propertiesPresenter.BuildLayout();
}
private void OnTimelineModified()
{
_timelineIsDirty = true;
MarkAsEdited();
}
private bool RefreshTempAsset()
{
if (_asset == null || _isWaitingForTimelineLoad)
return true;
if (_timeline.IsModified)
{
_timeline.Save(_asset);
}
_propertiesPresenter.BuildLayoutOnUpdate();
return false;
} }
private string GetPreviewModelCacheName() private string GetPreviewModelCacheName()
{ {
return _asset.ID + ".PreviewModel"; return _item.ID + ".PreviewModel";
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -361,7 +381,11 @@ namespace FlaxEditor.Windows.Assets
if (!IsEdited) if (!IsEdited)
return; return;
_timeline.Save(_asset); if (RefreshTempAsset())
return;
if (SaveToOriginal())
return;
ClearEditedFlag(); ClearEditedFlag();
_item.RefreshThumbnail(); _item.RefreshThumbnail();
} }
@@ -414,10 +438,18 @@ namespace FlaxEditor.Windows.Assets
{ {
base.Update(deltaTime); base.Update(deltaTime);
// Check if temporary asset need to be updated
if (_timelineIsDirty)
{
_timelineIsDirty = false;
RefreshTempAsset();
}
// Check if need to load timeline
if (_isWaitingForTimelineLoad && _asset.IsLoaded) if (_isWaitingForTimelineLoad && _asset.IsLoaded)
{ {
_isWaitingForTimelineLoad = false; _isWaitingForTimelineLoad = false;
_timeline._id = _asset.ID; _timeline._id = _item.ID;
_timeline.Load(_asset); _timeline.Load(_asset);
_undo.Clear(); _undo.Clear();
_timeline.Enabled = true; _timeline.Enabled = true;
@@ -70,6 +70,13 @@ namespace FlaxEditor.Windows.Assets
return; return;
var nodes = proxy.Asset.Nodes; var nodes = proxy.Asset.Nodes;
var bones = proxy.Asset.Bones; var bones = proxy.Asset.Bones;
var blendShapes = proxy.Asset.BlendShapes;
// Info
{
var group = layout.Group("Info");
group.Label($"Nodes: {nodes.Length}\nBones: {bones.Length}\nBlend Shapes: {blendShapes.Length}").AddCopyContextMenu().Label.Height *= 2.5f;
}
// Skeleton Bones // Skeleton Bones
{ {
@@ -109,7 +116,6 @@ namespace FlaxEditor.Windows.Assets
} }
// Blend Shapes // Blend Shapes
var blendShapes = proxy.Asset.BlendShapes;
if (blendShapes.Length != 0) if (blendShapes.Length != 0)
{ {
var group = layout.Group("Blend Shapes"); var group = layout.Group("Blend Shapes");
+3 -9
View File
@@ -429,6 +429,7 @@ namespace FlaxEditor.Windows
writer.WriteAttributeString("FarPlane", Viewport.FarPlane.ToString()); writer.WriteAttributeString("FarPlane", Viewport.FarPlane.ToString());
writer.WriteAttributeString("FieldOfView", Viewport.FieldOfView.ToString()); writer.WriteAttributeString("FieldOfView", Viewport.FieldOfView.ToString());
writer.WriteAttributeString("MovementSpeed", Viewport.MovementSpeed.ToString()); writer.WriteAttributeString("MovementSpeed", Viewport.MovementSpeed.ToString());
writer.WriteAttributeString("ViewportIconsScale", ViewportIconsRenderer.Scale.ToString());
writer.WriteAttributeString("OrthographicScale", Viewport.OrthographicScale.ToString()); writer.WriteAttributeString("OrthographicScale", Viewport.OrthographicScale.ToString());
writer.WriteAttributeString("UseOrthographicProjection", Viewport.UseOrthographicProjection.ToString()); writer.WriteAttributeString("UseOrthographicProjection", Viewport.UseOrthographicProjection.ToString());
writer.WriteAttributeString("ViewFlags", ((ulong)Viewport.Task.View.Flags).ToString()); writer.WriteAttributeString("ViewFlags", ((ulong)Viewport.Task.View.Flags).ToString());
@@ -439,31 +440,24 @@ namespace FlaxEditor.Windows
{ {
if (bool.TryParse(node.GetAttribute("GridEnabled"), out bool value1)) if (bool.TryParse(node.GetAttribute("GridEnabled"), out bool value1))
Viewport.Grid.Enabled = value1; Viewport.Grid.Enabled = value1;
if (bool.TryParse(node.GetAttribute("ShowFpsCounter"), out value1)) if (bool.TryParse(node.GetAttribute("ShowFpsCounter"), out value1))
Viewport.ShowFpsCounter = value1; Viewport.ShowFpsCounter = value1;
if (bool.TryParse(node.GetAttribute("ShowNavigation"), out value1)) if (bool.TryParse(node.GetAttribute("ShowNavigation"), out value1))
Viewport.ShowNavigation = value1; Viewport.ShowNavigation = value1;
if (float.TryParse(node.GetAttribute("NearPlane"), out float value2)) if (float.TryParse(node.GetAttribute("NearPlane"), out float value2))
Viewport.NearPlane = value2; Viewport.NearPlane = value2;
if (float.TryParse(node.GetAttribute("FarPlane"), out value2)) if (float.TryParse(node.GetAttribute("FarPlane"), out value2))
Viewport.FarPlane = value2; Viewport.FarPlane = value2;
if (float.TryParse(node.GetAttribute("FieldOfView"), out value2)) if (float.TryParse(node.GetAttribute("FieldOfView"), out value2))
Viewport.FieldOfView = value2; Viewport.FieldOfView = value2;
if (float.TryParse(node.GetAttribute("MovementSpeed"), out value2)) if (float.TryParse(node.GetAttribute("MovementSpeed"), out value2))
Viewport.MovementSpeed = value2; Viewport.MovementSpeed = value2;
if (float.TryParse(node.GetAttribute("ViewportIconsScale"), out value2))
ViewportIconsRenderer.Scale = value2;
if (float.TryParse(node.GetAttribute("OrthographicScale"), out value2)) if (float.TryParse(node.GetAttribute("OrthographicScale"), out value2))
Viewport.OrthographicScale = value2; Viewport.OrthographicScale = value2;
if (bool.TryParse(node.GetAttribute("UseOrthographicProjection"), out value1)) if (bool.TryParse(node.GetAttribute("UseOrthographicProjection"), out value1))
Viewport.UseOrthographicProjection = value1; Viewport.UseOrthographicProjection = value1;
if (ulong.TryParse(node.GetAttribute("ViewFlags"), out ulong value3)) if (ulong.TryParse(node.GetAttribute("ViewFlags"), out ulong value3))
Viewport.Task.ViewFlags = (ViewFlags)value3; Viewport.Task.ViewFlags = (ViewFlags)value3;
+3 -1
View File
@@ -336,11 +336,13 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
SkeletonData* animResultSkeleton = &skeleton; SkeletonData* animResultSkeleton = &skeleton;
// Retarget animation when using output pose from other skeleton // Retarget animation when using output pose from other skeleton
AnimGraphImpulse retargetNodes;
if (_graph.BaseModel != data.NodesSkeleton) if (_graph.BaseModel != data.NodesSkeleton)
{ {
ANIM_GRAPH_PROFILE_EVENT("Retarget"); ANIM_GRAPH_PROFILE_EVENT("Retarget");
auto& targetSkeleton = data.NodesSkeleton->Skeleton; auto& targetSkeleton = data.NodesSkeleton->Skeleton;
if (context.PoseCacheSize == context.PoseCache.Count())
context.PoseCache.AddOne();
auto& retargetNodes = context.PoseCache[context.PoseCacheSize++];
retargetNodes = *animResult; retargetNodes = *animResult;
retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count()); retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
Transform* targetNodes = retargetNodes.Nodes.Get(); Transform* targetNodes = retargetNodes.Nodes.Get();
@@ -109,86 +109,84 @@ namespace
nodes->RootMotion.Orientation.Normalize(); nodes->RootMotion.Orientation.Normalize();
} }
} }
Matrix ComputeWorldMatrixRecursive(const SkeletonData& skeleton, int32 index, Matrix localMatrix)
{
const auto& node = skeleton.Nodes[index];
index = node.ParentIndex;
while (index != -1)
{
const auto& parent = skeleton.Nodes[index];
localMatrix *= parent.LocalTransform.GetWorld();
index = parent.ParentIndex;
}
return localMatrix;
}
Matrix ComputeInverseParentMatrixRecursive(const SkeletonData& skeleton, int32 index)
{
Matrix inverseParentMatrix = Matrix::Identity;
const auto& node = skeleton.Nodes[index];
if (node.ParentIndex != -1)
{
inverseParentMatrix = ComputeWorldMatrixRecursive(skeleton, index, inverseParentMatrix);
inverseParentMatrix = Matrix::Invert(inverseParentMatrix);
}
return inverseParentMatrix;
}
} }
void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 targetIndex) // Utility for retargeting animation poses between skeletons.
struct Retargeting
{ {
// sourceSkeleton - skeleton of Anim Graph (Base Locomotion pack) private:
// targetSkeleton - visual mesh skeleton (City Characters pack) const Matrix* _sourcePosePtr, * _targetPosePtr;
// target - anim graph input/output transformation of that node const SkeletonData* _sourceSkeleton, *_targetSkeleton;
const auto& targetNode = targetSkeleton.Nodes[targetIndex]; const SkinnedModel::SkeletonMapping* _sourceMapping;
const int32 sourceIndex = sourceMapping.NodesMapping[targetIndex];
if (sourceIndex == -1) public:
void Init(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping)
{ {
// Use T-pose ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == sourceMapping.NodesMapping.Length());
node = targetNode.LocalTransform;
return; // Cache world-space poses for source and target skeletons to avoid redundant calculations during retargeting
_sourcePosePtr = sourceSkeleton.GetNodesPose().Get();
_targetPosePtr = targetSkeleton.GetNodesPose().Get();
_sourceSkeleton = &sourceSkeleton;
_targetSkeleton = &targetSkeleton;
_sourceMapping = &sourceMapping;
} }
const auto& sourceNode = sourceSkeleton.Nodes[sourceIndex];
// [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/] void RetargetNode(const Transform& source, Transform& target, int32 sourceIndex, int32 targetIndex)
// Calculate T-Pose of source node, target node and target parent node
Matrix bindMatrix = ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, sourceNode.LocalTransform.GetWorld());
Matrix inverseBindMatrix = Matrix::Invert(bindMatrix);
Matrix targetMatrix = ComputeWorldMatrixRecursive(targetSkeleton, targetIndex, targetNode.LocalTransform.GetWorld());
Matrix inverseParentMatrix = ComputeInverseParentMatrixRecursive(targetSkeleton, targetIndex);
// Target node animation is world-space difference of the animated source node inside the target's parent node world-space
Matrix localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, node.GetWorld());
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
// Extract local node transformation
localMatrix.Decompose(node);
}
void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes)
{
// TODO: cache source and target skeletons world-space poses for faster retargeting (use some pooled memory)
ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == mapping.NodesMapping.Length());
for (int32 targetIndex = 0; targetIndex < targetSkeleton.Nodes.Count(); targetIndex++)
{ {
auto& targetNode = targetSkeleton.Nodes.Get()[targetIndex]; // sourceSkeleton - skeleton of Anim Graph
const int32 sourceIndex = mapping.NodesMapping.Get()[targetIndex]; // targetSkeleton - visual mesh skeleton
Transform node; // target - anim graph input/output transformation of that node
const SkeletonNode& targetNode = _targetSkeleton->Nodes.Get()[targetIndex];
if (sourceIndex == -1) if (sourceIndex == -1)
{ {
// Use T-pose // Use T-pose
node = targetNode.LocalTransform; target = targetNode.LocalTransform;
} }
else else
{ {
// Retarget // [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/]
node = sourceNodes[sourceIndex];
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, targetIndex); // Calculate T-Pose of source node, target node and target parent node
const Matrix* sourcePosePtr = _sourcePosePtr;
const Matrix* targetPosePtr = _targetPosePtr;
const Matrix& bindMatrix = sourcePosePtr[sourceIndex];
const Matrix& targetMatrix = targetPosePtr[targetIndex];
Matrix inverseParentMatrix;
if (targetNode.ParentIndex != -1)
Matrix::Invert(targetPosePtr[targetNode.ParentIndex], inverseParentMatrix);
else
inverseParentMatrix = Matrix::Identity;
// Target node animation is world-space difference of the animated source node inside the target's parent node world-space
const SkeletonNode& sourceNode = _sourceSkeleton->Nodes.Get()[sourceIndex];
Matrix localMatrix = source.GetWorld();
if (sourceNode.ParentIndex != -1)
localMatrix = localMatrix * sourcePosePtr[sourceNode.ParentIndex];
localMatrix = Matrix::Invert(bindMatrix) * localMatrix;
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
// Extract local node transformation
localMatrix.Decompose(target);
} }
targetNodes[targetIndex] = node;
} }
FORCE_INLINE void RetargetPose(const Transform* sourceNodes, Transform* targetNodes)
{
for (int32 targetIndex = 0; targetIndex < _targetSkeleton->Nodes.Count(); targetIndex++)
{
const int32 sourceIndex = _sourceMapping->NodesMapping.Get()[targetIndex];
RetargetNode(sourceNodes[sourceIndex], targetNodes[targetIndex], sourceIndex, targetIndex);
}
}
};
void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes)
{
Retargeting retargeting;
retargeting.Init(sourceSkeleton, targetSkeleton, mapping);
retargeting.RetargetPose(sourceNodes, targetNodes);
} }
AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node) AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node)
@@ -431,9 +429,13 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
const bool weighted = weight < 1.0f; const bool weighted = weight < 1.0f;
const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton; const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton;
const auto emptyNodes = GetEmptyNodes(); const auto emptyNodes = GetEmptyNodes();
Retargeting retargeting;
SkinnedModel::SkeletonMapping sourceMapping; SkinnedModel::SkeletonMapping sourceMapping;
if (retarget) if (retarget)
{
sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton); sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
retargeting.Init(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, mapping);
}
for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++) for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++)
{ {
const int32 nodeToChannel = mapping.NodesMapping[nodeIndex]; const int32 nodeToChannel = mapping.NodesMapping[nodeIndex];
@@ -447,7 +449,8 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
// Optionally retarget animation into the skeleton used by the Anim Graph // Optionally retarget animation into the skeleton used by the Anim Graph
if (retarget) if (retarget)
{ {
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, nodeIndex); const int32 sourceIndex = sourceMapping.NodesMapping[nodeIndex];
retargeting.RetargetNode(srcNode, srcNode, sourceIndex, nodeIndex);
} }
// Mark node as used // Mark node as used
@@ -958,6 +961,21 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
} }
break; break;
} }
// Set Parameter
case 5:
{
// Set parameter value
int32 paramIndex;
const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex);
if (param)
{
context.Data->Parameters[paramIndex].Value = tryGetValue(node->GetBox(1), 1, Value::Null);
}
// Pass over the pose
value = tryGetValue(node->GetBox(2), Value::Null);
break;
}
default: default:
break; break;
} }
+1 -3
View File
@@ -132,9 +132,7 @@ int32 AudioClip::GetFirstBufferIndex(float time, float& offset) const
if (_buffersStartTimes[i + 1] > time) if (_buffersStartTimes[i + 1] > time)
{ {
offset = time - _buffersStartTimes[i]; offset = time - _buffersStartTimes[i];
#if BUILD_DEBUG ASSERT_LOW_LAYER(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
ASSERT(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
#endif
return i; return i;
} }
} }
+1
View File
@@ -18,6 +18,7 @@ class AudioSource;
API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource
{ {
DECLARE_BINARY_ASSET_HEADER(AudioClip, 2); DECLARE_BINARY_ASSET_HEADER(AudioClip, 2);
friend class AudioBackendOAL;
public: public:
/// <summary> /// <summary>
+6 -4
View File
@@ -37,15 +37,17 @@ void AudioListener::OnEnable()
{ {
_prevPos = GetPosition(); _prevPos = GetPosition();
_velocity = Vector3::Zero; _velocity = Vector3::Zero;
ASSERT(!Audio::Listeners.Contains(this));
if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS) if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS)
{ {
LOG(Error, "Unsupported amount of the audio listeners!"); if IF_CONSTEXPR (AUDIO_MAX_LISTENERS == 1)
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
else
LOG(Warning, "Too many Audio Listener active.");
} }
else else
{ {
ASSERT(!Audio::Listeners.Contains(this));
if (Audio::Listeners.Count() > 0)
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
Audio::Listeners.Add(this); Audio::Listeners.Add(this);
AudioBackend::Listener::Reset(); AudioBackend::Listener::Reset();
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation()); AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
+69 -9
View File
@@ -14,6 +14,9 @@
#include "Engine/Audio/AudioListener.h" #include "Engine/Audio/AudioListener.h"
#include "Engine/Audio/AudioSource.h" #include "Engine/Audio/AudioSource.h"
#include "Engine/Audio/AudioSettings.h" #include "Engine/Audio/AudioSettings.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Level.h"
#include "Engine/Video/VideoPlayer.h"
// Include OpenAL library // Include OpenAL library
// Source: https://github.com/kcat/openal-soft // Source: https://github.com/kcat/openal-soft
@@ -73,6 +76,7 @@ namespace ALC
ALCdevice* Device = nullptr; ALCdevice* Device = nullptr;
ALCcontext* Context = nullptr; ALCcontext* Context = nullptr;
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None; AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
bool Inited = false;
CriticalSection Locker; CriticalSection Locker;
Dictionary<uint32, SourceData> SourcesData; Dictionary<uint32, SourceData> SourcesData;
@@ -164,12 +168,9 @@ namespace ALC
float Time; float Time;
}; };
void RebuildContext(const Array<AudioSourceState>& states) void RebuildContext()
{ {
LOG(Info, "Rebuilding audio contexts");
ClearContext(); ClearContext();
if (Device == nullptr) if (Device == nullptr)
return; return;
@@ -182,10 +183,16 @@ namespace ALC
Context = alcCreateContext(Device, attrList); Context = alcCreateContext(Device, attrList);
alcMakeContextCurrent(Context); alcMakeContextCurrent(Context);
}
void RebuildListeners()
{
for (AudioListener* listener : Audio::Listeners) for (AudioListener* listener : Audio::Listeners)
Listener::Rebuild(listener); Listener::Rebuild(listener);
}
void RebuildSources(const Array<AudioSourceState>& states)
{
for (int32 i = 0; i < states.Count(); i++) for (int32 i = 0; i < states.Count(); i++)
{ {
AudioSource* source = Audio::Sources[i]; AudioSource* source = Audio::Sources[i];
@@ -205,6 +212,13 @@ namespace ALC
} }
} }
void RebuildContext(const Array<AudioSourceState>& states)
{
RebuildContext();
RebuildListeners();
RebuildSources(states);
}
void RebuildContext(bool isChangingDevice) void RebuildContext(bool isChangingDevice)
{ {
Array<AudioSourceState> states; Array<AudioSourceState> states;
@@ -400,7 +414,7 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{ {
ALC::Locker.Lock(); ALC::Locker.Lock();
const bool pan = ALC::SourcesData[sourceID].Spatial; const float pan = ALC::SourcesData[sourceID].Pan;
ALC::Locker.Unlock(); ALC::Locker.Unlock();
if (spatial) if (spatial)
{ {
@@ -629,6 +643,7 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
void AudioBackendOAL::Base_OnActiveDeviceChanged() void AudioBackendOAL::Base_OnActiveDeviceChanged()
{ {
PROFILE_CPU();
PROFILE_MEM(Audio); PROFILE_MEM(Audio);
// Cleanup // Cleanup
@@ -659,9 +674,53 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name)); LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name));
return; return;
} }
if (ALC::Inited)
LOG(Info, "Changed audio device to: {}", String(Audio::GetActiveDevice()->Name));
// Setup // Rebuild context
ALC::RebuildContext(states); ALC::RebuildContext();
if (ALC::Inited)
{
// Reload all audio clips to recreate their buffers
for (AudioClip* audioClip : Content::GetAssets<AudioClip>())
{
audioClip->WaitForLoaded();
ScopeLock lock(audioClip->Locker);
// Clear old buffer IDs
for (uint32& bufferID : audioClip->Buffers)
bufferID = 0;
if (audioClip->IsStreamable())
{
// Let the streaming recreate missing buffers
audioClip->RequestStreamingUpdate();
}
else
{
// Reload audio clip
auto assetLock = audioClip->Storage->Lock();
audioClip->LoadChunk(0);
audioClip->Buffers[0] = AudioBackend::Buffer::Create();
audioClip->WriteBuffer(0);
}
}
// Reload all videos to recreate their buffers
for (VideoPlayer* videoPlayer : Level::GetActors<VideoPlayer>(true))
{
VideoBackendPlayer& player = videoPlayer->_player;
// Clear audio state
for (uint32& bufferID : player.AudioBuffers)
bufferID = 0;
player.NextAudioBuffer = 0;
player.AudioSource = 0;
}
}
ALC::RebuildListeners();
ALC::RebuildSources(states);
} }
void AudioBackendOAL::Base_SetDopplerFactor(float value) void AudioBackendOAL::Base_SetDopplerFactor(float value)
@@ -782,6 +841,7 @@ bool AudioBackendOAL::Base_Init()
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize")) if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel); ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
#endif #endif
ALC::Inited = true;
// Log service info // Log service info
LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION))); LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION)));
@@ -672,7 +672,7 @@ bool AudioBackendXAudio2::Base_Init()
HRESULT hr = XAudio2Create(&XAudio2::Instance, 0, XAUDIO2_DEFAULT_PROCESSOR); HRESULT hr = XAudio2Create(&XAudio2::Instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr)) if (FAILED(hr))
{ {
LOG(Error, "Failed to initalize XAudio2. Error: 0x{0:x}", hr); LOG(Error, "Failed to initialize XAudio2. Error: 0x{0:x}", hr);
return true; return true;
} }
XAudio2::Instance->RegisterForCallbacks(&XAudio2::Callback); XAudio2::Instance->RegisterForCallbacks(&XAudio2::Callback);
@@ -681,7 +681,7 @@ bool AudioBackendXAudio2::Base_Init()
hr = XAudio2::Instance->CreateMasteringVoice(&XAudio2::MasteringVoice); hr = XAudio2::Instance->CreateMasteringVoice(&XAudio2::MasteringVoice);
if (FAILED(hr)) if (FAILED(hr))
{ {
LOG(Error, "Failed to initalize XAudio2 mastering voice. Error: 0x{0:x}", hr); LOG(Error, "Failed to initialize XAudio2 mastering voice. Error: 0x{0:x}", hr);
return true; return true;
} }
XAUDIO2_VOICE_DETAILS details; XAUDIO2_VOICE_DETAILS details;
+2
View File
@@ -487,6 +487,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask); const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask == nullptr) if (loadingTask == nullptr)
{ {
if (IsLoaded())
return false;
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString()); LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
return true; return true;
} }
+20 -9
View File
@@ -61,16 +61,24 @@ Array<String> SkinnedModel::GetBlendShapes()
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget) SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget)
{ {
// Fast-path to use cached mapping
SkeletonMapping mapping; SkeletonMapping mapping;
mapping.TargetSkeleton = this; mapping.TargetSkeleton = this;
SkeletonMappingData mappingData;
if (_skeletonMappingCache.TryGet(source, mappingData))
{
mapping.SourceSkeleton = mappingData.SourceSkeleton;
mapping.NodesMapping = mappingData.NodesMapping;
return mapping;
}
mapping.SourceSkeleton = nullptr;
if (WaitForLoaded() || !source || source->WaitForLoaded()) if (WaitForLoaded() || !source || source->WaitForLoaded())
return mapping; return mapping;
PROFILE_CPU();
ScopeLock lock(Locker); ScopeLock lock(Locker);
SkeletonMappingData mappingData;
if (!_skeletonMappingCache.TryGet(source, mappingData)) if (!_skeletonMappingCache.TryGet(source, mappingData))
{ {
PROFILE_CPU();
// Initialize the mapping // Initialize the mapping
SkeletonRetarget* retarget = nullptr; SkeletonRetarget* retarget = nullptr;
const Guid sourceId = source->GetID(); const Guid sourceId = source->GetID();
@@ -370,6 +378,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
model->Skeleton.Bones[i].LocalTransform = node.LocalTransform; model->Skeleton.Bones[i].LocalTransform = node.LocalTransform;
model->Skeleton.Bones[i].NodeIndex = i; model->Skeleton.Bones[i].NodeIndex = i;
} }
model->Skeleton.Dirty();
ClearSkeletonMapping(); ClearSkeletonMapping();
// Calculate offset matrix (inverse bind pose transform) for every bone manually // Calculate offset matrix (inverse bind pose transform) for every bone manually
@@ -427,6 +436,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
// Setup // Setup
model->Skeleton.Nodes = nodes; model->Skeleton.Nodes = nodes;
model->Skeleton.Bones = bones; model->Skeleton.Bones = bones;
model->Skeleton.Dirty();
ClearSkeletonMapping(); ClearSkeletonMapping();
// Calculate offset matrix (inverse bind pose transform) for every bone manually // Calculate offset matrix (inverse bind pose transform) for every bone manually
@@ -823,13 +833,13 @@ bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int
void SkinnedModel::ClearSkeletonMapping() void SkinnedModel::ClearSkeletonMapping()
{ {
for (auto& e : _skeletonMappingCache) for (const auto& e : _skeletonMappingCache)
{ {
e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this); e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#if USE_EDITOR #if USE_EDITOR
e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this); e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#endif #endif
Allocator::Free(e.Value.NodesMapping.Get()); Allocator::Free((void*)e.Value.NodesMapping.Get());
} }
_skeletonMappingCache.Clear(); _skeletonMappingCache.Clear();
} }
@@ -837,8 +847,9 @@ void SkinnedModel::ClearSkeletonMapping()
void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj) void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
{ {
ScopeLock lock(Locker); ScopeLock lock(Locker);
auto i = _skeletonMappingCache.Find(obj); SkeletonMappingData mappingData;
ASSERT(i != _skeletonMappingCache.End()); bool found = _skeletonMappingCache.TryGet(obj, mappingData);
ASSERT(found);
// Unlink event // Unlink event
obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this); obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
@@ -847,8 +858,8 @@ void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
#endif #endif
// Clear cache // Clear cache
Allocator::Free(i->Value.NodesMapping.Get()); Allocator::Free(mappingData.NodesMapping.Get());
_skeletonMappingCache.Remove(i); _skeletonMappingCache.Remove(obj);
} }
uint64 SkinnedModel::GetMemoryUsage() const uint64 SkinnedModel::GetMemoryUsage() const
+4 -4
View File
@@ -3,7 +3,7 @@
#pragma once #pragma once
#include "ModelBase.h" #include "ModelBase.h"
#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Threading/ConcurrentDictionary.h"
#include "Engine/Graphics/Models/SkinnedMesh.h" #include "Engine/Graphics/Models/SkinnedMesh.h"
#include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Graphics/Models/SkeletonData.h"
@@ -101,9 +101,9 @@ public:
struct FLAXENGINE_API SkeletonMapping struct FLAXENGINE_API SkeletonMapping
{ {
// Target skeleton. // Target skeleton.
AssetReference<SkinnedModel> TargetSkeleton; SkinnedModel* TargetSkeleton;
// Source skeleton. // Source skeleton.
AssetReference<SkinnedModel> SourceSkeleton; SkinnedModel* SourceSkeleton;
// The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node. // The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
Span<int32> NodesMapping; Span<int32> NodesMapping;
}; };
@@ -115,7 +115,7 @@ private:
Span<int32> NodesMapping; Span<int32> NodesMapping;
}; };
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache; ConcurrentDictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
public: public:
/// <summary> /// <summary>
@@ -1700,6 +1700,8 @@ void VisualScript::CacheScriptingType()
VisualScriptingBinaryModule::VisualScriptingBinaryModule() VisualScriptingBinaryModule::VisualScriptingBinaryModule()
: _name("Visual Scripting") : _name("Visual Scripting")
{ {
// Visual Scripts can be unloaded and loaded again even in game
CanReload = true;
} }
ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const ScriptingObjectSpawnParams& params) ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const ScriptingObjectSpawnParams& params)
+13
View File
@@ -684,6 +684,19 @@ Array<Asset*> Content::GetAssets()
return assets; return assets;
} }
Array<Asset*> Content::GetAssets(const MClass* type)
{
Array<Asset*> assets;
AssetsLocker.Lock();
for (auto& e : Assets)
{
if (e.Value->Is(type))
assets.Add(e.Value);
}
AssetsLocker.Unlock();
return assets;
}
const Dictionary<Guid, Asset*>& Content::GetAssetsRaw() const Dictionary<Guid, Asset*>& Content::GetAssetsRaw()
{ {
AssetsLocker.Lock(); AssetsLocker.Lock();
+23 -1
View File
@@ -3,6 +3,9 @@
#pragma once #pragma once
#include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/ScriptingType.h"
#ifndef _MSC_VER
#include "Engine/Core/Collections/Array.h"
#endif
#include "AssetInfo.h" #include "AssetInfo.h"
#include "Asset.h" #include "Asset.h"
#include "Config.h" #include "Config.h"
@@ -122,7 +125,26 @@ public:
/// Gets the assets (loaded or during load). /// Gets the assets (loaded or during load).
/// </summary> /// </summary>
/// <returns>The collection of assets.</returns> /// <returns>The collection of assets.</returns>
static Array<Asset*, HeapAllocation> GetAssets(); API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets();
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
/// <param name="type">Type of the assets to search for. Includes any assets derived from the type.</param>
/// <returns>Found actors list.</returns>
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type);
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Found actors list.</returns>
template<typename T>
static Array<T*, HeapAllocation> GetAssets()
{
Array<Asset*, HeapAllocation> assets = GetAssets(T::GetStaticClass());
return *(Array<T*, HeapAllocation>*) & assets;
}
/// <summary> /// <summary>
/// Gets the raw dictionary of assets (loaded or during load). /// Gets the raw dictionary of assets (loaded or during load).
@@ -478,16 +478,23 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
} }
// Check if restore local changes on asset reimport // Check if restore local changes on asset reimport
constexpr bool RestoreModelOptionsOnReimport = true;
constexpr bool RestoreAnimEventsOnReimport = true; constexpr bool RestoreAnimEventsOnReimport = true;
const bool restoreModelOptions = RestoreModelOptionsOnReimport && (options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel);
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems(); const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems(); const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath)) if ((restoreModelOptions || restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
{ {
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath); AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset && !asset->WaitForLoaded()) if (asset && !asset->WaitForLoaded())
{ {
auto* model = ScriptingObject::Cast<ModelBase>(asset); auto* model = ScriptingObject::Cast<ModelBase>(asset);
auto* animation = ScriptingObject::Cast<Animation>(asset); auto* animation = ScriptingObject::Cast<Animation>(asset);
if (restoreModelOptions && model)
{
// Copy general properties
data->MinScreenSize = model->MinScreenSize;
}
if (restoreMaterials && model) if (restoreMaterials && model)
{ {
// Copy material settings // Copy material settings
@@ -4,6 +4,9 @@
#include "HashSetBase.h" #include "HashSetBase.h"
template<typename KeyType, typename ValueType, typename AllocationType>
class ConcurrentDictionary;
/// <summary> /// <summary>
/// Describes single portion of space for the key and value pair in a hash map. /// Describes single portion of space for the key and value pair in a hash map.
/// </summary> /// </summary>
@@ -13,6 +16,7 @@ struct DictionaryBucket
friend Memory; friend Memory;
friend HashSetBase<AllocationType, DictionaryBucket>; friend HashSetBase<AllocationType, DictionaryBucket>;
friend Dictionary<KeyType, ValueType, AllocationType>; friend Dictionary<KeyType, ValueType, AllocationType>;
friend ConcurrentDictionary<KeyType, ValueType, AllocationType>;
/// <summary>The key.</summary> /// <summary>The key.</summary>
KeyType Key; KeyType Key;
+1 -1
View File
@@ -57,5 +57,5 @@
#define API_PARAM(...) #define API_PARAM(...)
#define API_TYPEDEF(...) #define API_TYPEDEF(...)
#define API_INJECT_CODE(...) #define API_INJECT_CODE(...)
#define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; #define API_AUTO_SERIALIZATION(...) public: bool ShouldSerialize(const void* otherObj) const override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer; #define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer;
+7
View File
@@ -45,6 +45,13 @@ public:
/// </summary> /// </summary>
virtual ~ISerializable() = default; virtual ~ISerializable() = default;
/// <summary>
/// Compares with other instance to decide whether serialize this instance (eg. any field orp property is modified). Used to skip object serialization if not needed.
/// </summary>
/// <param name="otherObj">The instance of the object (always valid) to compare with to decide whether serialize this instance.</param>
/// <returns>True if any field or property is modified compared to the other object instance, otherwise false.</returns>
virtual bool ShouldSerialize(const void* otherObj) const { return true; }
/// <summary> /// <summary>
/// Serializes object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. /// Serializes object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties.
/// </summary> /// </summary>
+8 -1
View File
@@ -11,7 +11,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
#endif #endif
partial struct Color partial struct Color : Json.ICustomValueEquals
{ {
/// <summary> /// <summary>
/// The size of the <see cref="Color" /> type, in bytes. /// The size of the <see cref="Color" /> type, in bytes.
@@ -196,6 +196,13 @@ namespace FlaxEngine
A = values[3]; A = values[3];
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Color)other;
return Equals(ref o);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object value) public override bool Equals(object value)
{ {
+8 -1
View File
@@ -65,7 +65,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
#endif #endif
partial struct Double2 : IEquatable<Double2>, IFormattable partial struct Double2 : IEquatable<Double2>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
@@ -1574,6 +1574,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Double2)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Double2" /> is equal to this instance. /// Determines whether the specified <see cref="Double2" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -66,7 +66,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
#endif #endif
partial struct Double3 : IEquatable<Double3>, IFormattable partial struct Double3 : IEquatable<Double3>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
@@ -1872,6 +1872,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Double3)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Double3" /> is equal to this instance. /// Determines whether the specified <see cref="Double3" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -66,7 +66,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
#endif #endif
partial struct Double4 : IEquatable<Double4>, IFormattable partial struct Double4 : IEquatable<Double4>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -1372,6 +1372,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Double4)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Double4" /> is equal to this instance. /// Determines whether the specified <see cref="Double4" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
#endif #endif
partial struct Float2 : IEquatable<Float2>, IFormattable partial struct Float2 : IEquatable<Float2>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
@@ -1650,6 +1650,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Float2)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Float2" /> is equal to this instance. /// Determines whether the specified <see cref="Float2" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
#endif #endif
partial struct Float3 : IEquatable<Float3>, IFormattable partial struct Float3 : IEquatable<Float3>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
@@ -1904,6 +1904,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Float3)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Float3" /> is equal to this instance. /// Determines whether the specified <see cref="Float3" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
#endif #endif
partial struct Float4 : IEquatable<Float4>, IFormattable partial struct Float4 : IEquatable<Float4>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -1412,6 +1412,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Float4)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Float4" /> is equal to this instance. /// Determines whether the specified <see cref="Float4" /> is equal to this instance.
/// </summary> /// </summary>
+14
View File
@@ -5,6 +5,7 @@
#include "Math.h" #include "Math.h"
#include "Vector2.h" #include "Vector2.h"
#include "Vector3.h" #include "Vector3.h"
#include "Vector4.h"
/// <summary> /// <summary>
/// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa /// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa
@@ -248,6 +249,19 @@ public:
explicit Half4(const Color& c); explicit Half4(const Color& c);
explicit Half4(const Rectangle& rect); explicit Half4(const Rectangle& rect);
operator Float2() const
{
return ToFloat2();
}
operator Float3() const
{
return ToFloat3();
}
operator Float4() const
{
return ToFloat4();
}
public: public:
Float2 ToFloat2() const; Float2 ToFloat2() const;
Float3 ToFloat3() const; Float3 ToFloat3() const;
+8 -1
View File
@@ -14,7 +14,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
#endif #endif
partial struct Int2 : IEquatable<Int2>, IFormattable partial struct Int2 : IEquatable<Int2>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0} Y:{1}"; private static readonly string _formatString = "X:{0} Y:{1}";
@@ -940,6 +940,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Int2)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Int2" /> is equal to this instance. /// Determines whether the specified <see cref="Int2" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -14,7 +14,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
#endif #endif
partial struct Int3 : IEquatable<Int3>, IFormattable partial struct Int3 : IEquatable<Int3>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}"; private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";
@@ -1023,6 +1023,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Int3)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Int3" /> is equal to this instance. /// Determines whether the specified <see cref="Int3" /> is equal to this instance.
/// </summary> /// </summary>
+8 -1
View File
@@ -14,7 +14,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
#endif #endif
partial struct Int4 : IEquatable<Int4>, IFormattable partial struct Int4 : IEquatable<Int4>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}"; private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";
@@ -881,6 +881,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Int4)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Int4" /> is equal to this instance. /// Determines whether the specified <see cref="Int4" /> is equal to this instance.
/// </summary> /// </summary>
-10
View File
@@ -41,16 +41,6 @@ FloatR10G10B10A2::FloatR10G10B10A2(const float* values)
{ {
} }
FloatR10G10B10A2::operator Float3() const
{
return ToFloat3();
}
FloatR10G10B10A2::operator Float4() const
{
return ToFloat4();
}
Float3 FloatR10G10B10A2::ToFloat3() const Float3 FloatR10G10B10A2::ToFloat3() const
{ {
Float3 vectorOut; Float3 vectorOut;
+8 -3
View File
@@ -40,9 +40,14 @@ struct FLAXENGINE_API FloatR10G10B10A2
{ {
return Value; return Value;
} }
operator Float3() const
operator Float3() const; {
operator Float4() const; return ToFloat3();
}
operator Float4() const
{
return ToFloat4();
}
FloatR10G10B10A2& operator=(const FloatR10G10B10A2& other) FloatR10G10B10A2& operator=(const FloatR10G10B10A2& other)
{ {
+8 -1
View File
@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
#endif #endif
partial struct Quaternion : IEquatable<Quaternion>, IFormattable partial struct Quaternion : IEquatable<Quaternion>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -1681,6 +1681,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Quaternion)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Tests whether one quaternion is near another quaternion. /// Tests whether one quaternion is near another quaternion.
/// </summary> /// </summary>
+8 -1
View File
@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
namespace FlaxEngine namespace FlaxEngine
{ {
partial struct Rectangle : IEquatable<Rectangle> partial struct Rectangle : IEquatable<Rectangle>, Json.ICustomValueEquals
{ {
/// <summary> /// <summary>
/// A <see cref="Rectangle"/> which represents an empty space. /// A <see cref="Rectangle"/> which represents an empty space.
@@ -523,6 +523,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Rectangle)other;
return Equals(ref o);
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
+8 -1
View File
@@ -16,7 +16,7 @@ using System.Runtime.InteropServices;
namespace FlaxEngine namespace FlaxEngine
{ {
[Serializable] [Serializable]
partial struct Transform : IEquatable<Transform>, IFormattable partial struct Transform : IEquatable<Transform>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "Translation:{0} Orientation:{1} Scale:{2}"; private static readonly string _formatString = "Translation:{0} Orientation:{1} Scale:{2}";
@@ -673,6 +673,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Transform)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Tests whether one transform is near another transform. /// Tests whether one transform is near another transform.
/// </summary> /// </summary>
+35 -1
View File
@@ -73,7 +73,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))]
#endif #endif
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
@@ -954,6 +954,33 @@ namespace FlaxEngine
return result; return result;
} }
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
/// <param name="result">>When the method completes, contains the linear interpolation of the two vectors.</param>
public static void Slerp(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result)
{
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
var theta = Mathr.Acos(dot) * amount;
Vector2 relativeVector = (end - start * dot).Normalized;
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
public static Vector2 Slerp(Vector2 start, Vector2 end, float amount)
{
Slerp(ref start, ref end, amount, out Vector2 result);
return result;
}
/// <summary> /// <summary>
/// Performs a gradual change of a vector towards a specified target over time /// Performs a gradual change of a vector towards a specified target over time
/// </summary> /// </summary>
@@ -1774,6 +1801,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Vector2)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Vector2" /> is equal to this instance. /// Determines whether the specified <see cref="Vector2" /> is equal to this instance.
/// </summary> /// </summary>
+18
View File
@@ -558,6 +558,24 @@ public:
return result; return result;
} }
// Performs a spherical linear interpolation between two vectors.
static void Slerp(const Vector2Base& start, const Vector2Base& end, T amount, Vector2Base& result)
{
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
T theta = Math::Acos(dot) * amount;
Vector2Base relativeVector = end - start * dot;
relativeVector.Normalize();
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
}
// Performs a spherical linear interpolation between two vectors.
static Vector2Base Slerp(const Vector2Base& start, const Vector2Base& end, T amount)
{
Vector2Base result;
Slerp(start, end, amount, result);
return result;
}
public: public:
/// <summary> /// <summary>
/// Calculates the area of the triangle. /// Calculates the area of the triangle.
+35 -1
View File
@@ -73,7 +73,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))]
#endif #endif
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
@@ -1043,6 +1043,33 @@ namespace FlaxEngine
return result; return result;
} }
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
/// <param name="result">When the method completes, contains the linear interpolation of the two vectors.</param>
public static void Slerp(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result)
{
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
var theta = Mathr.Acos(dot) * amount;
Vector3 relativeVector = (end - start * dot).Normalized;
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
public static Vector3 Slerp(Vector3 start, Vector3 end, float amount)
{
Slerp(ref start, ref end, amount, out var result);
return result;
}
/// <summary> /// <summary>
/// Performs a gradual change of a vector towards a specified target over time /// Performs a gradual change of a vector towards a specified target over time
/// </summary> /// </summary>
@@ -2133,6 +2160,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Vector3)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Vector3" /> is equal to this instance. /// Determines whether the specified <see cref="Vector3" /> is equal to this instance.
/// </summary> /// </summary>
+18
View File
@@ -686,6 +686,24 @@ public:
return result; return result;
} }
// Performs a spherical linear interpolation between two vectors.
static void Slerp(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result)
{
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
T theta = Math::Acos(dot) * amount;
Vector3Base relativeVector = end - start * dot;
relativeVector.Normalize();
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
}
// Performs a spherical linear interpolation between two vectors.
static Vector3Base Slerp(const Vector3Base& start, const Vector3Base& end, T amount)
{
Vector3Base result;
Slerp(start, end, amount, result);
return result;
}
// Performs a cubic interpolation between two vectors. // Performs a cubic interpolation between two vectors.
static void SmoothStep(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result) static void SmoothStep(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result)
{ {
+48 -1
View File
@@ -72,7 +72,7 @@ namespace FlaxEngine
#if FLAX_EDITOR #if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))] [System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))]
#endif #endif
public partial struct Vector4 : IEquatable<Vector4>, IFormattable public partial struct Vector4 : IEquatable<Vector4>, IFormattable, Json.ICustomValueEquals
{ {
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}"; private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -260,6 +260,19 @@ namespace FlaxEngine
/// </summary> /// </summary>
public bool IsNormalized => Mathr.Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f; public bool IsNormalized => Mathr.Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f;
/// <summary>
/// Gets the normalized vector. Returned vector has length equal 1.
/// </summary>
public Vector4 Normalized
{
get
{
Vector4 vector4 = this;
vector4.Normalize();
return vector4;
}
}
/// <summary> /// <summary>
/// Gets a value indicting whether this vector is zero /// Gets a value indicting whether this vector is zero
/// </summary> /// </summary>
@@ -878,6 +891,33 @@ namespace FlaxEngine
return result; return result;
} }
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
/// <param name="result">When the method completes, contains the linear interpolation of the two vectors.</param>
public static void Slerp(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result)
{
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
var theta = Mathr.Acos(dot) * amount;
Vector4 relativeVector = (end - start * dot).Normalized;
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
public static Vector4 Slerp(Vector4 start, Vector4 end, Real amount)
{
Slerp(ref start, ref end, amount, out var result);
return result;
}
/// <summary> /// <summary>
/// Performs a cubic interpolation between two vectors. /// Performs a cubic interpolation between two vectors.
/// </summary> /// </summary>
@@ -1486,6 +1526,13 @@ namespace FlaxEngine
} }
} }
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Vector4)other;
return Equals(ref o);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance. /// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary> /// </summary>
+98
View File
@@ -129,6 +129,12 @@ public:
FLAXENGINE_API String ToString() const; FLAXENGINE_API String ToString() const;
public: public:
// Gets a value indicting whether this instance is normalized.
bool IsNormalized() const
{
return Math::Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f;
}
// Gets a value indicting whether this vector is zero. // Gets a value indicting whether this vector is zero.
bool IsZero() const bool IsZero() const
{ {
@@ -219,6 +225,45 @@ public:
return Vector4Base(-X, -Y, -Z, -W); return Vector4Base(-X, -Y, -Z, -W);
} }
/// <summary>
/// Calculates a normalized vector that has length equal to 1.
/// </summary>
Vector4Base GetNormalized() const
{
Vector4Base result(X, Y, Z, W);
result.Normalize();
return result;
}
public:
/// <summary>
/// Performs vector normalization (scales vector up to unit length).
/// </summary>
void Normalize()
{
const T length = Math::Sqrt(X * X + Y * Y + Z * Z + W * W);
if (length >= ZeroTolerance)
{
const T inv = (T)1.0f / length;
X *= inv;
Y *= inv;
Z *= inv;
W *= inv;
}
}
/// <summary>
/// Performs fast vector normalization (scales vector up to unit length).
/// </summary>
void NormalizeFast()
{
const T inv = 1.0f / Math::Sqrt(X * X + Y * Y + Z * Z + W * W);
X *= inv;
Y *= inv;
Z *= inv;
W *= inv;
}
public: public:
Vector4Base operator+(const Vector4Base& b) const Vector4Base operator+(const Vector4Base& b) const
{ {
@@ -469,6 +514,41 @@ public:
result = Vector4Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z), Math::Clamp(v.W, min.W, max.W)); result = Vector4Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z), Math::Clamp(v.W, min.W, max.W));
} }
// Performs vector normalization (scales vector up to unit length).
static Vector4Base Normalize(const Vector4Base& v)
{
Vector4Base r = v;
const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y + r.Z * r.Z + r.W * r.W);
if (length >= ZeroTolerance)
{
const T inv = (T)1.0f / length;
r.X *= inv;
r.Y *= inv;
r.Z *= inv;
r.W *= inv;
}
return r;
}
// Performs vector normalization (scales vector up to unit length). This is a faster version that does not perform check for length equal 0 (it assumes that input vector is not empty).
static Vector4Base NormalizeFast(const Vector4Base& v)
{
const T inv = 1.0f / v.Length();
return Vector4Base(v.X * inv, v.Y * inv, v.Z * inv, v.W * inv);
}
// Performs vector normalization (scales vector up to unit length).
static FORCE_INLINE void Normalize(const Vector4Base& input, Vector4Base& result)
{
result = Normalize(input);
}
// Calculates the dot product of two vectors.
FORCE_INLINE static T Dot(const Vector4Base& a, const Vector4Base& b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
}
// Performs a linear interpolation between two vectors. // Performs a linear interpolation between two vectors.
static void Lerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result) static void Lerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result)
{ {
@@ -486,6 +566,24 @@ public:
return result; return result;
} }
// Performs a spherical linear interpolation between two vectors.
static void Slerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result)
{
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
T theta = Math::Acos(dot) * amount;
Vector4Base relativeVector = end - start * dot;
relativeVector.Normalize();
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
}
// Performs a spherical linear interpolation between two vectors.
static Vector4Base Slerp(const Vector4Base& start, const Vector4Base& end, T amount)
{
Vector4Base result;
Slerp(start, end, amount, result);
return result;
}
FLAXENGINE_API static Vector4Base Transform(const Vector4Base& v, const Matrix& m); FLAXENGINE_API static Vector4Base Transform(const Vector4Base& v, const Matrix& m);
}; };
+120 -45
View File
@@ -18,8 +18,10 @@
#include "Engine/Core/Math/Ray.h" #include "Engine/Core/Math/Ray.h"
#include "Engine/Core/Math/Rectangle.h" #include "Engine/Core/Math/Rectangle.h"
#include "Engine/Core/Math/Transform.h" #include "Engine/Core/Math/Transform.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Scripting/ManagedCLR/MUtils.h"
@@ -88,6 +90,7 @@ static_assert((int32)VariantType::Types::MAX == ARRAY_COUNT(InBuiltTypesTypeName
VariantType::VariantType(Types type, const StringView& typeName) VariantType::VariantType(Types type, const StringView& typeName)
{ {
Type = type; Type = type;
StaticName = 0;
TypeName = nullptr; TypeName = nullptr;
const int32 length = typeName.Length(); const int32 length = typeName.Length();
if (length) if (length)
@@ -98,32 +101,41 @@ VariantType::VariantType(Types type, const StringView& typeName)
} }
} }
VariantType::VariantType(Types type, const StringAnsiView& typeName) VariantType::VariantType(Types type, const StringAnsiView& typeName, bool staticName)
{ {
Type = type; Type = type;
TypeName = nullptr; StaticName = staticName && (typeName.HasChars() && typeName[typeName.Length()] == 0); // Require string to be null-terminated (not fully safe check)
int32 length = typeName.Length(); if (staticName)
if (length)
{ {
TypeName = static_cast<char*>(Allocator::Allocate(length + 1)); TypeName = (char*)typeName.Get();
Platform::MemoryCopy(TypeName, typeName.Get(), length);
TypeName[length] = 0;
} }
else
{
TypeName = nullptr;
int32 length = typeName.Length();
if (length)
{
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, typeName.Get(), length);
TypeName[length] = 0;
}
}
}
VariantType::VariantType(Types type, const ScriptingType& sType)
: VariantType(type)
{
SetTypeName(sType);
} }
VariantType::VariantType(Types type, const MClass* klass) VariantType::VariantType(Types type, const MClass* klass)
{ {
Type = type; Type = type;
StaticName = false;
TypeName = nullptr; TypeName = nullptr;
#if USE_CSHARP #if USE_CSHARP
if (klass) if (klass)
{ SetTypeName(*klass);
const StringAnsiView typeName = klass->GetFullName();
const int32 length = typeName.Length();
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, typeName.Get(), length);
TypeName[length] = 0;
}
#endif #endif
} }
@@ -190,9 +202,9 @@ VariantType::VariantType(const StringAnsiView& typeName)
if (const auto mclass = Scripting::FindClass(typeName)) if (const auto mclass = Scripting::FindClass(typeName))
{ {
if (mclass->IsEnum()) if (mclass->IsEnum())
new(this) VariantType(Enum, typeName); new(this) VariantType(Enum, mclass);
else else
new(this) VariantType(ManagedObject, typeName); new(this) VariantType(ManagedObject, mclass);
return; return;
} }
#endif #endif
@@ -204,36 +216,48 @@ VariantType::VariantType(const StringAnsiView& typeName)
VariantType::VariantType(const VariantType& other) VariantType::VariantType(const VariantType& other)
{ {
Type = other.Type; Type = other.Type;
TypeName = nullptr; StaticName = other.StaticName;
const int32 length = StringUtils::Length(other.TypeName); if (StaticName)
if (length)
{ {
TypeName = static_cast<char*>(Allocator::Allocate(length + 1)); TypeName = other.TypeName;
Platform::MemoryCopy(TypeName, other.TypeName, length); }
TypeName[length] = 0; else
{
TypeName = nullptr;
const int32 length = StringUtils::Length(other.TypeName);
if (length)
{
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, other.TypeName, length);
TypeName[length] = 0;
}
} }
} }
VariantType::VariantType(VariantType&& other) noexcept VariantType::VariantType(VariantType&& other) noexcept
{ {
Type = other.Type; Type = other.Type;
StaticName = other.StaticName;
TypeName = other.TypeName; TypeName = other.TypeName;
other.Type = Null; other.Type = Null;
other.TypeName = nullptr; other.TypeName = nullptr;
other.StaticName = 0;
} }
VariantType& VariantType::operator=(const Types& type) VariantType& VariantType::operator=(const Types& type)
{ {
Type = type; Type = type;
Allocator::Free(TypeName); if (StaticName)
Allocator::Free(TypeName);
TypeName = nullptr; TypeName = nullptr;
StaticName = 0;
return *this; return *this;
} }
VariantType& VariantType::operator=(VariantType&& other) VariantType& VariantType::operator=(VariantType&& other)
{ {
ASSERT(this != &other); ASSERT(this != &other);
Swap(Type, other.Type); Swap(Packed, other.Packed);
Swap(TypeName, other.TypeName); Swap(TypeName, other.TypeName);
return *this; return *this;
} }
@@ -242,14 +266,23 @@ VariantType& VariantType::operator=(const VariantType& other)
{ {
ASSERT(this != &other); ASSERT(this != &other);
Type = other.Type; Type = other.Type;
Allocator::Free(TypeName); if (StaticName)
TypeName = nullptr; Allocator::Free(TypeName);
const int32 length = StringUtils::Length(other.TypeName); StaticName = other.StaticName;
if (length) if (StaticName)
{ {
TypeName = static_cast<char*>(Allocator::Allocate(length + 1)); TypeName = other.TypeName;
Platform::MemoryCopy(TypeName, other.TypeName, length); }
TypeName[length] = 0; else
{
TypeName = nullptr;
const int32 length = StringUtils::Length(other.TypeName);
if (length)
{
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, other.TypeName, length);
TypeName[length] = 0;
}
} }
return *this; return *this;
} }
@@ -283,24 +316,45 @@ void VariantType::SetTypeName(const StringView& typeName)
{ {
if (StringUtils::Length(TypeName) != typeName.Length()) if (StringUtils::Length(TypeName) != typeName.Length())
{ {
Allocator::Free(TypeName); if (StaticName)
Allocator::Free(TypeName);
StaticName = 0;
TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1)); TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1));
TypeName[typeName.Length()] = 0; TypeName[typeName.Length()] = 0;
} }
StringUtils::ConvertUTF162ANSI(typeName.Get(), TypeName, typeName.Length()); StringUtils::ConvertUTF162ANSI(typeName.Get(), TypeName, typeName.Length());
} }
void VariantType::SetTypeName(const StringAnsiView& typeName) void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName)
{ {
if (StringUtils::Length(TypeName) != typeName.Length()) if (StringUtils::Length(TypeName) != typeName.Length() || StaticName != staticName)
{ {
Allocator::Free(TypeName); if (StaticName)
Allocator::Free(TypeName);
StaticName = staticName;
if (staticName)
{
TypeName = (char*)typeName.Get();
return;
}
TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1)); TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1));
TypeName[typeName.Length()] = 0; TypeName[typeName.Length()] = 0;
} }
Platform::MemoryCopy(TypeName, typeName.Get(), typeName.Length()); Platform::MemoryCopy(TypeName, typeName.Get(), typeName.Length());
} }
void VariantType::SetTypeName(const ScriptingType& type)
{
SetTypeName(type.Fullname, type.Module->CanReload);
}
void VariantType::SetTypeName(const MClass& klass)
{
#if USE_CSHARP
SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload());
#endif
}
const char* VariantType::GetTypeName() const const char* VariantType::GetTypeName() const
{ {
if (TypeName) if (TypeName)
@@ -322,6 +376,29 @@ VariantType VariantType::GetElementType() const
return VariantType(); return VariantType();
} }
void VariantType::Inline()
{
// Check if the typename comes from static assembly which can be used to inline name instead of dynamic memory allocation
StringAnsiView typeName(TypeName);
auto& modules = BinaryModule::GetModules();
for (auto module : modules)
{
int32 typeIndex;
if (!module->CanReload && module->FindScriptingType(typeName, typeIndex))
{
ScriptingTypeHandle typeHandle(module, typeIndex);
SetTypeName(typeHandle.GetType().Fullname, true);
return;
}
}
#if USE_CSHARP
// Try with C#-only types
if (const auto mclass = Scripting::FindClass(TypeName))
SetTypeName(*mclass);
#endif
}
::String VariantType::ToString() const ::String VariantType::ToString() const
{ {
::String result; ::String result;
@@ -632,8 +709,7 @@ Variant::Variant(ScriptingObject* v)
AsObject = v; AsObject = v;
if (v) if (v)
{ {
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types) Type.SetTypeName(v->GetType());
Type.SetTypeName(v->GetType().Fullname);
v->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this); v->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
} }
} }
@@ -644,9 +720,8 @@ Variant::Variant(Asset* v)
AsAsset = v; AsAsset = v;
if (v) if (v)
{ {
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
Type.SetTypeName(v->GetType().Fullname);
v->AddReference(); v->AddReference();
Type.SetTypeName(v->GetType());
v->OnUnloaded.Bind<Variant, &Variant::OnAssetUnloaded>(this); v->OnUnloaded.Bind<Variant, &Variant::OnAssetUnloaded>(this);
} }
} }
@@ -3007,16 +3082,16 @@ Variant Variant::NewValue(const StringAnsiView& typeName)
switch (type.Type) switch (type.Type)
{ {
case ScriptingTypes::Script: case ScriptingTypes::Script:
v.SetType(VariantType(VariantType::Object, typeName)); v.SetType(VariantType(VariantType::Object, type));
v.AsObject = type.Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), typeHandle)); v.AsObject = type.Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), typeHandle));
if (v.AsObject) if (v.AsObject)
v.AsObject->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(&v); v.AsObject->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(&v);
break; break;
case ScriptingTypes::Structure: case ScriptingTypes::Structure:
v.SetType(VariantType(VariantType::Structure, typeName)); v.SetType(VariantType(VariantType::Structure, type));
break; break;
case ScriptingTypes::Enum: case ScriptingTypes::Enum:
v.SetType(VariantType(VariantType::Enum, typeName)); v.SetType(VariantType(VariantType::Enum, type));
v.AsEnum = 0; v.AsEnum = 0;
break; break;
default: default:
@@ -3030,16 +3105,16 @@ Variant Variant::NewValue(const StringAnsiView& typeName)
// Fallback to C#-only types // Fallback to C#-only types
if (mclass->IsEnum()) if (mclass->IsEnum())
{ {
v.SetType(VariantType(VariantType::Enum, typeName)); v.SetType(VariantType(VariantType::Enum, mclass));
v.AsEnum = 0; v.AsEnum = 0;
} }
else if (mclass->IsValueType()) else if (mclass->IsValueType())
{ {
v.SetType(VariantType(VariantType::Structure, typeName)); v.SetType(VariantType(VariantType::Structure, mclass));
} }
else else
{ {
v.SetType(VariantType(VariantType::ManagedObject, typeName)); v.SetType(VariantType(VariantType::ManagedObject, mclass));
MObject* instance = mclass->CreateInstance(); MObject* instance = mclass->CreateInstance();
if (instance) if (instance)
{ {
+28 -8
View File
@@ -17,7 +17,7 @@ struct ScriptingTypeHandle;
/// </summary> /// </summary>
API_STRUCT(InBuild) struct FLAXENGINE_API VariantType API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
{ {
enum Types enum Types : uint8
{ {
Null = 0, Null = 0,
Void, Void,
@@ -80,10 +80,22 @@ API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
}; };
public: public:
/// <summary> union
/// The type of the variant. {
/// </summary> struct
Types Type; {
/// <summary>
/// The type of the variant.
/// </summary>
Types Type;
/// <summary>
/// Internal flag used to indicate that pointer to TypeName has been linked from a static/external memory that is stable (eg. ScriptingType or MClass). Allows avoiding dynamic memory allocation.
/// </summary>
uint8 StaticName : 1;
};
uint16 Packed;
};
/// <summary> /// <summary>
/// The optional additional full name of the scripting type. Used for Asset, Object, Enum, Structure types to describe type precisely. /// The optional additional full name of the scripting type. Used for Asset, Object, Enum, Structure types to describe type precisely.
@@ -94,17 +106,20 @@ public:
FORCE_INLINE VariantType() FORCE_INLINE VariantType()
{ {
Type = Null; Type = Null;
StaticName = 0;
TypeName = nullptr; TypeName = nullptr;
} }
FORCE_INLINE explicit VariantType(Types type) FORCE_INLINE explicit VariantType(Types type)
{ {
Type = type; Type = type;
StaticName = 0;
TypeName = nullptr; TypeName = nullptr;
} }
explicit VariantType(Types type, const StringView& typeName); explicit VariantType(Types type, const StringView& typeName);
explicit VariantType(Types type, const StringAnsiView& typeName); explicit VariantType(Types type, const StringAnsiView& typeName, bool staticName = false);
explicit VariantType(Types type, const ScriptingType& sType);
explicit VariantType(Types type, const MClass* klass); explicit VariantType(Types type, const MClass* klass);
explicit VariantType(const StringAnsiView& typeName); explicit VariantType(const StringAnsiView& typeName);
VariantType(const VariantType& other); VariantType(const VariantType& other);
@@ -112,7 +127,8 @@ public:
FORCE_INLINE ~VariantType() FORCE_INLINE ~VariantType()
{ {
Allocator::Free(TypeName); if (!StaticName)
Allocator::Free(TypeName);
} }
public: public:
@@ -130,9 +146,13 @@ public:
public: public:
void SetTypeName(const StringView& typeName); void SetTypeName(const StringView& typeName);
void SetTypeName(const StringAnsiView& typeName); void SetTypeName(const StringAnsiView& typeName, bool staticName = false);
void SetTypeName(const ScriptingType& type);
void SetTypeName(const MClass& klass);
const char* GetTypeName() const; const char* GetTypeName() const;
VariantType GetElementType() const; VariantType GetElementType() const;
// Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types.
void Inline();
::String ToString() const; ::String ToString() const;
}; };
+4
View File
@@ -8,6 +8,7 @@
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#include "Engine/Threading/Task.h" #include "Engine/Threading/Task.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h"
@@ -219,6 +220,7 @@ namespace
if (module == GetBinaryModuleCorlib()) if (module == GetBinaryModuleCorlib())
return; return;
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(EngineDebug);
#if USE_CSHARP #if USE_CSHARP
if (auto* managedModule = dynamic_cast<ManagedBinaryModule*>(module)) if (auto* managedModule = dynamic_cast<ManagedBinaryModule*>(module))
@@ -381,6 +383,7 @@ DebugCommandsService DebugCommandsServiceInstance;
void DebugCommands::Execute(StringView command) void DebugCommands::Execute(StringView command)
{ {
PROFILE_MEM(EngineDebug);
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String commandCopy = command; String commandCopy = command;
command = commandCopy; command = commandCopy;
@@ -423,6 +426,7 @@ void DebugCommands::Search(StringView searchText, Array<StringView>& matches, bo
{ {
if (searchText.IsEmpty()) if (searchText.IsEmpty())
return; return;
PROFILE_MEM(EngineDebug);
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String searchTextCopy = searchText; String searchTextCopy = searchText;
searchText = searchTextCopy; searchText = searchTextCopy;
+28 -2
View File
@@ -480,6 +480,7 @@ DebugDrawCall WriteLists(int32& vertexCounter, const Array<T>& listA, const Arra
FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool depthTest) FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool depthTest)
{ {
PROFILE_MEM(EngineDebug);
Array<DebugTriangle>* list; Array<DebugTriangle>* list;
if (depthTest) if (depthTest)
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
@@ -492,6 +493,7 @@ FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool de
FORCE_INLINE DebugTriangle* AppendWireTriangles(int32 count, float duration, bool depthTest) FORCE_INLINE DebugTriangle* AppendWireTriangles(int32 count, float duration, bool depthTest)
{ {
PROFILE_MEM(EngineDebug);
Array<DebugTriangle>* list; Array<DebugTriangle>* list;
if (depthTest) if (depthTest)
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultWireTriangles : &Context->DebugDrawDepthTest.OneFrameWireTriangles; list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultWireTriangles : &Context->DebugDrawDepthTest.OneFrameWireTriangles;
@@ -539,7 +541,7 @@ DebugDrawService DebugDrawServiceInstance;
bool DebugDrawService::Init() bool DebugDrawService::Init()
{ {
PROFILE_MEM(Graphics); PROFILE_MEM(EngineDebug);
Context = &GlobalContext; Context = &GlobalContext;
// Init wireframe sphere cache // Init wireframe sphere cache
@@ -658,7 +660,7 @@ void DebugDrawService::Update()
} }
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Graphics); PROFILE_MEM(EngineDebug);
// Update lists // Update lists
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds(); float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
@@ -1114,6 +1116,7 @@ void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest) void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest)
{ {
PROFILE_MEM(EngineDebug);
const Float3 startF = start - Context->Origin, endF = end - Context->Origin; const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
@@ -1132,6 +1135,7 @@ void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color&
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration, bool depthTest) void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration, bool depthTest)
{ {
PROFILE_MEM(EngineDebug);
const Float3 startF = start - Context->Origin, endF = end - Context->Origin; const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
@@ -1161,6 +1165,7 @@ void DebugDraw::DrawLines(const Span<Float3>& lines, const Matrix& transform, co
} }
// Draw lines // Draw lines
PROFILE_MEM(EngineDebug);
const Float3* p = lines.Get(); const Float3* p = lines.Get();
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
const Matrix transformF = transform * Matrix::Translation(-Context->Origin); const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
@@ -1200,6 +1205,7 @@ void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float durat
} }
// Draw lines // Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne(); auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = lines; geometry.Buffer = lines;
@@ -1224,6 +1230,7 @@ void DebugDraw::DrawLines(const Span<Double3>& lines, const Matrix& transform, c
} }
// Draw lines // Draw lines
PROFILE_MEM(EngineDebug);
const Double3* p = lines.Get(); const Double3* p = lines.Get();
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
const Matrix transformF = transform * Matrix::Translation(-Context->Origin); const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
@@ -1270,6 +1277,7 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3&
const float segmentCountInv = 1.0f / (float)segmentCount; const float segmentCountInv = 1.0f / (float)segmentCount;
// Draw segmented curve from lines // Draw segmented curve from lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
{ {
@@ -1310,6 +1318,7 @@ void DebugDraw::DrawWireBox(const BoundingBox& box, const Color& color, float du
c -= Context->Origin; c -= Context->Origin;
// Draw lines // Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
{ {
@@ -1344,6 +1353,7 @@ void DebugDraw::DrawWireFrustum(const BoundingFrustum& frustum, const Color& col
c -= Context->Origin; c -= Context->Origin;
// Draw lines // Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
{ {
@@ -1378,6 +1388,7 @@ void DebugDraw::DrawWireBox(const OrientedBoundingBox& box, const Color& color,
c -= Context->Origin; c -= Context->Origin;
// Draw lines // Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
{ {
@@ -1419,6 +1430,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
auto& cache = SphereCache[index]; auto& cache = SphereCache[index];
// Draw lines of the unit sphere after linear transform // Draw lines of the unit sphere after linear transform
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0) if (duration > 0)
{ {
@@ -1454,6 +1466,7 @@ void DebugDraw::DrawSphere(const BoundingSphere& sphere, const Color& color, flo
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + SphereTriangleCache.Count()); list->EnsureCapacity(list->Count() + SphereTriangleCache.Count());
const Float3 centerF = sphere.Center - Context->Origin; const Float3 centerF = sphere.Center - Context->Origin;
@@ -1485,6 +1498,7 @@ void DebugDraw::DrawCircle(const Vector3& position, const Float3& normal, float
Matrix::Multiply(scale, world, matrix); Matrix::Multiply(scale, world, matrix);
// Draw lines of the unit circle after linear transform // Draw lines of the unit circle after linear transform
PROFILE_MEM(EngineDebug);
Float3 prev = Float3::Transform(CircleCache[0], matrix); Float3 prev = Float3::Transform(CircleCache[0], matrix);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
for (int32 i = 1; i < DEBUG_DRAW_CIRCLE_VERTICES;) for (int32 i = 1; i < DEBUG_DRAW_CIRCLE_VERTICES;)
@@ -1515,6 +1529,7 @@ void DebugDraw::DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vec
void DebugDraw::DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration, bool depthTest) void DebugDraw::DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration, bool depthTest)
{ {
PROFILE_MEM(EngineDebug);
DebugTriangle t; DebugTriangle t;
t.Color = Color32(color); t.Color = Color32(color);
t.TimeLeft = duration; t.TimeLeft = duration;
@@ -1570,6 +1585,7 @@ void DebugDraw::DrawTriangles(GPUBuffer* triangles, const Matrix& transform, flo
DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array"); DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array");
return; return;
} }
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne(); auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = triangles; geometry.Buffer = triangles;
@@ -1859,6 +1875,7 @@ void DebugDraw::DrawWireCapsule(const Vector3& position, const Quaternion& orien
Matrix::Multiply(rotation, translation, world); Matrix::Multiply(rotation, translation, world);
// Write vertices // Write vertices
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
Color32 color32(color); Color32 color32(color);
if (duration > 0) if (duration > 0)
@@ -1953,6 +1970,7 @@ namespace
void DrawCylinder(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration) void DrawCylinder(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration)
{ {
// Setup cache // Setup cache
PROFILE_MEM(EngineDebug);
Float3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES]; Float3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES];
const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION; const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION;
const float verticalOffset = height * 0.5f; const float verticalOffset = height * 0.5f;
@@ -2024,6 +2042,7 @@ namespace
void DrawCone(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration) void DrawCone(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration)
{ {
PROFILE_MEM(EngineDebug);
const float tolerance = 0.001f; const float tolerance = 0.001f;
const float angle1 = Math::Clamp(angleXY, tolerance, PI - tolerance); const float angle1 = Math::Clamp(angleXY, tolerance, PI - tolerance);
const float angle2 = Math::Clamp(angleXZ, tolerance, PI - tolerance); const float angle2 = Math::Clamp(angleXZ, tolerance, PI - tolerance);
@@ -2113,6 +2132,7 @@ void DebugDraw::DrawArc(const Vector3& position, const Quaternion& orientation,
{ {
if (angle <= 0) if (angle <= 0)
return; return;
PROFILE_MEM(EngineDebug);
if (angle > TWO_PI) if (angle > TWO_PI)
angle = TWO_PI; angle = TWO_PI;
Array<DebugTriangle>* list; Array<DebugTriangle>* list;
@@ -2145,6 +2165,7 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati
{ {
if (angle <= 0) if (angle <= 0)
return; return;
PROFILE_MEM(EngineDebug);
if (angle > TWO_PI) if (angle > TWO_PI)
angle = TWO_PI; angle = TWO_PI;
const int32 resolution = Math::CeilToInt((float)DEBUG_DRAW_CONE_RESOLUTION / TWO_PI * angle); const int32 resolution = Math::CeilToInt((float)DEBUG_DRAW_CONE_RESOLUTION / TWO_PI * angle);
@@ -2211,6 +2232,7 @@ void DebugDraw::DrawBox(const BoundingBox& box, const Color& color, float durati
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + 36); list->EnsureCapacity(list->Count() + 36);
for (int i0 = 0; i0 < 36;) for (int i0 = 0; i0 < 36;)
{ {
@@ -2239,6 +2261,7 @@ void DebugDraw::DrawBox(const OrientedBoundingBox& box, const Color& color, floa
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles; list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + 36); list->EnsureCapacity(list->Count() + 36);
for (int i0 = 0; i0 < 36;) for (int i0 = 0; i0 < 36;)
{ {
@@ -2254,6 +2277,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
{ {
if (text.Length() == 0 || size < 4) if (text.Length() == 0 || size < 4)
return; return;
PROFILE_MEM(EngineDebug);
Array<DebugText2D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText2D : &Context->DebugDrawDefault.OneFrameText2D; Array<DebugText2D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText2D : &Context->DebugDrawDefault.OneFrameText2D;
auto& t = list->AddOne(); auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1); t.Text.Resize(text.Length() + 1);
@@ -2269,6 +2293,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
{ {
if (text.Length() == 0 || size < 4) if (text.Length() == 0 || size < 4)
return; return;
PROFILE_MEM(EngineDebug);
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D; Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
auto& t = list->AddOne(); auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1); t.Text.Resize(text.Length() + 1);
@@ -2286,6 +2311,7 @@ void DebugDraw::DrawText(const StringView& text, const Transform& transform, con
{ {
if (text.Length() == 0 || size < 4) if (text.Length() == 0 || size < 4)
return; return;
PROFILE_MEM(EngineDebug);
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D; Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
auto& t = list->AddOne(); auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1); t.Text.Resize(text.Length() + 1);
+85 -42
View File
@@ -7,17 +7,17 @@
#include "Engine/Core/Random.h" #include "Engine/Core/Random.h"
#include "Engine/Engine/Engine.h" #include "Engine/Engine/Engine.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Content/Deprecated.h" #include "Engine/Content/Deprecated.h"
#if !FOLIAGE_USE_SINGLE_QUAD_TREE #if !FOLIAGE_USE_SINGLE_QUAD_TREE
#include "Engine/Threading/JobSystem.h" #include "Engine/Threading/JobSystem.h"
#if FOLIAGE_USE_DRAW_CALLS_BATCHING #if FOLIAGE_USE_DRAW_CALLS_BATCHING
#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Renderer/RenderList.h"
#endif #endif
#endif #endif
#include "Engine/Level/SceneQuery.h" #include "Engine/Level/SceneQuery.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" #include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
@@ -41,23 +41,42 @@ Foliage::Foliage(const SpawnParams& params)
void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE>& clusters, FoliageCluster* cluster, FoliageInstance& instance) void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE>& clusters, FoliageCluster* cluster, FoliageInstance& instance)
{ {
ASSERT(instance.Bounds.Radius > ZeroTolerance); ASSERT_LOW_LAYER(instance.Bounds.Radius > ZeroTolerance);
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
// Find target cluster // Minor clusters don't use bounds intersection but try to find the first free cluster instead
while (cluster->Children[0]) if (cluster->IsMinor)
{ {
// Insert into the first non-full child cluster or subdivide 1st child
#define CHECK_CHILD(idx) \
if (cluster->Children[idx]->Instances.Count() < FOLIAGE_CLUSTER_CAPACITY) \
{ \
cluster->Children[idx]->Instances.Add(&instance); \
return; \
}
CHECK_CHILD(3);
CHECK_CHILD(2);
CHECK_CHILD(1);
cluster = cluster->Children[0];
#undef CHECK_CHILD
}
else
{
// Find target cluster
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
while (cluster->Children[0])
{
#define CHECK_CHILD(idx) \ #define CHECK_CHILD(idx) \
if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \ if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \
{ \ { \
cluster = cluster->Children[idx]; \ cluster = cluster->Children[idx]; \
continue; \ continue; \
} }
CHECK_CHILD(0); CHECK_CHILD(0);
CHECK_CHILD(1); CHECK_CHILD(1);
CHECK_CHILD(2); CHECK_CHILD(2);
CHECK_CHILD(3); CHECK_CHILD(3);
#undef CHECK_CHILD #undef CHECK_CHILD
}
} }
// Check if it's not full // Check if it's not full
@@ -79,11 +98,20 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
// Setup children // Setup children
const Vector3 min = cluster->Bounds.Minimum; const Vector3 min = cluster->Bounds.Minimum;
const Vector3 max = cluster->Bounds.Maximum; const Vector3 max = cluster->Bounds.Maximum;
const Vector3 size = cluster->Bounds.GetSize(); const Vector3 size = max - min;
cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f))); cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f)));
cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max)); cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max));
cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f))); cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f)));
cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f))); cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f)));
if (cluster->IsMinor || size.MinValue() < 1.0f)
{
// Mark children as minor to avoid infinite subdivision
cluster->IsMinor = true;
cluster->Children[0]->IsMinor = true;
cluster->Children[1]->IsMinor = true;
cluster->Children[2]->IsMinor = true;
cluster->Children[3]->IsMinor = true;
}
// Move instances to a proper cells // Move instances to a proper cells
for (int32 i = 0; i < cluster->Instances.Count(); i++) for (int32 i = 0; i < cluster->Instances.Count(); i++)
@@ -165,6 +193,8 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
// Draw visible instances // Draw visible instances
const auto frame = Engine::FrameCount; const auto frame = Engine::FrameCount;
const auto model = type.Model.Get(); const auto model = type.Model.Get();
const auto transitionLOD = renderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions
// TODO: move DrawState to be stored per-view (so shadows can fade objects on their own)
for (int32 i = 0; i < cluster->Instances.Count(); i++) for (int32 i = 0; i < cluster->Instances.Count(); i++)
{ {
auto& instance = *cluster->Instances.Get()[i]; auto& instance = *cluster->Instances.Get()[i];
@@ -182,20 +212,29 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
// Handling model fade-out transition // Handling model fade-out transition
if (modelFrame == frame && instance.DrawState.PrevLOD != -1) if (modelFrame == frame && instance.DrawState.PrevLOD != -1)
{ {
// Check if start transition if (transitionLOD)
if (instance.DrawState.LODTransition == 255)
{ {
instance.DrawState.LODTransition = 0; // Check if start transition
} if (instance.DrawState.LODTransition == 255)
{
instance.DrawState.LODTransition = 0;
}
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition); RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
// Check if end transition // Check if end transition
if (instance.DrawState.LODTransition == 255) if (instance.DrawState.LODTransition == 255)
{ {
instance.DrawState.PrevLOD = lodIndex; instance.DrawState.PrevLOD = lodIndex;
}
else
{
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result);
}
} }
else else if (instance.DrawState.LODTransition < 255)
{ {
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD); const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f); const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
@@ -208,29 +247,32 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
lodIndex += renderContext.View.ModelLODBias; lodIndex += renderContext.View.ModelLODBias;
lodIndex = model->ClampLODIndex(lodIndex); lodIndex = model->ClampLODIndex(lodIndex);
// Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports) if (transitionLOD)
if (modelFrame == frame)
{ {
// Check if start transition // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
if (instance.DrawState.PrevLOD != lodIndex && instance.DrawState.LODTransition == 255) if (modelFrame == frame)
{ {
// Check if start transition
if (instance.DrawState.PrevLOD != lodIndex && instance.DrawState.LODTransition == 255)
{
instance.DrawState.LODTransition = 0;
}
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
// Check if end transition
if (instance.DrawState.LODTransition == 255)
{
instance.DrawState.PrevLOD = lodIndex;
}
}
// Check if there was a gap between frames in drawing this model instance
else if (modelFrame < frame || instance.DrawState.PrevLOD == -1)
{
// Reset state
instance.DrawState.PrevLOD = lodIndex;
instance.DrawState.LODTransition = 0; instance.DrawState.LODTransition = 0;
} }
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
// Check if end transition
if (instance.DrawState.LODTransition == 255)
{
instance.DrawState.PrevLOD = lodIndex;
}
}
// Check if there was a gap between frames in drawing this model instance
else if (modelFrame < frame || instance.DrawState.PrevLOD == -1)
{
// Reset state
instance.DrawState.PrevLOD = lodIndex;
instance.DrawState.LODTransition = 255;
} }
// Draw // Draw
@@ -253,7 +295,8 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen); //DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
instance.DrawState.PrevFrame = frame; if (transitionLOD)
instance.DrawState.PrevFrame = frame;
} }
} }
} }
@@ -322,7 +365,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
draw.DrawState = &instance.DrawState; draw.DrawState = &instance.DrawState;
draw.Bounds = sphere; draw.Bounds = sphere;
draw.PerInstanceRandom = instance.Random; draw.PerInstanceRandom = instance.Random;
draw.DrawModes = type._drawModes; draw.DrawModes = type.DrawModes;
draw.SetStencilValue(_layer); draw.SetStencilValue(_layer);
type.Model->Draw(renderContext, draw); type.Model->Draw(renderContext, draw);
+26 -20
View File
@@ -9,6 +9,7 @@ void FoliageCluster::Init(const BoundingBox& bounds)
Bounds = bounds; Bounds = bounds;
TotalBounds = bounds; TotalBounds = bounds;
MaxCullDistance = 0.0f; MaxCullDistance = 0.0f;
IsMinor = false;
Children[0] = nullptr; Children[0] = nullptr;
Children[1] = nullptr; Children[1] = nullptr;
@@ -20,26 +21,7 @@ void FoliageCluster::Init(const BoundingBox& bounds)
void FoliageCluster::UpdateTotalBoundsAndCullDistance() void FoliageCluster::UpdateTotalBoundsAndCullDistance()
{ {
if (Children[0]) if (Instances.HasItems())
{
ASSERT(Instances.IsEmpty());
Children[0]->UpdateTotalBoundsAndCullDistance();
Children[1]->UpdateTotalBoundsAndCullDistance();
Children[2]->UpdateTotalBoundsAndCullDistance();
Children[3]->UpdateTotalBoundsAndCullDistance();
TotalBounds = Children[0]->TotalBounds;
BoundingBox::Merge(TotalBounds, Children[1]->TotalBounds, TotalBounds);
BoundingBox::Merge(TotalBounds, Children[2]->TotalBounds, TotalBounds);
BoundingBox::Merge(TotalBounds, Children[3]->TotalBounds, TotalBounds);
MaxCullDistance = Children[0]->MaxCullDistance;
MaxCullDistance = Math::Max(MaxCullDistance, Children[1]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[2]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[3]->MaxCullDistance);
}
else if (Instances.HasItems())
{ {
BoundingBox box; BoundingBox box;
BoundingBox::FromSphere(Instances[0]->Bounds, TotalBounds); BoundingBox::FromSphere(Instances[0]->Bounds, TotalBounds);
@@ -57,6 +39,30 @@ void FoliageCluster::UpdateTotalBoundsAndCullDistance()
MaxCullDistance = 0; MaxCullDistance = 0;
} }
if (Children[0])
{
Children[0]->UpdateTotalBoundsAndCullDistance();
Children[1]->UpdateTotalBoundsAndCullDistance();
Children[2]->UpdateTotalBoundsAndCullDistance();
Children[3]->UpdateTotalBoundsAndCullDistance();
if (Instances.HasItems())
BoundingBox::Merge(TotalBounds, Children[0]->TotalBounds, TotalBounds);
else
TotalBounds = Children[0]->TotalBounds;
BoundingBox::Merge(TotalBounds, Children[1]->TotalBounds, TotalBounds);
BoundingBox::Merge(TotalBounds, Children[2]->TotalBounds, TotalBounds);
BoundingBox::Merge(TotalBounds, Children[3]->TotalBounds, TotalBounds);
if (Instances.HasItems())
MaxCullDistance = Math::Max(MaxCullDistance, Children[0]->MaxCullDistance);
else
MaxCullDistance = Children[0]->MaxCullDistance;
MaxCullDistance = Math::Max(MaxCullDistance, Children[1]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[2]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[3]->MaxCullDistance);
}
BoundingSphere::FromBox(TotalBounds, TotalBoundsSphere); BoundingSphere::FromBox(TotalBounds, TotalBoundsSphere);
} }
+5
View File
@@ -33,6 +33,11 @@ public:
/// </summary> /// </summary>
float MaxCullDistance; float MaxCullDistance;
/// <summary>
/// Flag used by clusters that are not typical quad-tree nodes but have no volume (eg. lots of instances placed on top of each other).
/// </summary>
int32 IsMinor : 1;
/// <summary> /// <summary>
/// The child clusters. If any element is valid then all are created. /// The child clusters. If any element is valid then all are created.
/// </summary> /// </summary>
+5
View File
@@ -1112,6 +1112,11 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : uint64
/// Default flags for materials/models previews generating. /// Default flags for materials/models previews generating.
/// </summary> /// </summary>
DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | Sky | Particles, DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | Sky | Particles,
/// <summary>
/// All flags enabled.
/// </summary>
All = None | DebugDraw | EditorSprites | Reflections | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | Decals | DepthOfField | PhysicsDebug | Fog | MotionBlur | ContactShadows | GlobalSDF | Sky | LightsDebug | Particles,
}; };
DECLARE_ENUM_OPERATORS(ViewFlags); DECLARE_ENUM_OPERATORS(ViewFlags);
@@ -10,7 +10,7 @@
/// <summary> /// <summary>
/// Current materials shader version. /// Current materials shader version.
/// </summary> /// </summary>
#define MATERIAL_GRAPH_VERSION 181 #define MATERIAL_GRAPH_VERSION 182
class Material; class Material;
class GPUShader; class GPUShader;
@@ -10,9 +10,11 @@
#if USE_EDITOR #if USE_EDITOR
#include "Engine/Renderer/Lightmaps.h" #include "Engine/Renderer/Lightmaps.h"
#endif #endif
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUContext.h"
#include "Engine/Level/Scene/Lightmap.h" #include "Engine/Level/Scene/Lightmap.h"
#include "Engine/Level/Actors/EnvironmentProbe.h" #include "Engine/Level/Actors/EnvironmentProbe.h"
#include "Engine/Renderer/ReflectionsPass.h"
void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<byte>& cb, int32& srv) void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<byte>& cb, int32& srv)
{ {
@@ -26,6 +28,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
const int32 shadowsBufferRegisterIndex = srv + 2; const int32 shadowsBufferRegisterIndex = srv + 2;
const int32 shadowMapShaderRegisterIndex = srv + 3; const int32 shadowMapShaderRegisterIndex = srv + 3;
const int32 volumetricFogTextureRegisterIndex = srv + 4; const int32 volumetricFogTextureRegisterIndex = srv + 4;
const int32 preIntegratedGFRegisterIndex = srv + 5;
const bool canUseShadow = view.Pass != DrawPass::Depth; const bool canUseShadow = view.Pass != DrawPass::Depth;
// Set fog input // Set fog input
@@ -83,9 +86,12 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
} }
if (noEnvProbe) if (noEnvProbe)
{ {
data.EnvironmentProbe.Data1 = Float4::Zero; Platform::MemoryClear(&data.EnvironmentProbe, sizeof(data.EnvironmentProbe));
params.GPUContext->UnBindSR(envProbeShaderRegisterIndex); params.GPUContext->UnBindSR(envProbeShaderRegisterIndex);
} }
// TODO: find a better way to find this texture (eg. cache GPUTextureView* handle within ForwardShading cache for a whole frame)
static AssetReference<Texture> PreIntegratedGF = Content::LoadAsyncInternal<Texture>(PRE_INTEGRATED_GF_ASSET_NAME);
params.GPUContext->BindSR(preIntegratedGFRegisterIndex, PreIntegratedGF->GetTexture());
// Set local lights // Set local lights
data.LocalLightsCount = 0; data.LocalLightsCount = 0;
@@ -25,10 +25,9 @@ struct ForwardShadingFeature : MaterialShaderFeature
{ {
enum { MaxLocalLights = 4 }; enum { MaxLocalLights = 4 };
enum { SRVs = 5 }; enum { SRVs = 6 };
PACK_STRUCT(struct Data PACK_STRUCT(struct Data {
{
ShaderLightData DirectionalLight; ShaderLightData DirectionalLight;
ShaderLightData SkyLight; ShaderLightData SkyLight;
ShaderEnvProbeData EnvironmentProbe; ShaderEnvProbeData EnvironmentProbe;
@@ -77,8 +76,7 @@ struct GlobalIlluminationFeature : MaterialShaderFeature
{ {
enum { SRVs = 3 }; enum { SRVs = 3 };
PACK_STRUCT(struct Data PACK_STRUCT(struct Data {
{
DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI;
}); });
@@ -93,13 +91,10 @@ struct SDFReflectionsFeature : MaterialShaderFeature
{ {
enum { SRVs = 7 }; enum { SRVs = 7 };
PACK_STRUCT(struct Data PACK_STRUCT(struct Data {
{
GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; GlobalSignDistanceFieldPass::ConstantsData GlobalSDF;
GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas;
}); });
static bool Bind(MaterialShader::BindParameters& params, Span<byte>& cb, int32& srv); static bool Bind(MaterialShader::BindParameters& params, Span<byte>& cb, int32& srv);
#if USE_EDITOR #if USE_EDITOR
+25 -8
View File
@@ -6,7 +6,9 @@
#include "Engine/Core/Math/Transform.h" #include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Math/Ray.h" #include "Engine/Core/Math/Ray.h"
#include "Engine/Core/Math/CollisionsHelper.h" #include "Engine/Core/Math/CollisionsHelper.h"
#include "Engine/Core/Math/Half.h"
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Array.h"
#include "Engine/Graphics/PixelFormat.h"
/// <summary> /// <summary>
/// Helper container used for detailed triangle mesh intersections tests. /// Helper container used for detailed triangle mesh intersections tests.
@@ -31,23 +33,38 @@ public:
} }
template<typename IndexType> template<typename IndexType>
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3)) void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3), PixelFormat positionsFormat = PixelFormat::R32G32B32_Float)
{ {
Triangles.Clear(); Triangles.Clear();
Triangles.EnsureCapacity(triangles, false); Triangles.EnsureCapacity(triangles, false);
const IndexType* it = indices; const IndexType* it = indices;
for (uint32 i = 0; i < triangles; i++) #define LOOP_BEGIN() \
for (uint32 i = 0; i < triangles; i++) \
{ \
const IndexType i0 = *(it++); \
const IndexType i1 = *(it++); \
const IndexType i2 = *(it++); \
if (i0 < vertices && i1 < vertices && i2 < vertices) \
{ {
const IndexType i0 = *(it++); #define LOOP_END() } }
const IndexType i1 = *(it++); if (positionsFormat == PixelFormat::R32G32B32_Float)
const IndexType i2 = *(it++); {
if (i0 < vertices && i1 < vertices && i2 < vertices) LOOP_BEGIN()
{
#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx) #define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx)
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) }); Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS #undef GET_POS
} LOOP_END()
} }
else if (positionsFormat == PixelFormat::R16G16B16A16_Float)
{
LOOP_BEGIN()
#define GET_POS(idx) ((const Half4*)((const byte*)positions + positionsStride * idx))->ToFloat3()
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS
LOOP_END()
}
#undef LOOP_BEGIN
#undef LOOP_END
} }
void Clear() void Clear()
+92 -6
View File
@@ -265,6 +265,39 @@ namespace FlaxEngine
} }
} }
/// <summary>
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
/// </summary>
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<uint> src)
{
if (IsLinear(PixelFormat.R32_UInt))
{
src.CopyTo(MemoryMarshal.Cast<byte, uint>(_data));
}
else if (IsLinear(PixelFormat.R16_UInt))
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
((ushort*)data)[i] = (ushort)src[i];
}
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i]);
_sampler.Write(data + i * _stride, ref v);
}
}
}
}
/// <summary> /// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />. /// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary> /// </summary>
@@ -281,9 +314,7 @@ namespace FlaxEngine
fixed (byte* data = _data) fixed (byte* data = _data)
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{
dst[i] = new Float2(_sampler.Read(data + i * _stride)); dst[i] = new Float2(_sampler.Read(data + i * _stride));
}
} }
} }
} }
@@ -304,9 +335,7 @@ namespace FlaxEngine
fixed (byte* data = _data) fixed (byte* data = _data)
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{
dst[i] = new Float3(_sampler.Read(data + i * _stride)); dst[i] = new Float3(_sampler.Read(data + i * _stride));
}
} }
} }
} }
@@ -327,9 +356,37 @@ namespace FlaxEngine
fixed (byte* data = _data) fixed (byte* data = _data)
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{
dst[i] = (Color)_sampler.Read(data + i * _stride); dst[i] = (Color)_sampler.Read(data + i * _stride);
} }
}
}
/// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary>
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<uint> dst)
{
if (IsLinear(PixelFormat.R32_UInt))
{
_data.CopyTo(MemoryMarshal.Cast<uint, byte>(dst));
}
else if (IsLinear(PixelFormat.R16_UInt))
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
dst[i] = ((ushort*)data)[i];
}
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
dst[i] = (uint)_sampler.Read(data + i * _stride).X;
} }
} }
} }
@@ -619,6 +676,16 @@ namespace FlaxEngine
return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel)); return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel));
} }
/// <summary>
/// Gets or sets the index buffer with triangle indices.
/// </summary>
/// <remarks>Uses <see cref="Index"/> stream to read or write data to the index buffer.</remarks>
public uint[] Triangles
{
get => GetStreamUInt(Index());
set => SetStreamUInt(Index(), value);
}
/// <summary> /// <summary>
/// Gets or sets the vertex positions. Null if <see cref="VertexElement.Types.Position"/> does not exist in vertex buffers of the mesh. /// Gets or sets the vertex positions. Null if <see cref="VertexElement.Types.Position"/> does not exist in vertex buffers of the mesh.
/// </summary> /// </summary>
@@ -659,6 +726,25 @@ namespace FlaxEngine
set => SetStreamFloat2(VertexElement.Types.TexCoord, value); set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
} }
private uint[] GetStreamUInt(Stream stream)
{
uint[] result = null;
if (stream.IsValid)
{
result = new uint[stream.Count];
stream.CopyTo(result);
}
return result;
}
private void SetStreamUInt(Stream stream, uint[] value)
{
if (stream.IsValid)
{
stream.Set(value);
}
}
private delegate void TransformDelegate3(ref Float3 value); private delegate void TransformDelegate3(ref Float3 value);
private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null) private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null)
+6 -2
View File
@@ -441,6 +441,9 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
GPUBuffer* vertexBuffer1 = nullptr; GPUBuffer* vertexBuffer1 = nullptr;
GPUBuffer* vertexBuffer2 = nullptr; GPUBuffer* vertexBuffer2 = nullptr;
GPUBuffer* indexBuffer = nullptr; GPUBuffer* indexBuffer = nullptr;
#if MODEL_USE_PRECISE_MESH_INTERSECTS
VertexElement positionsElement;
#endif
// Create GPU buffers // Create GPU buffers
#if GPU_ENABLE_RESOURCE_NAMING #if GPU_ENABLE_RESOURCE_NAMING
@@ -470,10 +473,11 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
// Init collision proxy // Init collision proxy
#if MODEL_USE_PRECISE_MESH_INTERSECTS #if MODEL_USE_PRECISE_MESH_INTERSECTS
positionsElement = vbLayout[0]->FindElement(VertexElement::Types::Position);
if (use16BitIndexBuffer) if (use16BitIndexBuffer)
_collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData); _collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData, vertexBuffer0->GetStride(), positionsElement.Format);
else else
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData); _collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData, vertexBuffer0->GetStride(), positionsElement.Format);
#endif #endif
// Free old buffers // Free old buffers
@@ -73,6 +73,10 @@ struct TIsPODType<SkeletonBone>
/// </remarks> /// </remarks>
class FLAXENGINE_API SkeletonData class FLAXENGINE_API SkeletonData
{ {
private:
mutable volatile int64 _dirty = 1;
mutable Array<Matrix> _cachedPose;
public: public:
/// <summary> /// <summary>
/// The nodes in this hierarchy. The root node is always at the index 0. /// The nodes in this hierarchy. The root node is always at the index 0.
@@ -114,6 +118,11 @@ public:
int32 FindNode(const StringView& name) const; int32 FindNode(const StringView& name) const;
int32 FindBone(int32 nodeIndex) const; int32 FindBone(int32 nodeIndex) const;
// Gets the skeleton nodes transforms in mesh space (pose). Calculated from the local node transforms and hierarchy. Cached internally and updated when data is dirty.
const Array<Matrix>& GetNodesPose() const;
// Marks data as dirty (modified) to update internal state and recalculate cached data if needed (eg. skeleton pose).
void Dirty();
uint64 GetMemoryUsage() const; uint64 GetMemoryUsage() const;
/// <summary> /// <summary>
@@ -154,6 +154,8 @@ void SkeletonData::Swap(SkeletonData& other)
{ {
Nodes.Swap(other.Nodes); Nodes.Swap(other.Nodes);
Bones.Swap(other.Bones); Bones.Swap(other.Bones);
Dirty();
other.Dirty();
} }
Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const
@@ -171,6 +173,7 @@ Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const
void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value) void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value)
{ {
CHECK(Nodes.IsValidIndex(nodeIndex)); CHECK(Nodes.IsValidIndex(nodeIndex));
Dirty();
const int32 parentIndex = Nodes[nodeIndex].ParentIndex; const int32 parentIndex = Nodes[nodeIndex].ParentIndex;
if (parentIndex == -1) if (parentIndex == -1)
{ {
@@ -201,6 +204,39 @@ int32 SkeletonData::FindBone(int32 nodeIndex) const
return -1; return -1;
} }
const Array<Matrix>& SkeletonData::GetNodesPose() const
{
// Guard with a simple atomic flag to avoid locking if the pose is up to date
if (Platform::AtomicRead(&_dirty))
{
ScopeLock lock(RenderContext::GPULocker);
if (Platform::AtomicRead(&_dirty))
{
const SkeletonNode* nodes = Nodes.Get();
const int32 nodesCount = Nodes.Count();
_cachedPose.Resize(nodesCount);
Matrix* posePtr = _cachedPose.Get();
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
{
const SkeletonNode& node = nodes[nodeIndex];
Matrix local;
Matrix::Transformation(node.LocalTransform.Scale, node.LocalTransform.Orientation, node.LocalTransform.Translation, local);
if (node.ParentIndex != -1)
Matrix::Multiply(local, posePtr[node.ParentIndex], posePtr[nodeIndex]);
else
posePtr[nodeIndex] = local;
}
Platform::AtomicStore(&_dirty, 0);
}
}
return _cachedPose;
}
void SkeletonData::Dirty()
{
Platform::AtomicStore(&_dirty, 1);
}
uint64 SkeletonData::GetMemoryUsage() const uint64 SkeletonData::GetMemoryUsage() const
{ {
uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone);
@@ -5,11 +5,6 @@
#include "Engine/Animations/Config.h" #include "Engine/Animations/Config.h"
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Math/Matrix3x4.h"
SkinnedMeshDrawData::SkinnedMeshDrawData()
{
}
SkinnedMeshDrawData::~SkinnedMeshDrawData() SkinnedMeshDrawData::~SkinnedMeshDrawData()
{ {
@@ -33,7 +28,7 @@ void SkinnedMeshDrawData::Setup(int32 bonesCount)
BonesCount = bonesCount; BonesCount = bonesCount;
_hasValidData = false; _hasValidData = false;
_isDirty = false; _isDirty = true;
Data.Resize(BoneMatrices->GetSize()); Data.Resize(BoneMatrices->GetSize());
SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices); SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices);
} }
@@ -36,11 +36,6 @@ public:
Array<byte> Data; Array<byte> Data;
public: public:
/// <summary>
/// Initializes a new instance of the <see cref="SkinnedMeshDrawData"/> class.
/// </summary>
SkinnedMeshDrawData();
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="SkinnedMeshDrawData"/> class. /// Finalizes an instance of the <see cref="SkinnedMeshDrawData"/> class.
/// </summary> /// </summary>
@@ -76,7 +71,7 @@ public:
void OnDataChanged(bool dropHistory); void OnDataChanged(bool dropHistory);
/// <summary> /// <summary>
/// After bones Data has been send to the GPU buffer. /// After bones Data has been sent to the GPU buffer.
/// </summary> /// </summary>
void OnFlush() void OnFlush()
{ {
@@ -8,6 +8,7 @@
#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Threading/ConcurrentDictionary.h"
#if GPU_ENABLE_RESOURCE_NAMING #if GPU_ENABLE_RESOURCE_NAMING
#include "Engine/Scripting/Enums.h" #include "Engine/Scripting/Enums.h"
#endif #endif
@@ -40,27 +41,37 @@ uint32 GetHash(const VertexBufferLayouts& key)
namespace namespace
{ {
CriticalSection CacheLocker; ConcurrentDictionary<uint32, GPUVertexLayout*> LayoutCache;
Dictionary<uint32, GPUVertexLayout*> LayoutCache; ConcurrentDictionary<VertexBufferLayouts, GPUVertexLayout*> VertexBufferCache;
Dictionary<VertexBufferLayouts, GPUVertexLayout*> VertexBufferCache;
GPUVertexLayout* AddCache(const VertexBufferLayouts& key, int32 count) GPUVertexLayout* GetCache(const VertexBufferLayouts& key, int32 count)
{ {
GPUVertexLayout::Elements elements; GPUVertexLayout* result;
bool anyValid = false; if (!VertexBufferCache.TryGet(key, result))
for (int32 slot = 0; slot < count; slot++)
{ {
if (key.Layouts[slot]) GPUVertexLayout::Elements elements;
bool anyValid = false;
for (int32 slot = 0; slot < count; slot++)
{ {
anyValid = true; if (key.Layouts[slot])
int32 start = elements.Count(); {
elements.Add(key.Layouts[slot]->GetElements()); anyValid = true;
for (int32 j = start; j < elements.Count(); j++) int32 start = elements.Count();
elements.Get()[j].Slot = (byte)slot; elements.Add(key.Layouts[slot]->GetElements());
for (int32 j = start; j < elements.Count(); j++)
elements.Get()[j].Slot = (byte)slot;
}
} }
result = anyValid ? GPUVertexLayout::Get(elements, true) : nullptr;
if (!VertexBufferCache.Add(key, result))
{
// Other thread added the value
Delete(result);
bool found = VertexBufferCache.TryGet(key, result);
ASSERT(found);
}
} }
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements) : nullptr;
VertexBufferCache.Add(key, result);
return result; return result;
} }
} }
@@ -97,6 +108,7 @@ GPUVertexLayout::GPUVertexLayout()
void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets) void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets)
{ {
uint32 offsets[GPU_MAX_VB_BINDED + 1] = {}; uint32 offsets[GPU_MAX_VB_BINDED + 1] = {};
uint32 maxOffset[GPU_MAX_VB_BINDED + 1] = {};
_elements = elements; _elements = elements;
for (int32 i = 0; i < _elements.Count(); i++) for (int32 i = 0; i < _elements.Count(); i++)
{ {
@@ -108,9 +120,10 @@ void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets
else else
e.Offset = (byte)offset; e.Offset = (byte)offset;
offset += PixelFormatExtensions::SizeInBytes(e.Format); offset += PixelFormatExtensions::SizeInBytes(e.Format);
maxOffset[e.Slot] = Math::Max(maxOffset[e.Slot], offset);
} }
_stride = 0; _stride = 0;
for (uint32 offset : offsets) for (uint32 offset : maxOffset)
_stride += offset; _stride += offset;
} }
@@ -139,14 +152,13 @@ VertexElement GPUVertexLayout::FindElement(VertexElement::Types type) const
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets) GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
{ {
// Hash input layout // Hash input layout
uint32 hash = 0; uint32 hash = explicitOffsets ? 131 : 0;
for (const VertexElement& element : elements) for (const VertexElement& element : elements)
{ {
CombineHash(hash, GetHash(element)); CombineHash(hash, GetHash(element));
} }
// Lookup existing cache // Lookup existing cache
CacheLocker.Lock();
GPUVertexLayout* result; GPUVertexLayout* result;
if (!LayoutCache.TryGet(hash, result)) if (!LayoutCache.TryGet(hash, result))
{ {
@@ -158,12 +170,16 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOff
LOG(Error, " {}", e.ToString()); LOG(Error, " {}", e.ToString());
#endif #endif
LOG(Error, "Failed to create vertex layout"); LOG(Error, "Failed to create vertex layout");
CacheLocker.Unlock();
return nullptr; return nullptr;
} }
LayoutCache.Add(hash, result); if (!LayoutCache.Add(hash, result))
{
// Other thread added the value
Delete(result);
bool found = LayoutCache.TryGet(hash, result);
ASSERT(found);
}
} }
CacheLocker.Unlock();
return result; return result;
} }
@@ -183,13 +199,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUBuffer*>& vertexBuffers)
key.Layouts[i] = nullptr; key.Layouts[i] = nullptr;
// Lookup existing cache // Lookup existing cache
CacheLocker.Lock(); return GetCache(key, vertexBuffers.Length());
GPUVertexLayout* result;
if (!VertexBufferCache.TryGet(key, result))
result = AddCache(key, vertexBuffers.Length());
CacheLocker.Unlock();
return result;
} }
GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts) GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts)
@@ -207,13 +217,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts)
key.Layouts[i] = nullptr; key.Layouts[i] = nullptr;
// Lookup existing cache // Lookup existing cache
CacheLocker.Lock(); return GetCache(key, layouts.Length());
GPUVertexLayout* result;
if (!VertexBufferCache.TryGet(key, result))
result = AddCache(key, layouts.Length());
CacheLocker.Unlock();
return result;
} }
GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride, bool referenceOrder) GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride, bool referenceOrder)
@@ -4,6 +4,7 @@
#include "Engine/Graphics/Shaders/GPUShaderProgram.h" #include "Engine/Graphics/Shaders/GPUShaderProgram.h"
#include "Engine/Core/Types/DataContainer.h" #include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../IncludeDirectXHeaders.h" #include "../IncludeDirectXHeaders.h"
#if GRAPHICS_API_DIRECTX11 #if GRAPHICS_API_DIRECTX11
@@ -3,16 +3,9 @@
#if GRAPHICS_API_DIRECTX12 #if GRAPHICS_API_DIRECTX12
#include "Engine/Graphics/Config.h" #include "Engine/Graphics/Config.h"
#include "Engine/Platform/Platform.h"
#include "../IncludeDirectXHeaders.h"
#if USE_PIX && GPU_ALLOW_PROFILE_EVENTS #if USE_PIX && GPU_ALLOW_PROFILE_EVENTS
// Include these header files before pix3
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define NOGDI
#define NODRAWTEXT
//#define NOCTLMGR
#define NOFLATSBAPIS
#include <Windows.h>
#include <d3d12.h>
#include <ThirdParty/WinPixEventRuntime/pix3.h> #include <ThirdParty/WinPixEventRuntime/pix3.h>
#endif #endif
#include "GPUContextDX12.h" #include "GPUContextDX12.h"
+2 -2
View File
@@ -1685,7 +1685,7 @@ Quaternion Actor::LookingAt(const Vector3& worldPos) const
{ {
const Vector3 direction = worldPos - _transform.Translation; const Vector3 direction = worldPos - _transform.Translation;
if (direction.LengthSquared() < ZeroTolerance) if (direction.LengthSquared() < ZeroTolerance)
return _parent->GetOrientation(); return _parent ? _parent->GetOrientation() : Quaternion::Identity;
const Float3 newForward = Vector3::Normalize(direction); const Float3 newForward = Vector3::Normalize(direction);
const Float3 oldForward = _transform.Orientation * Vector3::Forward; const Float3 oldForward = _transform.Orientation * Vector3::Forward;
@@ -1712,7 +1712,7 @@ Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp) con
{ {
const Vector3 direction = worldPos - _transform.Translation; const Vector3 direction = worldPos - _transform.Translation;
if (direction.LengthSquared() < ZeroTolerance) if (direction.LengthSquared() < ZeroTolerance)
return _parent->GetOrientation(); return _parent ? _parent->GetOrientation() : Quaternion::Identity;
const Float3 forward = Vector3::Normalize(direction); const Float3 forward = Vector3::Normalize(direction);
const Float3 up = Vector3::Normalize(worldUp); const Float3 up = Vector3::Normalize(worldUp);
if (Math::IsOne(Float3::Dot(forward, up))) if (Math::IsOne(Float3::Dot(forward, up)))
+76 -8
View File
@@ -14,14 +14,84 @@
#include "Engine/Content/Deprecated.h" #include "Engine/Content/Deprecated.h"
#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUPass.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Graphics/Models/MeshAccessor.h"
#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Profiler/Profiler.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
// Implements efficient skinning data update within a shared GPUMemoryPass with manual resource transitions batched for all animated models.
class AnimatedModelRenderListExtension : public RenderList::IExtension
{
public:
struct Item
{
GPUBuffer* BoneMatrices;
void* Data;
int32 Size;
};
RenderListBuffer<Item> Items;
void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override
{
Items.Clear();
}
void PostDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override
{
const int32 count = Items.Count();
if (count == 0)
return;
PROFILE_GPU_CPU_NAMED("Update Bones");
GPUMemoryPass pass(context);
Item* items = Items.Get();
// Special case for D3D11 backend that doesn't need transitions
if (context->GetDevice()->GetRendererType() <= RendererType::DirectX11)
{
for (int32 i = 0; i < count; i++)
{
Item& item = items[i];
context->UpdateBuffer(item.BoneMatrices, item.Data, item.Size);
}
}
else
{
// Batch resource barriers for buffer update
for (int32 i = 0; i < count; i++)
pass.Transition(items[i].BoneMatrices, GPUResourceAccess::CopyWrite);
// Update all buffers within Memory Pass (no barriers between)
for (int32 i = 0; i < count; i++)
{
Item& item = items[i];
context->UpdateBuffer(item.BoneMatrices, item.Data, item.Size);
}
// Batch resource barriers for reading in Vertex Shader
for (int32 i = 0; i < count; i++)
pass.Transition(items[i].BoneMatrices, GPUResourceAccess::ShaderReadGraphics);
}
#if COMPILE_WITH_PROFILER
// Insert amount of kilobytes of data updated into profiler trace
uint32 dataSize = 0;
for (int32 i = 0; i < count; i++)
dataSize += items[i].Size;
ZoneValue(dataSize / 1024);
#endif
Items.Clear();
}
};
AnimatedModelRenderListExtension RenderListExtension;
AnimatedModel::AnimatedModel(const SpawnParams& params) AnimatedModel::AnimatedModel(const SpawnParams& params)
: ModelInstanceActor(params) : ModelInstanceActor(params)
, _actualMode(AnimationUpdateMode::Never) , _actualMode(AnimationUpdateMode::Never)
@@ -1002,7 +1072,7 @@ void AnimatedModel::Draw(RenderContext& renderContext)
if (renderContext.View.Pass == DrawPass::GlobalSDF) if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; return;
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
return; // No supported return; // Not supported
ACTOR_GET_WORLD_MATRIX(this, view, world); ACTOR_GET_WORLD_MATRIX(this, view, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
@@ -1012,9 +1082,8 @@ void AnimatedModel::Draw(RenderContext& renderContext)
// Flush skinning data with GPU // Flush skinning data with GPU
if (_skinningData.IsDirty()) if (_skinningData.IsDirty())
{ {
RenderContext::GPULocker.Lock(); RenderListExtension.Items.Add({ _skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count() });
GPUDevice::Instance->GetMainContext()->UpdateBuffer(_skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count()); _skinningData.OnFlush();
RenderContext::GPULocker.Unlock();
} }
SkinnedMesh::DrawInfo draw; SkinnedMesh::DrawInfo draw;
@@ -1056,9 +1125,8 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch)
// Flush skinning data with GPU // Flush skinning data with GPU
if (_skinningData.IsDirty()) if (_skinningData.IsDirty())
{ {
RenderContext::GPULocker.Lock(); RenderListExtension.Items.Add({ _skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count() });
GPUDevice::Instance->GetMainContext()->UpdateBuffer(_skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count()); _skinningData.OnFlush();
RenderContext::GPULocker.Unlock();
} }
SkinnedMesh::DrawInfo draw; SkinnedMesh::DrawInfo draw;

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