Merge branch 'master' of https://github.com/FlaxEngine/FlaxEngine into feature/1151-play-mode-actions

This commit is contained in:
envision3d
2023-07-20 02:00:53 -05:00
128 changed files with 1679 additions and 608 deletions

Binary file not shown.

BIN
Content/Editor/Gizmo/Material.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/TexturePreviewMaterial.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -3,7 +3,7 @@
"Version": { "Version": {
"Major": 1, "Major": 1,
"Minor": 6, "Minor": 6,
"Build": 6342 "Build": 6344
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",

View File

@@ -29,7 +29,7 @@ namespace FlaxEditor.Content
if (asset) if (asset)
{ {
var source = Editor.GetShaderSourceCode(asset); var source = Editor.GetShaderSourceCode(asset);
Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source", item.RootWindow.Window); Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source", item.RootWindow?.Window);
} }
return null; return null;
} }

View File

@@ -3,7 +3,6 @@
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine;
using DockState = FlaxEditor.GUI.Docking.DockState; using DockState = FlaxEditor.GUI.Docking.DockState;
namespace FlaxEditor namespace FlaxEditor
@@ -86,8 +85,12 @@ namespace FlaxEditor
if (!FlaxEngine.Scripting.IsTypeFromGameScripts(type)) if (!FlaxEngine.Scripting.IsTypeFromGameScripts(type))
return; return;
if (!Window.IsHidden)
{
Editor.Instance.Windows.AddToRestore(this); Editor.Instance.Windows.AddToRestore(this);
}
Window.Close(); Window.Close();
Window.Dispose();
} }
/// <summary> /// <summary>

View File

@@ -167,7 +167,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Presenter.Undo?.AddAction(new MultiUndoAction(actions)); Presenter.Undo?.AddAction(new MultiUndoAction(actions));
// Build ragdoll // Build ragdoll
SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll); AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
} }
private void OnRebuildBone(Button button) private void OnRebuildBone(Button button)
@@ -191,7 +191,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
} }
// Build ragdoll // Build ragdoll
SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name); AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name);
} }
private void OnRemoveBone(Button button) private void OnRemoveBone(Button button)

View File

@@ -33,7 +33,12 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(Asset)), DefaultEditor] [CustomEditor(typeof(Asset)), DefaultEditor]
public class AssetRefEditor : CustomEditor public class AssetRefEditor : CustomEditor
{ {
private AssetPicker _picker; /// <summary>
/// The asset picker used to get a reference to an asset.
/// </summary>
public AssetPicker Picker;
private bool _isRefreshing;
private ScriptType _valueType; private ScriptType _valueType;
/// <inheritdoc /> /// <inheritdoc />
@@ -44,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
if (HasDifferentTypes) if (HasDifferentTypes)
return; return;
_picker = layout.Custom<AssetPicker>().CustomControl; Picker = layout.Custom<AssetPicker>().CustomControl;
_valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
var assetType = _valueType; var assetType = _valueType;
@@ -66,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
// Generic file picker // Generic file picker
assetType = ScriptType.Null; assetType = ScriptType.Null;
_picker.FileExtension = assetReference.TypeName; Picker.FileExtension = assetReference.TypeName;
} }
else else
{ {
@@ -78,23 +83,25 @@ namespace FlaxEditor.CustomEditors.Editors
} }
} }
_picker.AssetType = assetType; Picker.AssetType = assetType;
_picker.Height = height; Picker.Height = height;
_picker.SelectedItemChanged += OnSelectedItemChanged; Picker.SelectedItemChanged += OnSelectedItemChanged;
} }
private void OnSelectedItemChanged() private void OnSelectedItemChanged()
{ {
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type)) if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
SetValue(_picker.SelectedItem); SetValue(Picker.SelectedItem);
else if (_valueType.Type == typeof(Guid)) else if (_valueType.Type == typeof(Guid))
SetValue(_picker.SelectedID); SetValue(Picker.SelectedID);
else if (_valueType.Type == typeof(SceneReference)) else if (_valueType.Type == typeof(SceneReference))
SetValue(new SceneReference(_picker.SelectedID)); SetValue(new SceneReference(Picker.SelectedID));
else if (_valueType.Type == typeof(string)) else if (_valueType.Type == typeof(string))
SetValue(_picker.SelectedPath); SetValue(Picker.SelectedPath);
else else
SetValue(_picker.SelectedAsset); SetValue(Picker.SelectedAsset);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -104,16 +111,18 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentValues) if (!HasDifferentValues)
{ {
_isRefreshing = true;
if (Values[0] is AssetItem assetItem) if (Values[0] is AssetItem assetItem)
_picker.SelectedItem = assetItem; Picker.SelectedItem = assetItem;
else if (Values[0] is Guid guid) else if (Values[0] is Guid guid)
_picker.SelectedID = guid; Picker.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset) else if (Values[0] is SceneReference sceneAsset)
_picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path) else if (Values[0] is string path)
_picker.SelectedPath = path; Picker.SelectedPath = path;
else else
_picker.SelectedAsset = Values[0] as Asset; Picker.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
} }
} }
} }

View File

@@ -41,7 +41,17 @@ namespace FlaxEditor.CustomEditors.Editors
} }
else else
{ {
element.CustomControl.Value = (Color)Values[0]; var value = Values[0];
if (value is Color asColor)
element.CustomControl.Value = asColor;
else if (value is Color32 asColor32)
element.CustomControl.Value = asColor32;
else if (value is Float4 asFloat4)
element.CustomControl.Value = asFloat4;
else if (value is Double4 asDouble4)
element.CustomControl.Value = (Float4)asDouble4;
else if (value is Vector4 asVector4)
element.CustomControl.Value = asVector4;
} }
} }
} }

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors namespace FlaxEditor.CustomEditors.Editors
@@ -13,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
private GroupElement _group; private GroupElement _group;
private bool _updateName; private bool _updateName;
private int _entryIndex;
private bool _isRefreshing;
private MaterialBase _material;
private ModelInstanceActor _modelInstance;
private AssetRefEditor _materialEditor;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
@@ -21,47 +28,122 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry"); var group = layout.Group("Entry");
_group = group; _group = group;
if (ParentEditor == null)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
{
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
if (entry.Material == slots[entryIndex].Material)
{
// Ensure that entry with default material set is set back to null
modelInstance.SetMaterial(entryIndex, null);
}
_material = modelInstance.GetMaterial(entryIndex);
var defaultValue = GPUDevice.Instance.DefaultMaterial;
if (slots[entryIndex].Material)
{
// Use default value set on asset (eg. Model Asset)
defaultValue = slots[entryIndex].Material;
}
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();
materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged;
_materialEditor = materialEditor;
}
base.Initialize(group); base.Initialize(group);
} }
private void OnSelectedMaterialChanged()
{
if (_isRefreshing)
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
_materialEditor.Picker.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)
{
// Asset default material
value.Material = null;
}
else if (material == defaultMaterial && !slots[_entryIndex].Material)
{
// Default material while asset has no set as well
value.Material = null;
}
else
{
// Custom material
value.Material = material;
}
if (prevMaterial != value.Material)
SetValue(value);
_isRefreshing = false;
}
/// <inheritdoc />
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
{
// Skip material member as it is overridden
if (item.Info.Name == "Material" && _materialEditor != null)
return;
base.SpawnProperty(itemLayout, itemValues, item);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() public override void Refresh()
{ {
// Update panel title to match material slot name
if (_updateName && if (_updateName &&
_group != null && _group != null &&
ParentEditor?.ParentEditor != null && ParentEditor?.ParentEditor != null &&
ParentEditor.ParentEditor.Values.Count > 0) ParentEditor.ParentEditor.Values.Count > 0)
{ {
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this); var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
if (ParentEditor.ParentEditor.Values[0] is StaticModel staticModel) if (ParentEditor.ParentEditor.Values[0] is ModelInstanceActor modelInstance)
{ {
var model = staticModel.Model; var slots = modelInstance.MaterialSlots;
if (model && model.IsLoaded)
{
var slots = model.MaterialSlots;
if (slots != null && slots.Length > entryIndex) if (slots != null && slots.Length > entryIndex)
{ {
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
_updateName = false; _updateName = false;
}
}
}
else if (ParentEditor.ParentEditor.Values[0] is AnimatedModel animatedModel)
{
var model = animatedModel.SkinnedModel;
if (model && model.IsLoaded)
{
var slots = model.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name; _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
_updateName = false;
}
} }
} }
} }
// Refresh currently selected material
_material = _modelInstance.GetMaterial(_entryIndex);
base.Refresh(); base.Refresh();
} }
/// <inheritdoc />
protected override void Deinitialize()
{
_material = null;
_modelInstance = null;
_materialEditor = null;
base.Deinitialize();
}
} }
} }

View File

@@ -390,7 +390,6 @@ namespace FlaxEditor.CustomEditors.Editors
if (addTagDropPanel.IsClosed) if (addTagDropPanel.IsClosed)
{ {
Debug.Log("Hit");
nameTextBox.BorderColor = Color.Transparent; nameTextBox.BorderColor = Color.Transparent;
nameTextBox.BorderSelectedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; nameTextBox.BorderSelectedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected;
return; return;

View File

@@ -37,7 +37,8 @@ public class Editor : EditorModule
{ {
base.Setup(options); base.Setup(options);
options.ScriptingAPI.SystemReferences.Add("System.Private.Xml"); options.ScriptingAPI.SystemReferences.Add("System.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter"); options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
@@ -101,5 +102,7 @@ public class Editor : EditorModule
files.Add(Path.Combine(FolderPath, "Cooker/GameCooker.h")); files.Add(Path.Combine(FolderPath, "Cooker/GameCooker.h"));
files.Add(Path.Combine(FolderPath, "Cooker/PlatformTools.h")); files.Add(Path.Combine(FolderPath, "Cooker/PlatformTools.h"));
files.Add(Path.Combine(FolderPath, "Cooker/Steps/CookAssetsStep.h")); files.Add(Path.Combine(FolderPath, "Cooker/Steps/CookAssetsStep.h"));
files.Add(Path.Combine(FolderPath, "Utilities/ScreenUtilities.h"));
files.Add(Path.Combine(FolderPath, "Utilities/ViewportIconsRenderer.h"));
} }
} }

View File

@@ -27,6 +27,17 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
public ScriptType Type => _type; public ScriptType Type => _type;
/// <summary>
/// Initializes a new instance of the <see cref="TypeItemView"/> class.
/// </summary>
public TypeItemView()
{
_type = ScriptType.Null;
Name = "<null>";
TooltipText = "Unset value.";
Tag = _type;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TypeItemView"/> class. /// Initializes a new instance of the <see cref="TypeItemView"/> class.
/// </summary> /// </summary>
@@ -83,6 +94,7 @@ namespace FlaxEditor.GUI
// TODO: use async thread to search types without UI stall // TODO: use async thread to search types without UI stall
var allTypes = Editor.Instance.CodeEditing.All.Get(); var allTypes = Editor.Instance.CodeEditing.All.Get();
AddItem(new TypeItemView());
for (int i = 0; i < allTypes.Count; i++) for (int i = 0; i < allTypes.Count; i++)
{ {
var type = allTypes[i]; var type = allTypes[i];

View File

@@ -178,6 +178,8 @@ namespace FlaxEditor.Gizmo
if (selection[i] is ActorNode actorNode && actorNode.Actor != null) if (selection[i] is ActorNode actorNode && actorNode.Actor != null)
CollectActors(actorNode.Actor); CollectActors(actorNode.Actor);
} }
if (_actors.Count == 0)
return;
// Render selected objects depth // Render selected objects depth
Renderer.DrawSceneDepth(context, task, customDepth, _actors); Renderer.DrawSceneDepth(context, task, customDepth, _actors);

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param> /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
if (value == null) if (value == null)
{ {
@@ -44,7 +44,7 @@ namespace FlaxEditor
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param> /// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns> /// <returns>The object value.</returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.TokenType == JsonToken.Null) if (reader.TokenType == JsonToken.Null)
{ {

View File

@@ -112,6 +112,7 @@ public:
{ {
Version = ::Version(1, 0); Version = ::Version(1, 0);
DefaultSceneSpawn = Ray(Vector3::Zero, Vector3::Forward); DefaultSceneSpawn = Ray(Vector3::Zero, Vector3::Forward);
DefaultScene = Guid::Empty;
} }
/// <summary> /// <summary>

View File

@@ -613,6 +613,28 @@ bool ScriptsBuilderService::Init()
const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration; const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration;
Array<String> files; Array<String> files;
FileSystem::DirectoryGetFiles(files, targetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly); FileSystem::DirectoryGetFiles(files, targetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
for (const auto& reference : Editor::Project->References)
{
if (reference.Project->Name == TEXT("Flax"))
continue;
String referenceTarget;
if (reference.Project->EditorTarget.HasChars())
{
referenceTarget = reference.Project->EditorTarget.Get();
}
else if (reference.Project->GameTarget.HasChars())
{
referenceTarget = reference.Project->GameTarget.Get();
}
if (referenceTarget.IsEmpty())
continue;
const String referenceTargetOutput = reference.Project->ProjectFolderPath / TEXT("Binaries") / referenceTarget / platform / architecture / configuration;
FileSystem::DirectoryGetFiles(files, referenceTargetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
}
if (files.HasItems()) if (files.HasItems())
LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count()); LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count());
for (auto& file : files) for (auto& file : files)

View File

@@ -363,7 +363,6 @@ namespace FlaxEditor.Surface.ContextMenu
Profiler.BeginEvent("VisjectCM.RemoveGroup"); Profiler.BeginEvent("VisjectCM.RemoveGroup");
if (group.Archetypes.Count == 0) if (group.Archetypes.Count == 0)
{ {
Debug.Log("Remove");
_groups.RemoveAt(i); _groups.RemoveAt(i);
group.Dispose(); group.Dispose();
} }

View File

@@ -292,7 +292,6 @@ void FoliageTools::Paint(Foliage* foliage, Span<int32> foliageTypesIndices, cons
{ {
PROFILE_CPU_NAMED("Place Instances"); PROFILE_CPU_NAMED("Place Instances");
Matrix matrix;
FoliageInstance instance; FoliageInstance instance;
Quaternion tmp; Quaternion tmp;
Matrix world; Matrix world;

View File

@@ -1538,6 +1538,7 @@ namespace FlaxEditor.Viewport
new ViewFlagOptions(ViewFlags.MotionBlur, "Motion Blur"), new ViewFlagOptions(ViewFlags.MotionBlur, "Motion Blur"),
new ViewFlagOptions(ViewFlags.ContactShadows, "Contact Shadows"), new ViewFlagOptions(ViewFlags.ContactShadows, "Contact Shadows"),
new ViewFlagOptions(ViewFlags.PhysicsDebug, "Physics Debug"), new ViewFlagOptions(ViewFlags.PhysicsDebug, "Physics Debug"),
new ViewFlagOptions(ViewFlags.LightsDebug, "Lights Debug"),
new ViewFlagOptions(ViewFlags.DebugDraw, "Debug Draw"), new ViewFlagOptions(ViewFlags.DebugDraw, "Debug Draw"),
}; };

View File

@@ -3,8 +3,6 @@
using System; using System;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEngine; using FlaxEngine;
using FlaxEditor.GUI.Input;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews namespace FlaxEditor.Viewport.Previews
@@ -15,11 +13,10 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AssetPreview" /> /// <seealso cref="AssetPreview" />
public class AnimatedModelPreview : AssetPreview public class AnimatedModelPreview : AssetPreview
{ {
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD, _showNodesNames;
private AnimatedModel _previewModel; private AnimatedModel _previewModel;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel; private StaticModel _floorModel;
private ContextMenuButton _showCurrentLODButton;
private bool _playAnimation, _playAnimationOnce; private bool _playAnimation, _playAnimationOnce;
private float _playSpeed = 1.0f; private float _playSpeed = 1.0f;
@@ -188,8 +185,8 @@ namespace FlaxEditor.Viewport.Previews
{ {
UseTimeScale = false, UseTimeScale = false,
UpdateWhenOffscreen = true, UpdateWhenOffscreen = true,
//_previewModel.BoundsScale = 1000.0f; BoundsScale = 100.0f,
UpdateMode = AnimatedModel.AnimationUpdateMode.Manual UpdateMode = AnimatedModel.AnimationUpdateMode.Manual,
}; };
Task.AddCustomActor(_previewModel); Task.AddCustomActor(_previewModel);
@@ -207,26 +204,6 @@ namespace FlaxEditor.Viewport.Previews
// Show Floor // Show Floor
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor); _showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_showFloorButton.IndexInParent = 1; _showFloorButton.IndexInParent = 1;
// Show Current LOD
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
{
_showCurrentLOD = !_showCurrentLOD;
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
});
_showCurrentLODButton.IndexInParent = 2;
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
}
} }
// Enable shadows // Enable shadows
@@ -339,44 +316,6 @@ namespace FlaxEditor.Viewport.Previews
_previewModel.ResetAnimation(); _previewModel.ResetAnimation();
} }
private int ComputeLODIndex(SkinnedModel model)
{
if (PreviewActor.ForcedLOD != -1)
return PreviewActor.ForcedLOD;
// Based on RenderTools::ComputeModelLOD
CreateProjectionMatrix(out var projectionMatrix);
float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22);
var sphere = PreviewActor.Sphere;
var viewOrigin = ViewPosition;
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
// Skip if no need to calculate LOD
if (model.LoadedLODs == 0)
return -1;
var lods = model.LODs;
if (lods.Length == 0)
return -1;
if (lods.Length == 1)
return 0;
// Iterate backwards and return the first matching LOD
for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--)
{
if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
{
return lodIndex + PreviewActor.LODBias;
}
}
return 0;
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext) protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
{ {
@@ -440,45 +379,6 @@ namespace FlaxEditor.Viewport.Previews
} }
} }
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var skinnedModel = _previewModel.SkinnedModel;
if (skinnedModel == null || !skinnedModel.IsLoaded)
return;
var lods = skinnedModel.LODs;
if (lods.Length == 0)
{
// Force show skeleton for models without geometry
ShowNodes = true;
return;
}
if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel);
string text = string.Format("Current LOD: {0}", lodIndex);
if (lodIndex != -1)
{
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++)
{
var mesh = lod.Meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount);
}
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
@@ -498,14 +398,21 @@ namespace FlaxEditor.Viewport.Previews
} }
} }
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(_previewModel.Box);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key) public override bool OnKeyDown(KeyboardKeys key)
{ {
switch (key) switch (key)
{ {
case KeyboardKeys.F: case KeyboardKeys.F:
// Pay respect.. ResetCamera();
ViewportCamera.SetArcBallView(_previewModel.Box);
return true; return true;
case KeyboardKeys.Spacebar: case KeyboardKeys.Spacebar:
PlayAnimation = !PlayAnimation; PlayAnimation = !PlayAnimation;
@@ -525,7 +432,6 @@ namespace FlaxEditor.Viewport.Previews
_showNodesButton = null; _showNodesButton = null;
_showBoundsButton = null; _showBoundsButton = null;
_showFloorButton = null; _showFloorButton = null;
_showCurrentLODButton = null;
_showNodesNamesButton = null; _showNodesNamesButton = null;
base.OnDestroy(); base.OnDestroy();

View File

@@ -4,6 +4,8 @@ using System;
using FlaxEditor.Surface; using FlaxEditor.Surface;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews namespace FlaxEditor.Viewport.Previews
@@ -46,6 +48,7 @@ namespace FlaxEditor.Viewport.Previews
private int _selectedModelIndex; private int _selectedModelIndex;
private Image _guiMaterialControl; private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1]; private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu;
/// <summary> /// <summary>
/// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>. /// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>.
@@ -95,20 +98,33 @@ namespace FlaxEditor.Viewport.Previews
Task.AddCustomActor(_previewModel); Task.AddCustomActor(_previewModel);
// Create context menu for primitive switching // Create context menu for primitive switching
if (useWidgets && ViewWidgetButtonMenu != null) if (useWidgets)
{ {
ViewWidgetButtonMenu.AddSeparator(); // Model mode widget
var modelSelect = ViewWidgetButtonMenu.AddChildMenu("Model").ContextMenu; var modelMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_modelWidgetButtonMenu = new ContextMenu();
_modelWidgetButtonMenu.VisibleChanged += control =>
{
if (!control.Visible)
return;
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
// Fill out all models // Fill out all models
for (int i = 0; i < Models.Length; i++) for (int i = 0; i < Models.Length; i++)
{ {
var button = modelSelect.AddButton(Models[i]); var index = i;
button.Tag = i; var button = _modelWidgetButtonMenu.AddButton(Models[index]);
button.ButtonClicked += _ => SelectedModelIndex = index;
button.Checked = SelectedModelIndex == index;
button.Tag = index;
} }
};
// Link the action new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
modelSelect.ButtonClicked += (button) => SelectedModelIndex = (int)button.Tag; {
TooltipText = "Change material model",
Parent = modelMode,
};
modelMode.Parent = this;
} }
} }

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.Input;
using FlaxEngine; using FlaxEngine;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
@@ -56,25 +55,14 @@ namespace FlaxEditor.Viewport.Previews
// Link actors for rendering // Link actors for rendering
Task.AddCustomActor(StaticModel); Task.AddCustomActor(StaticModel);
Task.AddCustomActor(AnimatedModel); Task.AddCustomActor(AnimatedModel);
}
if (useWidgets) /// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{ {
// Preview LOD ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () =>
{
StaticModel.ForcedLOD = previewLODValue.Value;
AnimatedModel.ForcedLOD = previewLODValue.Value;
};
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD;
}
}
} }
private void OnBegin(RenderTask task, GPUContext context) private void OnBegin(RenderTask task, GPUContext context)
@@ -103,8 +91,7 @@ namespace FlaxEditor.Viewport.Previews
switch (key) switch (key)
{ {
case KeyboardKeys.F: case KeyboardKeys.F:
// Pay respect.. ResetCamera();
ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
break; break;
} }
return base.OnKeyDown(key); return base.OnKeyDown(key);

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
using FlaxEditor.Viewport.Widgets;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews namespace FlaxEditor.Viewport.Previews
@@ -16,10 +16,27 @@ namespace FlaxEditor.Viewport.Previews
public class ModelPreview : AssetPreview public class ModelPreview : AssetPreview
{ {
private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton; private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton;
private ContextMenu _previewLODsWidgetButtonMenu;
private StaticModel _previewModel, _floorModel; private StaticModel _previewModel, _floorModel;
private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor; private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor;
private MeshDataCache _meshDatas; private MeshDataCache _meshDatas;
/// <summary>
/// Gets or sets a value that shows LOD statistics
/// </summary>
public bool ShowCurrentLOD
{
get => _showCurrentLOD;
set
{
if (_showCurrentLOD == value)
return;
_showCurrentLOD = value;
if (_showCurrentLODButton != null)
_showCurrentLODButton.Checked = value;
}
}
/// <summary> /// <summary>
/// Gets or sets the model asset to preview. /// Gets or sets the model asset to preview.
/// </summary> /// </summary>
@@ -198,18 +215,37 @@ namespace FlaxEditor.Viewport.Previews
}); });
_showCurrentLODButton.IndexInParent = 2; _showCurrentLODButton.IndexInParent = 2;
// Preview LOD // Preview LODs mode widget
var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{ {
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); if (!control.Visible)
previewLOD.CloseMenuOnClick = false; return;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f) var model = _previewModel.Model;
if (model && !model.WaitForLoaded())
{ {
Parent = previewLOD _previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
}; var lods = model.LODs.Length;
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value; for (int i = -1; i < lods; i++)
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD; {
var index = i;
var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index));
button.ButtonClicked += _ => _previewModel.ForcedLOD = index;
button.Checked = _previewModel.ForcedLOD == index;
button.Tag = index;
if (lods <= 1)
break;
} }
} }
};
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
Parent = PreviewLODsMode,
};
PreviewLODsMode.Parent = this;
}
} }
private void OnBegin(RenderTask task, GPUContext context) private void OnBegin(RenderTask task, GPUContext context)
@@ -347,7 +383,10 @@ namespace FlaxEditor.Viewport.Previews
{ {
var asset = Model; var asset = Model;
var lodIndex = ComputeLODIndex(asset, out var screenSize); var lodIndex = ComputeLODIndex(asset, out var screenSize);
string text = string.Format("Current LOD: {0}\nScreen Size: {1:F2}", lodIndex, screenSize); var auto = _previewModel.ForcedLOD == -1;
string text = auto ? "LOD Automatic" : "";
text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : "";
text += string.Format("\nCurrent LOD: {0}", lodIndex);
if (lodIndex != -1) if (lodIndex != -1)
{ {
var lods = asset.LODs; var lods = asset.LODs;
@@ -369,14 +408,21 @@ namespace FlaxEditor.Viewport.Previews
} }
} }
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(_previewModel.Box);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key) public override bool OnKeyDown(KeyboardKeys key)
{ {
switch (key) switch (key)
{ {
case KeyboardKeys.F: case KeyboardKeys.F:
// Pay respect.. ResetCamera();
ViewportCamera.SetArcBallView(_previewModel.Box);
break; break;
} }
return base.OnKeyDown(key); return base.OnKeyDown(key);
@@ -389,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews
Object.Destroy(ref _previewModel); Object.Destroy(ref _previewModel);
_showBoundsButton = null; _showBoundsButton = null;
_showCurrentLODButton = null; _showCurrentLODButton = null;
_previewLODsWidgetButtonMenu = null;
_showNormalsButton = null; _showNormalsButton = null;
_showTangentsButton = null; _showTangentsButton = null;
_showBitangentsButton = null; _showBitangentsButton = null;

View File

@@ -0,0 +1,177 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
namespace FlaxEditor.Viewport.Previews
{
/// <summary>
/// Animation asset preview editor viewport.
/// </summary>
/// <seealso cref="AnimatedModelPreview" />
public class SkinnedModelPreview : AnimatedModelPreview
{
private bool _showCurrentLOD;
private ContextMenuButton _showCurrentLODButton;
private ContextMenu _previewLODsWidgetButtonMenu;
/// <summary>
/// Gets or sets a value that shows LOD statistics
/// </summary>
public bool ShowCurrentLOD
{
get => _showCurrentLOD;
set
{
if (_showCurrentLOD == value)
return;
_showCurrentLOD = value;
if (_showCurrentLODButton != null)
_showCurrentLODButton.Checked = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SkinnedModelPreview"/> class.
/// </summary>
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
public SkinnedModelPreview(bool useWidgets)
: base(useWidgets)
{
if (useWidgets)
{
// Show Current LOD
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
{
_showCurrentLOD = !_showCurrentLOD;
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
});
_showCurrentLODButton.IndexInParent = 2;
// PreviewLODS mode widget
var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
if (!control.Visible)
return;
var skinned = PreviewActor.SkinnedModel;
if (skinned && !skinned.WaitForLoaded())
{
_previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
var lods = skinned.LODs.Length;
for (int i = -1; i < lods; i++)
{
var index = i;
var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index));
button.ButtonClicked += (button) => PreviewActor.ForcedLOD = index;
button.Checked = PreviewActor.ForcedLOD == index;
button.Tag = index;
if (lods <= 1)
break;
}
}
};
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
Parent = PreviewLODSMode,
};
PreviewLODSMode.Parent = this;
}
}
private int ComputeLODIndex(SkinnedModel model, out float screenSize)
{
screenSize = 1.0f;
if (PreviewActor.ForcedLOD != -1)
return PreviewActor.ForcedLOD;
// Based on RenderTools::ComputeModelLOD
CreateProjectionMatrix(out var projectionMatrix);
float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22);
var sphere = PreviewActor.Sphere;
var viewOrigin = ViewPosition;
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
screenSize = Mathf.Sqrt((float)screenRadiusSquared) * 2.0f;
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
// Skip if no need to calculate LOD
if (model.LoadedLODs == 0)
return -1;
var lods = model.LODs;
if (lods.Length == 0)
return -1;
if (lods.Length == 1)
return 0;
// Iterate backwards and return the first matching LOD
for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--)
{
if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
{
return lodIndex + PreviewActor.LODBias;
}
}
return 0;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var skinnedModel = PreviewActor.SkinnedModel;
if (skinnedModel == null || !skinnedModel.IsLoaded)
return;
var lods = skinnedModel.LODs;
if (lods.Length == 0)
{
// Force show skeleton for models without geometry
ShowNodes = true;
return;
}
if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel, out var screenSize);
var auto = PreviewActor.ForcedLOD == -1;
string text = auto ? "LOD Automatic" : "";
text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : "";
text += string.Format("\nCurrent LOD: {0}", lodIndex);
if (lodIndex != -1)
{
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++)
{
var mesh = lod.Meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount);
}
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
/// <inheritdoc />
public override void OnDestroy()
{
_showCurrentLODButton = null;
_previewLODsWidgetButtonMenu = null;
base.OnDestroy();
}
}
}

View File

@@ -182,6 +182,7 @@ namespace FlaxEditor.Windows.Assets
{ {
// Toolstrip // Toolstrip
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more");
// Split Panel // Split Panel

View File

@@ -779,6 +779,7 @@ namespace FlaxEditor.Windows.Assets
private MeshDataCache _meshData; private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings(); private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f; private float _backfacesThreshold = 0.6f;
private ToolStripButton _showCurrentLODButton;
/// <inheritdoc /> /// <inheritdoc />
public ModelWindow(Editor editor, AssetItem item) public ModelWindow(Editor editor, AssetItem item)
@@ -786,6 +787,9 @@ namespace FlaxEditor.Windows.Assets
{ {
// Toolstrip // Toolstrip
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more");
// Model preview // Model preview
@@ -869,6 +873,8 @@ namespace FlaxEditor.Windows.Assets
} }
} }
_showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
base.Update(deltaTime); base.Update(deltaTime);
} }
@@ -946,6 +952,7 @@ namespace FlaxEditor.Windows.Assets
base.OnDestroy(); base.OnDestroy();
Object.Destroy(ref _highlightActor); Object.Destroy(ref _highlightActor);
_showCurrentLODButton = null;
} }
} }
} }

View File

@@ -31,7 +31,7 @@ namespace FlaxEditor.Windows.Assets
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" /> /// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class SkinnedModelWindow : ModelBaseWindow<SkinnedModel, SkinnedModelWindow> public sealed class SkinnedModelWindow : ModelBaseWindow<SkinnedModel, SkinnedModelWindow>
{ {
private sealed class Preview : AnimatedModelPreview private sealed class Preview : SkinnedModelPreview
{ {
private readonly SkinnedModelWindow _window; private readonly SkinnedModelWindow _window;
@@ -1105,6 +1105,7 @@ namespace FlaxEditor.Windows.Assets
private Preview _preview; private Preview _preview;
private AnimatedModel _highlightActor; private AnimatedModel _highlightActor;
private ToolStripButton _showNodesButton; private ToolStripButton _showNodesButton;
private ToolStripButton _showCurrentLODButton;
private MeshData[][] _meshDatas; private MeshData[][] _meshDatas;
private bool _meshDatasInProgress; private bool _meshDatasInProgress;
@@ -1116,7 +1117,9 @@ namespace FlaxEditor.Windows.Assets
{ {
// Toolstrip // Toolstrip
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");
_showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view"); _showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view");
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more");
@@ -1265,6 +1268,7 @@ namespace FlaxEditor.Windows.Assets
} }
} }
_showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
_showNodesButton.Checked = _preview.ShowNodes; _showNodesButton.Checked = _preview.ShowNodes;
base.Update(deltaTime); base.Update(deltaTime);
@@ -1349,6 +1353,7 @@ namespace FlaxEditor.Windows.Assets
Object.Destroy(ref _highlightActor); Object.Destroy(ref _highlightActor);
_preview = null; _preview = null;
_showNodesButton = null; _showNodesButton = null;
_showCurrentLODButton = null;
} }
} }
} }

View File

@@ -80,6 +80,7 @@ namespace FlaxEditor.Windows
public LogGroup Group; public LogGroup Group;
public LogEntryDescription Desc; public LogEntryDescription Desc;
public SpriteHandle Icon; public SpriteHandle Icon;
public int LogCount = 1;
public LogEntry(DebugLogWindow window, ref LogEntryDescription desc) public LogEntry(DebugLogWindow window, ref LogEntryDescription desc)
: base(0, 0, 120, DefaultHeight) : base(0, 0, 120, DefaultHeight)
@@ -137,7 +138,14 @@ namespace FlaxEditor.Windows
// Title // Title
var textRect = new Rectangle(38, 2, clientRect.Width - 40, clientRect.Height - 10); var textRect = new Rectangle(38, 2, clientRect.Width - 40, clientRect.Height - 10);
Render2D.PushClip(ref clientRect); Render2D.PushClip(ref clientRect);
if (LogCount == 1)
{
Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground); Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
}
else if (LogCount > 1)
{
Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground);
}
Render2D.PopClip(); Render2D.PopClip();
} }
@@ -289,6 +297,7 @@ namespace FlaxEditor.Windows
private readonly List<LogEntry> _pendingEntries = new List<LogEntry>(32); private readonly List<LogEntry> _pendingEntries = new List<LogEntry>(32);
private readonly ToolStripButton _clearOnPlayButton; private readonly ToolStripButton _clearOnPlayButton;
private readonly ToolStripButton _collapseLogsButton;
private readonly ToolStripButton _pauseOnErrorButton; private readonly ToolStripButton _pauseOnErrorButton;
private readonly ToolStripButton[] _groupButtons = new ToolStripButton[3]; private readonly ToolStripButton[] _groupButtons = new ToolStripButton[3];
@@ -316,6 +325,7 @@ namespace FlaxEditor.Windows
}; };
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries"); toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode"); _clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode");
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse").SetAutoCheck(true).SetChecked(true).LinkTooltip("Collapses similar logs.");
_pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error"); _pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator(); toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages"); _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages");
@@ -612,6 +622,30 @@ namespace FlaxEditor.Windows
var top = _entriesPanel.Children.Count != 0 ? _entriesPanel.Children[_entriesPanel.Children.Count - 1].Bottom + spacing : margin.Top; var top = _entriesPanel.Children.Count != 0 ? _entriesPanel.Children[_entriesPanel.Children.Count - 1].Bottom + spacing : margin.Top;
for (int i = 0; i < _pendingEntries.Count; i++) for (int i = 0; i < _pendingEntries.Count; i++)
{ {
if (_collapseLogsButton.Checked)
{
bool logExists = false;
foreach (var child in _entriesPanel.Children)
{
if (child is LogEntry entry)
{
var pendingEntry = _pendingEntries[i];
if (string.Equals(entry.Desc.Title, pendingEntry.Desc.Title, StringComparison.Ordinal) &&
string.Equals(entry.Desc.LocationFile, pendingEntry.Desc.LocationFile, StringComparison.Ordinal) &&
entry.Desc.Level == pendingEntry.Desc.Level &&
string.Equals(entry.Desc.Description, pendingEntry.Desc.Description, StringComparison.Ordinal) &&
entry.Desc.LocationLine == pendingEntry.Desc.LocationLine)
{
entry.LogCount += 1;
newEntry = entry;
logExists = true;
break;
}
}
}
if (logExists)
continue;
}
newEntry = _pendingEntries[i]; newEntry = _pendingEntries[i];
newEntry.Visible = _groupButtons[(int)newEntry.Group].Checked; newEntry.Visible = _groupButtons[(int)newEntry.Group].Checked;
anyVisible |= newEntry.Visible; anyVisible |= newEntry.Visible;

View File

@@ -217,6 +217,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed); const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
// Evaluate nested animations // Evaluate nested animations
bool hasNested = false;
if (anim->NestedAnims.Count() != 0) if (anim->NestedAnims.Count() != 0)
{ {
for (auto& e : anim->NestedAnims) for (auto& e : anim->NestedAnims)
@@ -239,6 +240,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos); GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode); ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
hasNested = true;
} }
} }
} }
@@ -291,7 +293,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
dstNode.Scale = srcNode.Scale * weight; dstNode.Scale = srcNode.Scale * weight;
dstNode.Orientation = srcNode.Orientation * weight; dstNode.Orientation = srcNode.Orientation * weight;
} }
else else if (!hasNested)
{ {
dstNode = srcNode; dstNode = srcNode;
} }

View File

@@ -27,7 +27,6 @@ public:
/// <summary> /// <summary>
/// Returns true if material is an material instance. /// Returns true if material is an material instance.
/// </summary> /// </summary>
/// <returns>True if it's a material instance, otherwise false.</returns>
virtual bool IsMaterialInstance() const = 0; virtual bool IsMaterialInstance() const = 0;
public: public:

View File

@@ -618,7 +618,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Return // Return
case 5: case 5:
{ {
auto& scope = ThreadStacks.Get().Stack->Scope; auto scope = ThreadStacks.Get().Stack->Scope;
scope->FunctionReturn = tryGetValue(node->GetBox(1), Value::Zero); scope->FunctionReturn = tryGetValue(node->GetBox(1), Value::Zero);
break; break;
} }
@@ -634,7 +634,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
else else
{ {
// Evaluate method parameter value from the current scope // Evaluate method parameter value from the current scope
auto& scope = ThreadStacks.Get().Stack->Scope; auto scope = ThreadStacks.Get().Stack->Scope;
int32 index = boxBase->ID - 1; int32 index = boxBase->ID - 1;
if (index < scope->Parameters.Length()) if (index < scope->Parameters.Length())
value = scope->Parameters.Get()[index]; value = scope->Parameters.Get()[index];
@@ -1138,7 +1138,7 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
break; break;
} }
int32 arrayIndex = 0; int32 arrayIndex = 0;
for (; iteratorIndex < scope->ReturnedValues.Count(); arrayIndex++) for (; arrayIndex < scope->ReturnedValues.Count(); arrayIndex++)
{ {
const auto& e = scope->ReturnedValues[arrayIndex]; const auto& e = scope->ReturnedValues[arrayIndex];
if (e.NodeId == node->ID && e.BoxId == 1) if (e.NodeId == node->ID && e.BoxId == 1)

View File

@@ -37,6 +37,9 @@ public class Content : EngineModule
files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.TopDirectoryOnly)); files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Assets"), "*.h", SearchOption.TopDirectoryOnly)); files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Assets"), "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Cache"), "*.h", SearchOption.TopDirectoryOnly)); files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Cache"), "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Factories"), "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Storage"), "*.h", SearchOption.TopDirectoryOnly)); files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Storage"), "*.h", SearchOption.TopDirectoryOnly));
files.Add(Path.Combine(FolderPath, "Upgraders/BinaryAssetUpgrader.h"));
files.Add(Path.Combine(FolderPath, "Upgraders/IAssetUpgrader.h"));
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Flax.Build; using Flax.Build;
using Flax.Build.NativeCpp; using Flax.Build.NativeCpp;
@@ -23,5 +24,7 @@ public class ContentExporters : EngineModule
/// <inheritdoc /> /// <inheritdoc />
public override void GetFilesToDeploy(List<string> files) public override void GetFilesToDeploy(List<string> files)
{ {
files.Add(Path.Combine(FolderPath, "AssetsExportingManager.h"));
files.Add(Path.Combine(FolderPath, "Types.h"));
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Flax.Build; using Flax.Build;
using Flax.Build.NativeCpp; using Flax.Build.NativeCpp;
@@ -31,5 +32,7 @@ public class ContentImporters : EngineModule
/// <inheritdoc /> /// <inheritdoc />
public override void GetFilesToDeploy(List<string> files) public override void GetFilesToDeploy(List<string> files)
{ {
files.Add(Path.Combine(FolderPath, "AssetsImportingManager.h"));
files.Add(Path.Combine(FolderPath, "Types.h"));
} }
} }

View File

@@ -357,8 +357,8 @@ public:
{ {
for (Iterator i = Begin(); i.IsNotEnd(); ++i) for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{ {
if (i->Value) if (i->Item)
::Delete(i->Value); ::Delete(i->Item);
} }
Clear(); Clear();
} }

View File

@@ -38,9 +38,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
BoundingBox() BoundingBox() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BoundingBox"/> struct. /// Initializes a new instance of the <see cref="BoundingBox"/> struct.

View File

@@ -12,6 +12,7 @@
/// </summary> /// </summary>
API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum
{ {
friend CollisionsHelper;
private: private:
Matrix _matrix; Matrix _matrix;
@@ -33,9 +34,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
BoundingFrustum() BoundingFrustum() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BoundingFrustum"/> struct. /// Initializes a new instance of the <see cref="BoundingFrustum"/> struct.

View File

@@ -34,9 +34,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
BoundingSphere() BoundingSphere() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BoundingSphere"/> struct. /// Initializes a new instance of the <see cref="BoundingSphere"/> struct.

View File

@@ -939,7 +939,6 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
normal = Vector3::Up; normal = Vector3::Up;
return false; return false;
} }
const Vector3 point = ray.Position + ray.Direction * distance; const Vector3 point = ray.Position + ray.Direction * distance;
normal = Vector3::Normalize(point - sphere.Center); normal = Vector3::Normalize(point - sphere.Center);
return true; return true;
@@ -953,22 +952,17 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
point = Vector3::Zero; point = Vector3::Zero;
return false; return false;
} }
point = ray.Position + ray.Direction * distance; point = ray.Position + ray.Direction * distance;
return true; return true;
} }
PlaneIntersectionType CollisionsHelper::PlaneIntersectsPoint(const Plane& plane, const Vector3& point) PlaneIntersectionType CollisionsHelper::PlaneIntersectsPoint(const Plane& plane, const Vector3& point)
{ {
Real distance = Vector3::Dot(plane.Normal, point); const Real distance = Vector3::Dot(plane.Normal, point) + plane.D;
distance += plane.D;
if (distance > Plane::DistanceEpsilon) if (distance > Plane::DistanceEpsilon)
return PlaneIntersectionType::Front; return PlaneIntersectionType::Front;
if (distance < Plane::DistanceEpsilon) if (distance < Plane::DistanceEpsilon)
return PlaneIntersectionType::Back; return PlaneIntersectionType::Back;
return PlaneIntersectionType::Intersecting; return PlaneIntersectionType::Intersecting;
} }
@@ -1169,7 +1163,6 @@ ContainmentType CollisionsHelper::SphereContainsPoint(const BoundingSphere& sphe
{ {
if (Vector3::DistanceSquared(point, sphere.Center) <= sphere.Radius * sphere.Radius) if (Vector3::DistanceSquared(point, sphere.Center) <= sphere.Radius * sphere.Radius)
return ContainmentType::Contains; return ContainmentType::Contains;
return ContainmentType::Disjoint; return ContainmentType::Disjoint;
} }
@@ -1254,13 +1247,10 @@ ContainmentType CollisionsHelper::SphereContainsBox(const BoundingSphere& sphere
ContainmentType CollisionsHelper::SphereContainsSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2) ContainmentType CollisionsHelper::SphereContainsSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2)
{ {
const Real distance = Vector3::Distance(sphere1.Center, sphere2.Center); const Real distance = Vector3::Distance(sphere1.Center, sphere2.Center);
if (sphere1.Radius + sphere2.Radius < distance) if (sphere1.Radius + sphere2.Radius < distance)
return ContainmentType::Disjoint; return ContainmentType::Disjoint;
if (sphere1.Radius - sphere2.Radius < distance) if (sphere1.Radius - sphere2.Radius < distance)
return ContainmentType::Intersects; return ContainmentType::Intersects;
return ContainmentType::Contains; return ContainmentType::Contains;
} }
@@ -1274,7 +1264,8 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
auto result = ContainmentType::Contains; auto result = ContainmentType::Contains;
for (int32 i = 0; i < 6; i++) for (int32 i = 0; i < 6; i++)
{ {
Plane plane = frustum.GetPlane(i); Plane plane = frustum._planes[i];
Vector3 p = box.Minimum; Vector3 p = box.Minimum;
if (plane.Normal.X >= 0) if (plane.Normal.X >= 0)
p.X = box.Maximum.X; p.X = box.Maximum.X;
@@ -1282,7 +1273,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Maximum.Y; p.Y = box.Maximum.Y;
if (plane.Normal.Z >= 0) if (plane.Normal.Z >= 0)
p.Z = box.Maximum.Z; p.Z = box.Maximum.Z;
if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back) if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
return ContainmentType::Disjoint; return ContainmentType::Disjoint;
p = box.Maximum; p = box.Maximum;
@@ -1292,7 +1283,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Minimum.Y; p.Y = box.Minimum.Y;
if (plane.Normal.Z >= 0) if (plane.Normal.Z >= 0)
p.Z = box.Minimum.Z; p.Z = box.Minimum.Z;
if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back) if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
result = ContainmentType::Intersects; result = ContainmentType::Intersects;
} }
return result; return result;

View File

@@ -50,9 +50,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Color() Color() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.

View File

@@ -53,9 +53,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Color32() Color32() = default;
{
}
/// <summary> /// <summary>
/// Constructs a new Color32 with given r, g, b, a components. /// Constructs a new Color32 with given r, g, b, a components.

View File

@@ -121,9 +121,7 @@ public:
/// <summary> /// <summary>
/// Default constructor /// Default constructor
/// </summary> /// </summary>
Half2() Half2() = default;
{
}
/// <summary> /// <summary>
/// Init /// Init
@@ -185,9 +183,7 @@ public:
Half Z; Half Z;
public: public:
Half3() Half3() = default;
{
}
Half3(Half x, Half y, Half z) Half3(Half x, Half y, Half z)
: X(x) : X(x)
@@ -242,9 +238,7 @@ public:
Half W; Half W;
public: public:
Half4() Half4() = default;
{
}
Half4(Half x, Half y, Half z, Half w) Half4(Half x, Half y, Half z, Half w)
: X(x) : X(x)

View File

@@ -83,9 +83,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Matrix() Matrix() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Matrix"/> struct. /// Initializes a new instance of the <see cref="Matrix"/> struct.

View File

@@ -63,9 +63,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Matrix3x3() Matrix3x3() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> struct. /// Initializes a new instance of the <see cref="Matrix3x3"/> struct.

View File

@@ -30,9 +30,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Plane() Plane() = default;
{
}
/// <summary> /// <summary>
/// Init /// Init

View File

@@ -67,9 +67,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Quaternion() Quaternion() = default;
{
}
/// <summary> /// <summary>
/// Init /// Init

View File

@@ -35,9 +35,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Ray() Ray() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Ray"/> struct. /// Initializes a new instance of the <see cref="Ray"/> struct.

View File

@@ -31,9 +31,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Rectangle() Rectangle() = default;
{
}
// Init // Init
// @param x Rectangle location X coordinate // @param x Rectangle location X coordinate

View File

@@ -40,9 +40,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Transform() Transform() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Transform"/> struct. /// Initializes a new instance of the <see cref="Transform"/> struct.

View File

@@ -30,9 +30,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Triangle() Triangle() = default;
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Triangle"/> struct. /// Initializes a new instance of the <see cref="Triangle"/> struct.

View File

@@ -60,9 +60,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Vector2Base() Vector2Base() = default;
{
}
FORCE_INLINE Vector2Base(T xy) FORCE_INLINE Vector2Base(T xy)
: X(xy) : X(xy)

View File

@@ -89,9 +89,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Vector3Base() Vector3Base() = default;
{
}
FORCE_INLINE Vector3Base(T xyz) FORCE_INLINE Vector3Base(T xyz)
: X(xyz) : X(xyz)

View File

@@ -76,9 +76,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Vector4Base() Vector4Base() = default;
{
}
FORCE_INLINE Vector4Base(T xyzw) FORCE_INLINE Vector4Base(T xyzw)
: X(xyzw) : X(xyzw)

View File

@@ -52,9 +52,7 @@ public:
/// <summary> /// <summary>
/// Empty constructor. /// Empty constructor.
/// </summary> /// </summary>
Viewport() Viewport() = default;
{
}
// Init // Init
// @param x The x coordinate of the upper-left corner of the viewport in pixels // @param x The x coordinate of the upper-left corner of the viewport in pixels

View File

@@ -36,6 +36,32 @@ namespace AllocatorExt
return result; return result;
} }
/// <summary>
/// Reallocates block of the memory.
/// </summary>
/// </summary>
/// <param name="ptr">A pointer to the memory block to reallocate.</param>
/// <param name="newSize">The size of the new allocation (in bytes).</param>
/// <param name="alignment">The memory alignment (in bytes). Must be an integer power of 2.</param>
/// <returns>The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment.</returns>
inline void* ReallocAligned(void* ptr, uint64 newSize, uint64 alignment)
{
if (newSize == 0)
{
Allocator::Free(ptr);
return nullptr;
}
if (!ptr)
return Allocator::Allocate(newSize, alignment);
void* result = Allocator::Allocate(newSize, alignment);
if (result)
{
Platform::MemoryCopy(result, ptr, newSize);
Allocator::Free(ptr);
}
return result;
}
/// <summary> /// <summary>
/// Reallocates block of the memory. /// Reallocates block of the memory.
/// </summary> /// </summary>

View File

@@ -114,3 +114,20 @@ inline Span<T> ToSpan(const T* ptr, int32 length)
{ {
return Span<T>(ptr, length); return Span<T>(ptr, length);
} }
template<typename T, typename U = T, typename AllocationType = HeapAllocation>
inline Span<U> ToSpan(const Array<T, AllocationType>& data)
{
return Span<U>((U*)data.Get(), data.Count());
}
template<typename T>
inline bool SpanContains(const Span<T> span, const T& value)
{
for (int32 i = 0; i < span.Length(); i++)
{
if (span.Get()[i] == value)
return true;
}
return false;
}

View File

@@ -73,7 +73,7 @@ void String::Set(const char* chars, int32 length)
_length = length; _length = length;
} }
if (chars) if (chars)
StringUtils::ConvertANSI2UTF16(chars, _data, length); StringUtils::ConvertANSI2UTF16(chars, _data, length, _length);
} }
void String::SetUTF8(const char* chars, int32 length) void String::SetUTF8(const char* chars, int32 length)
@@ -112,7 +112,8 @@ void String::Append(const char* chars, int32 count)
_data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16); _data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16);
Platform::MemoryCopy(_data, oldData, oldLength * sizeof(Char)); Platform::MemoryCopy(_data, oldData, oldLength * sizeof(Char));
StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count * sizeof(Char)); StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count, _length);
_length += oldLength;
_data[_length] = 0; _data[_length] = 0;
Platform::Free(oldData); Platform::Free(oldData);

View File

@@ -125,7 +125,8 @@ public:
const int32 length = str && *str ? StringUtils::Length(str) : 0; const int32 length = str && *str ? StringUtils::Length(str) : 0;
const int32 prevCnt = _data.Count(); const int32 prevCnt = _data.Count();
_data.AddDefault(length); _data.AddDefault(length);
StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length); int32 tmp;
StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length, tmp);
return *this; return *this;
} }

View File

@@ -660,7 +660,8 @@ Variant::Variant(const StringAnsiView& v)
const int32 length = v.Length() * sizeof(Char) + 2; const int32 length = v.Length() * sizeof(Char) + 2;
AsBlob.Data = Allocator::Allocate(length); AsBlob.Data = Allocator::Allocate(length);
AsBlob.Length = length; AsBlob.Length = length;
StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length()); int32 tmp;
StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length(), tmp);
((Char*)AsBlob.Data)[v.Length()] = 0; ((Char*)AsBlob.Data)[v.Length()] = 0;
} }
else else
@@ -2578,7 +2579,8 @@ void Variant::SetString(const StringAnsiView& str)
AsBlob.Data = Allocator::Allocate(length); AsBlob.Data = Allocator::Allocate(length);
AsBlob.Length = length; AsBlob.Length = length;
} }
StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length()); int32 tmp;
StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length(), tmp);
((Char*)AsBlob.Data)[str.Length()] = 0; ((Char*)AsBlob.Data)[str.Length()] = 0;
} }
@@ -2794,6 +2796,122 @@ String Variant::ToString() const
} }
} }
void Variant::Inline()
{
VariantType::Types type = VariantType::Null;
byte data[sizeof(Matrix)];
if (Type.Type == VariantType::Structure && AsBlob.Data && AsBlob.Length <= sizeof(Matrix))
{
for (int32 i = 2; i < VariantType::MAX; i++)
{
if (StringUtils::Compare(Type.TypeName, InBuiltTypesTypeNames[i]) == 0)
{
type = (VariantType::Types)i;
break;
}
}
if (type == VariantType::Null)
{
// Aliases
if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector2") == 0)
type = VariantType::Types::Vector2;
else if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector3") == 0)
type = VariantType::Types::Vector3;
else if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector4") == 0)
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
}
if (type != VariantType::Null)
{
switch (type)
{
case VariantType::Bool:
*this = *(bool*)data;
break;
case VariantType::Int:
*this = *(int32*)data;
break;
case VariantType::Uint:
*this = *(uint32*)data;
break;
case VariantType::Int64:
*this = *(int64*)data;
break;
case VariantType::Uint64:
*this = *(uint64*)data;
break;
case VariantType::Float:
*this = *(float*)data;
break;
case VariantType::Double:
*this = *(double*)data;
break;
case VariantType::Float2:
*this = *(Float2*)data;
break;
case VariantType::Float3:
*this = *(Float3*)data;
break;
case VariantType::Float4:
*this = *(Float4*)data;
break;
case VariantType::Color:
*this = *(Color*)data;
break;
case VariantType::Guid:
*this = *(Guid*)data;
break;
case VariantType::BoundingBox:
*this = Variant(*(BoundingBox*)data);
break;
case VariantType::BoundingSphere:
*this = *(BoundingSphere*)data;
break;
case VariantType::Quaternion:
*this = *(Quaternion*)data;
break;
case VariantType::Transform:
*this = Variant(*(Transform*)data);
break;
case VariantType::Rectangle:
*this = *(Rectangle*)data;
break;
case VariantType::Ray:
*this = Variant(*(Ray*)data);
break;
case VariantType::Matrix:
*this = Variant(*(Matrix*)data);
break;
case VariantType::Int2:
*this = *(Int2*)data;
break;
case VariantType::Int3:
*this = *(Int3*)data;
break;
case VariantType::Int4:
*this = *(Int4*)data;
break;
case VariantType::Int16:
*this = *(int16*)data;
break;
case VariantType::Uint16:
*this = *(uint16*)data;
break;
case VariantType::Double2:
*this = *(Double2*)data;
break;
case VariantType::Double3:
*this = *(Double3*)data;
break;
case VariantType::Double4:
*this = *(Double4*)data;
break;
}
}
}
bool Variant::CanCast(const Variant& v, const VariantType& to) bool Variant::CanCast(const Variant& v, const VariantType& to)
{ {
if (v.Type == to) if (v.Type == to)
@@ -3680,6 +3798,7 @@ void Variant::AllocStructure()
const ScriptingType& type = typeHandle.GetType(); const ScriptingType& type = typeHandle.GetType();
AsBlob.Length = type.Size; AsBlob.Length = type.Size;
AsBlob.Data = Allocator::Allocate(AsBlob.Length); AsBlob.Data = Allocator::Allocate(AsBlob.Length);
Platform::MemoryClear(AsBlob.Data, AsBlob.Length);
type.Struct.Ctor(AsBlob.Data); type.Struct.Ctor(AsBlob.Data);
} }
else if (typeName == "System.Int16" || typeName == "System.UInt16") else if (typeName == "System.Int16" || typeName == "System.UInt16")

View File

@@ -359,6 +359,9 @@ public:
void SetAsset(Asset* asset); void SetAsset(Asset* asset);
String ToString() const; String ToString() const;
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
FORCE_INLINE Variant Cast(const VariantType& to) const FORCE_INLINE Variant Cast(const VariantType& to) const
{ {
return Cast(*this, to); return Cast(*this, to);

View File

@@ -50,13 +50,13 @@ namespace FlaxEngine.Interop
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks> /// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray WrapPooledArray(Array arr, Type arrayType) public static ManagedArray WrapPooledArray(Array arr, Type arrayType)
{ {
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * Marshal.SizeOf(arr.GetType().GetElementType())); ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType()));
managedArray.WrapArray(arr, arrayType); managedArray.WrapArray(arr, arrayType);
return managedArray; return managedArray;
} }
internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType) internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType)
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, Marshal.SizeOf(elementType)), length, arrayType, elementType); => new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, NativeInterop.GetTypeSize(elementType)), length, arrayType, elementType);
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType) internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType)
=> new ManagedArray(ptr, length, arrayType, elementType); => new ManagedArray(ptr, length, arrayType, elementType);
@@ -86,7 +86,7 @@ namespace FlaxEngine.Interop
_length = arr.Length; _length = arr.Length;
_arrayType = arrayType; _arrayType = arrayType;
_elementType = arr.GetType().GetElementType(); _elementType = arr.GetType().GetElementType();
_elementSize = Marshal.SizeOf(_elementType); _elementSize = NativeInterop.GetTypeSize(_elementType);
} }
internal void Allocate<T>(int length) where T : unmanaged internal void Allocate<T>(int length) where T : unmanaged
@@ -117,7 +117,7 @@ namespace FlaxEngine.Interop
_length = length; _length = length;
_arrayType = arrayType; _arrayType = arrayType;
_elementType = elementType; _elementType = elementType;
_elementSize = Marshal.SizeOf(elementType); _elementSize = NativeInterop.GetTypeSize(_elementType);
} }
~ManagedArray() ~ManagedArray()

View File

@@ -350,14 +350,14 @@ namespace FlaxEngine.Interop
#endif #endif
public static class NativeToManaged public static class NativeToManaged
{ {
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
{ {
if (unmanaged is null) if (unmanaged is null)
return null; return null;
return new T[numElements]; return new T[numElements];
} }
public static Span<T> GetManagedValuesDestination(T[]? managed) => managed; public static Span<T> GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements) public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{ {
@@ -390,7 +390,7 @@ namespace FlaxEngine.Interop
#endif #endif
public static class ManagedToNative public static class ManagedToNative
{ {
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
{ {
if (managed is null) if (managed is null)
{ {
@@ -399,10 +399,10 @@ namespace FlaxEngine.Interop
} }
numElements = managed.Length; numElements = managed.Length;
ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length); ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Normal);
} }
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed; public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{ {
@@ -431,7 +431,7 @@ namespace FlaxEngine.Interop
ManagedArray unmanagedArray; ManagedArray unmanagedArray;
ManagedHandle handle; ManagedHandle handle;
public void FromManaged(T[]? managed) public void FromManaged(T[] managed)
{ {
if (managed == null) if (managed == null)
return; return;
@@ -476,7 +476,7 @@ namespace FlaxEngine.Interop
} }
} }
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
{ {
if (managed is null) if (managed is null)
{ {
@@ -489,7 +489,7 @@ namespace FlaxEngine.Interop
return (TUnmanagedElement*)handle; return (TUnmanagedElement*)handle;
} }
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed; public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{ {
@@ -499,9 +499,9 @@ namespace FlaxEngine.Interop
return unmanagedArray.ToSpan<TUnmanagedElement>(); return unmanagedArray.ToSpan<TUnmanagedElement>();
} }
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements]; public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements];
public static Span<T> GetManagedValuesDestination(T[]? managed) => managed; public static Span<T> GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements) public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{ {

View File

@@ -244,7 +244,25 @@ namespace FlaxEngine.Interop
@namespace = NativeAllocStringAnsi(type.Namespace ?? ""), @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
typeAttributes = (uint)type.Attributes, typeAttributes = (uint)type.Attributes,
}; };
*assemblyHandle = GetAssemblyHandle(type.Assembly);
Assembly assembly = null;
if (type.IsGenericType && !type.Assembly.IsCollectible)
{
// The owning assembly of a generic type with type arguments referencing
// collectible assemblies must be one of the collectible assemblies.
foreach (var genericType in type.GetGenericArguments())
{
if (genericType.Assembly.IsCollectible)
{
assembly = genericType.Assembly;
break;
}
}
}
if (assembly == null)
assembly = type.Assembly;
*assemblyHandle = GetAssemblyHandle(assembly);
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
@@ -770,6 +788,13 @@ namespace FlaxEngine.Interop
field.field.SetValue(fieldOwner, value); field.field.SetValue(fieldOwner, value);
} }
[UnmanagedCallersOnly]
internal static int FieldGetOffset(ManagedHandle fieldHandle)
{
FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
return (int)Marshal.OffsetOf(field.field.DeclaringType, field.field.Name);
}
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
internal static void FieldGetValue(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) internal static void FieldGetValue(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr)
{ {
@@ -778,6 +803,15 @@ namespace FlaxEngine.Interop
field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset); field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset);
} }
[UnmanagedCallersOnly]
internal static IntPtr FieldGetValueBoxed(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle)
{
object fieldOwner = fieldOwnerHandle.Target;
FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
object fieldValue = field.field.GetValue(fieldOwner);
return Invoker.MarshalReturnValueGeneric(field.field.FieldType, fieldValue);
}
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
internal static void WriteArrayReference(ManagedHandle arrayHandle, IntPtr valueHandle, int index) internal static void WriteArrayReference(ManagedHandle arrayHandle, IntPtr valueHandle, int index)
{ {
@@ -885,6 +919,7 @@ namespace FlaxEngine.Interop
handle.Free(); handle.Free();
fieldHandleCacheCollectible.Clear(); fieldHandleCacheCollectible.Clear();
#endif #endif
_typeSizeCache.Clear();
foreach (var pair in classAttributesCacheCollectible) foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free(); pair.Value.Free();
@@ -923,7 +958,7 @@ namespace FlaxEngine.Interop
if (nativeType.IsClass) if (nativeType.IsClass)
size = sizeof(IntPtr); size = sizeof(IntPtr);
else else
size = Marshal.SizeOf(nativeType); size = GetTypeSize(nativeType);
return size; return size;
} }

View File

@@ -46,6 +46,7 @@ namespace FlaxEngine.Interop
#endif #endif
private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new(); private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new();
private static Dictionary<Assembly, ManagedHandle> assemblyHandles = new(); private static Dictionary<Assembly, ManagedHandle> assemblyHandles = new();
private static Dictionary<Type, int> _typeSizeCache = new();
private static Dictionary<string, IntPtr> loadedNativeLibraries = new(); private static Dictionary<string, IntPtr> loadedNativeLibraries = new();
internal static Dictionary<string, string> nativeLibraryPaths = new(); internal static Dictionary<string, string> nativeLibraryPaths = new();
@@ -96,14 +97,17 @@ namespace FlaxEngine.Interop
} }
#if FLAX_EDITOR #if FLAX_EDITOR
private static Assembly? OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
{ {
// FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored // FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored
string editorTargetPath = Path.GetDirectoryName(nativeLibraryPaths.Keys.First(x => x != "FlaxEngine")); foreach (string nativeLibraryPath in nativeLibraryPaths.Values)
{
string editorTargetPath = Path.GetDirectoryName(nativeLibraryPath);
var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll"); var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll");
if (File.Exists(assemblyPath)) if (File.Exists(assemblyPath))
return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
}
return null; return null;
} }
#endif #endif
@@ -581,7 +585,7 @@ namespace FlaxEngine.Interop
else if (fieldType.IsClass || fieldType.IsPointer) else if (fieldType.IsClass || fieldType.IsPointer)
fieldAlignment = IntPtr.Size; fieldAlignment = IntPtr.Size;
else else
fieldAlignment = Marshal.SizeOf(fieldType); fieldAlignment = GetTypeSize(fieldType);
} }
internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset)
@@ -1085,6 +1089,29 @@ namespace FlaxEngine.Interop
return handle; return handle;
} }
internal static int GetTypeSize(Type type)
{
if (!_typeSizeCache.TryGetValue(type, out var size))
{
try
{
var marshalType = type;
if (type.IsEnum)
marshalType = type.GetEnumUnderlyingType();
size = Marshal.SizeOf(marshalType);
}
catch
{
// Workaround the issue where structure defined within generic type instance (eg. MyType<int>.MyStruct) fails to get size
// https://github.com/dotnet/runtime/issues/46426
var obj = Activator.CreateInstance(type);
size = Marshal.SizeOf(obj);
}
_typeSizeCache.Add(type, size);
}
return size;
}
private static class DelegateHelpers private static class DelegateHelpers
{ {
#if USE_AOT #if USE_AOT

View File

@@ -1031,6 +1031,11 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : uint64
/// </summary> /// </summary>
Sky = 1 << 26, Sky = 1 << 26,
/// <summary>
/// Shows/hides light debug shapes.
/// </summary>
LightsDebug = 1 << 27,
/// <summary> /// <summary>
/// Default flags for Game. /// Default flags for Game.
/// </summary> /// </summary>

View File

@@ -238,7 +238,7 @@ public:
/// <summary> /// <summary>
/// Gets the default material. /// Gets the default material.
/// </summary> /// </summary>
MaterialBase* GetDefaultMaterial() const; API_PROPERTY() MaterialBase* GetDefaultMaterial() const;
/// <summary> /// <summary>
/// Gets the default material (Deformable domain). /// Gets the default material (Deformable domain).

View File

@@ -145,7 +145,7 @@ bool GPUShader::Create(MemoryReadStream& stream)
// Create CB // Create CB
#if GPU_ENABLE_RESOURCE_NAMING #if GPU_ENABLE_RESOURCE_NAMING
String name = ToString() + TEXT(".CB") + i; String name = String::Format(TEXT("{}.CB{}"), ToString(), i);
#else #else
String name; String name;
#endif #endif

View File

@@ -1337,6 +1337,20 @@ Actor* Actor::FindActor(const MClass* type, const StringView& name) const
return nullptr; return nullptr;
} }
Actor* Actor::FindActor(const MClass* type, const Tag& tag) const
{
CHECK_RETURN(type, nullptr);
if (GetClass()->IsSubClassOf(type) && HasTag(tag))
return const_cast<Actor*>(this);
for (auto child : Children)
{
const auto actor = child->FindActor(type, tag);
if (actor)
return actor;
}
return nullptr;
}
Script* Actor::FindScript(const MClass* type) const Script* Actor::FindScript(const MClass* type) const
{ {
CHECK_RETURN(type, nullptr); CHECK_RETURN(type, nullptr);

View File

@@ -270,6 +270,17 @@ namespace FlaxEngine
return FindActor(typeof(T), name) as T; return FindActor(typeof(T), name) as T;
} }
/// <summary>
/// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary>
/// <param name="tag">A tag on the object.</param>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Actor instance if found, null otherwise.</returns>
public T FindActor<T>(Tag tag) where T : Actor
{
return FindActor(typeof(T), tag) as T;
}
/// <summary> /// <summary>
/// Searches for all actors of a specific type in this actor children list. /// Searches for all actors of a specific type in this actor children list.
/// </summary> /// </summary>

View File

@@ -739,6 +739,14 @@ public:
/// <returns>Actor instance if found, null otherwise.</returns> /// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name) const; API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name) const;
/// <summary>
/// Tries to find the actor of the given type and tag in this actor hierarchy.
/// </summary>
/// <param name="type">Type of the actor to search for. Includes any actors derived from the type.</param>
/// <param name="tag">The tag of the actor to search for.</param>
/// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag) const;
/// <summary> /// <summary>
/// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary> /// </summary>
@@ -760,6 +768,17 @@ public:
return (T*)FindActor(T::GetStaticClass(), name); return (T*)FindActor(T::GetStaticClass(), name);
} }
/// <summary>
/// Tries to find the actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <returns>Actor instance if found, null otherwise.</returns>
template<typename T>
FORCE_INLINE T* FindActor(const Tag& tag) const
{
return (T*)FindActor(T::GetStaticClass(), tag);
}
/// <summary> /// <summary>
/// Tries to find the script of the given type in this actor hierarchy (checks this actor and all children hierarchy). /// Tries to find the script of the given type in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary> /// </summary>

View File

@@ -144,7 +144,7 @@ void AnimatedModel::SetCurrentPose(const Array<Matrix>& nodesTransformation, boo
Matrix invWorld; Matrix invWorld;
Matrix::Invert(world, invWorld); Matrix::Invert(world, invWorld);
for (auto& m : GraphInstance.NodesPose) for (auto& m : GraphInstance.NodesPose)
m = invWorld * m; m = m * invWorld;
} }
OnAnimationUpdated(); OnAnimationUpdated();
} }
@@ -603,7 +603,13 @@ void AnimatedModel::OnAnimationUpdated_Sync()
// Update synchronous stuff // Update synchronous stuff
UpdateSockets(); UpdateSockets();
ApplyRootMotion(GraphInstance.RootMotion); ApplyRootMotion(GraphInstance.RootMotion);
if (!_isDuringUpdateEvent)
{
// Prevent stack-overflow when gameplay modifies the pose within the event
_isDuringUpdateEvent = true;
AnimationUpdated(); AnimationUpdated();
_isDuringUpdateEvent = false;
}
} }
void AnimatedModel::OnAnimationUpdated() void AnimatedModel::OnAnimationUpdated()
@@ -892,6 +898,31 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DrawModes |= DrawPass::GlobalSurfaceAtlas; DrawModes |= DrawPass::GlobalSurfaceAtlas;
} }
const Span<MaterialSlot> AnimatedModel::GetMaterialSlots() const
{
const auto model = SkinnedModel.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex)
{
if (SkinnedModel)
SkinnedModel->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material)
{
material = SkinnedModel->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultMaterial();
}
return material;
}
bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{ {
auto model = SkinnedModel.Get(); auto model = SkinnedModel.Get();

View File

@@ -60,6 +60,7 @@ private:
AnimationUpdateMode _actualMode; AnimationUpdateMode _actualMode;
uint32 _counter; uint32 _counter;
Real _lastMinDstSqr; Real _lastMinDstSqr;
bool _isDuringUpdateEvent = false;
uint64 _lastUpdateFrame; uint64 _lastUpdateFrame;
BlendShapesInstance _blendShapes; BlendShapesInstance _blendShapes;
ScriptingObjectReference<AnimatedModel> _masterPose; ScriptingObjectReference<AnimatedModel> _masterPose;
@@ -372,6 +373,8 @@ public:
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
const Span<MaterialSlot> GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
void OnDeleteObject() override; void OnDeleteObject() override;

View File

@@ -26,6 +26,7 @@ void Light::OnEnable()
GetSceneRendering()->AddActor(this, _sceneRenderingKey); GetSceneRendering()->AddActor(this, _sceneRenderingKey);
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->AddViewportIcon(this); GetSceneRendering()->AddViewportIcon(this);
GetSceneRendering()->AddLightsDebug<Light, &Light::DrawLightsDebug>(this);
#endif #endif
// Base // Base
@@ -36,6 +37,7 @@ void Light::OnDisable()
{ {
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->RemoveViewportIcon(this); GetSceneRendering()->RemoveViewportIcon(this);
GetSceneRendering()->RemoveLightsDebug<Light, &Light::DrawLightsDebug>(this);
#endif #endif
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
@@ -43,6 +45,12 @@ void Light::OnDisable()
Actor::OnDisable(); Actor::OnDisable();
} }
#if USE_EDITOR
void Light::DrawLightsDebug(RenderView& view)
{
}
#endif
void Light::Serialize(SerializeStream& stream, const void* otherObj) void Light::Serialize(SerializeStream& stream, const void* otherObj)
{ {
// Base // Base

View File

@@ -66,6 +66,8 @@ public:
const Vector3 size(50); const Vector3 size(50);
return BoundingBox(_transform.Translation - size, _transform.Translation + size); return BoundingBox(_transform.Translation - size, _transform.Translation + size);
} }
virtual void DrawLightsDebug(RenderView& view);
#endif #endif
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -39,10 +39,9 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material)
MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex) MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex)
{ {
WaitForModelLoad(); WaitForModelLoad();
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); MaterialBase* material = GetMaterial(entryIndex);
auto material = Entries[entryIndex].Material.Get();
CHECK_RETURN(material && !material->WaitForLoaded(), nullptr); CHECK_RETURN(material && !material->WaitForLoaded(), nullptr);
const auto result = material->CreateVirtualInstance(); MaterialInstance* result = material->CreateVirtualInstance();
Entries[entryIndex].Material = result; Entries[entryIndex].Material = result;
if (_sceneRenderingKey != -1) if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);

View File

@@ -35,6 +35,17 @@ public:
/// </summary> /// </summary>
API_PROPERTY() void SetEntries(const Array<ModelInstanceEntry>& value); API_PROPERTY() void SetEntries(const Array<ModelInstanceEntry>& value);
/// <summary>
/// Gets the material slots array set on the asset (eg. model or skinned model asset).
/// </summary>
API_PROPERTY(Sealed) virtual const Span<class MaterialSlot> GetMaterialSlots() const = 0;
/// <summary>
/// Gets the material used to draw the meshes which are assigned to that slot (set in Entries or model's default).
/// </summary>
/// <param name="entryIndex">The material slot entry index.</param>
API_FUNCTION(Sealed) virtual MaterialBase* GetMaterial(int32 entryIndex) = 0;
/// <summary> /// <summary>
/// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot. /// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot.
/// </summary> /// </summary>

View File

@@ -143,6 +143,16 @@ void PointLight::OnDebugDrawSelected()
LightWithShadow::OnDebugDrawSelected(); LightWithShadow::OnDebugDrawSelected();
} }
void PointLight::DrawLightsDebug(RenderView& view)
{
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere) || !EnumHasAnyFlags(view.Flags, ViewFlags::PointLights))
return;
// Draw influence range
DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::Yellow, 0, true);
}
#endif #endif
void PointLight::OnLayerChanged() void PointLight::OnLayerChanged()

View File

@@ -95,6 +95,7 @@ public:
#if USE_EDITOR #if USE_EDITOR
void OnDebugDraw() override; void OnDebugDraw() override;
void OnDebugDrawSelected() override; void OnDebugDrawSelected() override;
void DrawLightsDebug(RenderView& view) override;
#endif #endif
void OnLayerChanged() override; void OnLayerChanged() override;
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;

View File

@@ -341,6 +341,31 @@ void SplineModel::OnParentChanged()
OnSplineUpdated(); OnSplineUpdated();
} }
const Span<MaterialSlot> SplineModel::GetMaterialSlots() const
{
const auto model = Model.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* SplineModel::GetMaterial(int32 entryIndex)
{
if (Model)
Model->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultDeformableMaterial();
}
return material;
}
bool SplineModel::HasContentLoaded() const bool SplineModel::HasContentLoaded() const
{ {
return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded();

View File

@@ -115,6 +115,8 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnParentChanged() override; void OnParentChanged() override;
const Span<MaterialSlot> GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
protected: protected:
// [ModelInstanceActor] // [ModelInstanceActor]

View File

@@ -203,6 +203,11 @@ void SpotLight::OnDebugDrawSelected()
DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true); DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true); DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true); DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true); DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true);
@@ -210,6 +215,34 @@ void SpotLight::OnDebugDrawSelected()
LightWithShadow::OnDebugDrawSelected(); LightWithShadow::OnDebugDrawSelected();
} }
void SpotLight::DrawLightsDebug(RenderView& view)
{
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere) || !EnumHasAnyFlags(view.Flags, ViewFlags::SpotLights))
return;
const auto color = Color::Yellow;
Vector3 right = _transform.GetRight();
Vector3 up = _transform.GetUp();
Vector3 forward = GetDirection();
float radius = GetScaledRadius();
float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians);
float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians);
Vector3 position = GetPosition();
DEBUG_DRAW_LINE(position, position + forward * radius + up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true);
}
#endif #endif
void SpotLight::Serialize(SerializeStream& stream, const void* otherObj) void SpotLight::Serialize(SerializeStream& stream, const void* otherObj)

View File

@@ -127,6 +127,7 @@ public:
#if USE_EDITOR #if USE_EDITOR
void OnDebugDraw() override; void OnDebugDraw() override;
void OnDebugDrawSelected() override; void OnDebugDrawSelected() override;
void DrawLightsDebug(RenderView& view) override;
#endif #endif
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -535,6 +535,31 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
} }
} }
const Span<MaterialSlot> StaticModel::GetMaterialSlots() const
{
const auto model = Model.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
{
if (Model)
Model->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultMaterial();
}
return material;
}
bool StaticModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) bool StaticModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{ {
auto model = Model.Get(); auto model = Model.Get();

View File

@@ -121,7 +121,7 @@ public:
/// <param name="meshIndex">The zero-based mesh index.</param> /// <param name="meshIndex">The zero-based mesh index.</param>
/// <param name="lodIndex">The LOD index.</param> /// <param name="lodIndex">The LOD index.</param>
/// <returns>Material or null if not assigned.</returns> /// <returns>Material or null if not assigned.</returns>
API_FUNCTION() MaterialBase* GetMaterial(int32 meshIndex, int32 lodIndex = 0) const; API_FUNCTION() MaterialBase* GetMaterial(int32 meshIndex, int32 lodIndex) const;
/// <summary> /// <summary>
/// Gets the color of the painter vertex (this model instance). /// Gets the color of the painter vertex (this model instance).
@@ -166,6 +166,8 @@ public:
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
const Span<MaterialSlot> GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;

View File

@@ -726,116 +726,6 @@ int32 Level::GetLayerIndex(const StringView& layer)
return result; return result;
} }
Actor* FindActorRecursive(Actor* node, const Tag& tag)
{
if (node->HasTag(tag))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursive(child, tag);
if (result)
break;
}
return result;
}
void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorsRecursive(child, tag, activeOnly, result);
}
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
for (Tag tag : tags)
{
if (node->HasTag(tag))
{
result.Add(node);
break;
}
}
for (Actor* child : node->Children)
FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}
Actor* Level::FindActor(const Tag& tag, Actor* root)
{
PROFILE_CPU();
if (root)
return FindActorRecursive(root, tag);
Actor* result = nullptr;
for (Scene* scene : Scenes)
{
result = FindActorRecursive(scene, tag);
if (result)
break;
}
return result;
}
void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
{
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorRecursive(child, tag, result);
}
Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
if (root)
{
FindActorsRecursive(root, tag, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, activeOnly, result);
}
return result;
}
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
const Array<Tag> subTags = Tags::GetSubTags(parentTag);
if (subTags.Count() == 0)
{
return result;
}
if (subTags.Count() == 1)
{
result = FindActors(subTags[0], activeOnly, root);
return result;
}
if (root)
{
FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
}
return result;
}
void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
{ {
PROFILE_CPU(); PROFILE_CPU();
@@ -1505,6 +1395,143 @@ Actor* Level::FindActor(const MClass* type, const StringView& name)
return result; return result;
} }
Actor* FindActorRecursive(Actor* node, const Tag& tag)
{
if (node->HasTag(tag))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursive(child, tag);
if (result)
break;
}
return result;
}
Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag)
{
CHECK_RETURN(type, nullptr);
if (node->HasTag(tag) && node->GetClass()->IsSubClassOf(type))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursiveByType(child, type, tag);
if (result)
break;
}
return result;
}
void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorsRecursive(child, tag, activeOnly, result);
}
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
for (Tag tag : tags)
{
if (node->HasTag(tag))
{
result.Add(node);
break;
}
}
for (Actor* child : node->Children)
FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}
Actor* Level::FindActor(const Tag& tag, Actor* root)
{
PROFILE_CPU();
if (root)
return FindActorRecursive(root, tag);
Actor* result = nullptr;
for (Scene* scene : Scenes)
{
result = FindActorRecursive(scene, tag);
if (result)
break;
}
return result;
}
Actor* Level::FindActor(const MClass* type, const Tag& tag, Actor* root)
{
CHECK_RETURN(type, nullptr);
if (root)
return FindActorRecursiveByType(root, type, tag);
Actor* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
result = Scenes[i]->FindActor(type, tag);
return result;
}
void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
{
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorRecursive(child, tag, result);
}
Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
if (root)
{
FindActorsRecursive(root, tag, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, activeOnly, result);
}
return result;
}
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
const Array<Tag> subTags = Tags::GetSubTags(parentTag);
if (subTags.Count() == 0)
{
return result;
}
if (subTags.Count() == 1)
{
result = FindActors(subTags[0], activeOnly, root);
return result;
}
if (root)
{
FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
}
return result;
}
Script* Level::FindScript(const MClass* type) Script* Level::FindScript(const MClass* type)
{ {
CHECK_RETURN(type, nullptr); CHECK_RETURN(type, nullptr);

View File

@@ -78,6 +78,18 @@ namespace FlaxEngine
return FindActor(typeof(T), name) as T; return FindActor(typeof(T), name) as T;
} }
/// <summary>
/// Tries to find actor of the given type and tag in a root actor or all loaded scenes.
/// </summary>
/// <param name="tag">A tag on the object.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Found actor or null.</returns>
public static T FindActor<T>(Tag tag, Actor root = null) where T : Actor
{
return FindActor(typeof(T), tag, root) as T;
}
/// <summary> /// <summary>
/// Tries to find actor with the given ID in all loaded scenes. It's very fast O(1) lookup. /// Tries to find actor with the given ID in all loaded scenes. It's very fast O(1) lookup.
/// </summary> /// </summary>

View File

@@ -371,6 +371,41 @@ public:
/// <returns>Actor instance if found, null otherwise.</returns> /// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name); API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name);
/// <summary>
/// Tries to find the actor with the given tag (returns the first one found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actor or null.</returns>
API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr);
/// <summary>
/// Tries to find the actor of the given type and tag in all the loaded scenes.
/// </summary>
/// <param name="type">Type of the actor to search for. Includes any actors derived from the type.</param>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, Actor* root = nullptr);
/// <summary>
/// Tries to find the actors with the given tag (returns all found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actors or empty if none.</returns>
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary>
/// Search actors using a parent parentTag.
/// </summary>
/// <param name="parentTag">The tag to search actors with subtags belonging to this tag</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Returns all actors that have subtags belonging to the given parent parentTag</returns>
API_FUNCTION() static Array<Actor*> FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary> /// <summary>
/// Tries to find the actor of the given type in all the loaded scenes. /// Tries to find the actor of the given type in all the loaded scenes.
/// </summary> /// </summary>
@@ -392,6 +427,18 @@ public:
return (T*)FindActor(T::GetStaticClass(), name); return (T*)FindActor(T::GetStaticClass(), name);
} }
/// <summary>
/// Tries to find the actor of the given type and tag in a root actor or all the loaded scenes.
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// </summary>
/// <returns>Actor instance if found, null otherwise.</returns>
template<typename T>
FORCE_INLINE static T* FindActor(const Tag& tag, Actor* root = nullptr)
{
return (T*)FindActor(T::GetStaticClass(), tag, root);
}
/// <summary> /// <summary>
/// Tries to find the script of the given type in all the loaded scenes. /// Tries to find the script of the given type in all the loaded scenes.
/// </summary> /// </summary>
@@ -481,33 +528,6 @@ public:
/// </summary> /// </summary>
API_FUNCTION() static int32 GetLayerIndex(const StringView& layer); API_FUNCTION() static int32 GetLayerIndex(const StringView& layer);
public:
/// <summary>
/// Tries to find the actor with the given tag (returns the first one found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actor or null.</returns>
API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr);
/// <summary>
/// Tries to find the actors with the given tag (returns all found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actors or empty if none.</returns>
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary>
/// Search actors using a parent parentTag.
/// </summary>
/// <param name="parentTag">The tag to search actors with subtags belonging to this tag</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Returns all actors that have subtags belonging to the given parent parentTag</returns>
API_FUNCTION() static Array<Actor*> FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr);
private: private:
// Actor API // Actor API
enum class ActorEventType enum class ActorEventType

View File

@@ -96,6 +96,16 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
physicsDebugData[i](view); physicsDebugData[i](view);
} }
} }
// Draw light shapes
if (EnumHasAnyFlags(view.Flags, ViewFlags::LightsDebug))
{
const LightsDebugCallback* lightsDebugData = LightsDebug.Get();
for (int32 i = 0; i < LightsDebug.Count(); i++)
{
lightsDebugData[i](view);
}
}
} }
#endif #endif
} }
@@ -157,30 +167,34 @@ void SceneRendering::UpdateActor(Actor* a, int32& key)
const int32 category = a->_drawCategory; const int32 category = a->_drawCategory;
ScopeLock lock(Locker); ScopeLock lock(Locker);
auto& list = Actors[category]; auto& list = Actors[category];
if (list.IsEmpty()) if (list.Count() <= key) // Ignore invalid key softly
return; return;
auto& e = list[key]; auto& e = list[key];
ASSERT_LOW_LAYER(a == e.Actor); if (e.Actor == a)
{
for (auto* listener : _listeners) for (auto* listener : _listeners)
listener->OnSceneRenderingUpdateActor(a, e.Bounds); listener->OnSceneRenderingUpdateActor(a, e.Bounds);
e.LayerMask = a->GetLayerMask(); e.LayerMask = a->GetLayerMask();
e.Bounds = a->GetSphere(); e.Bounds = a->GetSphere();
} }
}
void SceneRendering::RemoveActor(Actor* a, int32& key) void SceneRendering::RemoveActor(Actor* a, int32& key)
{ {
const int32 category = a->_drawCategory; const int32 category = a->_drawCategory;
ScopeLock lock(Locker); ScopeLock lock(Locker);
auto& list = Actors[category]; auto& list = Actors[category];
if (list.HasItems()) if (list.Count() > key) // Ignore invalid key softly (eg. list after batch clear during scene unload)
{
auto& e = list.Get()[key];
if (e.Actor == a)
{ {
auto& e = list[key];
ASSERT_LOW_LAYER(a == e.Actor);
for (auto* listener : _listeners) for (auto* listener : _listeners)
listener->OnSceneRenderingRemoveActor(a); listener->OnSceneRenderingRemoveActor(a);
e.Actor = nullptr; e.Actor = nullptr;
e.LayerMask = 0; e.LayerMask = 0;
} }
}
key = -1; key = -1;
} }

View File

@@ -65,6 +65,7 @@ class FLAXENGINE_API SceneRendering
{ {
#if USE_EDITOR #if USE_EDITOR
typedef Function<void(RenderView&)> PhysicsDebugCallback; typedef Function<void(RenderView&)> PhysicsDebugCallback;
typedef Function<void(RenderView&)> LightsDebugCallback;
friend class ViewportIconsRendererService; friend class ViewportIconsRendererService;
#endif #endif
public: public:
@@ -95,6 +96,7 @@ public:
private: private:
#if USE_EDITOR #if USE_EDITOR
Array<PhysicsDebugCallback> PhysicsDebug; Array<PhysicsDebugCallback> PhysicsDebug;
Array<LightsDebugCallback> LightsDebug;
Array<Actor*> ViewportIcons; Array<Actor*> ViewportIcons;
#endif #endif
@@ -153,6 +155,22 @@ public:
PhysicsDebug.Remove(f); PhysicsDebug.Remove(f);
} }
template<class T, void(T::*Method)(RenderView&)>
FORCE_INLINE void AddLightsDebug(T* obj)
{
LightsDebugCallback f;
f.Bind<T, Method>(obj);
LightsDebug.Add(f);
}
template<class T, void(T::*Method)(RenderView&)>
void RemoveLightsDebug(T* obj)
{
LightsDebugCallback f;
f.Bind<T, Method>(obj);
LightsDebug.Remove(f);
}
FORCE_INLINE void AddViewportIcon(Actor* obj) FORCE_INLINE void AddViewportIcon(Actor* obj)
{ {
ViewportIcons.Add(obj); ViewportIcons.Add(obj);

View File

@@ -23,8 +23,8 @@ namespace FlaxEngine
if (_keyframes == null || _keyframes.Length != count) if (_keyframes == null || _keyframes.Length != count)
_keyframes = new BezierCurve<Transform>.Keyframe[count]; _keyframes = new BezierCurve<Transform>.Keyframe[count];
#if !BUILD_RELEASE #if !BUILD_RELEASE
if (Marshal.SizeOf(typeof(BezierCurve<Transform>.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float)) if (System.Runtime.CompilerServices.Unsafe.SizeOf<BezierCurve<Transform>.Keyframe>() != Transform.SizeInBytes * 3 + sizeof(float))
throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve<Transform>.Keyframe)) + " bytes."); throw new Exception("Invalid size of BezierCurve keyframe " + System.Runtime.CompilerServices.Unsafe.SizeOf<BezierCurve<Transform>.Keyframe>() + " bytes.");
#endif #endif
Internal_GetKeyframes(__unmanagedPtr, _keyframes); Internal_GetKeyframes(__unmanagedPtr, _keyframes);
return _keyframes; return _keyframes;

View File

@@ -23,7 +23,7 @@ void NetworkReplicationHierarchyUpdateResult::Init()
{ {
_clientsHaveLocation = false; _clientsHaveLocation = false;
_clients.Resize(NetworkManager::Clients.Count()); _clients.Resize(NetworkManager::Clients.Count());
_clientsMask = NetworkClientsMask(); _clientsMask = NetworkManager::Mode == NetworkManagerMode::Client ? NetworkClientsMask::All : NetworkClientsMask();
for (int32 i = 0; i < _clients.Count(); i++) for (int32 i = 0; i < _clients.Count(); i++)
_clientsMask.SetBit(i); _clientsMask.SetBit(i);
_entries.Clear(); _entries.Clear();
@@ -63,6 +63,17 @@ bool NetworkReplicationNode::RemoveObject(ScriptingObject* obj)
return !Objects.Remove(obj); return !Objects.Remove(obj);
} }
bool NetworkReplicationNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result)
{
const int32 index = Objects.Find(obj);
if (index != -1)
{
result = Objects[index];
return true;
}
return false;
}
bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj) bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj)
{ {
const int32 index = Objects.Find(obj); const int32 index = Objects.Find(obj);
@@ -156,6 +167,7 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
cell->MinCullDistance = obj.CullDistance; cell->MinCullDistance = obj.CullDistance;
} }
cell->Node->AddObject(obj); cell->Node->AddObject(obj);
_objectToCell[obj.Object] = coord;
// Cache minimum culling distance for a whole cell to skip it at once // Cache minimum culling distance for a whole cell to skip it at once
cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance); cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance);
@@ -163,14 +175,35 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj) bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj)
{ {
for (const auto& e : _children) Int3 coord;
if (!_objectToCell.TryGet(obj, coord))
{ {
if (e.Value.Node->RemoveObject(obj)) return false;
}
if (_children[coord].Node->RemoveObject(obj))
{ {
_objectToCell.Remove(obj);
// TODO: remove empty cells? // TODO: remove empty cells?
// TODO: update MinCullDistance for cell? // TODO: update MinCullDistance for cell?
return true; return true;
} }
return false;
}
bool NetworkReplicationGridNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result)
{
Int3 coord;
if (!_objectToCell.TryGet(obj, coord))
{
return false;
}
if (_children[coord].Node->GetObject(obj, result))
{
return true;
} }
return false; return false;
} }

View File

@@ -200,6 +200,14 @@ API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Ne
/// <returns>True on successful removal, otherwise false.</returns> /// <returns>True on successful removal, otherwise false.</returns>
API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj); API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj);
/// <summary>
/// Gets object from the hierarchy.
/// </summary>
/// <param name="obj">The object to get.</param>
/// <param name="result">The hierarchy object to retrieve.</param>
/// <returns>True on successful retrieval, otherwise false.</returns>
API_FUNCTION() virtual bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result);
/// <summary> /// <summary>
/// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization. /// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization.
/// </summary> /// </summary>
@@ -238,6 +246,7 @@ private:
}; };
Dictionary<Int3, Cell> _children; Dictionary<Int3, Cell> _children;
Dictionary<ScriptingObject*, Int3> _objectToCell;
public: public:
/// <summary> /// <summary>
@@ -247,6 +256,7 @@ public:
void AddObject(NetworkReplicationHierarchyObject obj) override; void AddObject(NetworkReplicationHierarchyObject obj) override;
bool RemoveObject(ScriptingObject* obj) override; bool RemoveObject(ScriptingObject* obj) override;
bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) override;
void Update(NetworkReplicationHierarchyUpdateResult* result) override; void Update(NetworkReplicationHierarchyUpdateResult* result) override;
}; };

View File

@@ -700,9 +700,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri
NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo; NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo;
} }
void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds) bool NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds)
{ {
EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds)); return EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds));
} }
StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name) StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name)
@@ -1113,12 +1113,12 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
return CachedWriteStream; return CachedWriteStream;
} }
void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds) bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
{ {
Scripting::ObjectsLookupIdMapping.Set(nullptr); Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline()) if (!info || !obj || NetworkManager::IsOffline())
return; return false;
ObjectsLock.Lock(); ObjectsLock.Lock();
auto& rpc = RpcQueue.AddOne(); auto& rpc = RpcQueue.AddOne();
rpc.Object = obj; rpc.Object = obj;
@@ -1135,12 +1135,23 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
} }
#endif #endif
ObjectsLock.Unlock(); ObjectsLock.Unlock();
// Check if skip local execution (eg. server rpc called from client or client rpc with specific targets)
const NetworkManagerMode networkMode = NetworkManager::Mode;
if (info->Server && networkMode == NetworkManagerMode::Client)
return true;
if (info->Client && networkMode == NetworkManagerMode::Server)
return true;
if (info->Client && networkMode == NetworkManagerMode::Host && targetIds.IsValid() && !SpanContains(targetIds, NetworkManager::LocalClientId))
return true;
return false;
} }
void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
{ {
ScopeLock lock(ObjectsLock); ScopeLock lock(ObjectsLock);
NewClients.Add(client); NewClients.Add(client);
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
} }
void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client) void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client)
@@ -1193,6 +1204,7 @@ void NetworkInternal::NetworkReplicatorClear()
Objects.Remove(it); Objects.Remove(it);
} }
} }
Objects.Clear();
RpcQueue.Clear(); RpcQueue.Clear();
SpawnQueue.Clear(); SpawnQueue.Clear();
DespawnQueue.Clear(); DespawnQueue.Clear();

View File

@@ -120,10 +120,11 @@ namespace FlaxEngine.Networking
/// <param name="name">The RPC name.</param> /// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param> /// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param> /// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
/// <returns>True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).</returns>
[Unmanaged] [Unmanaged]
public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null) public static bool EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null)
{ {
Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds); return Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds);
} }
/// <summary> /// <summary>

View File

@@ -195,13 +195,14 @@ public:
/// <param name="name">The RPC name.</param> /// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param> /// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param> /// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>()); /// <returns>True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).</returns>
static bool EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>());
private: private:
#if !COMPILE_WITHOUT_CSHARP #if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize); API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize);
API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function<void(void*, void*)>& execute, bool isServer, bool isClient, NetworkChannelType channel); API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function<void(void*, void*)>& execute, bool isServer, bool isClient, NetworkChannelType channel);
API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds); API_FUNCTION(NoProxy) static bool CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds);
static StringAnsiView GetCSharpCachedName(const StringAnsiView& name); static StringAnsiView GetCSharpCachedName(const StringAnsiView& name);
#endif #endif
}; };

View File

@@ -41,7 +41,7 @@ struct FLAXENGINE_API NetworkRpcInfo
uint8 Client : 1; uint8 Client : 1;
uint8 Channel : 4; uint8 Channel : 4;
void (*Execute)(ScriptingObject* obj, NetworkStream* stream, void* tag); void (*Execute)(ScriptingObject* obj, NetworkStream* stream, void* tag);
void (*Invoke)(ScriptingObject* obj, void** args); bool (*Invoke)(ScriptingObject* obj, void** args);
void* Tag; void* Tag;
/// <summary> /// <summary>
@@ -83,10 +83,7 @@ FORCE_INLINE void NetworkRpcInitArg(Array<void*, FixedAllocation<16>>& args, con
{ \ { \
Array<void*, FixedAllocation<16>> args; \ Array<void*, FixedAllocation<16>> args; \
NetworkRpcInitArg(args, __VA_ARGS__); \ NetworkRpcInitArg(args, __VA_ARGS__); \
rpcInfo.Invoke(this, args.Get()); \ if (rpcInfo.Invoke(this, args.Get())) \
if (rpcInfo.Server && networkMode == NetworkManagerMode::Client) \
return; \
if (rpcInfo.Client && networkMode == NetworkManagerMode::Server) \
return; \ return; \
} \ } \
} }

View File

@@ -189,7 +189,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va
const Matrix viewProjection = context.ViewTask ? context.ViewTask->View.PrevViewProjection : Matrix::Identity; const Matrix viewProjection = context.ViewTask ? context.ViewTask->View.PrevViewProjection : Matrix::Identity;
const Float3 position = (Float3)TryGetValue(node->GetBox(0), Value::Zero); const Float3 position = (Float3)TryGetValue(node->GetBox(0), Value::Zero);
Float4 projPos; Float4 projPos;
Float3::Transform(position, viewProjection); Float3::Transform(position, viewProjection, projPos);
projPos /= projPos.W; projPos /= projPos.W;
value = Float2(projPos.X * 0.5f + 0.5f, projPos.Y * 0.5f + 0.5f); value = Float2(projPos.X * 0.5f + 0.5f, projPos.Y * 0.5f + 0.5f);
break; break;

View File

@@ -230,10 +230,11 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
{ {
const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true);
DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true);
if (!data.State.IsInAir) if (!data.State.IsInAir)
{ {
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true);
@@ -260,6 +261,7 @@ void WheeledVehicle::OnDebugDrawSelected()
{ {
const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
Transform actorPose = Transform::Identity, shapePose = Transform::Identity; Transform actorPose = Transform::Identity, shapePose = Transform::Identity;
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation); PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation);
PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation); PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation);
@@ -267,7 +269,7 @@ void WheeledVehicle::OnDebugDrawSelected()
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false);
DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false);
if (!data.State.SuspensionTraceStart.IsZero()) if (!data.State.SuspensionTraceStart.IsZero())
{ {
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false);

View File

@@ -125,6 +125,7 @@ WindowBase::~WindowBase()
{ {
ASSERT(!RenderTask); ASSERT(!RenderTask);
ASSERT(!_swapChain); ASSERT(!_swapChain);
WindowsManager::Unregister((Window*)this);
} }
bool WindowBase::IsMain() const bool WindowBase::IsMain() const

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