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:
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -144,5 +144,11 @@ namespace FlaxEditor.Scripting
|
||||
{
|
||||
return Utils.GetEmptyArray<ScriptMemberInfo>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void TrackLifetime(Action<ScriptType> disposing)
|
||||
{
|
||||
ElementType.TrackLifetime(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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<>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <summary>
|
||||
/// Current materials shader version.
|
||||
/// </summary>
|
||||
#define MATERIAL_GRAPH_VERSION 164
|
||||
#define MATERIAL_GRAPH_VERSION 165
|
||||
|
||||
class Material;
|
||||
class GPUShader;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(¢erNavMesh.X, &extent.X, &filter, ¢erPoly, nullptr);
|
||||
if (!centerPoly)
|
||||
{
|
||||
if (!dtStatusSucceed(query->findNearestPoly(¢erNavMesh.X, &extent.X, &filter, ¢erPoly, nullptr)))
|
||||
return false;
|
||||
}
|
||||
|
||||
dtPolyRef randomPoly = 0;
|
||||
Float3 randomPt;
|
||||
query->findRandomPointAroundCircle(centerPoly, ¢erNavMesh.X, radius, &filter, Random::Rand, &randomPoly, &randomPt.X);
|
||||
if (!randomPoly)
|
||||
{
|
||||
if (!dtStatusSucceed(query->findRandomPointAroundCircle(centerPoly, ¢erNavMesh.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(¶ms)))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
Source/Platforms/DotNet/Newtonsoft.Json.dll
(Stored with Git LFS)
BIN
Source/Platforms/DotNet/Newtonsoft.Json.dll
(Stored with Git LFS)
Binary file not shown.
BIN
Source/Platforms/DotNet/Newtonsoft.Json.pdb
(Stored with Git LFS)
BIN
Source/Platforms/DotNet/Newtonsoft.Json.pdb
(Stored with Git LFS)
Binary file not shown.
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user