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

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

View File

@@ -430,7 +430,9 @@ float3x4 GetPrevBoneMatrix(int index)
float3 SkinPrevPosition(ModelInput_Skinned input)
{
float4 position = float4(input.Position.xyz, 1);
float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x);
float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w;
float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros
float3x4 boneMatrix = mainWeight * GetPrevBoneMatrix(input.BlendIndices.x);
boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y);
boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z);
boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w);
@@ -439,12 +441,6 @@ float3 SkinPrevPosition(ModelInput_Skinned input)
#endif
// Cached skinning data to avoid multiple calculation
struct SkinningData
{
float3x4 BlendMatrix;
};
// Calculates the transposed transform matrix for the given bone index
float3x4 GetBoneMatrix(int index)
{
@@ -457,7 +453,9 @@ float3x4 GetBoneMatrix(int index)
// Calculates the transposed transform matrix for the given vertex (uses blending)
float3x4 GetBoneMatrix(ModelInput_Skinned input)
{
float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x);
float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w;
float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros
float3x4 boneMatrix = mainWeight * GetBoneMatrix(input.BlendIndices.x);
boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y);
boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z);
boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w);
@@ -465,13 +463,13 @@ float3x4 GetBoneMatrix(ModelInput_Skinned input)
}
// Transforms the vertex position by weighted sum of the skinning matrices
float3 SkinPosition(ModelInput_Skinned input, SkinningData data)
float3 SkinPosition(ModelInput_Skinned input, float3x4 boneMatrix)
{
return mul(data.BlendMatrix, float4(input.Position.xyz, 1));
return mul(boneMatrix, float4(input.Position.xyz, 1));
}
// Transforms the vertex position by weighted sum of the skinning matrices
float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data)
float3x3 SkinTangents(ModelInput_Skinned input, float3x4 boneMatrix)
{
// Unpack vertex tangent frame
float bitangentSign = input.Tangent.w ? -1.0f : +1.0f;
@@ -479,10 +477,10 @@ float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data)
float3 tangent = input.Tangent.xyz * 2.0 - 1.0;
// Apply skinning
tangent = mul(data.BlendMatrix, float4(tangent, 0));
normal = mul(data.BlendMatrix, float4(normal, 0));
tangent = normalize(mul(boneMatrix, float4(tangent, 0)));
normal = normalize(mul(boneMatrix, float4(normal, 0)));
float3 bitangent = cross(normal, tangent) * bitangentSign;
float3 bitangent = normalize(cross(normal, tangent) * bitangentSign);
return float3x3(tangent, bitangent, normal);
}
@@ -501,10 +499,9 @@ VertexOutput VS_Skinned(ModelInput_Skinned input)
VertexOutput output;
// Perform skinning
SkinningData data;
data.BlendMatrix = GetBoneMatrix(input);
float3 position = SkinPosition(input, data);
float3x3 tangentToLocal = SkinTangents(input, data);
float3x4 boneMatrix = GetBoneMatrix(input);
float3 position = SkinPosition(input, boneMatrix);
float3x3 tangentToLocal = SkinTangents(input, boneMatrix);
// Compute world space vertex position
CalculateInstanceTransform(input);

View File

@@ -236,6 +236,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/GrammarAndSpelling/GrammarChecking/RulesStates/=LanguageTool_002EEN_002EE_005FG/@EntryIndexedValue">DisabledByUser</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/Color/@EntryValue">Blue</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/MatchComments/@EntryValue">True</s:Boolean>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ public:
namespace
{
FORCE_INLINE bool CanUpdateModel(AnimatedModel* animatedModel)
FORCE_INLINE bool CanUpdateModel(const AnimatedModel* animatedModel)
{
auto skinnedModel = animatedModel->SkinnedModel.Get();
auto animGraph = animatedModel->AnimationGraph.Get();

View File

@@ -117,11 +117,11 @@ void InstanceDataBucketInit(AnimGraphInstanceData::Bucket& bucket)
bucket.InstanceData.Init = true;
}
bool SortMultiBlend1D(const byte& a, const byte& b, AnimGraphNode* n)
bool SortMultiBlend1D(const ANIM_GRAPH_MULTI_BLEND_INDEX& a, const ANIM_GRAPH_MULTI_BLEND_INDEX& b, AnimGraphNode* n)
{
// Sort items by X location from the lowest to the highest
const auto aX = a == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS ? MAX_float : n->Values[4 + a * 2].AsFloat4().X;
const auto bX = b == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS ? MAX_float : n->Values[4 + b * 2].AsFloat4().X;
const auto aX = a == ANIM_GRAPH_MULTI_BLEND_INVALID ? MAX_float : n->Values[4 + a * 2].AsFloat4().X;
const auto bX = b == ANIM_GRAPH_MULTI_BLEND_INVALID ? MAX_float : n->Values[4 + b * 2].AsFloat4().X;
return aX < bX;
}
@@ -133,8 +133,6 @@ bool SortMultiBlend1D(const byte& a, const byte& b, AnimGraphNode* n)
bool AnimGraphBase::onNodeLoaded(Node* n)
{
((AnimGraphNode*)n)->Graph = _graph;
// Check if this node needs a state container
switch (n->GroupID)
{
// Tools
@@ -163,53 +161,51 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
// Animation
case 2:
ADD_BUCKET(AnimationBucketInit);
n->Assets.Resize(1);
n->Assets[0] = (Asset*)Content::LoadAsync<Animation>((Guid)n->Values[0]);
break;
// Blend with Mask
case 11:
n->Assets.Resize(1);
n->Assets[0] = (Asset*)Content::LoadAsync<SkeletonMask>((Guid)n->Values[1]);
break;
// Multi Blend 1D
case 12:
{
ADD_BUCKET(MultiBlendBucketInit);
n->Data.MultiBlend1D.Count = (ANIM_GRAPH_MULTI_BLEND_INDEX)((n->Values.Count() - 4) / 2); // 4 node values + 2 per blend point
n->Data.MultiBlend1D.Length = -1;
const Float4 range = n->Values[0].AsFloat4();
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
n->Data.MultiBlend1D.IndicesSorted = (ANIM_GRAPH_MULTI_BLEND_INDEX*)Allocator::Allocate(sizeof(ANIM_GRAPH_MULTI_BLEND_INDEX) * n->Data.MultiBlend1D.Count);
n->Assets.Resize(n->Data.MultiBlend1D.Count);
for (int32 i = 0; i < n->Data.MultiBlend1D.Count; i++)
{
n->Assets[i] = Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
n->Data.MultiBlend1D.IndicesSorted[i] = n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS;
n->Data.MultiBlend1D.IndicesSorted[i] = (ANIM_GRAPH_MULTI_BLEND_INDEX)(n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_INVALID);
}
Sorting::SortArray(n->Data.MultiBlend1D.IndicesSorted, ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS, &SortMultiBlend1D, n);
Sorting::SortArray(n->Data.MultiBlend1D.IndicesSorted, n->Data.MultiBlend1D.Count, &SortMultiBlend1D, n);
break;
}
// Multi Blend 2D
case 13:
{
ADD_BUCKET(MultiBlendBucketInit);
n->Data.MultiBlend1D.Count = (ANIM_GRAPH_MULTI_BLEND_INDEX)((n->Values.Count() - 4) / 2); // 4 node values + 2 per blend point
n->Data.MultiBlend2D.Length = -1;
// Get blend points locations
Array<Float2, FixedAllocation<ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS + 3>> vertices;
byte vertexIndexToAnimIndex[ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS];
const Float4 range = n->Values[0].AsFloat4();
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
Array<Float2> vertices;
Array<ANIM_GRAPH_MULTI_BLEND_INDEX> vertexToAnim;
n->Assets.Resize(n->Data.MultiBlend1D.Count);
for (int32 i = 0; i < n->Data.MultiBlend1D.Count; i++)
{
n->Assets[i] = (Asset*)Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
n->Assets[i] = Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
if (n->Assets[i])
{
const int32 vertexIndex = vertices.Count();
vertexIndexToAnimIndex[vertexIndex] = i;
vertices.Add(Float2(n->Values[i * 2 + 4].AsFloat4()));
}
else
{
vertexIndexToAnimIndex[i] = -1;
vertexToAnim.Add((ANIM_GRAPH_MULTI_BLEND_INDEX)i);
}
}
// Triangulate
Array<Delaunay2D::Triangle, FixedAllocation<ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS>> triangles;
Array<Delaunay2D::Triangle> triangles;
Delaunay2D::Triangulate(vertices, triangles);
if (triangles.Count() == 0)
{
@@ -227,15 +223,14 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
}
// Store triangles vertices indices (map the back to the anim node slots)
for (int32 i = 0; i < triangles.Count(); i++)
n->Data.MultiBlend2D.TrianglesCount = triangles.Count();
n->Data.MultiBlend2D.Triangles = (ANIM_GRAPH_MULTI_BLEND_INDEX*)Allocator::Allocate(triangles.Count() * 3 - sizeof(ANIM_GRAPH_MULTI_BLEND_INDEX));
for (int32 i = 0, t = 0; i < triangles.Count(); i++)
{
n->Data.MultiBlend2D.TrianglesP0[i] = vertexIndexToAnimIndex[triangles[i].Indices[0]];
n->Data.MultiBlend2D.TrianglesP1[i] = vertexIndexToAnimIndex[triangles[i].Indices[1]];
n->Data.MultiBlend2D.TrianglesP2[i] = vertexIndexToAnimIndex[triangles[i].Indices[2]];
n->Data.MultiBlend2D.Triangles[t++] = vertexToAnim[triangles[i].Indices[0]];
n->Data.MultiBlend2D.Triangles[t++] = vertexToAnim[triangles[i].Indices[1]];
n->Data.MultiBlend2D.Triangles[t++] = vertexToAnim[triangles[i].Indices[2]];
}
if (triangles.Count() < ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS)
n->Data.MultiBlend2D.TrianglesP0[triangles.Count()] = ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS;
break;
}
// Blend Pose
@@ -307,6 +302,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
data.Graph = nullptr;
break;
}
n->Assets.Resize(1);
n->Assets[0] = function;
// Load the graph
@@ -384,6 +380,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData)
{
int32 validTransitions = 0;
data.Transitions = nullptr;
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
{
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
@@ -398,8 +395,11 @@ void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Val
int32 transitionsCount;
stream.ReadInt32(&transitionsCount);
if (transitionsCount == 0)
return;
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
data.Transitions = (uint16*)Allocator::Allocate(transitionsCount * sizeof(uint16));
AnimGraphStateTransition transition;
for (int32 i = 0; i < transitionsCount; i++)
@@ -434,7 +434,6 @@ void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Val
// Skip disabled transitions
continue;
}
if (ruleSize != 0)
{
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
@@ -444,25 +443,19 @@ void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Val
continue;
}
}
if (transition.Destination == nullptr)
{
LOG(Warning, "Missing target node for the state machine transition.");
continue;
}
if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
{
LOG(Warning, "State uses too many transitions.");
continue;
}
data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
StateTransitions.Add(transition);
}
}
if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
// Last entry is invalid to indicate end
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
}
// Release data to don't use that memory
transitionsData = AnimGraphExecutor::Value::Null;

View File

@@ -102,6 +102,34 @@ AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(Ani
return out;
}
AnimGraphNode::~AnimGraphNode()
{
// Free allocated memory
switch (GroupID)
{
// Animation
case 9:
switch (TypeID)
{
// Multi Blend 1D
case 12:
Allocator::Free(Data.MultiBlend1D.IndicesSorted);
break;
// Multi Blend 2D
case 13:
Allocator::Free(Data.MultiBlend2D.Triangles);
break;
// State
case 20:
// Any State
case 34:
Allocator::Free(Data.State.Transitions);
break;
}
break;
}
}
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
{
auto& context = *AnimGraphExecutor::Context.Get();

View File

@@ -13,9 +13,8 @@
#define ANIM_GRAPH_PARAM_BASE_MODEL_ID Guid(1000, 0, 0, 0)
#define ANIM_GRAPH_IS_VALID_PTR(value) (value.Type.Type == VariantType::Pointer && value.AsPointer != nullptr)
#define ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS 14
#define ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS 32
#define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64
#define ANIM_GRAPH_MULTI_BLEND_INDEX byte
#define ANIM_GRAPH_MULTI_BLEND_INVALID 0xff
#define ANIM_GRAPH_MAX_CALL_STACK 100
#define ANIM_GRAPH_MAX_EVENTS 64
@@ -427,61 +426,31 @@ struct AnimGraphTransitionData
float Length;
};
class AnimGraphBox : public VisjectGraphBox
{
public:
AnimGraphBox()
{
}
AnimGraphBox(AnimGraphNode* parent, byte id, const VariantType::Types type)
: VisjectGraphBox(parent, id, type)
{
}
AnimGraphBox(AnimGraphNode* parent, byte id, const VariantType& type)
: VisjectGraphBox(parent, id, type)
{
}
};
typedef VisjectGraphBox AnimGraphBox;
class AnimGraphNode : public VisjectGraphNode<AnimGraphBox>
{
public:
struct MultiBlend1DData
{
/// <summary>
/// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
/// </summary>
// Amount of blend points.
ANIM_GRAPH_MULTI_BLEND_INDEX Count;
// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
float Length;
/// <summary>
/// The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS which is invalid.
/// </summary>
byte IndicesSorted[ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS];
// The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_INVALID which is invalid.
ANIM_GRAPH_MULTI_BLEND_INDEX* IndicesSorted;
};
struct MultiBlend2DData
{
/// <summary>
/// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
/// </summary>
// Amount of blend points.
ANIM_GRAPH_MULTI_BLEND_INDEX Count;
// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
float Length;
/// <summary>
/// Cached triangles vertices (vertex 0). Contains list of indices for triangles to use for blending.
/// </summary>
byte TrianglesP0[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS];
/// <summary>
/// Cached triangles vertices (vertex 1). Contains list of indices for triangles to use for blending.
/// </summary>
byte TrianglesP1[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS];
/// <summary>
/// Cached triangles vertices (vertex 2). Contains list of indices for triangles to use for blending.
/// </summary>
byte TrianglesP2[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS];
// Amount of triangles.
int32 TrianglesCount;
// Cached triangles vertices (3 bytes per triangle). Contains list of indices for triangles to use for blending.
ANIM_GRAPH_MULTI_BLEND_INDEX* Triangles;
};
struct StateMachineData
@@ -494,15 +463,11 @@ public:
struct StateBaseData
{
/// <summary>
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
/// </summary>
// The invalid transition valid used in Transitions to indicate invalid transition linkage.
const static uint16 InvalidTransitionIndex = MAX_uint16;
/// <summary>
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
/// </summary>
uint16 Transitions[ANIM_GRAPH_MAX_STATE_TRANSITIONS];
// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
uint16* Transitions;
};
struct StateData : StateBaseData
@@ -598,12 +563,14 @@ public:
{
}
~AnimGraphNode();
public:
/// <summary>
/// Gets the per-node node transformations cache (cached).
/// </summary>
/// <param name="executor">The Graph execution context.</param>
/// <returns>The modes data.</returns>
/// <returns>Nodes data.</returns>
AnimGraphImpulse* GetNodes(AnimGraphExecutor* executor);
};
@@ -800,7 +767,7 @@ struct AnimGraphContext
bool StackOverFlow;
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK> > NodePath;
Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> NodePath;
Dictionary<VisjectExecutor::Node*, VisjectExecutor::Graph*> Functions;
ChunkedArray<AnimGraphImpulse, 256> PoseCache;
int32 PoseCacheSize;

View File

@@ -247,6 +247,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{
// Per-channel bit to indicate which channels were used by nested
usedNodesThis.Resize(nodes->Nodes.Count());
usedNodesThis.SetAll(false);
usedNodes = &usedNodesThis;
}
@@ -286,11 +287,11 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
SkinnedModel::SkeletonMapping sourceMapping;
if (retarget)
sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++)
{
const int32 nodeToChannel = mapping.NodesMapping[i];
Transform& dstNode = nodes->Nodes[i];
Transform srcNode = emptyNodes->Nodes[i];
const int32 nodeToChannel = mapping.NodesMapping[nodeIndex];
Transform& dstNode = nodes->Nodes[nodeIndex];
Transform srcNode = emptyNodes->Nodes[nodeIndex];
if (nodeToChannel != -1)
{
// Calculate the animated node transformation
@@ -299,14 +300,14 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
// Optionally retarget animation into the skeleton used by the Anim Graph
if (retarget)
{
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, nodeIndex);
}
// Mark node as used
if (usedNodes)
usedNodes->Set(i, true);
usedNodes->Set(nodeIndex, true);
}
else if (usedNodes && usedNodes != &usedNodesThis)
else if (usedNodes && (usedNodes != &usedNodesThis || usedNodes->Get(nodeIndex)))
{
// Skip for nested animations so other one or top-level anim will update remaining nodes
continue;
@@ -585,7 +586,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState)
{
int32 transitionIndex = 0;
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
while (stateData.Transitions && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
{
const uint16 idx = stateData.Transitions[transitionIndex];
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
@@ -673,19 +674,20 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
// TODO: lock graph or graph asset here? make it thread safe
length = 0.0f;
for (int32 i = 0; i < ARRAY_COUNT(node->Assets); i++)
for (int32 i = 0; i < node->Assets.Count(); i++)
{
if (node->Assets[i])
auto& asset = node->Assets[i];
if (asset)
{
// TODO: maybe don't update if not all anims are loaded? just skip the node with the bind pose?
if (node->Assets[i]->WaitForLoaded())
if (asset->WaitForLoaded())
{
node->Assets[i] = nullptr;
asset = nullptr;
LOG(Warning, "Failed to load one of the animations.");
}
else
{
const auto anim = node->Assets[i].As<Animation>();
const auto anim = asset.As<Animation>();
const auto aData = node->Values[4 + i * 2].AsFloat4();
length = Math::Max(length, anim->GetLength() * Math::Abs(aData.W));
}
@@ -1268,13 +1270,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto& data = node->Data.MultiBlend1D;
// Check if not valid animation binded
if (data.IndicesSorted[0] == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS)
if (data.Count == 0)
break;
// Get axis X
float x = (float)tryGetValue(node->GetBox(4), Value::Zero);
x = Math::Clamp(x, range.X, range.Y);
// Add to trace
if (context.Data->EnableTracing)
{
auto& trace = context.AddTraceEvent(node);
trace.Value = x;
}
// Check if need to evaluate multi blend length
if (data.Length < 0)
ComputeMultiBlendLength(data.Length, node);
@@ -1292,7 +1301,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D");
// Find 2 animations to blend (line)
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS - 1; i++)
for (int32 i = 0; i < data.Count - 1; i++)
{
const auto a = data.IndicesSorted[i];
const auto b = data.IndicesSorted[i + 1];
@@ -1302,14 +1311,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto aData = node->Values[4 + a * 2].AsFloat4();
// Check single A case or the last valid animation
if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || b == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS)
if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || b == ANIM_GRAPH_MULTI_BLEND_INVALID)
{
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W);
break;
}
// Get B animation data
ASSERT(b != ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS);
ASSERT(b != ANIM_GRAPH_MULTI_BLEND_INVALID);
const auto bAnim = node->Assets[b].As<Animation>();
auto bData = node->Values[4 + b * 2].AsFloat4();
@@ -1357,7 +1366,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto& data = node->Data.MultiBlend2D;
// Check if not valid animation binded
if (data.TrianglesP0[0] == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS)
if (data.TrianglesCount == 0)
break;
// Get axis X
@@ -1368,6 +1377,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
float y = (float)tryGetValue(node->GetBox(5), Value::Zero);
y = Math::Clamp(y, range.Z, range.W);
// Add to trace
if (context.Data->EnableTracing)
{
auto& trace = context.AddTraceEvent(node);
const Half2 packed(x, y); // Pack xy into 32-bits
*(uint32*)&trace.Value = *(uint32*)&packed;
}
// Check if need to evaluate multi blend length
if (data.Length < 0)
ComputeMultiBlendLength(data.Length, node);
@@ -1390,20 +1407,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
Float2 bestPoint;
float bestWeight = 0.0f;
byte bestAnims[2];
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS && data.TrianglesP0[i] != ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
for (int32 i = 0, t = 0; i < data.TrianglesCount; i++)
{
// Get A animation data
const auto a = data.TrianglesP0[i];
const auto a = data.Triangles[t++];
const auto aAnim = node->Assets[a].As<Animation>();
const auto aData = node->Values[4 + a * 2].AsFloat4();
// Get B animation data
const auto b = data.TrianglesP1[i];
const auto b = data.Triangles[t++];
const auto bAnim = node->Assets[b].As<Animation>();
const auto bData = node->Values[4 + b * 2].AsFloat4();
// Get C animation data
const auto c = data.TrianglesP2[i];
const auto c = data.Triangles[t++];
const auto cAnim = node->Assets[c].As<Animation>();
const auto cData = node->Values[4 + c * 2].AsFloat4();

View File

@@ -10,7 +10,6 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target,
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection);
}
void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
{
// Calculate limb segment lengths
@@ -82,17 +81,20 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
Vector3 newEndEffectorPos = targetPosition;
Vector3 newMidJointPos = midJointPos;
if (toTargetLength >= totalLimbLength) {
if (toTargetLength >= totalLimbLength)
{
// Target is beyond the reach of the limb
Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
// Calculate the slight offset towards the pole vector
Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) {
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance)
{
slightBendDirection = Vector3::Up;
}
else {
else
{
slightBendDirection.Normalize();
}
@@ -140,7 +142,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
rootTransform.Orientation = newRootJointOrientation;
}
// Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
{
// Vector from mid joint to end effector (local Y-axis direction after rotation)
@@ -150,7 +151,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
// Vector from mid joint to end effector (local Y-axis direction)
Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
@@ -163,7 +163,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
localZ = Vector3::Cross(localX, localY).GetNormalized();
// Construct a rotation from the orthogonal basis vectors
// The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up

View File

@@ -35,7 +35,7 @@ const BytesContainer& SceneAnimation::LoadTimeline()
#if USE_EDITOR
bool SceneAnimation::SaveTimeline(BytesContainer& data)
bool SceneAnimation::SaveTimeline(const BytesContainer& data)
{
// Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error)
if (LastLoadFailed())

View File

@@ -454,10 +454,10 @@ public:
/// <summary>
/// Saves the serialized timeline data to the asset.
/// </summary>
/// <remarks>The cannot be used by virtual assets.</remarks>
/// <remarks>It cannot be used by virtual assets.</remarks>
/// <param name="data">The timeline data container.</param>
/// <returns><c>true</c> failed to save data; otherwise, <c>false</c>.</returns>
API_FUNCTION() bool SaveTimeline(BytesContainer& data);
API_FUNCTION() bool SaveTimeline(const BytesContainer& data);
#endif

View File

@@ -587,8 +587,8 @@ bool Asset::IsInternalType() const
bool Asset::onLoad(LoadAssetTask* task)
{
// It may fail when task is cancelled and new one is created later (don't crash but just end with an error)
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
// It may fail when task is cancelled and new one was created later (don't crash but just end with an error)
return true;
Locker.Lock();
@@ -601,7 +601,6 @@ bool Asset::onLoad(LoadAssetTask* task)
}
const bool isLoaded = result == LoadResult::Ok;
const bool failed = !isLoaded;
LoadState state = LoadState::Loaded;
Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
if (failed)
{

View File

@@ -103,7 +103,7 @@ public:
public:
/// <summary>
/// Gets the path to the asset storage file. In Editor it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers.
/// Gets the path to the asset storage file. In Editor, it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers.
/// </summary>
API_PROPERTY() virtual const String& GetPath() const = 0;

View File

@@ -550,7 +550,7 @@ BytesContainer Material::LoadSurface(bool createDefaultIfMissing)
#if USE_EDITOR
bool Material::SaveSurface(BytesContainer& data, const MaterialInfo& info)
bool Material::SaveSurface(const BytesContainer& data, const MaterialInfo& info)
{
// Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error)
if (LastLoadFailed())

View File

@@ -32,7 +32,7 @@ public:
/// <param name="data">The surface graph data.</param>
/// <param name="info">The material info structure.</param>
/// <returns>True if cannot save it, otherwise false.</returns>
API_FUNCTION() bool SaveSurface(BytesContainer& data, const MaterialInfo& info);
API_FUNCTION() bool SaveSurface(const BytesContainer& data, const MaterialInfo& info);
#endif

View File

@@ -507,8 +507,7 @@ public:
/// </summary>
/// <param name="asset">The asset.</param>
InitAssetTask(BinaryAsset* asset)
: ContentLoadTask(Type::Custom)
, _asset(asset)
: _asset(asset)
, _dataLock(asset->Storage->Lock())
{
}
@@ -527,8 +526,6 @@ protected:
AssetReference<BinaryAsset> ref = _asset.Get();
if (ref == nullptr)
return Result::MissingReferences;
// Prepare
auto storage = ref->Storage;
auto factory = (BinaryAssetFactoryBase*)Content::GetAssetFactory(ref->GetTypeName());
ASSERT(factory);
@@ -548,7 +545,6 @@ protected:
_dataLock.Release();
_asset = nullptr;
// Base
ContentLoadTask::OnEnd();
}
};

View File

@@ -403,7 +403,7 @@ ContentStats Content::GetStats()
return stats;
}
Asset* Content::LoadAsyncInternal(const StringView& internalPath, MClass* type)
Asset* Content::LoadAsyncInternal(const StringView& internalPath, const MClass* type)
{
CHECK_RETURN(type, nullptr);
const auto scriptingType = Scripting::FindScriptingType(type->GetFullName());
@@ -445,7 +445,7 @@ FLAXENGINE_API Asset* LoadAsset(const Guid& id, const ScriptingTypeHandle& type)
return Content::LoadAsync(id, type);
}
Asset* Content::LoadAsync(const StringView& path, MClass* type)
Asset* Content::LoadAsync(const StringView& path, const MClass* type)
{
CHECK_RETURN(type, nullptr);
const auto scriptingType = Scripting::FindScriptingType(type->GetFullName());
@@ -832,7 +832,7 @@ void Content::UnloadAsset(Asset* asset)
asset->DeleteObject();
}
Asset* Content::CreateVirtualAsset(MClass* type)
Asset* Content::CreateVirtualAsset(const MClass* type)
{
CHECK_RETURN(type, nullptr);
const auto scriptingType = Scripting::FindScriptingType(type->GetFullName());

View File

@@ -164,7 +164,7 @@ public:
/// <param name="path">The path of the asset (absolute or relative to the current workspace directory).</param>
/// <param name="type">The asset type. If loaded object has different type (excluding types derived from the given) the loading fails.</param>
/// <returns>Loaded asset or null if cannot</returns>
API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsync(const StringView& path, MClass* type);
API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsync(const StringView& path, const MClass* type);
/// <summary>
/// Loads asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async.
@@ -192,7 +192,7 @@ public:
/// <param name="internalPath">The path of the asset relative to the engine internal content (excluding the extension).</param>
/// <param name="type">The asset type. If loaded object has different type (excluding types derived from the given) the loading fails.</param>
/// <returns>The loaded asset or null if failed.</returns>
API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsyncInternal(const StringView& internalPath, MClass* type);
API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsyncInternal(const StringView& internalPath, const MClass* type);
/// <summary>
/// Loads internal engine asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async.
@@ -342,7 +342,7 @@ public:
/// </summary>
/// <param name="type">The asset type klass.</param>
/// <returns>Created asset or null if failed.</returns>
API_FUNCTION() static Asset* CreateVirtualAsset(API_PARAM(Attributes="TypeReference(typeof(Asset))") MClass* type);
API_FUNCTION() static Asset* CreateVirtualAsset(API_PARAM(Attributes="TypeReference(typeof(Asset))") const MClass* type);
/// <summary>
/// Creates temporary and virtual asset of the given type.

View File

@@ -15,52 +15,11 @@ class ContentLoadTask : public Task
friend LoadingThread;
public:
/// <summary>
/// Describes work type
/// </summary>
DECLARE_ENUM_3(Type, Custom, LoadAsset, LoadAssetData);
/// <summary>
/// Describes work result value
/// </summary>
DECLARE_ENUM_5(Result, Ok, AssetLoadError, MissingReferences, LoadDataError, TaskFailed);
private:
/// <summary>
/// Task type
/// </summary>
Type _type;
protected:
/// <summary>
/// Initializes a new instance of the <see cref="ContentLoadTask"/> class.
/// </summary>
/// <param name="type">The task type.</param>
ContentLoadTask(const Type type)
: _type(type)
{
}
public:
/// <summary>
/// Gets a task type.
/// </summary>
FORCE_INLINE Type GetType() const
{
return _type;
}
public:
/// <summary>
/// Checks if async task is loading given asset resource
/// </summary>
/// <param name="asset">Target asset to check</param>
/// <returns>True if is loading that asset, otherwise false</returns>
bool IsLoading(Asset* asset) const
{
return _type == Type::LoadAsset && HasReference((Object*)asset);
}
protected:
virtual Result run() = 0;

View File

@@ -4,6 +4,7 @@
#include "ContentLoadTask.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/CPUInfo.h"
#include "Engine/Platform/Thread.h"
#include "Engine/Platform/ConditionVariable.h"
@@ -212,7 +213,7 @@ void ContentLoadingManagerService::Dispose()
String ContentLoadTask::ToString() const
{
return String::Format(TEXT("Content Load Task {0} ({1})"), ToString(GetType()), (int32)GetState());
return String::Format(TEXT("Content Load Task ({})"), (int32)GetState());
}
void ContentLoadTask::Enqueue()

View File

@@ -15,28 +15,24 @@
class LoadAssetDataTask : public ContentLoadTask
{
private:
WeakAssetReference<BinaryAsset> _asset; // Don't keep ref to the asset (so it can be unloaded if none using it, task will fail then)
AssetChunksFlag _chunks;
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Initializes a new instance of the <see cref="LoadAssetDataTask"/> class.
/// </summary>
/// <param name="asset">The asset to load.</param>
/// <param name="chunks">The chunks to load.</param>
LoadAssetDataTask(BinaryAsset* asset, AssetChunksFlag chunks)
: ContentLoadTask(Type::LoadAssetData)
, _asset(asset)
: _asset(asset)
, _chunks(chunks)
, _dataLock(asset->Storage->Lock())
{
}
public:
// [ContentLoadTask]
bool HasReference(Object* obj) const override
{
@@ -44,7 +40,6 @@ public:
}
protected:
// [ContentLoadTask]
Result run() override
{

View File

@@ -15,38 +15,35 @@
class LoadAssetTask : public ContentLoadTask
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="LoadAssetTask"/> class.
/// </summary>
/// <param name="asset">The asset to load.</param>
LoadAssetTask(Asset* asset)
: ContentLoadTask(Type::LoadAsset)
, Asset(asset)
: Asset(asset)
{
}
~LoadAssetTask()
{
if (Asset)
auto asset = Asset.Get();
if (asset)
{
Asset->Locker.Lock();
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
asset->Locker.Lock();
if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this)
{
Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
Platform::AtomicStore(&Asset->_loadingTask, 0);
Platform::AtomicStore(&asset->_loadState, (int64)Asset::LoadState::LoadFailed);
Platform::AtomicStore(&asset->_loadingTask, 0);
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
}
Asset->Locker.Unlock();
asset->Locker.Unlock();
}
}
public:
WeakAssetReference<Asset> Asset;
public:
// [ContentLoadTask]
bool HasReference(Object* obj) const override
{
@@ -54,7 +51,6 @@ public:
}
protected:
// [ContentLoadTask]
Result run() override
{
@@ -68,32 +64,36 @@ protected:
// Call loading
if (ref->onLoad(this))
return Result::AssetLoadError;
return Result::Ok;
}
void OnFail() override
{
if (Asset)
auto asset = Asset.Get();
if (asset)
{
Asset->Locker.Lock();
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock();
Asset = nullptr;
asset->Locker.Lock();
if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this)
Platform::AtomicStore(&asset->_loadingTask, 0);
asset->Locker.Unlock();
}
// Base
ContentLoadTask::OnFail();
}
void OnEnd() override
{
if (Asset)
auto asset = Asset.Get();
if (asset)
{
Asset->Locker.Lock();
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock();
Asset = nullptr;
asset->Locker.Lock();
if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this)
Platform::AtomicStore(&asset->_loadingTask, 0);
asset->Locker.Unlock();
asset = nullptr;
}
// Base

View File

@@ -440,7 +440,7 @@ public:
/// <param name="silentMode">In silent mode don't reload opened storage container that is using target file.</param>
/// <param name="customData">Custom options.</param>
/// <returns>True if cannot create package, otherwise false</returns>
FORCE_INLINE static bool Create(const StringView& path, AssetInitData& data, bool silentMode = false, const CustomData* customData = nullptr)
FORCE_INLINE static bool Create(const StringView& path, const AssetInitData& data, bool silentMode = false, const CustomData* customData = nullptr)
{
return Create(path, &data, 1, silentMode, customData);
}

View File

@@ -467,7 +467,7 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
writer.StartObject();
eKey.Value.Instance->Serialize(writer, nullptr);
writer.EndObject();
cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize());
cloneKey.Value.JsonData.Set(buffer.GetString(), (int32)buffer.GetSize());
}
}
}

View File

@@ -1281,8 +1281,8 @@ namespace FlaxEngine
/// <returns>The position snapped to the grid.</returns>
public static Float2 SnapToGrid(Float2 pos, Float2 gridSize)
{
pos.X = Mathf.Ceil((pos.X - (gridSize.X * 0.5f)) / gridSize.Y) * gridSize.X;
pos.Y = Mathf.Ceil((pos.Y - (gridSize.Y * 0.5f)) / gridSize.X) * gridSize.Y;
pos.X = Mathf.Ceil((pos.X - (gridSize.X * 0.5f)) / gridSize.X) * gridSize.X;
pos.Y = Mathf.Ceil((pos.Y - (gridSize.Y * 0.5f)) / gridSize.Y) * gridSize.Y;
return pos;
}

View File

@@ -774,7 +774,7 @@ void Matrix::Transformation(const Float3& scalingCenter, const Quaternion& scali
result = Translation(-scalingCenter) * Transpose(sr) * Scaling(scaling) * sr * Translation(scalingCenter) * Translation(-rotationCenter) * RotationQuaternion(rotation) * Translation(rotationCenter) * Translation(translation);
}
void Matrix::Transformation2D(Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result)
void Matrix::Transformation2D(const Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result)
{
result = Translation((Float3)-scalingCenter) * RotationZ(-scalingRotation) * Scaling((Float3)scaling) * RotationZ(scalingRotation) * Translation((Float3)scalingCenter) * Translation((Float3)-rotationCenter) * RotationZ(rotation) * Translation((Float3)rotationCenter) * Translation((Float3)translation);
result.M33 = 1.0f;

View File

@@ -941,7 +941,7 @@ public:
// @param rotation The rotation of the transformation.
// @param translation The translation factor of the transformation.
// @param result When the method completes, contains the created transformation matrix.
static void Transformation2D(Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result);
static void Transformation2D(const Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result);
// Creates a world matrix with the specified parameters.
// @param position Position of the object. This value is used in translation operations.

View File

@@ -99,7 +99,7 @@ Rectangle Rectangle::FromPoints(const Float2& p1, const Float2& p2)
return Rectangle(upperLeft, Math::Max(rightBottom - upperLeft, Float2::Zero));
}
Rectangle Rectangle::FromPoints(Float2* points, int32 pointsCount)
Rectangle Rectangle::FromPoints(const Float2* points, int32 pointsCount)
{
ASSERT(pointsCount > 0);
Float2 upperLeft = points[0];

View File

@@ -287,7 +287,7 @@ public:
// @returns Rectangle that contains both p1 and p2
static Rectangle FromPoints(const Float2& p1, const Float2& p2);
static Rectangle FromPoints(Float2* points, int32 pointsCount);
static Rectangle FromPoints(const Float2* points, int32 pointsCount);
};
template<>

View File

@@ -320,7 +320,7 @@ public:
/// </summary>
/// <param name="data">The data.</param>
/// <param name="length">The length.</param>
void Append(T* data, int32 length)
void Append(const T* data, int32 length)
{
if (length <= 0)
return;

View File

@@ -112,7 +112,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName)
}
}
VariantType::VariantType(Types type, MClass* klass)
VariantType::VariantType(Types type, const MClass* klass)
{
Type = type;
TypeName = nullptr;

View File

@@ -105,7 +105,7 @@ public:
explicit VariantType(Types type, const StringView& typeName);
explicit VariantType(Types type, const StringAnsiView& typeName);
explicit VariantType(Types type, MClass* klass);
explicit VariantType(Types type, const MClass* klass);
explicit VariantType(const StringAnsiView& typeName);
VariantType(const VariantType& other);
VariantType(VariantType&& other) noexcept;

View File

@@ -60,6 +60,7 @@ namespace EngineImpl
DateTime Engine::StartupTime;
bool Engine::HasFocus = false;
uint64 Engine::UpdateCount = 0;
uint64 Engine::FrameCount = 0;
Action Engine::FixedUpdate;
Action Engine::Update;
@@ -156,7 +157,7 @@ int32 Engine::Main(const Char* cmdLine)
#endif
Log::Logger::WriteFloor();
LOG_FLUSH();
Time::OnBeforeRun();
Time::Synchronize();
EngineImpl::IsReady = true;
// Main engine loop
@@ -191,8 +192,11 @@ int32 Engine::Main(const Char* cmdLine)
OnUnpause();
}
// Use the same time for all ticks to improve synchronization
const double time = Platform::GetTimeSeconds();
// Update game logic
if (Time::OnBeginUpdate())
if (Time::OnBeginUpdate(time))
{
OnUpdate();
OnLateUpdate();
@@ -200,7 +204,7 @@ int32 Engine::Main(const Char* cmdLine)
}
// Start physics simulation
if (Time::OnBeginPhysics())
if (Time::OnBeginPhysics(time))
{
OnFixedUpdate();
OnLateFixedUpdate();
@@ -208,7 +212,7 @@ int32 Engine::Main(const Char* cmdLine)
}
// Draw frame
if (Time::OnBeginDraw())
if (Time::OnBeginDraw(time))
{
OnDraw();
Time::OnEndDraw();
@@ -296,6 +300,8 @@ void Engine::OnUpdate()
{
PROFILE_CPU_NAMED("Update");
UpdateCount++;
// Update application (will gather data and other platform related events)
{
PROFILE_CPU_NAMED("Platform.Tick");
@@ -465,7 +471,7 @@ void Engine::OnUnpause()
LOG(Info, "App unpaused");
Unpause();
Time::OnBeforeRun();
Time::Synchronize();
}
void Engine::OnExit()

View File

@@ -28,7 +28,12 @@ public:
API_FIELD(ReadOnly) static bool HasFocus;
/// <summary>
/// Gets the current frame count since the start of the game.
/// Gets the current update counter since the start of the game.
/// </summary>
API_FIELD(ReadOnly) static uint64 UpdateCount;
/// <summary>
/// Gets the current frame (drawing) count since the start of the game.
/// </summary>
API_FIELD(ReadOnly) static uint64 FrameCount;

View File

@@ -54,7 +54,7 @@ void TimeSettings::Apply()
::MaxUpdateDeltaTime = MaxUpdateDeltaTime;
}
void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
void Time::TickData::Synchronize(float targetFps, double currentTime)
{
Time = UnscaledTime = TimeSpan::Zero();
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
@@ -72,10 +72,9 @@ void Time::TickData::OnReset(float targetFps, double currentTime)
LastEnd = currentTime;
}
bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime)
bool Time::TickData::OnTickBegin(double time, float targetFps, float maxDeltaTime)
{
// Check if can perform a tick
const double time = Platform::GetTimeSeconds();
double deltaTime;
if (FixedDeltaTimeEnable)
{
@@ -126,10 +125,9 @@ void Time::TickData::Advance(double time, double deltaTime)
TicksCount++;
}
bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime)
bool Time::FixedStepTickData::OnTickBegin(double time, float targetFps, float maxDeltaTime)
{
// Check if can perform a tick
double time = Platform::GetTimeSeconds();
double deltaTime, minDeltaTime;
if (FixedDeltaTimeEnable)
{
@@ -240,18 +238,18 @@ void Time::SetFixedDeltaTime(bool enable, float value)
FixedDeltaTimeValue = value;
}
void Time::OnBeforeRun()
void Time::Synchronize()
{
// Initialize tick data (based on a time settings)
const double time = Platform::GetTimeSeconds();
Update.OnBeforeRun(UpdateFPS, time);
Physics.OnBeforeRun(PhysicsFPS, time);
Draw.OnBeforeRun(DrawFPS, time);
Update.Synchronize(UpdateFPS, time);
Physics.Synchronize(PhysicsFPS, time);
Draw.Synchronize(DrawFPS, time);
}
bool Time::OnBeginUpdate()
bool Time::OnBeginUpdate(double time)
{
if (Update.OnTickBegin(UpdateFPS, MaxUpdateDeltaTime))
if (Update.OnTickBegin(time, UpdateFPS, MaxUpdateDeltaTime))
{
Current = &Update;
return true;
@@ -259,9 +257,9 @@ bool Time::OnBeginUpdate()
return false;
}
bool Time::OnBeginPhysics()
bool Time::OnBeginPhysics(double time)
{
if (Physics.OnTickBegin(PhysicsFPS, _physicsMaxDeltaTime))
if (Physics.OnTickBegin(time, PhysicsFPS, _physicsMaxDeltaTime))
{
Current = &Physics;
return true;
@@ -269,9 +267,9 @@ bool Time::OnBeginPhysics()
return false;
}
bool Time::OnBeginDraw()
bool Time::OnBeginDraw(double time)
{
if (Draw.OnTickBegin(DrawFPS, 1.0f))
if (Draw.OnTickBegin(time, DrawFPS, 1.0f))
{
Current = &Draw;
return true;

View File

@@ -12,12 +12,12 @@
/// </summary>
API_CLASS(Static) class FLAXENGINE_API Time
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time);
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time);
friend class Engine;
friend class TimeService;
friend class PhysicsSettings;
public:
public:
/// <summary>
/// Engine subsystem updating data.
/// Used to invoke game logic updates, physics updates and rendering with possibly different frequencies.
@@ -25,7 +25,6 @@ public:
class FLAXENGINE_API TickData
{
public:
virtual ~TickData() = default;
/// <summary>
@@ -75,14 +74,12 @@ public:
TimeSpan UnscaledTime;
public:
virtual void OnBeforeRun(float targetFps, double currentTime);
virtual void Synchronize(float targetFps, double currentTime);
virtual void OnReset(float targetFps, double currentTime);
virtual bool OnTickBegin(float targetFps, float maxDeltaTime);
virtual bool OnTickBegin(double time, float targetFps, float maxDeltaTime);
virtual void OnTickEnd();
protected:
void Advance(double time, double deltaTime);
};
@@ -92,25 +89,21 @@ public:
class FixedStepTickData : public TickData
{
public:
/// <summary>
/// The last few ticks delta times. Used to check if can use fixed steps or whenever is running slowly so should use normal stepping.
/// </summary>
SamplesBuffer<double, 4> Samples;
public:
// [TickData]
bool OnTickBegin(float targetFps, float maxDeltaTime) override;
bool OnTickBegin(double time, float targetFps, float maxDeltaTime) override;
};
private:
static bool _gamePaused;
static float _physicsMaxDeltaTime;
public:
/// <summary>
/// The time at which the game started (UTC local).
/// </summary>
@@ -140,7 +133,6 @@ public:
API_FIELD() static float TimeScale;
public:
/// <summary>
/// The game logic updating data.
/// </summary>
@@ -162,7 +154,6 @@ public:
static TickData* Current;
public:
/// <summary>
/// Gets the current tick data (safety so returns Update tick data if no active).
/// </summary>
@@ -225,15 +216,17 @@ public:
/// <param name="value">The fixed draw/update rate for the time.</param>
API_FUNCTION() static void SetFixedDeltaTime(bool enable, float value);
private:
/// <summary>
/// Synchronizes update, fixed update and draw. Resets any pending deltas for fresh ticking in sync.
/// </summary>
API_FUNCTION() static void Synchronize();
private:
// Methods used by the Engine class
static void OnBeforeRun();
static bool OnBeginUpdate();
static bool OnBeginPhysics();
static bool OnBeginDraw();
static bool OnBeginUpdate(double time);
static bool OnBeginPhysics(double time);
static bool OnBeginDraw(double time);
static void OnEndUpdate();
static void OnEndPhysics();

View File

@@ -115,7 +115,7 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan
e = &result[key];
ASSERT_LOW_LAYER(key.Mat);
e->DrawCall.Material = key.Mat;
e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
}
// Add instance to the draw batch
@@ -1172,7 +1172,7 @@ void Foliage::Draw(RenderContext& renderContext)
draw.ForcedLOD = -1;
draw.SortOrder = 0;
draw.VertexColors = nullptr;
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex);
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &instance.Lightmap.UVsArea;
draw.Buffer = &type.Entries;
draw.World = &world;

View File

@@ -182,7 +182,7 @@ public:
/// <param name="elementsCount">The elements count.</param>
/// <param name="data">The data.</param>
/// <returns>The buffer description.</returns>
static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, void* data)
static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, const void* data)
{
return Buffer(elementsCount * elementStride, GPUBufferFlags::VertexBuffer, PixelFormat::Unknown, data, elementStride, GPUResourceUsage::Default);
}
@@ -217,7 +217,7 @@ public:
/// <param name="elementsCount">The elements count.</param>
/// <param name="data">The data.</param>
/// <returns>The buffer description.</returns>
static GPUBufferDescription Index(int32 elementStride, int32 elementsCount, void* data)
static GPUBufferDescription Index(int32 elementStride, int32 elementsCount, const void* data)
{
const auto format = elementStride == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt;
return Buffer(elementsCount * elementStride, GPUBufferFlags::IndexBuffer, format, data, elementStride, GPUResourceUsage::Default);

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 164
#define MATERIAL_GRAPH_VERSION 165
class Material;
class GPUShader;

View File

@@ -24,7 +24,7 @@
namespace
{
template<typename IndexType>
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, Float3* vertices, IndexType* triangles, Float3* normals, Float3* tangents, Float2* uvs, Color32* colors)
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const IndexType* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
auto model = mesh->GetModel();
CHECK_RETURN(model && model->IsVirtual(), true);
@@ -63,40 +63,39 @@ namespace
const auto t = Float1010102(Float3::UnitX);
for (uint32 i = 0; i < vertexCount; i++)
{
vb1[i].Normal = n;
vb1[i].Tangent = t;
vb1.Get()[i].Normal = n;
vb1.Get()[i].Tangent = t;
}
}
if (uvs)
{
for (uint32 i = 0; i < vertexCount; i++)
vb1[i].TexCoord = Half2(uvs[i]);
vb1.Get()[i].TexCoord = Half2(uvs[i]);
}
else
{
auto v = Half2::Zero;
for (uint32 i = 0; i < vertexCount; i++)
vb1[i].TexCoord = v;
vb1.Get()[i].TexCoord = v;
}
{
auto v = Half2::Zero;
for (uint32 i = 0; i < vertexCount; i++)
vb1[i].LightmapUVs = v;
vb1.Get()[i].LightmapUVs = v;
}
if (colors)
{
vb2.Resize(vertexCount);
for (uint32 i = 0; i < vertexCount; i++)
vb2[i].Color = colors[i];
vb2.Get()[i].Color = colors[i];
}
return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles);
}
#if !COMPILE_WITHOUT_CSHARP
template<typename IndexType>
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj)
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
{
ASSERT((uint32)MCore::Array::GetLength(verticesObj) >= vertexCount);
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
@@ -110,7 +109,7 @@ namespace
}
template<typename IndexType>
bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MArray* trianglesObj)
bool UpdateTriangles(Mesh* mesh, int32 triangleCount, const MArray* trianglesObj)
{
const auto model = mesh->GetModel();
ASSERT(model && model->IsVirtual() && trianglesObj);
@@ -121,7 +120,6 @@ namespace
return mesh->UpdateTriangles(triangleCount, ib);
}
#endif
}
@@ -130,7 +128,7 @@ bool Mesh::HasVertexColors() const
return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated();
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices)
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices)
{
auto model = (Model*)_model;
@@ -145,7 +143,7 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType*
// Calculate mesh bounds
BoundingBox bounds;
BoundingBox::FromPoints((Float3*)vb0, vertexCount, bounds);
BoundingBox::FromPoints((const Float3*)vb0, vertexCount, bounds);
SetBounds(bounds);
// Send event (actors using this model can update bounds, etc.)
@@ -155,17 +153,17 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType*
return failed;
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint16* triangles, Float3* normals, Float3* tangents, Float2* uvs, Color32* colors)
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh<uint16>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint32* triangles, Float3* normals, Float3* tangents, Float2* uvs, Color32* colors)
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh<uint32>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
}
bool Mesh::UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices)
bool Mesh::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices)
{
// Cache data
uint32 indicesCount = triangleCount * 3;
@@ -217,7 +215,7 @@ Mesh::~Mesh()
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
}
bool Mesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer)
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
{
// Cache data
uint32 indicesCount = triangles * 3;
@@ -697,22 +695,22 @@ ScriptingObject* Mesh::GetParentModel()
#if !COMPILE_WITHOUT_CSHARP
bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj)
bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
{
return ::UpdateMesh<uint32>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj)
bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
{
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
bool Mesh::UpdateTrianglesUInt(int32 triangleCount, MArray* trianglesObj)
bool Mesh::UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj)
{
return ::UpdateTriangles<uint32>(this, triangleCount, trianglesObj);
}
bool Mesh::UpdateTrianglesUShort(int32 triangleCount, MArray* trianglesObj)
bool Mesh::UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj)
{
return ::UpdateTriangles<uint16>(this, triangleCount, trianglesObj);
}

View File

@@ -112,7 +112,7 @@ public:
/// <param name="vb2">The third vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint32* ib)
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
}
@@ -127,7 +127,7 @@ public:
/// <param name="vb2">The third vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint16* ib)
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
}
@@ -145,7 +145,7 @@ public:
/// <param name="ib">The index buffer in clockwise order.</param>
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices);
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
@@ -161,7 +161,7 @@ public:
/// <param name="uvs">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint16* triangles, Float3* normals = nullptr, Float3* tangents = nullptr, Float2* uvs = nullptr, Color32* colors = nullptr);
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
@@ -177,7 +177,7 @@ public:
/// <param name="uvs">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint32* triangles, Float3* normals = nullptr, Float3* tangents = nullptr, Float2* uvs = nullptr, Color32* colors = nullptr);
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
public:
/// <summary>
@@ -186,7 +186,7 @@ public:
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="ib">The index buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint32* ib)
FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint32* ib)
{
return UpdateTriangles(triangleCount, ib, false);
}
@@ -197,7 +197,7 @@ public:
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="ib">The index buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint16* ib)
FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint16* ib)
{
return UpdateTriangles(triangleCount, ib, true);
}
@@ -209,7 +209,7 @@ public:
/// <param name="ib">The index buffer.</param>
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices);
bool UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices);
public:
/// <summary>
@@ -235,7 +235,7 @@ public:
/// <param name="ib">Index buffer data</param>
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
/// <returns>True if cannot load data, otherwise false.</returns>
bool Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer);
bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer);
/// <summary>
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
@@ -315,10 +315,10 @@ private:
// Internal bindings
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, MArray* trianglesObj);
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, MArray* trianglesObj);
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj);
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
#endif
};

View File

@@ -508,6 +508,31 @@ void MeshData::TransformBuffer(const Matrix& matrix)
Matrix::Invert(matrix, inverseTransposeMatrix);
Matrix::Transpose(inverseTransposeMatrix, inverseTransposeMatrix);
// Transform blend shapes
for (auto& blendShape : BlendShapes)
{
const auto vv = blendShape.Vertices.Get();
for (int32 i = 0; i < blendShape.Vertices.Count(); i++)
{
auto& v = vv[i];
Float3 p = Positions[v.VertexIndex];
Float3 vp = p + v.PositionDelta;
Float3::Transform(vp, matrix, vp);
Float3::Transform(p, matrix, p);
v.PositionDelta = vp - p;
Float3 n = Normals[v.VertexIndex];
Float3 vn = n + v.NormalDelta;
vn.Normalize();
Float3::TransformNormal(vn, inverseTransposeMatrix, vn);
vn.Normalize();
Float3::TransformNormal(n, inverseTransposeMatrix, n);
n.Normalize();
v.NormalDelta = vn - n;
}
}
// Transform positions
const auto pp = Positions.Get();
for (int32 i = 0; i < Positions.Count(); i++)
@@ -531,18 +556,6 @@ void MeshData::TransformBuffer(const Matrix& matrix)
Float3::TransformNormal(t, inverseTransposeMatrix, t);
t.Normalize();
}
// Transform blend shapes
for (auto& blendShape : BlendShapes)
{
for (int32 i = 0; i < blendShape.Vertices.Count(); i++)
{
auto& v = blendShape.Vertices[i];
Float3::Transform(v.PositionDelta, matrix, v.PositionDelta);
Float3::TransformNormal(v.NormalDelta, inverseTransposeMatrix, v.NormalDelta);
v.NormalDelta.Normalize();
}
}
}
void MeshData::NormalizeBlendWeights()
@@ -550,9 +563,10 @@ void MeshData::NormalizeBlendWeights()
ASSERT(Positions.Count() == BlendWeights.Count());
for (int32 i = 0; i < Positions.Count(); i++)
{
const float sum = BlendWeights[i].SumValues();
Float4& weights = BlendWeights.Get()[i];
const float sum = weights.SumValues();
const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f;
BlendWeights[i] *= invSum;
weights *= invSum;
}
}

View File

@@ -105,7 +105,7 @@ SkinnedMesh::~SkinnedMesh()
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
}
bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer)
bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer)
{
// Cache data
uint32 indicesCount = triangles * 3;
@@ -159,7 +159,7 @@ void SkinnedMesh::Unload()
_use16BitIndexBuffer = false;
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices)
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices)
{
auto model = (SkinnedModel*)_model;
@@ -169,7 +169,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0Skinne
{
// Calculate mesh bounds
BoundingBox bounds;
BoundingBox::FromPoints((Float3*)vb, vertexCount, bounds);
BoundingBox::FromPoints((const Float3*)vb, vertexCount, bounds);
SetBounds(bounds);
// Send event (actors using this model can update bounds, etc.)
@@ -429,7 +429,7 @@ ScriptingObject* SkinnedMesh::GetParentModel()
#if !COMPILE_WITHOUT_CSHARP
template<typename IndexType>
bool UpdateMesh(SkinnedMesh* mesh, MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj)
bool UpdateMesh(SkinnedMesh* mesh, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
{
auto model = mesh->GetSkinnedModel();
ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj && blendIndicesObj && blendWeightsObj);
@@ -505,12 +505,12 @@ bool UpdateMesh(SkinnedMesh* mesh, MArray* verticesObj, MArray* trianglesObj, MA
return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib);
}
bool SkinnedMesh::UpdateMeshUInt(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj)
bool SkinnedMesh::UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
{
return ::UpdateMesh<uint32>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
}
bool SkinnedMesh::UpdateMeshUShort(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj)
bool SkinnedMesh::UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
{
return ::UpdateMesh<uint16>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
}

View File

@@ -76,7 +76,7 @@ public:
/// <param name="ib">Index buffer data</param>
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
/// <returns>True if cannot load data, otherwise false.</returns>
bool Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer);
bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer);
/// <summary>
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
@@ -92,7 +92,7 @@ public:
/// <param name="vb">The vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, int32* ib)
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
}
@@ -105,7 +105,7 @@ public:
/// <param name="vb">The vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint32* ib)
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
}
@@ -118,7 +118,7 @@ public:
/// <param name="vb">The vertex buffer data.</param>
/// <param name="ib">The index buffer, clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint16* ib)
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
}
@@ -132,7 +132,7 @@ public:
/// <param name="ib">The index buffer in clockwise order.</param>
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices);
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices);
public:
/// <summary>
@@ -188,8 +188,8 @@ private:
// Internal bindings
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) bool UpdateMeshUInt(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj);
API_FUNCTION(NoProxy) bool UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj);
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
#endif
};

View File

@@ -131,14 +131,14 @@ void GPUTextureViewVulkan::Release()
{
Device->OnImageViewDestroy(ViewFramebuffer);
Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, ViewFramebuffer);
ViewFramebuffer = VK_NULL_HANDLE;
}
ViewFramebuffer = VK_NULL_HANDLE;
if (ViewSRV != View && ViewSRV != VK_NULL_HANDLE)
{
Device->OnImageViewDestroy(ViewSRV);
Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, ViewSRV);
ViewSRV = VK_NULL_HANDLE;
}
ViewSRV = VK_NULL_HANDLE;
Device->OnImageViewDestroy(View);
Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, View);

View File

@@ -55,10 +55,10 @@ void AnimatedModel::UpdateAnimation()
|| !IsActiveInHierarchy()
|| SkinnedModel == nullptr
|| !SkinnedModel->IsLoaded()
|| _lastUpdateFrame == Engine::FrameCount
|| _lastUpdateFrame == Engine::UpdateCount
|| _masterPose)
return;
_lastUpdateFrame = Engine::FrameCount;
_lastUpdateFrame = Engine::UpdateCount;
if (AnimationGraph && AnimationGraph->IsLoaded() && AnimationGraph->Graph.IsReady())
{
@@ -113,7 +113,7 @@ void AnimatedModel::PreInitSkinningData()
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
{
auto& bone = skeleton.Bones[boneIndex];
identityMatrices[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex];
identityMatrices.Get()[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex];
}
_skinningData.SetData(identityMatrices.Get(), true);
@@ -640,7 +640,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta;
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta * q.Second;
vertex.Normal = normal * 0.5f + 0.5f;
}
}

View File

@@ -346,7 +346,7 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
draw.DrawModes = DrawModes;
@@ -380,7 +380,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch)
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
draw.DrawModes = DrawModes;

View File

@@ -628,6 +628,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
LOG(Warning, "Could not build Detour navmesh.");
return true;
}
ASSERT_LOW_LAYER(navDataSize > 4 && *(uint32*)navData == DT_NAVMESH_MAGIC); // Sanity check for Detour header
{
PROFILE_CPU_NAMED("Navigation.CreateTile");

View File

@@ -17,7 +17,7 @@ void NavMeshData::Save(WriteStream& stream)
// Write tiles
for (int32 tileIndex = 0; tileIndex < Tiles.Count(); tileIndex++)
{
auto& tile = Tiles[tileIndex];
auto& tile = Tiles.Get()[tileIndex];
// Write tile header
NavMeshTileDataHeader tileHeader;
@@ -41,7 +41,6 @@ void NavMeshData::Save(WriteStream& stream)
bool NavMeshData::Load(BytesContainer& data, bool copyData)
{
// No data
if (data.Length() < sizeof(NavMeshDataHeader))
{
LOG(Warning, "No valid navmesh data.");
@@ -50,7 +49,7 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData)
MemoryReadStream stream(data.Get(), data.Length());
// Read header
const auto header = stream.Move<NavMeshDataHeader>(1);
const auto header = stream.Move<NavMeshDataHeader>();
if (header->Version != 1)
{
LOG(Warning, "Invalid valid navmesh data version {0}.", header->Version);
@@ -67,10 +66,10 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData)
// Read tiles
for (int32 tileIndex = 0; tileIndex < Tiles.Count(); tileIndex++)
{
auto& tile = Tiles[tileIndex];
auto& tile = Tiles.Get()[tileIndex];
// Read tile header
const auto tileHeader = stream.Move<NavMeshTileDataHeader>(1);
const auto tileHeader = stream.Move<NavMeshTileDataHeader>();
if (tileHeader->DataSize <= 0)
{
LOG(Warning, "Invalid navmesh tile data.");
@@ -83,13 +82,9 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData)
// Read tile data
const auto tileData = stream.Move<byte>(tileHeader->DataSize);
if (copyData)
{
tile.Data.Copy(tileData, tileHeader->DataSize);
}
else
{
tile.Data.Link(tileData, tileHeader->DataSize);
}
}
return false;

View File

@@ -60,17 +60,12 @@ bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit
Float3::Transform(startPosition, Properties.Rotation, startPositionNavMesh);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr)))
return false;
}
Float3 hitPosition, hitNormal;
if (!dtStatusSucceed(query->findDistanceToWall(startPoly, &startPositionNavMesh.X, maxDistance, &filter, &hitInfo.Distance, &hitPosition.X, &hitNormal.X)))
{
return false;
}
Quaternion invRotation;
Quaternion::Invert(Properties.Rotation, invRotation);
@@ -98,17 +93,11 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo
Float3::Transform(endPosition, Properties.Rotation, endPositionNavMesh);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr)))
return false;
}
dtPolyRef endPoly = 0;
query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr);
if (!endPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr)))
return false;
}
dtPolyRef path[NAV_MESH_PATH_MAX_SIZE];
int32 pathSize;
@@ -166,30 +155,19 @@ bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPo
Float3::Transform(endPosition, Properties.Rotation, endPositionNavMesh);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr)))
return false;
}
dtPolyRef endPoly = 0;
query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr);
if (!endPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr)))
return false;
}
dtPolyRef path[NAV_MESH_PATH_MAX_SIZE];
int32 pathSize;
const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE);
if (dtStatusFailed(findPathStatus))
{
return false;
}
if (dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT))
{
return false;
}
return true;
}
@@ -210,11 +188,8 @@ bool NavMeshRuntime::FindClosestPoint(const Vector3& point, Vector3& result) con
dtPolyRef startPoly = 0;
Float3 nearestPt;
query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &nearestPt.X);
if (!startPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &nearestPt.X)))
return false;
}
Quaternion invRotation;
Quaternion::Invert(Properties.Rotation, invRotation);
@@ -235,11 +210,8 @@ bool NavMeshRuntime::FindRandomPoint(Vector3& result) const
dtPolyRef randomPoly = 0;
Float3 randomPt;
query->findRandomPoint(&filter, Random::Rand, &randomPoly, &randomPt.X);
if (!randomPoly)
{
if (!dtStatusSucceed(query->findRandomPoint(&filter, Random::Rand, &randomPoly, &randomPt.X)))
return false;
}
Quaternion invRotation;
Quaternion::Invert(Properties.Rotation, invRotation);
@@ -257,25 +229,19 @@ bool NavMeshRuntime::FindRandomPointAroundCircle(const Vector3& center, float ra
dtQueryFilter filter;
InitFilter(filter);
Float3 extent = Properties.DefaultQueryExtent;
Float3 extent(radius);
Float3 centerNavMesh;
Float3::Transform(center, Properties.Rotation, centerNavMesh);
dtPolyRef centerPoly = 0;
query->findNearestPoly(&centerNavMesh.X, &extent.X, &filter, &centerPoly, nullptr);
if (!centerPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&centerNavMesh.X, &extent.X, &filter, &centerPoly, nullptr)))
return false;
}
dtPolyRef randomPoly = 0;
Float3 randomPt;
query->findRandomPointAroundCircle(centerPoly, &centerNavMesh.X, radius, &filter, Random::Rand, &randomPoly, &randomPt.X);
if (!randomPoly)
{
if (!dtStatusSucceed(query->findRandomPointAroundCircle(centerPoly, &centerNavMesh.X, radius, &filter, Random::Rand, &randomPoly, &randomPt.X)))
return false;
}
Quaternion invRotation;
Quaternion::Invert(Properties.Rotation, invRotation);
@@ -300,11 +266,8 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos
Float3::Transform(endPosition, Properties.Rotation, endPositionNavMesh);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr)))
return false;
}
dtRaycastHit hit;
hit.path = nullptr;
@@ -361,14 +324,6 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
// Ensure to have size assigned
ASSERT(_tileSize != 0);
// Allocate navmesh and initialize the default query
if (!_navMesh)
_navMesh = dtAllocNavMesh();
if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES)))
{
LOG(Error, "Failed to initialize navmesh {0}.", Properties.Name);
}
// Prepare parameters
dtNavMeshParams params;
params.orig[0] = 0.0f;
@@ -381,11 +336,17 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
params.maxPolys = 1 << (22 - tilesBits);
// Initialize nav mesh
if (!_navMesh)
_navMesh = dtAllocNavMesh();
if (dtStatusFailed(_navMesh->init(&params)))
{
LOG(Error, "Navmesh {0} init failed.", Properties.Name);
LOG(Error, "Navmesh {0} init failed", Properties.Name);
return;
}
if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES)))
{
LOG(Error, "Navmesh query {0} init failed", Properties.Name);
}
// Prepare tiles container
_tiles.EnsureCapacity(newCapacity);
@@ -405,7 +366,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
const auto result = _navMesh->addTile(data, dataSize, flags, 0, nullptr);
if (dtStatusFailed(result))
{
LOG(Warning, "Could not add tile to navmesh {0} (error: {1}).", Properties.Name, result & ~DT_FAILURE);
LOG(Warning, "Could not add tile ({2}x{3}, layer {4}) to navmesh {0} (error: {1})", Properties.Name, result & ~DT_FAILURE, tile.X, tile.Y, tile.Layer);
}
}
}
@@ -490,13 +451,11 @@ void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer)
const auto tileRef = _navMesh->getTileRefAt(x, y, layer);
if (tileRef == 0)
{
return;
}
if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr)))
{
LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name);
LOG(Warning, "Failed to remove tile ({1}x{2}, layer {3}) from navmesh {0}", Properties.Name, x, y, layer);
}
for (int32 i = 0; i < _tiles.Count(); i++)
@@ -532,7 +491,7 @@ void NavMeshRuntime::RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMes
{
if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr)))
{
LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name);
LOG(Warning, "Failed to remove tile ({1}x{2}, layer {3}) from navmesh {0}", Properties.Name, tile.X, tile.Y, tile.Layer);
}
}
@@ -668,7 +627,7 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData
// Remove any existing tile at that location
if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr)))
{
LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name);
LOG(Warning, "Failed to remove tile from navmesh {0}", Properties.Name);
}
// Reuse tile data container
@@ -712,6 +671,6 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData
const auto result = _navMesh->addTile(data, dataSize, flags, 0, nullptr);
if (dtStatusFailed(result))
{
LOG(Warning, "Could not add tile to navmesh {0} (error: {1}).", Properties.Name, result & ~DT_FAILURE);
LOG(Warning, "Could not add tile ({2}x{3}, layer {4}) to navmesh {0} (error: {1})", Properties.Name, result & ~DT_FAILURE, tileData.PosX, tileData.PosY, tileData.Layer);
}
}

View File

@@ -160,7 +160,7 @@ public:
/// <param name="point">The source point.</param>
/// <param name="result">The result position on the navmesh (valid only if method returns true).</param>
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
API_FUNCTION() bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const
API_FUNCTION() DEPRECATED bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const
{
return FindClosestPoint(point, result);
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Text;
@@ -441,5 +441,23 @@ namespace FlaxEngine.Networking
ReadBytes((byte*)&value, sizeof(bool));
return value;
}
/// <summary>
/// Writes the <see cref="INetworkSerializable"/> object data to the stream. Object has to be allocated.
/// </summary>
/// <param name="obj">The serializable object.</param>
public void Write(INetworkSerializable obj)
{
obj.Serialize(this);
}
/// <summary>
/// Reads the <see cref="INetworkSerializable"/> object data from the stream. Object has to be allocated.
/// </summary>
/// <param name="obj">The serializable object.</param>
public void Read(INetworkSerializable obj)
{
obj.Deserialize(this);
}
}
}

View File

@@ -26,7 +26,7 @@ public:
/// <summary>
/// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory.
/// </summary>
AssetReference<Asset> Assets[14];
Array<AssetReference<Asset>> Assets;
};
/// <summary>

View File

@@ -177,6 +177,7 @@ public:
// Get Gameplay Global
case GRAPH_NODE_MAKE_TYPE(7, 16):
{
node->Assets.Resize(1);
node->Assets[0] = Content::LoadAsync<Asset>((Guid)node->Values[0]);
break;
}
@@ -296,6 +297,7 @@ public:
break;
// Particle Emitter Function
case GRAPH_NODE_MAKE_TYPE(14, 300):
node->Assets.Resize(1);
InitParticleEmitterFunctionCall((Guid)node->Values[0], node->Assets[0], node->UsesParticleData, Layout);
break;
// Particle Index
@@ -447,6 +449,7 @@ public:
// Sprite Rendering
case GRAPH_NODE_MAKE_TYPE(15, 400):
{
node->Assets.Resize(1);
node->Assets[0] = Content::LoadAsync<Asset>((Guid)node->Values[2]);
USE_ATTRIBUTE(Position, Float3, 0);
USE_ATTRIBUTE(Rotation, Float3, 1);
@@ -484,6 +487,7 @@ public:
// Model Rendering
case GRAPH_NODE_MAKE_TYPE(15, 403):
{
node->Assets.Resize(2);
node->Assets[0] = Content::LoadAsync<Asset>((Guid)node->Values[2]);
node->Assets[1] = Content::LoadAsync<Asset>((Guid)node->Values[3]);
USE_ATTRIBUTE(Position, Float3, 0);
@@ -494,6 +498,7 @@ public:
// Ribbon Rendering
case GRAPH_NODE_MAKE_TYPE(15, 404):
{
node->Assets.Resize(1);
node->Assets[0] = Content::LoadAsync<Asset>((Guid)node->Values[2]);
USE_ATTRIBUTE(Position, Float3, 0);
// TODO: add support for custom sorting key - not only by age
@@ -503,6 +508,7 @@ public:
// Volumetric Fog Rendering
case GRAPH_NODE_MAKE_TYPE(15, 405):
{
node->Assets.Resize(1);
node->Assets[0] = Content::LoadAsync<Asset>((Guid)node->Values[2]);
USE_ATTRIBUTE(Position, Float3, 0);
USE_ATTRIBUTE(Radius, Float, 1);

View File

@@ -270,11 +270,11 @@ void ParticleEffect::UpdateSimulation(bool singleFrame)
if (!IsActiveInHierarchy()
|| ParticleSystem == nullptr
|| !ParticleSystem->IsLoaded()
|| _lastUpdateFrame == Engine::FrameCount)
|| _lastUpdateFrame == Engine::UpdateCount)
return;
// Request update
_lastUpdateFrame = Engine::FrameCount;
_lastUpdateFrame = Engine::UpdateCount;
_lastMinDstSqr = MAX_Real;
if (singleFrame)
Instance.LastUpdateTime = (UseTimeScale ? Time::Update.Time : Time::Update.UnscaledTime).GetTotalSeconds();
@@ -371,7 +371,7 @@ void ParticleEffect::Sync()
SceneRenderTask* ParticleEffect::GetRenderTask() const
{
const uint64 minFrame = Engine::FrameCount - 2;
const uint64 minFrame = Engine::UpdateCount - 2;
// Custom task
const auto customViewRenderTask = CustomViewRenderTask.Get();

View File

@@ -988,7 +988,7 @@ void DrawBatch(int32 startIndex, int32 count)
MaterialBase::BindParameters bindParams(Context, *(RenderContext*)nullptr);
Render2D::CustomData customData;
customData.ViewProjection = ViewProjection;
customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height);
customData.ViewSize = Float2::One;
bindParams.CustomData = &customData;
material->Bind(bindParams);

View File

@@ -42,7 +42,7 @@ void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBu
bool dropHistory = renderContext.Buffers->LastEyeAdaptationTime < ZeroTolerance || renderContext.Task->IsCameraCut;
const float time = Time::Draw.UnscaledTime.GetTotalSeconds();
//const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds();
const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime;
const float frameDelta = Math::Clamp(time - renderContext.Buffers->LastEyeAdaptationTime, 0.0f, 1.0f);
renderContext.Buffers->LastEyeAdaptationTime = 0.0f;
if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass())
return;

View File

@@ -109,6 +109,45 @@ namespace
#if USE_EDITOR
bool LastBinariesLoadTriggeredCompilation = false;
#endif
void ReleaseObjects(bool gameOnly)
{
// Flush objects already enqueued objects to delete
ObjectsRemovalService::Flush();
// Give GC a try to cleanup old user objects and the other mess
MCore::GC::Collect();
MCore::GC::WaitForPendingFinalizers();
// Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload)
const auto flaxModule = GetBinaryModuleFlaxEngine();
_objectsLocker.Lock();
for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Value;
if (gameOnly && obj->GetTypeHandle().Module == flaxModule)
continue;
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName));
#endif
obj->OnScriptingDispose();
}
_objectsLocker.Unlock();
// Release assets sourced from game assemblies
Array<Asset*> assets = Content::GetAssets();
for (auto asset : assets)
{
if (asset->GetTypeHandle().Module == flaxModule)
{
continue;
}
asset->DeleteObject();
}
ObjectsRemovalService::Flush();
}
}
Delegate<BinaryModule*> Scripting::BinaryModuleLoaded;
@@ -566,36 +605,8 @@ void Scripting::Release()
// Fire event
ScriptsUnload();
// Cleanup
ObjectsRemovalService::Flush();
// Cleanup some managed objects
MCore::GC::Collect();
MCore::GC::WaitForPendingFinalizers();
// Release managed objects instances for persistent objects (assets etc.)
_objectsLocker.Lock();
{
for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Value;
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName));
#endif
obj->OnScriptingDispose();
}
}
_objectsLocker.Unlock();
// Release assets sourced from game assemblies
const auto flaxModule = GetBinaryModuleFlaxEngine();
for (auto asset : Content::GetAssets())
{
if (asset->GetTypeHandle().Module == flaxModule)
continue;
asset->DeleteObjectNow();
}
ReleaseObjects(false);
auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine();
onEngineUnloading(flaxEngineModule->Assembly);
@@ -673,39 +684,8 @@ void Scripting::Reload(bool canTriggerSceneReload)
LOG(Info, "Start user scripts reload");
ScriptsReloading();
// Flush cache (some objects may be deleted after reload start event)
ObjectsRemovalService::Flush();
// Give GC a try to cleanup old user objects and the other mess
MCore::GC::Collect();
MCore::GC::WaitForPendingFinalizers();
// Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload)
const auto flaxModule = GetBinaryModuleFlaxEngine();
_objectsLocker.Lock();
{
for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Value;
if (obj->GetTypeHandle().Module == flaxModule)
continue;
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName));
#endif
obj->OnScriptingDispose();
}
}
_objectsLocker.Unlock();
// Release assets sourced from game assemblies
for (auto asset : Content::GetAssets())
{
if (asset->GetTypeHandle().Module == flaxModule)
continue;
asset->DeleteObjectNow();
}
ReleaseObjects(true);
// Unload all game modules
LOG(Info, "Unloading game binary modules");
@@ -741,6 +721,15 @@ void Scripting::Reload(bool canTriggerSceneReload)
#endif
Array<ScriptingObject*, HeapAllocation> Scripting::GetObjects()
{
Array<ScriptingObject*> objects;
_objectsLocker.Lock();
_objectsDictionary.GetValues(objects);
_objectsLocker.Unlock();
return objects;
}
MClass* Scripting::FindClass(const StringAnsiView& fullname)
{
if (fullname.IsEmpty())
@@ -860,7 +849,7 @@ void ScriptingObjectReferenceBase::OnDeleted(ScriptingObject* obj)
}
}
ScriptingObject* Scripting::FindObject(Guid id, MClass* type)
ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
{
if (!id.IsValid())
return nullptr;
@@ -914,7 +903,7 @@ ScriptingObject* Scripting::FindObject(Guid id, MClass* type)
return nullptr;
}
ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type)
ScriptingObject* Scripting::TryFindObject(Guid id, const MClass* type)
{
if (!id.IsValid())
return nullptr;
@@ -950,7 +939,7 @@ ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type)
return result;
}
ScriptingObject* Scripting::TryFindObject(MClass* type)
ScriptingObject* Scripting::TryFindObject(const MClass* type)
{
if (type == nullptr)
return nullptr;
@@ -1020,7 +1009,7 @@ bool Scripting::IsEveryAssemblyLoaded()
return true;
}
bool Scripting::IsTypeFromGameScripts(MClass* type)
bool Scripting::IsTypeFromGameScripts(const MClass* type)
{
const auto binaryModule = ManagedBinaryModule::GetModule(type ? type->GetAssembly() : nullptr);
return binaryModule && binaryModule != GetBinaryModuleCorlib() && binaryModule != GetBinaryModuleFlaxEngine();

View File

@@ -79,6 +79,13 @@ public:
public:
/// <summary>
/// Gets all registered scripting objects.
/// </summary>
/// <remarks>Use with caution due to potentially large memory allocation.</remarks>
/// <returns>The collection of the objects.</returns>
static Array<ScriptingObject*, HeapAllocation> GetObjects();
/// <summary>
/// Finds the class with given fully qualified name within whole assembly.
/// </summary>
@@ -133,14 +140,14 @@ public:
/// <param name="id">The object unique identifier.</param>
/// <param name="type">The type of the object to find (optional).</param>
/// <returns>The found object or null if missing.</returns>
static ScriptingObject* FindObject(Guid id, MClass* type = nullptr);
static ScriptingObject* FindObject(Guid id, const MClass* type = nullptr);
/// <summary>
/// Tries to find the object by the given class.
/// </summary>
/// <param name="type">The type of the object to find.</param>
/// <returns>The found object or null if missing.</returns>
static ScriptingObject* TryFindObject(MClass* type);
static ScriptingObject* TryFindObject(const MClass* type);
/// <summary>
/// Tries to find the object by the given identifier.
@@ -159,7 +166,7 @@ public:
/// <param name="id">The object unique identifier.</param>
/// <param name="type">The type of the object to find (optional).</param>
/// <returns>The found object or null if missing.</returns>
static ScriptingObject* TryFindObject(Guid id, MClass* type = nullptr);
static ScriptingObject* TryFindObject(Guid id, const MClass* type = nullptr);
/// <summary>
/// Finds the object by the given managed instance handle. Searches only registered scene objects.
@@ -189,7 +196,7 @@ public:
/// <summary>
/// Returns true if given type is from one of the game scripts assemblies.
/// </summary>
static bool IsTypeFromGameScripts(MClass* type);
static bool IsTypeFromGameScripts(const MClass* type);
static void ProcessBuildInfoPath(String& path, const String& projectFolderPath);

View File

@@ -1808,6 +1808,9 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod
}
#endif
// Mark as modified (need to save texture data during scene saving)
_wasHeightModified = true;
if (!wasHeightChanged)
return false;
@@ -1820,9 +1823,6 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod
#endif
_collisionVertices.Resize(0);
// Mark as modified (need to save texture data during scene saving)
_wasHeightModified = true;
// Note: if terrain is using virtual storage then it won't be updated, we could synchronize that data...
// TODO: disable heightmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container

View File

@@ -7,7 +7,6 @@
#include "Engine/Core/NonCopyable.h"
#include "Engine/Core/Enums.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/Platform.h"
/// <summary>
@@ -188,8 +187,8 @@ public:
/// <param name="tasks">The tasks list to wait for.</param>
/// <param name="timeoutMilliseconds">The maximum amount of milliseconds to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting.</param>
/// <returns>True if any task failed or has been canceled or has timeout, otherwise false.</returns>
template<class T = Task>
static bool WaitAll(Array<T*>& tasks, double timeoutMilliseconds = -1)
template<class T = Task, typename AllocationType = HeapAllocation>
static bool WaitAll(Array<T*, AllocationType>& tasks, double timeoutMilliseconds = -1)
{
for (int32 i = 0; i < tasks.Count(); i++)
{
@@ -300,27 +299,22 @@ public:
/// <summary>
/// Cancels all the tasks from the list and clears it.
/// </summary>
template<class T = Task>
static void CancelAll(Array<T*>& tasks)
template<class T = Task, typename AllocationType = HeapAllocation>
static void CancelAll(Array<T*, AllocationType>& tasks)
{
for (int32 i = 0; i < tasks.Count(); i++)
{
tasks[i]->Cancel();
}
tasks.Clear();
}
protected:
/// <summary>
/// Executes this task.
/// It should be called by the task consumer (thread pool or other executor of this task type).
/// It calls run() and handles result).
/// Executes this task. It should be called by the task consumer (thread pool or other executor of this task type). It calls run() and handles result).
/// </summary>
void Execute();
/// <summary>
/// Runs the task specified operations
/// Does not handles any task related logic, only performs the actual job.
/// Runs the task specified operations. It does not handle any task related logic, but only performs the actual job.
/// </summary>
/// <returns>The task execution result. Returns true if failed, otherwise false.</returns>
virtual bool Run() = 0;

View File

@@ -8,6 +8,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/ConditionVariable.h"

View File

@@ -720,23 +720,23 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
{
int vtxIndex = clusterIndices[j] - firstVertexOffset;
float vtxWeight = (float)clusterWeights[j];
if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= vertexCount)
continue;
auto& indices = mesh.BlendIndices[vtxIndex];
auto& weights = mesh.BlendWeights[vtxIndex];
Int4& indices = mesh.BlendIndices.Get()[vtxIndex];
Float4& weights = mesh.BlendWeights.Get()[vtxIndex];
for (int32 k = 0; k < 4; k++)
{
if (vtxWeight >= weights.Raw[k])
{
// Move lower weights by one down
for (int32 l = 2; l >= k; l--)
{
indices.Raw[l + 1] = indices.Raw[l];
weights.Raw[l + 1] = weights.Raw[l];
}
// Set bone influence
indices.Raw[k] = boneIndex;
weights.Raw[k] = vtxWeight;
break;
@@ -786,11 +786,13 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
auto shapeNormals = shape->getNormals();
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
{
/*auto delta = ToFloat3(shapeNormals[i + firstVertexOffset]) - mesh.Normals[i];
auto length = delta.Length();
if (length > ZeroTolerance)
delta /= length;*/
auto delta = Float3::Zero; // TODO: blend shape normals deltas fix when importing from fbx
auto delta = ToFloat3(shapeNormals[i + firstVertexOffset]);
if (data.ConvertRH)
{
// Mirror normals along the Z axis
delta.Z *= -1.0f;
}
delta = delta - mesh.Normals.Get()[i];
blendShapeData.Vertices.Get()[i].NormalDelta = delta;
}
}
@@ -800,7 +802,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
{
// Mirror positions along the Z axis
for (int32 i = 0; i < vertexCount; i++)
mesh.Positions[i].Z *= -1.0f;
mesh.Positions.Get()[i].Z *= -1.0f;
for (auto& blendShapeData : mesh.BlendShapes)
{
for (auto& v : blendShapeData.Vertices)
@@ -815,7 +817,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
{
// Invert the order
for (int32 i = 0; i < mesh.Indices.Count(); i += 3)
Swap(mesh.Indices[i], mesh.Indices[i + 2]);
Swap(mesh.Indices.Get()[i], mesh.Indices.Get()[i + 2]);
}
if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems())

View File

@@ -589,6 +589,9 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
{
for (auto& n : mesh->Normals)
n *= -1;
for (auto& shape : mesh->BlendShapes)
for (auto& v : shape.Vertices)
v.NormalDelta *= -1;
}
}
}

View File

@@ -11,8 +11,6 @@ class GraphNode;
#define GRAPH_NODE_MAKE_TYPE(groupID, typeID) (uint32)((groupID) << 16 | (typeID))
#define GRAPH_NODE_MAX_VALUES 32
/// <summary>
/// Represents single box of the graph node
/// </summary>
@@ -114,14 +112,13 @@ public:
uint16 TypeID;
uint16 GroupID;
};
uint32 Type;
};
/// <summary>
/// List of all node values. Array size and value types are constant over time. Only value data can change.
/// </summary>
Array<Variant, FixedAllocation<GRAPH_NODE_MAX_VALUES>> Values;
Array<Variant, InlinedAllocation<8>> Values;
/// <summary>
/// Node boxes cache. Array index matches the box ID (for fast O(1) lookups).

View File

@@ -6,13 +6,12 @@
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/AssetsContainer.h"
#include "Engine/Animations/Curve.h"
#define VISJECT_GRAPH_NODE_MAX_ASSETS 14
template<class BoxType>
class VisjectGraphNode;
@@ -94,7 +93,7 @@ public:
/// <summary>
/// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory.
/// </summary>
AssetReference<Asset> Assets[VISJECT_GRAPH_NODE_MAX_ASSETS];
Array<AssetReference<Asset>> Assets;
};
/// <summary>
@@ -188,11 +187,10 @@ public:
#undef SETUP_CURVE
// Get Gameplay Global
case 16:
{
n->Assets.Resize(1);
n->Assets[0] = ::LoadAsset((Guid)n->Values[0], Asset::TypeInitializer);
break;
}
}
}
// Base

Binary file not shown.

Binary file not shown.

View File

@@ -487,14 +487,37 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f
int checksLimit = 100;
do
{
// Loop until finds a point on a random poly that is in the radius
const float s = frand();
const float t = frand();
dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt);
}
while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr && checksLimit-- > 0);
if (checksLimit <= 0)
return DT_FAILURE;
{
// Check nearby polygons
float halfExtents[3] = { maxRadius, maxRadius, maxRadius };
dtPolyRef polys[32];
int polyCount;
queryPolygons(centerPos, halfExtents, filter, polys, &polyCount, 32);
for (int i = 0; i < polyCount && checksLimit <= 0; i++)
{
checksLimit = 100;
randomPolyRef = polys[i];
m_nav->getTileAndPolyByRefUnsafe(randomPolyRef, &randomTile, &randomPoly);
do
{
// Loop until finds a point on a random poly that is in the radius
const float s = frand();
const float t = frand();
dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt);
}
while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr && checksLimit-- > 0);
}
if (checksLimit <= 0)
return DT_FAILURE;
}
closestPointOnPoly(randomPolyRef, pt, pt, NULL);
dtVcopy(randomPt, pt);