diff --git a/.gitattributes b/.gitattributes
index f84404f19..df7d89342 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,5 +1,5 @@
# Set the default behavior, in case people don't have core.autocrlf set.
-* text=auto
+* text=auto eol=lf
# Explicitly declare text files you want to always be normalized and converted to native line endings on checkout.
*.c text diff=cpp
diff --git a/Flax.flaxproj b/Flax.flaxproj
index d8e86b1ed..7cebe1714 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 6,
- "Build": 6342
+ "Build": 6343
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs
index 86c374fee..57e55d3fe 100644
--- a/Source/Editor/Content/Items/ContentFolder.cs
+++ b/Source/Editor/Content/Items/ContentFolder.cs
@@ -113,7 +113,15 @@ namespace FlaxEditor.Content
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other;
///
- public override bool CanRename => ParentFolder != null; // Deny rename action for root folders
+ public override bool CanRename
+ {
+ get
+ {
+ var hasParentFolder = ParentFolder != null;
+ var isContentFolder = Node is MainContentTreeNode;
+ return hasParentFolder && !isContentFolder;
+ }
+ }
///
public override bool CanDrag => ParentFolder != null; // Deny rename action for root folders
diff --git a/Source/Editor/CustomEditorWindow.cs b/Source/Editor/CustomEditorWindow.cs
index 2d5293f30..242b4d28d 100644
--- a/Source/Editor/CustomEditorWindow.cs
+++ b/Source/Editor/CustomEditorWindow.cs
@@ -3,7 +3,6 @@
using FlaxEditor.CustomEditors;
using FlaxEditor.Windows;
using FlaxEngine.GUI;
-using FlaxEngine;
using DockState = FlaxEditor.GUI.Docking.DockState;
namespace FlaxEditor
@@ -86,8 +85,12 @@ namespace FlaxEditor
if (!FlaxEngine.Scripting.IsTypeFromGameScripts(type))
return;
- Editor.Instance.Windows.AddToRestore(this);
+ if (!Window.IsHidden)
+ {
+ Editor.Instance.Windows.AddToRestore(this);
+ }
Window.Close();
+ Window.Dispose();
}
///
diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs
index 28c039dc8..5a10b9a52 100644
--- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs
@@ -167,7 +167,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Presenter.Undo?.AddAction(new MultiUndoAction(actions));
// Build ragdoll
- SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
+ AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
}
private void OnRebuildBone(Button button)
@@ -191,7 +191,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
// 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)
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index 64578570c..2e769f431 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -37,7 +37,8 @@ public class Editor : EditorModule
{
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.ComponentModel.TypeConverter");
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index 8a92d11f4..527bfeb6d 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -184,7 +184,7 @@ bool Editor::CheckProjectUpgrade()
" BuildNativeCode = false;\n"
" }}\n"
"}}\n"
- ), codeName), Encoding::Unicode);
+ ), codeName), Encoding::UTF8);
if (useEditorModule)
{
File::WriteAllText(gameEditorModuleFolder / String::Format(TEXT("{0}Editor.Build.cs"), codeName), String::Format(TEXT(
@@ -211,7 +211,7 @@ bool Editor::CheckProjectUpgrade()
" BuildNativeCode = false;\n"
" }}\n"
"}}\n"
- ), codeName), Encoding::Unicode);
+ ), codeName), Encoding::UTF8);
}
// Generate target files
@@ -229,7 +229,7 @@ bool Editor::CheckProjectUpgrade()
" Modules.Add(\"{0}\");\n"
" }}\n"
"}}\n"
- ), codeName), Encoding::Unicode);
+ ), codeName), Encoding::UTF8);
const String editorTargetGameEditorModule = useEditorModule ? String::Format(TEXT(" Modules.Add(\"{0}Editor\");\n"), codeName) : String::Empty;
File::WriteAllText(sourceFolder / String::Format(TEXT("{0}EditorTarget.Build.cs"), codeName), String::Format(TEXT(
"using Flax.Build;\n"
@@ -246,7 +246,7 @@ bool Editor::CheckProjectUpgrade()
"{1}"
" }}\n"
"}}\n"
- ), codeName, editorTargetGameEditorModule), Encoding::Unicode);
+ ), codeName, editorTargetGameEditorModule), Encoding::UTF8);
// Generate new project file
Project->ProjectPath = root / String::Format(TEXT("{0}.flaxproj"), codeName);
@@ -454,7 +454,7 @@ int32 Editor::LoadProduct()
" // Reference the modules for game\n"
" Modules.Add(\"Game\");\n"
" }\n"
- "}\n"), Encoding::Unicode);
+ "}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT(
"using Flax.Build;\n"
"\n"
@@ -468,7 +468,7 @@ int32 Editor::LoadProduct()
" // Reference the modules for editor\n"
" Modules.Add(\"Game\");\n"
" }\n"
- "}\n"), Encoding::Unicode);
+ "}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT(
"using Flax.Build;\n"
"using Flax.Build.NativeCpp;\n"
@@ -496,7 +496,7 @@ int32 Editor::LoadProduct()
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n"
" // To learn more see scripting documentation.\n"
" }\n"
- "}\n"), Encoding::Unicode);
+ "}\n"), Encoding::UTF8);
if (failed)
return 12;
}
diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs
index 9d9ee2040..73c5baab7 100644
--- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs
+++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs
@@ -27,6 +27,17 @@ namespace FlaxEditor.GUI
///
public ScriptType Type => _type;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TypeItemView()
+ {
+ _type = ScriptType.Null;
+ Name = "";
+ TooltipText = "Unset value.";
+ Tag = _type;
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -83,6 +94,7 @@ namespace FlaxEditor.GUI
// TODO: use async thread to search types without UI stall
var allTypes = Editor.Instance.CodeEditing.All.Get();
+ AddItem(new TypeItemView());
for (int i = 0; i < allTypes.Count; i++)
{
var type = allTypes[i];
diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs
index a36bb8da1..8b4f49986 100644
--- a/Source/Editor/Gizmo/GridGizmo.cs
+++ b/Source/Editor/Gizmo/GridGizmo.cs
@@ -12,19 +12,98 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
public class GridGizmo : GizmoBase
{
- private bool _enabled = true;
+ [HideInEditor]
+ private sealed class Renderer : PostProcessEffect
+ {
+ private IntPtr _debugDrawContext;
+
+ public Renderer()
+ {
+ Order = -100;
+ UseSingleTarget = true;
+ Location = PostProcessEffectLocation.BeforeForwardPass;
+ }
+
+ ~Renderer()
+ {
+ if (_debugDrawContext != IntPtr.Zero)
+ {
+ DebugDraw.FreeContext(_debugDrawContext);
+ _debugDrawContext = IntPtr.Zero;
+ }
+ }
+
+ public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
+ {
+ Profiler.BeginEventGPU("Editor Grid");
+
+ if (_debugDrawContext == IntPtr.Zero)
+ _debugDrawContext = DebugDraw.AllocateContext();
+ DebugDraw.SetContext(_debugDrawContext);
+ DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
+
+ var viewPos = (Vector3)renderContext.View.Position;
+ var plane = new Plane(Vector3.Zero, Vector3.UnitY);
+ var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
+
+ float space, size;
+ if (dst <= 500.0f)
+ {
+ space = 50;
+ size = 8000;
+ }
+ else if (dst <= 2000.0f)
+ {
+ space = 100;
+ size = 8000;
+ }
+ else
+ {
+ space = 1000;
+ size = 100000;
+ }
+
+ Color color = Color.Gray * 0.7f;
+ int count = (int)(size / space);
+
+ Vector3 start = new Vector3(0, 0, size * -0.5f);
+ Vector3 end = new Vector3(0, 0, size * 0.5f);
+
+ for (int i = 0; i <= count; i++)
+ {
+ start.X = end.X = i * space + start.Z;
+ DebugDraw.DrawLine(start, end, color);
+ }
+
+ start = new Vector3(size * -0.5f, 0, 0);
+ end = new Vector3(size * 0.5f, 0, 0);
+
+ for (int i = 0; i <= count; i++)
+ {
+ start.Z = end.Z = i * space + start.X;
+ DebugDraw.DrawLine(start, end, color);
+ }
+
+ DebugDraw.Draw(ref renderContext, input.View(), null, true);
+ DebugDraw.SetContext(IntPtr.Zero);
+
+ Profiler.EndEventGPU();
+ }
+ }
+
+ private Renderer _renderer;
///
/// Gets or sets a value indicating whether this is enabled.
///
public bool Enabled
{
- get => _enabled;
+ get => _renderer.Enabled;
set
{
- if (_enabled != value)
+ if (_renderer.Enabled != value)
{
- _enabled = value;
+ _renderer.Enabled = value;
EnabledChanged?.Invoke(this);
}
}
@@ -42,55 +121,16 @@ namespace FlaxEditor.Gizmo
public GridGizmo(IGizmoOwner owner)
: base(owner)
{
+ _renderer = new Renderer();
+ owner.RenderTask.AddCustomPostFx(_renderer);
}
- ///
- public override void Draw(ref RenderContext renderContext)
+ ///
+ /// Destructor.
+ ///
+ ~GridGizmo()
{
- if (!Enabled)
- return;
-
- var viewPos = Owner.ViewPosition;
- var plane = new Plane(Vector3.Zero, Vector3.UnitY);
- var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
-
- float space, size;
- if (dst <= 500.0f)
- {
- space = 50;
- size = 8000;
- }
- else if (dst <= 2000.0f)
- {
- space = 100;
- size = 8000;
- }
- else
- {
- space = 1000;
- size = 100000;
- }
-
- Color color = Color.Gray * 0.7f;
- int count = (int)(size / space);
-
- Vector3 start = new Vector3(0, 0, size * -0.5f);
- Vector3 end = new Vector3(0, 0, size * 0.5f);
-
- for (int i = 0; i <= count; i++)
- {
- start.X = end.X = i * space + start.Z;
- DebugDraw.DrawLine(start, end, color);
- }
-
- start = new Vector3(size * -0.5f, 0, 0);
- end = new Vector3(size * 0.5f, 0, 0);
-
- for (int i = 0; i <= count; i++)
- {
- start.Z = end.Z = i * space + start.X;
- DebugDraw.DrawLine(start, end, color);
- }
+ FlaxEngine.Object.Destroy(ref _renderer);
}
}
}
diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs
index ed9470c35..b00c4e042 100644
--- a/Source/Editor/ProjectInfo.cs
+++ b/Source/Editor/ProjectInfo.cs
@@ -20,7 +20,7 @@ namespace FlaxEditor
/// The to write to.
/// The value.
/// The calling serializer.
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
@@ -44,7 +44,7 @@ namespace FlaxEditor
/// The existing property value of the JSON that is being converted.
/// The calling serializer.
/// The object value.
- 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)
{
diff --git a/Source/Editor/ProjectInfo.h b/Source/Editor/ProjectInfo.h
index 256269cc9..9f52e414f 100644
--- a/Source/Editor/ProjectInfo.h
+++ b/Source/Editor/ProjectInfo.h
@@ -112,6 +112,7 @@ public:
{
Version = ::Version(1, 0);
DefaultSceneSpawn = Ray(Vector3::Zero, Vector3::Forward);
+ DefaultScene = Guid::Empty;
}
///
diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs
index d174b7dff..454c3a5d2 100644
--- a/Source/Editor/Scripting/ScriptType.cs
+++ b/Source/Editor/Scripting/ScriptType.cs
@@ -832,7 +832,7 @@ namespace FlaxEditor.Scripting
get
{
if (_managed != null)
- return _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
+ return _managed.IsValueType || _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
return _custom?.CanCreateInstance ?? false;
}
}
@@ -893,9 +893,16 @@ namespace FlaxEditor.Scripting
{
if (_managed != null)
{
- var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
object value = RuntimeHelpers.GetUninitializedObject(_managed);
- ctor.Invoke(value, null);
+ if (!_managed.IsValueType)
+ {
+ var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
+#if !BUILD_RELEASE
+ if (ctor == null)
+ throw new Exception($"Missing empty constructor for type {_managed.FullName}.");
+#endif
+ ctor.Invoke(value, null);
+ }
return value;
}
return _custom.CreateInstance();
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index eaffe5691..3082a531e 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -613,6 +613,28 @@ bool ScriptsBuilderService::Init()
const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration;
Array files;
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())
LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count());
for (auto& file : files)
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index d980b0c4a..fe801bbaf 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -1125,7 +1125,9 @@ namespace FlaxEditor.Utilities
public static string GetAssetNamePathWithExt(string path)
{
var projectFolder = Globals.ProjectFolder;
- if (path.StartsWith(projectFolder))
+ if (path == projectFolder)
+ path = string.Empty;
+ else if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
return path;
}
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index f56370352..b4fb8f8f1 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -1168,14 +1168,15 @@ namespace FlaxEditor.Viewport
{
offset = Float2.Zero;
}
- offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
- offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
- _mouseDelta = offset / size;
- _mouseDelta.Y *= size.Y / size.X;
var mouseDelta = Float2.Zero;
if (_useMouseFiltering)
{
+ offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
+ offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
+ _mouseDelta = offset / size;
+ _mouseDelta.Y *= size.Y / size.X;
+
// Update delta filtering buffer
_deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta;
_deltaFilteringStep++;
@@ -1192,6 +1193,8 @@ namespace FlaxEditor.Viewport
}
else
{
+ _mouseDelta = offset / size;
+ _mouseDelta.Y *= size.Y / size.X;
mouseDelta = _mouseDelta;
}
diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
index 2310668b6..9a7ec6800 100644
--- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
@@ -3,8 +3,6 @@
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
-using FlaxEditor.GUI.Input;
-using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -15,11 +13,10 @@ namespace FlaxEditor.Viewport.Previews
///
public class AnimatedModelPreview : AssetPreview
{
- private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
- private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD, _showNodesNames;
private AnimatedModel _previewModel;
+ private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
+ private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel;
- private ContextMenuButton _showCurrentLODButton;
private bool _playAnimation, _playAnimationOnce;
private float _playSpeed = 1.0f;
@@ -207,26 +204,6 @@ namespace FlaxEditor.Viewport.Previews
// Show Floor
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_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
@@ -339,44 +316,6 @@ namespace FlaxEditor.Viewport.Previews
_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;
- }
-
///
protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
{
@@ -440,45 +379,6 @@ namespace FlaxEditor.Viewport.Previews
}
}
- ///
- 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);
- }
- }
-
///
public override void Update(float deltaTime)
{
@@ -498,14 +398,21 @@ namespace FlaxEditor.Viewport.Previews
}
}
+ ///
+ /// Resets the camera to focus on a object.
+ ///
+ public void ResetCamera()
+ {
+ ViewportCamera.SetArcBallView(_previewModel.Box);
+ }
+
///
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
- // Pay respect..
- ViewportCamera.SetArcBallView(_previewModel.Box);
+ ResetCamera();
return true;
case KeyboardKeys.Spacebar:
PlayAnimation = !PlayAnimation;
@@ -525,7 +432,6 @@ namespace FlaxEditor.Viewport.Previews
_showNodesButton = null;
_showBoundsButton = null;
_showFloorButton = null;
- _showCurrentLODButton = null;
_showNodesNamesButton = null;
base.OnDestroy();
diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs
index 632ec199e..5520d6c4c 100644
--- a/Source/Editor/Viewport/Previews/MaterialPreview.cs
+++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs
@@ -4,6 +4,8 @@ using System;
using FlaxEditor.Surface;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEditor.Viewport.Widgets;
+using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -46,6 +48,7 @@ namespace FlaxEditor.Viewport.Previews
private int _selectedModelIndex;
private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
+ private ContextMenu _modelWidgetButtonMenu;
///
/// Gets or sets the material asset to preview. It can be or .
@@ -95,20 +98,33 @@ namespace FlaxEditor.Viewport.Previews
Task.AddCustomActor(_previewModel);
// Create context menu for primitive switching
- if (useWidgets && ViewWidgetButtonMenu != null)
+ if (useWidgets)
{
- ViewWidgetButtonMenu.AddSeparator();
- var modelSelect = ViewWidgetButtonMenu.AddChildMenu("Model").ContextMenu;
-
- // Fill out all models
- for (int i = 0; i < Models.Length; i++)
+ // Model mode widget
+ var modelMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
+ _modelWidgetButtonMenu = new ContextMenu();
+ _modelWidgetButtonMenu.VisibleChanged += control =>
{
- var button = modelSelect.AddButton(Models[i]);
- button.Tag = i;
- }
+ if (!control.Visible)
+ return;
+ _modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
- // Link the action
- modelSelect.ButtonClicked += (button) => SelectedModelIndex = (int)button.Tag;
+ // Fill out all models
+ for (int i = 0; i < Models.Length; i++)
+ {
+ var index = i;
+ var button = _modelWidgetButtonMenu.AddButton(Models[index]);
+ button.ButtonClicked += _ => SelectedModelIndex = index;
+ button.Checked = SelectedModelIndex == index;
+ button.Tag = index;
+ }
+ };
+ new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
+ {
+ TooltipText = "Change material model",
+ Parent = modelMode,
+ };
+ modelMode.Parent = this;
}
}
diff --git a/Source/Editor/Viewport/Previews/ModelBasePreview.cs b/Source/Editor/Viewport/Previews/ModelBasePreview.cs
index ffba23094..e4f1109d4 100644
--- a/Source/Editor/Viewport/Previews/ModelBasePreview.cs
+++ b/Source/Editor/Viewport/Previews/ModelBasePreview.cs
@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-using FlaxEditor.GUI.Input;
using FlaxEngine;
using Object = FlaxEngine.Object;
@@ -56,25 +55,14 @@ namespace FlaxEditor.Viewport.Previews
// Link actors for rendering
Task.AddCustomActor(StaticModel);
Task.AddCustomActor(AnimatedModel);
+ }
- if (useWidgets)
- {
- // 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 += () =>
- {
- StaticModel.ForcedLOD = previewLODValue.Value;
- AnimatedModel.ForcedLOD = previewLODValue.Value;
- };
- ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD;
- }
- }
+ ///
+ /// Resets the camera to focus on a object.
+ ///
+ public void ResetCamera()
+ {
+ ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
}
private void OnBegin(RenderTask task, GPUContext context)
@@ -103,8 +91,7 @@ namespace FlaxEditor.Viewport.Previews
switch (key)
{
case KeyboardKeys.F:
- // Pay respect..
- ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
+ ResetCamera();
break;
}
return base.OnKeyDown(key);
diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs
index 9193b6d40..a0b6d0ca0 100644
--- a/Source/Editor/Viewport/Previews/ModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/ModelPreview.cs
@@ -1,10 +1,10 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
-using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
+using FlaxEditor.Viewport.Widgets;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -16,10 +16,27 @@ namespace FlaxEditor.Viewport.Previews
public class ModelPreview : AssetPreview
{
private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton;
+ private ContextMenu _previewLODsWidgetButtonMenu;
private StaticModel _previewModel, _floorModel;
private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor;
private MeshDataCache _meshDatas;
+ ///
+ /// Gets or sets a value that shows LOD statistics
+ ///
+ public bool ShowCurrentLOD
+ {
+ get => _showCurrentLOD;
+ set
+ {
+ if (_showCurrentLOD == value)
+ return;
+ _showCurrentLOD = value;
+ if (_showCurrentLODButton != null)
+ _showCurrentLODButton.Checked = value;
+ }
+ }
+
///
/// Gets or sets the model asset to preview.
///
@@ -198,17 +215,36 @@ namespace FlaxEditor.Viewport.Previews
});
_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");
- previewLOD.CloseMenuOnClick = false;
- var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
+ if (!control.Visible)
+ return;
+ var model = _previewModel.Model;
+ if (model && !model.WaitForLoaded())
{
- Parent = previewLOD
- };
- previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
- }
+ _previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
+ var lods = model.LODs.Length;
+ for (int i = -1; i < lods; i++)
+ {
+ 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;
}
}
@@ -312,7 +348,7 @@ namespace FlaxEditor.Viewport.Previews
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;
@@ -346,8 +382,11 @@ namespace FlaxEditor.Viewport.Previews
if (_showCurrentLOD)
{
var asset = Model;
- var lodIndex = ComputeLODIndex(asset, out var screenSize);
- string text = string.Format("Current LOD: {0}\nScreen Size: {1:F2}", lodIndex, screenSize);
+ var lodIndex = ComputeLODIndex(asset, out var 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)
{
var lods = asset.LODs;
@@ -369,14 +408,21 @@ namespace FlaxEditor.Viewport.Previews
}
}
+ ///
+ /// Resets the camera to focus on a object.
+ ///
+ public void ResetCamera()
+ {
+ ViewportCamera.SetArcBallView(_previewModel.Box);
+ }
+
///
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
- // Pay respect..
- ViewportCamera.SetArcBallView(_previewModel.Box);
+ ResetCamera();
break;
}
return base.OnKeyDown(key);
@@ -389,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews
Object.Destroy(ref _previewModel);
_showBoundsButton = null;
_showCurrentLODButton = null;
+ _previewLODsWidgetButtonMenu = null;
_showNormalsButton = null;
_showTangentsButton = null;
_showBitangentsButton = null;
diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs
new file mode 100644
index 000000000..08f8d8509
--- /dev/null
+++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs
@@ -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
+{
+ ///
+ /// Animation asset preview editor viewport.
+ ///
+ ///
+ public class SkinnedModelPreview : AnimatedModelPreview
+ {
+ private bool _showCurrentLOD;
+ private ContextMenuButton _showCurrentLODButton;
+ private ContextMenu _previewLODsWidgetButtonMenu;
+
+ ///
+ /// Gets or sets a value that shows LOD statistics
+ ///
+ public bool ShowCurrentLOD
+ {
+ get => _showCurrentLOD;
+ set
+ {
+ if (_showCurrentLOD == value)
+ return;
+ _showCurrentLOD = value;
+ if (_showCurrentLODButton != null)
+ _showCurrentLODButton.Checked = value;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// if set to true use widgets.
+ 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;
+ }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ _showCurrentLODButton = null;
+ _previewLODsWidgetButtonMenu = null;
+
+ base.OnDestroy();
+ }
+ }
+}
diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs
index 9ce4c8765..cc1daa45b 100644
--- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs
+++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs
@@ -182,6 +182,7 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_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");
// Split Panel
diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs
index e44882b73..f70f6a3b5 100644
--- a/Source/Editor/Windows/Assets/ModelWindow.cs
+++ b/Source/Editor/Windows/Assets/ModelWindow.cs
@@ -779,6 +779,7 @@ namespace FlaxEditor.Windows.Assets
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f;
+ private ToolStripButton _showCurrentLODButton;
///
public ModelWindow(Editor editor, AssetItem item)
@@ -786,6 +787,9 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_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");
// Model preview
@@ -869,6 +873,8 @@ namespace FlaxEditor.Windows.Assets
}
}
+ _showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
+
base.Update(deltaTime);
}
@@ -946,6 +952,7 @@ namespace FlaxEditor.Windows.Assets
base.OnDestroy();
Object.Destroy(ref _highlightActor);
+ _showCurrentLODButton = null;
}
}
}
diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
index d18593549..a7ee6e767 100644
--- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
+++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
@@ -31,7 +31,7 @@ namespace FlaxEditor.Windows.Assets
///
public sealed class SkinnedModelWindow : ModelBaseWindow
{
- private sealed class Preview : AnimatedModelPreview
+ private sealed class Preview : SkinnedModelPreview
{
private readonly SkinnedModelWindow _window;
@@ -1105,6 +1105,7 @@ namespace FlaxEditor.Windows.Assets
private Preview _preview;
private AnimatedModel _highlightActor;
private ToolStripButton _showNodesButton;
+ private ToolStripButton _showCurrentLODButton;
private MeshData[][] _meshDatas;
private bool _meshDatasInProgress;
@@ -1116,7 +1117,9 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_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");
+ _toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator();
_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;
base.Update(deltaTime);
@@ -1349,6 +1353,7 @@ namespace FlaxEditor.Windows.Assets
Object.Destroy(ref _highlightActor);
_preview = null;
_showNodesButton = null;
+ _showCurrentLODButton = null;
}
}
}
diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp
index 3e36d3618..7cf7a7f78 100644
--- a/Source/Editor/Windows/SplashScreen.cpp
+++ b/Source/Editor/Windows/SplashScreen.cpp
@@ -304,6 +304,6 @@ void SplashScreen::OnFontLoaded(Asset* asset)
// Create fonts
const float s = _dpiScale;
- _titleFont = font->CreateFont((uint32)(35 * s));
- _subtitleFont = font->CreateFont((uint32)(9 * s));
+ _titleFont = font->CreateFont(35 * s);
+ _subtitleFont = font->CreateFont(9 * s);
}
diff --git a/Source/Engine/ContentExporters/AssetsExportingManager.h b/Source/Engine/ContentExporters/AssetsExportingManager.h
index f9f50075e..7e3b11b6d 100644
--- a/Source/Engine/ContentExporters/AssetsExportingManager.h
+++ b/Source/Engine/ContentExporters/AssetsExportingManager.h
@@ -10,7 +10,7 @@
///
/// Assets Importing service allows to import or create new assets
///
-class AssetsExportingManager
+class FLAXENGINE_API AssetsExportingManager
{
public:
///
diff --git a/Source/Engine/ContentExporters/Types.h b/Source/Engine/ContentExporters/Types.h
index cfc341f37..a2095934f 100644
--- a/Source/Engine/ContentExporters/Types.h
+++ b/Source/Engine/ContentExporters/Types.h
@@ -26,7 +26,7 @@ typedef Function ExportAssetFunction;
///
/// Exporting asset context structure
///
-class ExportAssetContext : public NonCopyable
+class FLAXENGINE_API ExportAssetContext : public NonCopyable
{
public:
///
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.h b/Source/Engine/ContentImporters/AssetsImportingManager.h
index 2f8369a41..2930f8979 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.h
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.h
@@ -9,7 +9,7 @@
///
/// Assets Importing service allows to import or create new assets
///
-class AssetsImportingManager
+class FLAXENGINE_API AssetsImportingManager
{
public:
///
diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h
index 05d1fc116..4ebf55583 100644
--- a/Source/Engine/ContentImporters/Types.h
+++ b/Source/Engine/ContentImporters/Types.h
@@ -28,7 +28,7 @@ typedef Function CreateAssetFunction;
///
/// Importing/creating asset context structure
///
-class CreateAssetContext : public NonCopyable
+class FLAXENGINE_API CreateAssetContext : public NonCopyable
{
private:
CreateAssetResult _applyChangesResult;
diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h
index 698788869..ad6f8ffc6 100644
--- a/Source/Engine/Core/Collections/HashSet.h
+++ b/Source/Engine/Core/Collections/HashSet.h
@@ -357,8 +357,8 @@ public:
{
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
- if (i->Value)
- ::Delete(i->Value);
+ if (i->Item)
+ ::Delete(i->Item);
}
Clear();
}
diff --git a/Source/Engine/Core/Encoding.h b/Source/Engine/Core/Encoding.h
index 6ac716ba8..3e74144e2 100644
--- a/Source/Engine/Core/Encoding.h
+++ b/Source/Engine/Core/Encoding.h
@@ -4,4 +4,4 @@
#include "Enums.h"
-DECLARE_ENUM_3(Encoding, ANSI, Unicode, UnicodeBigEndian);
+DECLARE_ENUM_4(Encoding, ANSI, Unicode, UnicodeBigEndian, UTF8);
diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h
index 61c5d122f..287d33c2f 100644
--- a/Source/Engine/Core/Math/BoundingFrustum.h
+++ b/Source/Engine/Core/Math/BoundingFrustum.h
@@ -12,6 +12,7 @@
///
API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum
{
+ friend CollisionsHelper;
private:
Matrix _matrix;
diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp
index 60ba61f28..8ad5b7673 100644
--- a/Source/Engine/Core/Math/CollisionsHelper.cpp
+++ b/Source/Engine/Core/Math/CollisionsHelper.cpp
@@ -939,7 +939,6 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
normal = Vector3::Up;
return false;
}
-
const Vector3 point = ray.Position + ray.Direction * distance;
normal = Vector3::Normalize(point - sphere.Center);
return true;
@@ -953,22 +952,17 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
point = Vector3::Zero;
return false;
}
-
point = ray.Position + ray.Direction * distance;
return true;
}
PlaneIntersectionType CollisionsHelper::PlaneIntersectsPoint(const Plane& plane, const Vector3& point)
{
- Real distance = Vector3::Dot(plane.Normal, point);
- distance += plane.D;
-
+ const Real distance = Vector3::Dot(plane.Normal, point) + plane.D;
if (distance > Plane::DistanceEpsilon)
return PlaneIntersectionType::Front;
-
if (distance < Plane::DistanceEpsilon)
return PlaneIntersectionType::Back;
-
return PlaneIntersectionType::Intersecting;
}
@@ -1169,7 +1163,6 @@ ContainmentType CollisionsHelper::SphereContainsPoint(const BoundingSphere& sphe
{
if (Vector3::DistanceSquared(point, sphere.Center) <= sphere.Radius * sphere.Radius)
return ContainmentType::Contains;
-
return ContainmentType::Disjoint;
}
@@ -1254,13 +1247,10 @@ ContainmentType CollisionsHelper::SphereContainsBox(const BoundingSphere& sphere
ContainmentType CollisionsHelper::SphereContainsSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2)
{
const Real distance = Vector3::Distance(sphere1.Center, sphere2.Center);
-
if (sphere1.Radius + sphere2.Radius < distance)
return ContainmentType::Disjoint;
-
if (sphere1.Radius - sphere2.Radius < distance)
return ContainmentType::Intersects;
-
return ContainmentType::Contains;
}
@@ -1274,7 +1264,8 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
auto result = ContainmentType::Contains;
for (int32 i = 0; i < 6; i++)
{
- Plane plane = frustum.GetPlane(i);
+ Plane plane = frustum._planes[i];
+
Vector3 p = box.Minimum;
if (plane.Normal.X >= 0)
p.X = box.Maximum.X;
@@ -1282,7 +1273,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Maximum.Y;
if (plane.Normal.Z >= 0)
p.Z = box.Maximum.Z;
- if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back)
+ if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
return ContainmentType::Disjoint;
p = box.Maximum;
@@ -1292,7 +1283,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Minimum.Y;
if (plane.Normal.Z >= 0)
p.Z = box.Minimum.Z;
- if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back)
+ if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
result = ContainmentType::Intersects;
}
return result;
diff --git a/Source/Engine/Core/Memory/Memory.h b/Source/Engine/Core/Memory/Memory.h
index 770fd2c22..96acc0746 100644
--- a/Source/Engine/Core/Memory/Memory.h
+++ b/Source/Engine/Core/Memory/Memory.h
@@ -36,6 +36,32 @@ namespace AllocatorExt
return result;
}
+ ///
+ /// Reallocates block of the memory.
+ ///
+ ///
+ /// A pointer to the memory block to reallocate.
+ /// The size of the new allocation (in bytes).
+ /// The memory alignment (in bytes). Must be an integer power of 2.
+ /// The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment.
+ 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;
+ }
+
///
/// Reallocates block of the memory.
///
diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h
index 678550e85..e7bea5fbd 100644
--- a/Source/Engine/Core/Types/Span.h
+++ b/Source/Engine/Core/Types/Span.h
@@ -114,3 +114,14 @@ inline Span ToSpan(const T* ptr, int32 length)
{
return Span(ptr, length);
}
+
+template
+inline bool SpanContains(const Span span, const T& value)
+{
+ for (int32 i = 0; i < span.Length(); i++)
+ {
+ if (span.Get()[i] == value)
+ return true;
+ }
+ return false;
+}
diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp
index cee60c07c..3de255039 100644
--- a/Source/Engine/Core/Types/String.cpp
+++ b/Source/Engine/Core/Types/String.cpp
@@ -73,7 +73,7 @@ void String::Set(const char* chars, int32 length)
_length = length;
}
if (chars)
- StringUtils::ConvertANSI2UTF16(chars, _data, length);
+ StringUtils::ConvertANSI2UTF16(chars, _data, length, _length);
}
void String::SetUTF8(const char* chars, int32 length)
@@ -112,7 +112,7 @@ void String::Append(const char* chars, int32 count)
_data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16);
Platform::MemoryCopy(_data, oldData, oldLength * sizeof(Char));
- StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count * sizeof(Char));
+ StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count, _length);
_data[_length] = 0;
Platform::Free(oldData);
diff --git a/Source/Engine/Core/Types/StringBuilder.h b/Source/Engine/Core/Types/StringBuilder.h
index 39c1b3a84..051554a23 100644
--- a/Source/Engine/Core/Types/StringBuilder.h
+++ b/Source/Engine/Core/Types/StringBuilder.h
@@ -125,7 +125,8 @@ public:
const int32 length = str && *str ? StringUtils::Length(str) : 0;
const int32 prevCnt = _data.Count();
_data.AddDefault(length);
- StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length);
+ int32 tmp;
+ StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length, tmp);
return *this;
}
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index 33ffde349..8232d415f 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -660,7 +660,8 @@ Variant::Variant(const StringAnsiView& v)
const int32 length = v.Length() * sizeof(Char) + 2;
AsBlob.Data = Allocator::Allocate(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;
}
else
@@ -2578,7 +2579,8 @@ void Variant::SetString(const StringAnsiView& str)
AsBlob.Data = Allocator::Allocate(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;
}
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 7330f4339..e4d53fcb5 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -466,7 +466,7 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext,
Matrix::Multiply(fw, vp, m);
Render2D::Begin(context, target, depthBuffer, viewport, m);
const StringView text(t.Text.Get(), t.Text.Count() - 1);
- Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, Vector2::Zero);
+ Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, Vector2::Zero);
Render2D::End();
}
@@ -777,7 +777,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->BindSR(0, renderContext.Buffers->DepthBuffer);
const bool enableDepthWrite = data.EnableDepthTest;
- context->SetRenderTarget(depthBuffer ? depthBuffer : *renderContext.Buffers->DepthBuffer, target);
+ context->SetRenderTarget(depthBuffer ? depthBuffer : (data.EnableDepthTest ? nullptr : renderContext.Buffers->DepthBuffer->View()), target);
// Lines
if (depthTestLines.VertexCount)
@@ -859,12 +859,12 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
for (auto& t : Context->DebugDrawDefault.DefaultText2D)
{
const StringView text(t.Text.Get(), t.Text.Count() - 1);
- Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position);
+ Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, t.Position);
}
for (auto& t : Context->DebugDrawDefault.OneFrameText2D)
{
const StringView text(t.Text.Get(), t.Text.Count() - 1);
- Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position);
+ Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, t.Position);
}
Render2D::End();
}
diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs
index 7eda1cdec..797b02d5b 100644
--- a/Source/Engine/Engine/NativeInterop.Managed.cs
+++ b/Source/Engine/Engine/NativeInterop.Managed.cs
@@ -50,13 +50,13 @@ namespace FlaxEngine.Interop
/// The resources must be released by calling FreePooled() instead of Free()-method.
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);
return managedArray;
}
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)
=> new ManagedArray(ptr, length, arrayType, elementType);
@@ -86,7 +86,7 @@ namespace FlaxEngine.Interop
_length = arr.Length;
_arrayType = arrayType;
_elementType = arr.GetType().GetElementType();
- _elementSize = Marshal.SizeOf(_elementType);
+ _elementSize = NativeInterop.GetTypeSize(_elementType);
}
internal void Allocate(int length) where T : unmanaged
@@ -117,7 +117,7 @@ namespace FlaxEngine.Interop
_length = length;
_arrayType = arrayType;
_elementType = elementType;
- _elementSize = Marshal.SizeOf(elementType);
+ _elementSize = NativeInterop.GetTypeSize(_elementType);
}
~ManagedArray()
diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs
index a3531d11d..0a23bfcbd 100644
--- a/Source/Engine/Engine/NativeInterop.Marshallers.cs
+++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs
@@ -350,14 +350,14 @@ namespace FlaxEngine.Interop
#endif
public static class NativeToManaged
{
- public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
+ public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
{
if (unmanaged is null)
return null;
return new T[numElements];
}
- public static Span GetManagedValuesDestination(T[]? managed) => managed;
+ public static Span GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{
@@ -390,7 +390,7 @@ namespace FlaxEngine.Interop
#endif
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)
{
@@ -402,7 +402,7 @@ namespace FlaxEngine.Interop
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
}
- public static ReadOnlySpan GetManagedValuesSource(T[]? managed) => managed;
+ public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed;
public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{
@@ -431,7 +431,7 @@ namespace FlaxEngine.Interop
ManagedArray unmanagedArray;
ManagedHandle handle;
- public void FromManaged(T[]? managed)
+ public void FromManaged(T[] managed)
{
if (managed == null)
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)
{
@@ -489,7 +489,7 @@ namespace FlaxEngine.Interop
return (TUnmanagedElement*)handle;
}
- public static ReadOnlySpan GetManagedValuesSource(T[]? managed) => managed;
+ public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed;
public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{
@@ -499,9 +499,9 @@ namespace FlaxEngine.Interop
return unmanagedArray.ToSpan();
}
- 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 GetManagedValuesDestination(T[]? managed) => managed;
+ public static Span GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index c079a14e1..d4a8d44f8 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -885,6 +885,7 @@ namespace FlaxEngine.Interop
handle.Free();
fieldHandleCacheCollectible.Clear();
#endif
+ _typeSizeCache.Clear();
foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free();
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index b33e4bcb6..e82cb3858 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -46,6 +46,7 @@ namespace FlaxEngine.Interop
#endif
private static Dictionary
/// The tag of the actor to search for.
+ /// Find only active actors.
/// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.
/// Found actors or empty if none.
- API_FUNCTION() static Array FindActors(const Tag& tag, Actor* root = nullptr);
+ API_FUNCTION() static Array FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr);
///
/// Search actors using a parent parentTag.
///
/// The tag to search actors with subtags belonging to this tag
+ /// Find only active actors.
/// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.
/// Returns all actors that have subtags belonging to the given parent parentTag
- API_FUNCTION() static Array FindActorsByParentTag(const Tag& parentTag, Actor* root = nullptr);
+ API_FUNCTION() static Array FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr);
private:
// Actor API
diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs
index 28a6aff8c..a2c9aecde 100644
--- a/Source/Engine/Level/Spline.cs
+++ b/Source/Engine/Level/Spline.cs
@@ -23,8 +23,8 @@ namespace FlaxEngine
if (_keyframes == null || _keyframes.Length != count)
_keyframes = new BezierCurve.Keyframe[count];
#if !BUILD_RELEASE
- if (Marshal.SizeOf(typeof(BezierCurve.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float))
- throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve.Keyframe)) + " bytes.");
+ if (System.Runtime.CompilerServices.Unsafe.SizeOf.Keyframe>() != Transform.SizeInBytes * 3 + sizeof(float))
+ throw new Exception("Invalid size of BezierCurve keyframe " + System.Runtime.CompilerServices.Unsafe.SizeOf.Keyframe>() + " bytes.");
#endif
Internal_GetKeyframes(__unmanagedPtr, _keyframes);
return _keyframes;
diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.cpp b/Source/Engine/Networking/NetworkReplicationHierarchy.cpp
index d86acfc7f..6ec5e52c2 100644
--- a/Source/Engine/Networking/NetworkReplicationHierarchy.cpp
+++ b/Source/Engine/Networking/NetworkReplicationHierarchy.cpp
@@ -23,7 +23,7 @@ void NetworkReplicationHierarchyUpdateResult::Init()
{
_clientsHaveLocation = false;
_clients.Resize(NetworkManager::Clients.Count());
- _clientsMask = NetworkClientsMask();
+ _clientsMask = NetworkManager::Mode == NetworkManagerMode::Client ? NetworkClientsMask::All : NetworkClientsMask();
for (int32 i = 0; i < _clients.Count(); i++)
_clientsMask.SetBit(i);
_entries.Clear();
@@ -49,7 +49,7 @@ bool NetworkReplicationHierarchyUpdateResult::GetClientLocation(int32 clientInde
void NetworkReplicationNode::AddObject(NetworkReplicationHierarchyObject obj)
{
- if (obj.ReplicationFPS > 0.0f)
+ if (obj.ReplicationFPS > ZeroTolerance) // > 0
{
// Randomize initial replication update to spread rep rates more evenly for large scenes that register all objects within the same frame
obj.ReplicationUpdatesLeft = NetworkReplicationNodeObjectCounter++ % Math::Clamp(Math::RoundToInt(NetworkManager::NetworkFPS / obj.ReplicationFPS), 1, 60);
@@ -63,6 +63,17 @@ bool NetworkReplicationNode::RemoveObject(ScriptingObject* 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)
{
const int32 index = Objects.Find(obj);
@@ -80,7 +91,11 @@ void NetworkReplicationNode::Update(NetworkReplicationHierarchyUpdateResult* res
const float networkFPS = NetworkManager::NetworkFPS / result->ReplicationScale;
for (NetworkReplicationHierarchyObject& obj : Objects)
{
- if (obj.ReplicationFPS <= 0.0f)
+ if (obj.ReplicationFPS < -ZeroTolerance) // < 0
+ {
+ continue;
+ }
+ else if (obj.ReplicationFPS < ZeroTolerance) // == 0
{
// Always relevant
result->AddObject(obj.Object);
@@ -152,6 +167,7 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
cell->MinCullDistance = obj.CullDistance;
}
cell->Node->AddObject(obj);
+ _objectToCell[obj.Object] = coord;
// Cache minimum culling distance for a whole cell to skip it at once
cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance);
@@ -159,14 +175,35 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj)
{
- for (const auto& e : _children)
+ Int3 coord;
+
+ if (!_objectToCell.TryGet(obj, coord))
{
- if (e.Value.Node->RemoveObject(obj))
- {
- // TODO: remove empty cells?
- // TODO: update MinCullDistance for cell?
- return true;
- }
+ return false;
+ }
+
+ if (_children[coord].Node->RemoveObject(obj))
+ {
+ _objectToCell.Remove(obj);
+ // TODO: remove empty cells?
+ // TODO: update MinCullDistance for cell?
+ 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;
}
diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.h b/Source/Engine/Networking/NetworkReplicationHierarchy.h
index 55cd6b7b1..b054de453 100644
--- a/Source/Engine/Networking/NetworkReplicationHierarchy.h
+++ b/Source/Engine/Networking/NetworkReplicationHierarchy.h
@@ -20,7 +20,7 @@ API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API
// The object to replicate.
API_FIELD() ScriptingObjectReference Object;
- // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object.
+ // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object and less than 0 (eg. -1) for 'never relevant' objects that would only get synched on client join once.
API_FIELD() float ReplicationFPS = 60;
// The minimum distance from the player to the object at which it can process replication. For example, players further away won't receive object data. Use 0 if unused.
API_FIELD() float CullDistance = 15000;
@@ -200,6 +200,14 @@ API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Ne
/// True on successful removal, otherwise false.
API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj);
+ ///
+ /// Gets object from the hierarchy.
+ ///
+ /// The object to get.
+ /// The hierarchy object to retrieve.
+ /// True on successful retrieval, otherwise false.
+ API_FUNCTION() virtual bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result);
+
///
/// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization.
///
@@ -238,6 +246,7 @@ private:
};
Dictionary _children;
+ Dictionary _objectToCell;
public:
///
@@ -247,6 +256,7 @@ public:
void AddObject(NetworkReplicationHierarchyObject obj) override;
bool RemoveObject(ScriptingObject* obj) override;
+ bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) override;
void Update(NetworkReplicationHierarchyUpdateResult* result) override;
};
diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp
index 11bd5f916..ea3e8422a 100644
--- a/Source/Engine/Networking/NetworkReplicator.cpp
+++ b/Source/Engine/Networking/NetworkReplicator.cpp
@@ -700,9 +700,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri
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(targetIds));
+ return EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan(targetIds));
}
StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name)
@@ -1113,12 +1113,12 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
return CachedWriteStream;
}
-void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds)
+bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds)
{
Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline())
- return;
+ return false;
ObjectsLock.Lock();
auto& rpc = RpcQueue.AddOne();
rpc.Object = obj;
@@ -1135,12 +1135,23 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
}
#endif
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)
{
ScopeLock lock(ObjectsLock);
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)
@@ -1193,6 +1204,7 @@ void NetworkInternal::NetworkReplicatorClear()
Objects.Remove(it);
}
}
+ Objects.Clear();
RpcQueue.Clear();
SpawnQueue.Clear();
DespawnQueue.Clear();
diff --git a/Source/Engine/Networking/NetworkReplicator.cs b/Source/Engine/Networking/NetworkReplicator.cs
index 1ca251f75..bc437b82e 100644
--- a/Source/Engine/Networking/NetworkReplicator.cs
+++ b/Source/Engine/Networking/NetworkReplicator.cs
@@ -120,10 +120,11 @@ namespace FlaxEngine.Networking
/// The RPC name.
/// The RPC serialized arguments stream returned from BeginInvokeRPC.
/// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.
+ /// True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).
[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);
}
///
diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h
index 156e7eeea..ecccc52cd 100644
--- a/Source/Engine/Networking/NetworkReplicator.h
+++ b/Source/Engine/Networking/NetworkReplicator.h
@@ -195,13 +195,14 @@ public:
/// The RPC name.
/// The RPC serialized arguments stream returned from BeginInvokeRPC.
/// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.
- static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds = Span());
+ /// True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).
+ static bool EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds = Span());
private:
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function& serialize, const Function& deserialize);
API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function& 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);
#endif
};
diff --git a/Source/Engine/Networking/NetworkRpc.h b/Source/Engine/Networking/NetworkRpc.h
index c0c8a558e..6e8d26752 100644
--- a/Source/Engine/Networking/NetworkRpc.h
+++ b/Source/Engine/Networking/NetworkRpc.h
@@ -41,7 +41,7 @@ struct FLAXENGINE_API NetworkRpcInfo
uint8 Client : 1;
uint8 Channel : 4;
void (*Execute)(ScriptingObject* obj, NetworkStream* stream, void* tag);
- void (*Invoke)(ScriptingObject* obj, void** args);
+ bool (*Invoke)(ScriptingObject* obj, void** args);
void* Tag;
///
@@ -83,10 +83,7 @@ FORCE_INLINE void NetworkRpcInitArg(Array>& args, con
{ \
Array> args; \
NetworkRpcInitArg(args, __VA_ARGS__); \
- rpcInfo.Invoke(this, args.Get()); \
- if (rpcInfo.Server && networkMode == NetworkManagerMode::Client) \
- return; \
- if (rpcInfo.Client && networkMode == NetworkManagerMode::Server) \
+ if (rpcInfo.Invoke(this, args.Get())) \
return; \
} \
}
diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp
index 18bcbdd54..1d7fa18a1 100644
--- a/Source/Engine/Particles/ParticleEffect.cpp
+++ b/Source/Engine/Particles/ParticleEffect.cpp
@@ -299,17 +299,19 @@ void ParticleEffect::Stop()
void ParticleEffect::UpdateBounds()
{
BoundingBox bounds = BoundingBox::Empty;
- if (ParticleSystem && Instance.LastUpdateTime >= 0)
+ const auto particleSystem = ParticleSystem.Get();
+ if (particleSystem && Instance.LastUpdateTime >= 0)
{
- for (int32 j = 0; j < ParticleSystem->Tracks.Count(); j++)
+ for (int32 j = 0; j < particleSystem->Tracks.Count(); j++)
{
- const auto& track = ParticleSystem->Tracks[j];
+ const auto& track = particleSystem->Tracks[j];
if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled)
continue;
- auto& emitter = ParticleSystem->Emitters[track.AsEmitter.Index];
- auto& data = Instance.Emitters[track.AsEmitter.Index];
- if (!emitter || emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0)
+ const int32 emitterIndex = track.AsEmitter.Index;
+ ParticleEmitter* emitter = particleSystem->Emitters[emitterIndex].Get();
+ if (!emitter || emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0 || Instance.Emitters.Count() <= emitterIndex)
continue;
+ auto& data = Instance.Emitters[emitterIndex];
BoundingBox emitterBounds;
if (emitter->GraphExecutorCPU.ComputeBounds(emitter, this, data, emitterBounds))
diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp
index 2ed59c8f6..f5f729d39 100644
--- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp
+++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp
@@ -230,10 +230,11 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
{
const Vector3 currentPos = wheel.Collider->GetPosition();
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(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 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)
{
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 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;
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.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(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_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())
{
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false);
diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
index 52f6e58e4..57ef4ce17 100644
--- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
@@ -279,6 +279,60 @@ class CharacterControllerFilterPhysX : public PxControllerFilterCallback
}
};
+class CharacterControllerHitReportPhysX : public PxUserControllerHitReport
+{
+ void onHit(const PxControllerHit& hit, Collision& c)
+ {
+ ASSERT_LOW_LAYER(c.ThisActor && c.OtherActor);
+ c.Impulse = Vector3::Zero;
+ c.ThisVelocity = P2C(hit.dir) * hit.length;
+ c.OtherVelocity = Vector3::Zero;
+ c.ContactsCount = 1;
+ ContactPoint& contact = c.Contacts[0];
+ contact.Point = P2C(hit.worldPos);
+ contact.Normal = P2C(hit.worldNormal);
+ contact.Separation = 0.0f;
+
+ //auto simulationEventCallback = static_cast(hit.controller->getScene()->getSimulationEventCallback());
+ //simulationEventCallback->Collisions[SimulationEventCallback::CollidersPair(c.ThisActor, c.OtherActor)] = c;
+ // TODO: build additional list for hit-only events to properly send enter/exit pairs instead of spamming every frame whether controller executes move
+
+ // Single-hit collision
+ c.ThisActor->OnCollisionEnter(c);
+ c.SwapObjects();
+ c.ThisActor->OnCollisionEnter(c);
+ c.SwapObjects();
+ c.ThisActor->OnCollisionExit(c);
+ c.SwapObjects();
+ c.ThisActor->OnCollisionExit(c);
+ }
+
+ void onShapeHit(const PxControllerShapeHit& hit) override
+ {
+ Collision c;
+ PxShape* controllerShape;
+ hit.controller->getActor()->getShapes(&controllerShape, 1);
+ c.ThisActor = static_cast(controllerShape->userData);
+ c.OtherActor = static_cast(hit.shape->userData);
+ onHit(hit, c);
+ }
+
+ void onControllerHit(const PxControllersHit& hit) override
+ {
+ Collision c;
+ PxShape* controllerShape;
+ hit.controller->getActor()->getShapes(&controllerShape, 1);
+ c.ThisActor = static_cast(controllerShape->userData);
+ hit.other->getActor()->getShapes(&controllerShape, 1);
+ c.OtherActor = static_cast(controllerShape->userData);
+ onHit(hit, c);
+ }
+
+ void onObstacleHit(const PxControllerObstacleHit& hit) override
+ {
+ }
+};
+
#if WITH_VEHICLE
class WheelFilterPhysX : public PxQueryFilterCallback
@@ -463,6 +517,7 @@ namespace
QueryFilterPhysX QueryFilter;
CharacterQueryFilterPhysX CharacterQueryFilter;
CharacterControllerFilterPhysX CharacterControllerFilter;
+ CharacterControllerHitReportPhysX CharacterControllerHitReport;
Dictionary> SceneOrigins;
CriticalSection FlushLocker;
@@ -2811,6 +2866,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic
const Vector3 sceneOrigin = SceneOrigins[scenePhysX->Scene];
PxCapsuleControllerDesc desc;
desc.userData = actor;
+ desc.reportCallback = &CharacterControllerHitReport;
desc.contactOffset = Math::Max(contactOffset, ZeroTolerance);
desc.position = PxExtendedVec3(position.X - sceneOrigin.X, position.Y - sceneOrigin.Y, position.Z - sceneOrigin.Z);
desc.slopeLimit = Math::Cos(slopeLimit * DegreesToRadians);
diff --git a/Source/Engine/Platform/Base/FileBase.cpp b/Source/Engine/Platform/Base/FileBase.cpp
index f45685c7f..eb11292b0 100644
--- a/Source/Engine/Platform/Base/FileBase.cpp
+++ b/Source/Engine/Platform/Base/FileBase.cpp
@@ -309,6 +309,45 @@ bool FileBase::WriteAllText(const StringView& path, const Char* data, int32 leng
return WriteAllBytes(path, tmp.Get(), tmp.Count());
}
+ case Encoding::UTF8:
+ {
+ Array tmp;
+ tmp.SetCapacity(length);
+
+ for (int32 i = 0; i < length; i++)
+ {
+ Char c = data[i];
+ if (c < 0x0080)
+ {
+ tmp.Add((byte)c);
+ }
+ else if (c < 0x0800)
+ {
+ tmp.Add(0xC0 | (c >> 6));
+ tmp.Add(0x80 | (c & 0x3F));
+ }
+ else if (c < 0xD800 || c >= 0xE000)
+ {
+ tmp.Add(0xE0 | (c >> 12));
+ tmp.Add(0x80 | ((c >> 6) & 0x3F));
+ tmp.Add(0x80 | (c & 0x3F));
+ }
+ else // Surrogate pair
+ {
+ ++i;
+ if (i >= length)
+ return true;
+
+ uint32 p = 0x10000 + (((c & 0x3FF) << 10) | (data[i] & 0x3FF));
+ tmp.Add(0xF0 | (p >> 18));
+ tmp.Add(0x80 | ((p >> 12) & 0x3F));
+ tmp.Add(0x80 | ((p >> 6) & 0x3F));
+ tmp.Add(0x80 | (p & 0x3F));
+ }
+ }
+
+ return WriteAllBytes(path, tmp.Get(), tmp.Count());
+ }
default:
return true;
}
diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h
index 04e644d2e..72b3b5f4b 100644
--- a/Source/Engine/Platform/StringUtils.h
+++ b/Source/Engine/Platform/StringUtils.h
@@ -179,7 +179,7 @@ public:
public:
// Converts characters from ANSI to UTF-16
- static void ConvertANSI2UTF16(const char* from, Char* to, int32 len);
+ static void ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength);
// Converts characters from UTF-16 to ANSI
static void ConvertUTF162ANSI(const Char* from, char* to, int32 len);
diff --git a/Source/Engine/Platform/Unix/UnixStringUtils.cpp b/Source/Engine/Platform/Unix/UnixStringUtils.cpp
index 7e5c34b24..daa705769 100644
--- a/Source/Engine/Platform/Unix/UnixStringUtils.cpp
+++ b/Source/Engine/Platform/Unix/UnixStringUtils.cpp
@@ -311,14 +311,14 @@ static inline uint32 Utf8ToUtf32Codepoint(const char* src, int32 length)
}
}
-void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
+void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength)
{
- const char* const u8end = from + len;
+ const char* const u8end = from + fromLength;
const char* u8cur = from;
char16_t* u16cur = to;
while (u8cur < u8end)
{
- len = Utf8CodepointLength(*u8cur);
+ int32 len = Utf8CodepointLength(*u8cur);
uint32 codepoint = Utf8ToUtf32Codepoint(u8cur, len);
// Convert the UTF32 codepoint to one or more UTF16 codepoints
@@ -336,6 +336,7 @@ void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
}
u8cur += len;
}
+ toLength = (int32)(u16cur - to);
}
static const char32_t kByteMask = 0x000000BF;
diff --git a/Source/Engine/Platform/Win32/Win32StringUtils.cpp b/Source/Engine/Platform/Win32/Win32StringUtils.cpp
index d1634d90d..2a9bcf174 100644
--- a/Source/Engine/Platform/Win32/Win32StringUtils.cpp
+++ b/Source/Engine/Platform/Win32/Win32StringUtils.cpp
@@ -179,10 +179,12 @@ const char* StringUtils::Find(const char* str, const char* toFind)
return strstr(str, toFind);
}
-void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
+void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength)
{
- if (len)
- mbstowcs(to, from, len);
+ if (fromLength)
+ toLength = mbstowcs(to, from, fromLength);
+ else
+ toLength = 0;
}
void StringUtils::ConvertUTF162ANSI(const Char* from, char* to, int32 len)
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
index 8d02312af..4d43272d1 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
@@ -992,7 +992,8 @@ void ReadPipe(HANDLE pipe, Array& rawData, Array& logData, LogType l
// Log contents
logData.Clear();
logData.Resize(rawData.Count() + 1);
- StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count());
+ int32 tmp;
+ StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count(), tmp);
logData.Last() = '\0';
if (settings.LogOutput)
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));
diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp
index f12a9ce1c..90423f5ce 100644
--- a/Source/Engine/Render2D/Font.cpp
+++ b/Source/Engine/Render2D/Font.cpp
@@ -7,7 +7,7 @@
#include "Engine/Threading/Threading.h"
#include "IncludeFreeType.h"
-Font::Font(FontAsset* parentAsset, int32 size)
+Font::Font(FontAsset* parentAsset, float size)
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer))
, _asset(parentAsset)
, _size(size)
@@ -436,7 +436,7 @@ void Font::FlushFaceSize() const
{
// Set the character size
const FT_Face face = _asset->GetFTFace();
- const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6((float)_size * FontManager::FontScale), DefaultDPI, DefaultDPI);
+ const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6(_size * FontManager::FontScale), DefaultDPI, DefaultDPI);
if (error)
{
LOG_FT_ERROR(error);
diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h
index d68f497d6..90f723cd8 100644
--- a/Source/Engine/Render2D/Font.h
+++ b/Source/Engine/Render2D/Font.h
@@ -228,7 +228,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font);
private:
FontAsset* _asset;
- int32 _size;
+ float _size;
int32 _height;
int32 _ascender;
int32 _descender;
@@ -244,7 +244,7 @@ public:
///
/// The parent asset.
/// The size.
- Font(FontAsset* parentAsset, int32 size);
+ Font(FontAsset* parentAsset, float size);
///
/// Finalizes an instance of the class.
@@ -264,7 +264,7 @@ public:
///
/// Gets font size.
///
- API_PROPERTY() FORCE_INLINE int32 GetSize() const
+ API_PROPERTY() FORCE_INLINE float GetSize() const
{
return _size;
}
diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp
index 5fa07771f..f000eb3c6 100644
--- a/Source/Engine/Render2D/FontAsset.cpp
+++ b/Source/Engine/Render2D/FontAsset.cpp
@@ -92,7 +92,7 @@ void FontAsset::SetOptions(const FontOptions& value)
_options = value;
}
-Font* FontAsset::CreateFont(int32 size)
+Font* FontAsset::CreateFont(float size)
{
PROFILE_CPU();
diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h
index abc601f45..60dbee01d 100644
--- a/Source/Engine/Render2D/FontAsset.h
+++ b/Source/Engine/Render2D/FontAsset.h
@@ -139,7 +139,7 @@ public:
///
/// The font characters size.
/// The created font object.
- API_FUNCTION() Font* CreateFont(int32 size);
+ API_FUNCTION() Font* CreateFont(float size);
///
/// Gets the font with bold style. Returns itself or creates a new virtual font asset using this font but with bold option enabled.
diff --git a/Source/Engine/Render2D/FontReference.cs b/Source/Engine/Render2D/FontReference.cs
index 290e10312..cc32bcd9a 100644
--- a/Source/Engine/Render2D/FontReference.cs
+++ b/Source/Engine/Render2D/FontReference.cs
@@ -13,7 +13,7 @@ namespace FlaxEngine
private FontAsset _font;
[NoSerialize]
- private int _size;
+ private float _size;
[NoSerialize]
private Font _cachedFont;
@@ -33,7 +33,7 @@ namespace FlaxEngine
///
/// The font.
/// The font size.
- public FontReference(FontAsset font, int size)
+ public FontReference(FontAsset font, float size)
{
_font = font;
_size = size;
@@ -91,7 +91,7 @@ namespace FlaxEngine
/// The size of the font characters.
///
[EditorOrder(10), Limit(1, 500, 0.1f), Tooltip("The size of the font characters.")]
- public int Size
+ public float Size
{
get => _size;
set
@@ -187,7 +187,7 @@ namespace FlaxEngine
unchecked
{
int hashCode = _font ? _font.GetHashCode() : 0;
- hashCode = (hashCode * 397) ^ _size;
+ hashCode = (hashCode * 397) ^ _size.GetHashCode();
return hashCode;
}
}
diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp
index f2c5ee376..246c1620f 100644
--- a/Source/Engine/Scripting/Runtime/DotNet.cpp
+++ b/Source/Engine/Scripting/Runtime/DotNet.cpp
@@ -712,7 +712,8 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa
// Provide new path of hot-reloaded native library path for managed DllImport
if (nativePath.HasChars())
{
- RegisterNativeLibrary(assemblyPathAnsi.Get(), StringAnsi(nativePath).Get());
+ StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name);
+ RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get());
}
_hasCachedClasses = false;
@@ -845,7 +846,7 @@ bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const
bool MClass::HasInterface(const MClass* klass) const
{
static void* TypeIsAssignableFrom = GetStaticMethodPointer(TEXT("TypeIsAssignableFrom"));
- return klass && CallStaticMethod(TypeIsAssignableFrom, _handle, klass->_handle);
+ return klass && CallStaticMethod(TypeIsAssignableFrom, klass->_handle, _handle);
}
bool MClass::IsInstanceOfType(MObject* object) const
diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp
index 9bddc333e..e2d709653 100644
--- a/Source/Engine/Scripting/Scripting.cpp
+++ b/Source/Engine/Scripting/Scripting.cpp
@@ -559,6 +559,16 @@ void Scripting::Release()
}
_objectsLocker.Unlock();
+ // Release assets sourced from game assemblies
+ const auto flaxModule = GetBinaryModuleFlaxEngine();
+ for (auto asset : Content::GetAssets())
+ {
+ if (asset->GetTypeHandle().Module == flaxModule)
+ continue;
+
+ asset->DeleteObjectNow();
+ }
+
// Unload assemblies (from back to front)
{
LOG(Info, "Unloading binary modules");
@@ -640,9 +650,9 @@ void Scripting::Reload(bool canTriggerSceneReload)
MCore::GC::WaitForPendingFinalizers();
// Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload)
+ const auto flaxModule = GetBinaryModuleFlaxEngine();
_objectsLocker.Lock();
{
- const auto flaxModule = GetBinaryModuleFlaxEngine();
for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Value;
@@ -657,6 +667,15 @@ void Scripting::Reload(bool canTriggerSceneReload)
}
_objectsLocker.Unlock();
+ // Release assets sourced from game assemblies
+ for (auto asset : Content::GetAssets())
+ {
+ if (asset->GetTypeHandle().Module == flaxModule)
+ continue;
+
+ asset->DeleteObjectNow();
+ }
+
// Unload all game modules
LOG(Info, "Unloading game binary modules");
auto modules = BinaryModule::GetModules();
diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h
index 9d3215442..be80e3249 100644
--- a/Source/Engine/Serialization/ReadStream.h
+++ b/Source/Engine/Serialization/ReadStream.h
@@ -6,6 +6,7 @@
#include "Engine/Core/Templates.h"
extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type);
+extern FLAXENGINE_API class Asset* LoadAsset(const Guid& id, const struct ScriptingTypeHandle& type);
///
/// Base class for all data read streams
@@ -159,6 +160,34 @@ public:
Read(ptr);
v = ptr;
}
+ template
+ FORCE_INLINE void Read(SoftObjectReference& v)
+ {
+ uint32 id[4];
+ ReadBytes(id, sizeof(id));
+ v.Set(*(Guid*)id);
+ }
+ template
+ FORCE_INLINE void Read(AssetReference& v)
+ {
+ uint32 id[4];
+ ReadBytes(id, sizeof(id));
+ v = (T*)::LoadAsset(*(Guid*)id, T::TypeInitializer);
+ }
+ template
+ FORCE_INLINE void Read(WeakAssetReference& v)
+ {
+ uint32 id[4];
+ ReadBytes(id, sizeof(id));
+ v = (T*)::LoadAsset(*(Guid*)id, T::TypeInitializer);
+ }
+ template
+ FORCE_INLINE void Read(SoftAssetReference& v)
+ {
+ uint32 id[4];
+ ReadBytes(id, sizeof(id));
+ v.Set(*(Guid*)id);
+ }
///
/// Read data array
diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h
index 83f0eaf21..e477eee95 100644
--- a/Source/Engine/Serialization/Stream.h
+++ b/Source/Engine/Serialization/Stream.h
@@ -17,6 +17,14 @@ class ISerializable;
class ScriptingObject;
template
class ScriptingObjectReference;
+template
+class SoftObjectReference;
+template
+class AssetReference;
+template
+class WeakAssetReference;
+template
+class SoftAssetReference;
///
/// Base class for all data streams (memory streams, file streams etc.)
diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h
index 92ac211ca..95bd87df9 100644
--- a/Source/Engine/Serialization/WriteStream.h
+++ b/Source/Engine/Serialization/WriteStream.h
@@ -176,6 +176,26 @@ public:
{
Write(v.Get());
}
+ template
+ FORCE_INLINE void Write(const SoftObjectReference& v)
+ {
+ Write(v.Get());
+ }
+ template
+ FORCE_INLINE void Write(const AssetReference& v)
+ {
+ Write(v.Get());
+ }
+ template
+ FORCE_INLINE void Write(const WeakAssetReference& v)
+ {
+ Write(v.Get());
+ }
+ template
+ FORCE_INLINE void Write(const SoftAssetReference& v)
+ {
+ Write(v.Get());
+ }
template
void Write(const Array& data)
diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp
index d4db794eb..cd60abddf 100644
--- a/Source/Engine/UI/TextRender.cpp
+++ b/Source/Engine/UI/TextRender.cpp
@@ -73,14 +73,14 @@ void TextRender::SetColor(const Color& value)
}
}
-int32 TextRender::GetFontSize() const
+float TextRender::GetFontSize() const
{
return _size;
}
-void TextRender::SetFontSize(int32 value)
+void TextRender::SetFontSize(float value)
{
- value = Math::Clamp(value, 1, 1024);
+ value = Math::Clamp(value, 1.0f, 1024.0f);
if (_size != value)
{
_size = value;
diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h
index d69b5427b..816ad8aa4 100644
--- a/Source/Engine/UI/TextRender.h
+++ b/Source/Engine/UI/TextRender.h
@@ -38,7 +38,7 @@ private:
LocalizedString _text;
Color _color;
TextLayoutOptions _layoutOptions;
- int32 _size;
+ float _size;
int32 _sceneRenderingKey = -1;
BoundingBox _localBox;
@@ -91,12 +91,12 @@ public:
/// Gets the font characters size.
///
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(32), Limit(1, 1000), EditorDisplay(\"Text\")")
- int32 GetFontSize() const;
+ float GetFontSize() const;
///
/// Sets the font characters size.
///
- API_PROPERTY() void SetFontSize(int32 value);
+ API_PROPERTY() void SetFontSize(float value);
///
/// The draw passes to use for rendering this object.
diff --git a/Source/Engine/Utilities/StringConverter.h b/Source/Engine/Utilities/StringConverter.h
index 340278c00..e494a8b94 100644
--- a/Source/Engine/Utilities/StringConverter.h
+++ b/Source/Engine/Utilities/StringConverter.h
@@ -49,7 +49,7 @@ public:
{
}
- StringAsANSI(const Char* text, const int32 length)
+ StringAsANSI(const Char* text, int32 length)
{
if (length + 1 < InlinedSize)
{
@@ -83,7 +83,7 @@ public:
{
}
- StringAsUTF8(const Char* text, const int32 length)
+ StringAsUTF8(const Char* text, int32 length)
{
int32 lengthUtf8;
if (length + 1 < InlinedSize)
@@ -112,17 +112,17 @@ public:
{
}
- StringAsUTF16(const char* text, const int32 length)
+ StringAsUTF16(const char* text, int32 length)
{
if (length + 1 < InlinedSize)
{
- StringUtils::ConvertANSI2UTF16(text, this->_inlined, length);
+ StringUtils::ConvertANSI2UTF16(text, this->_inlined, length, length);
this->_inlined[length] = 0;
}
else
{
this->_dynamic = (CharType*)Allocator::Allocate((length + 1) * sizeof(CharType));
- StringUtils::ConvertANSI2UTF16(text, this->_dynamic, length);
+ StringUtils::ConvertANSI2UTF16(text, this->_dynamic, length, length);
this->_dynamic[length] = 0;
}
}
diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs
index 5ac43ecd9..cb385efcf 100644
--- a/Source/Engine/Utilities/Utils.cs
+++ b/Source/Engine/Utilities/Utils.cs
@@ -255,7 +255,7 @@ namespace FlaxEngine
#else
private class ExtractArrayFromListContext
{
- public static FieldInfo? itemsField;
+ public static FieldInfo itemsField;
}
internal static T[] ExtractArrayFromList(List list)
{
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 0cb8982a2..d69b8dcbf 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -1204,7 +1204,7 @@ namespace Flax.Build.Bindings
if (apiType != null)
{
if (parameterInfo.IsOut)
- contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, new TypeInfo(parameterInfo.Type) { IsRef = false }.GetFullNameNative(buildData, caller)).AppendLine();
+ contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, parameterInfo.Type.GetFullNameNative(buildData, caller, false)).AppendLine();
else
contents.Append(indent).AppendFormat("auto {0}Temp = {1};", parameterInfo.Name, param).AppendLine();
if (parameterInfo.Type.IsPtr && !parameterInfo.Type.IsRef)
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
index b09b0c137..c05ab4e73 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
@@ -1417,6 +1417,9 @@ namespace Flax.Build.Bindings
context.Tokenizer.SkipUntil(TokenType.Comma, out desc.Lang);
desc.Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\"");
desc.Code = desc.Code.Substring(1, desc.Code.Length - 2);
+ desc.Code = desc.Code.Replace("\\\n", "\n");
+ desc.Code = desc.Code.Replace("\\\r\n", "\n");
+ desc.Code = desc.Code.Replace("\t", " ");
context.Tokenizer.ExpectToken(TokenType.RightParent);
return desc;
}
diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
index 7b2326527..df47c85e4 100644
--- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
+++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
@@ -144,7 +144,7 @@ namespace Flax.Build.Bindings
GenericArgs = BindingsGenerator.Read(reader, GenericArgs);
}
- public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller)
+ public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller, bool canRef = true, bool canConst = true)
{
var type = BindingsGenerator.FindApiTypeInfo(buildData, this, caller);
if (type == null)
@@ -155,7 +155,7 @@ namespace Flax.Build.Bindings
return type.FullNameNative;
var sb = new StringBuilder(64);
- if (IsConst)
+ if (IsConst && canConst)
sb.Append("const ");
sb.Append(type.FullNameNative);
if (GenericArgs != null)
@@ -171,7 +171,7 @@ namespace Flax.Build.Bindings
}
if (IsPtr)
sb.Append('*');
- if (IsRef)
+ if (IsRef && canRef)
sb.Append('&');
return sb.ToString();
}
diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs
index 2860148de..4fbabe986 100644
--- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs
+++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs
@@ -330,7 +330,7 @@ namespace Flax.Build
var referenceBuildOptions = GetBuildOptions(referenceTarget, configurationData.TargetBuildOptions.Platform, configurationData.TargetBuildOptions.Toolchain, configurationData.Architecture, configurationData.Configuration, reference.Project.ProjectFolderPath);
referenceBuildOptions.Flags |= BuildFlags.GenerateProject;
var referenceModules = CollectModules(rules, referenceBuildOptions.Platform, referenceTarget, referenceBuildOptions, referenceBuildOptions.Toolchain, referenceBuildOptions.Architecture, referenceBuildOptions.Configuration);
- var referenceBinaryModules = GetBinaryModules(projectInfo, referenceTarget, referenceModules);
+ var referenceBinaryModules = referenceModules.Keys.GroupBy(x => x.BinaryModuleName).ToArray();
foreach (var binaryModule in referenceBinaryModules)
{
project.Defines.Add(binaryModule.Key.ToUpperInvariant() + "_API=");
diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs
index 0765a94ab..aa0f7c879 100644
--- a/Source/Tools/Flax.Build/Build/Builder.cs
+++ b/Source/Tools/Flax.Build/Build/Builder.cs
@@ -143,6 +143,16 @@ namespace Flax.Build
{
Log.Info("Removing: " + targetBuildOptions.IntermediateFolder);
CleanDirectory(intermediateFolder);
+ intermediateFolder.Create();
+ }
+
+ // Delete all output files
+ var outputFolder = new DirectoryInfo(targetBuildOptions.OutputFolder);
+ if (outputFolder.Exists)
+ {
+ Log.Info("Removing: " + targetBuildOptions.OutputFolder);
+ CleanDirectory(outputFolder);
+ outputFolder.Create();
}
}
}
diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
index 921a1e43f..13433f92c 100644
--- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
+++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
@@ -4,14 +4,16 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using Flax.Build.Graph;
+using Flax.Build.NativeCpp;
using Flax.Deploy;
namespace Flax.Build
{
static partial class Builder
{
- public static event Action> BuildDotNetAssembly;
+ public static event Action> BuildDotNetAssembly;
private static void BuildTargetDotNet(RulesAssembly rules, TaskGraph graph, Target target, Platform platform, TargetConfiguration configuration)
{
@@ -150,7 +152,7 @@ namespace Flax.Build
}
}
- private static void BuildDotNet(TaskGraph graph, BuildData buildData, NativeCpp.BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null)
+ private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null)
{
// Setup build options
var buildPlatform = Platform.BuildTargetPlatform;
@@ -163,6 +165,8 @@ namespace Flax.Build
if (!dotnetSdk.IsValid)
throw new Exception("Cannot compile C# without .NET SDK");
string dotnetPath = "dotnet", referenceAnalyzers;
+ string[] runtimeVersionNameParts = dotnetSdk.RuntimeVersionName.Split('.');
+ string runtimeVersionShort = runtimeVersionNameParts[0] + '.' + runtimeVersionNameParts[1];
#else
string monoRoot, monoPath;
#endif
@@ -173,7 +177,7 @@ namespace Flax.Build
#if USE_NETCORE
dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe");
cscPath = Path.Combine(dotnetSdk.RootPath, @$"sdk\{dotnetSdk.VersionName}\Roslyn\bincore\csc.dll");
- referenceAssemblies = Path.Combine(dotnetSdk.RootPath, @$"shared\Microsoft.NETCore.App\{dotnetSdk.RuntimeVersionName}\");
+ referenceAssemblies = Path.Combine(dotnetSdk.RootPath, @$"packs\Microsoft.NETCore.App.Ref\{dotnetSdk.RuntimeVersionName}\ref\net{runtimeVersionShort}\");
referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, @$"packs\Microsoft.NETCore.App.Ref\{dotnetSdk.RuntimeVersionName}\analyzers\dotnet\cs\");
#else
monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Windows", "Mono");
@@ -189,7 +193,7 @@ namespace Flax.Build
{
#if USE_NETCORE
cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll");
- referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"shared/Microsoft.NETCore.App/{dotnetSdk.RuntimeVersionName}/");
+ referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/");
referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/");
#else
monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Linux", "Mono");
@@ -203,7 +207,7 @@ namespace Flax.Build
{
#if USE_NETCORE
cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll");
- referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"shared/Microsoft.NETCore.App/{dotnetSdk.RuntimeVersionName}/");
+ referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/");
referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/");
#else
monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Mac", "Mono");
@@ -242,7 +246,9 @@ namespace Flax.Build
args.Add("/filealign:512");
#if USE_NETCORE
args.Add("/langversion:11.0");
- args.Add("-nowarn:8632"); // Nullable
+ args.Add(string.Format("/nullable:{0}", buildOptions.ScriptingAPI.CSharpNullableReferences.ToString().ToLowerInvariant()));
+ if (buildOptions.ScriptingAPI.CSharpNullableReferences == CSharpNullableReferences.Disable)
+ args.Add("-nowarn:8632"); // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#else
args.Add("/langversion:7.3");
#endif
@@ -253,6 +259,7 @@ namespace Flax.Build
args.Add(buildData.Configuration == TargetConfiguration.Release ? "/optimize+" : "/optimize-");
#else
args.Add(buildData.Configuration == TargetConfiguration.Debug ? "/optimize-" : "/optimize+");
+ args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies));
#endif
args.Add(string.Format("/out:\"{0}\"", outputFile));
args.Add(string.Format("/doc:\"{0}\"", outputDocFile));
@@ -260,7 +267,6 @@ namespace Flax.Build
args.Add("/define:" + string.Join(";", buildOptions.ScriptingAPI.Defines));
if (buildData.Configuration == TargetConfiguration.Debug)
args.Add("/define:DEBUG");
- args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies));
foreach (var reference in buildOptions.ScriptingAPI.SystemReferences)
args.Add(string.Format("/reference:\"{0}{1}.dll\"", referenceAssemblies, reference));
foreach (var reference in fileReferences)
@@ -272,6 +278,13 @@ namespace Flax.Build
foreach (var sourceFile in sourceFiles)
args.Add("\"" + sourceFile + "\"");
+#if USE_NETCORE
+ // Inject some assembly metadata (similar to msbuild in Visual Studio)
+ var assemblyAttributesPath = Path.Combine(buildOptions.IntermediateFolder, name + ".AssemblyAttributes.cs");
+ File.WriteAllText(assemblyAttributesPath, $"[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(\".NETCoreApp,Version=v{runtimeVersionShort}\", FrameworkDisplayName = \".NET {runtimeVersionShort}\")]\n", Encoding.UTF8);
+ args.Add("\"" + assemblyAttributesPath + "\"");
+#endif
+
// Generate response file with source files paths and compilation arguments
string responseFile = Path.Combine(buildOptions.IntermediateFolder, name + ".response");
Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args));
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
index 8a5a55873..ef365d3b7 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
@@ -23,6 +23,32 @@ namespace Flax.Build.NativeCpp
GenerateProject = 1,
}
+ ///
+ /// The nullable context type used with reference types (C#).
+ ///
+ public enum CSharpNullableReferences
+ {
+ ///
+ /// The code is nullable oblivious, nullable warnings and language analysis features are disabled.
+ ///
+ Disable,
+
+ ///
+ /// The compiler enables all null reference analysis and all language features.
+ ///
+ Enable,
+
+ ///
+ /// The compiler performs all null analysis and emits warnings when code might dereference null.
+ ///
+ Warnings,
+
+ ///
+ /// The compiler doesn't perform null analysis or emit warnings when code might dereference null.
+ ///
+ Annotations,
+ }
+
///
/// The native C++ module build settings container.
///
@@ -188,6 +214,15 @@ namespace Flax.Build.NativeCpp
///
public bool IgnoreMissingDocumentationWarnings;
+ ///
+ /// The nullable context used in C# project.
+ ///
+ public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable;
+
+ public ScriptingAPIOptions()
+ {
+ }
+
///
/// Adds the other options into this.
///
@@ -209,6 +244,8 @@ namespace Flax.Build.NativeCpp
Defines = new HashSet(),
SystemReferences = new HashSet
{
+ "mscorlib",
+ "netstandard",
"Microsoft.CSharp",
"System",
@@ -223,25 +260,32 @@ namespace Flax.Build.NativeCpp
//"System.ComponentModel.TypeConverter",
"System.Console",
"System.Core",
+ "System.Diagnostics.StackTrace",
"System.Globalization",
"System.IO",
"System.IO.Compression",
"System.IO.FileSystem.Watcher",
"System.Linq",
"System.Linq.Expressions",
+ "System.Memory",
+ "System.Net",
"System.Net.Http",
"System.Net.Primitives",
"System.ObjectModel",
- "System.Private.CoreLib",
- "System.Private.Uri",
- //"System.Private.Xml",
+ "System.ValueTuple",
- "System.Reflection",
"System.Runtime",
+ "System.Runtime.Extensions",
+ "System.Runtime.Handles",
+ "System.Runtime.Intrinsics",
+ "System.Runtime.Numerics",
+ "System.Runtime.Loader",
"System.Runtime.CompilerServices.Unsafe",
"System.Runtime.InteropServices",
"System.Runtime.InteropServices.RuntimeInformation",
- "System.Runtime.Serialization.Formatters", // BinaryFormatter
+ "System.Runtime.Serialization",
+ "System.Runtime.Serialization.Formatters",
+
"System.Security.Cryptography",
"System.Security.Cryptography.Algorithms",
"System.Security.Cryptography.Primitives",
@@ -249,8 +293,11 @@ namespace Flax.Build.NativeCpp
"System.Threading.Tasks.Parallel",
//"System.Xml",
+ "System.Threading",
+ "System.Threading.Thread",
+
+ "System.Reflection",
//"System.Reflection.Metadata",
- "netstandard",
},
SystemAnalyzers = new HashSet
{
diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs
index 21464ab55..d646a81cd 100644
--- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs
+++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs
@@ -241,7 +241,7 @@ namespace Flax.Build.Plugins
// Deserialize arguments
argNames += arg.Name;
- contents.AppendLine($" {arg.Type.Type} {arg.Name};");
+ contents.AppendLine($" {arg.Type.GetFullNameNative(buildData, typeInfo, false, false)} {arg.Name};");
contents.AppendLine($" stream->Read({arg.Name});");
}
@@ -254,7 +254,7 @@ namespace Flax.Build.Plugins
// Generated method thunk to invoke RPC to network
{
- contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)");
+ contents.Append(" static bool ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)");
contents.AppendLine(" {");
contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();");
contents.AppendLine(" Span targetIds;");
@@ -270,11 +270,11 @@ namespace Flax.Build.Plugins
}
// Serialize arguments
- contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);");
+ contents.AppendLine($" stream->Write(*(const {arg.Type.GetFullNameNative(buildData, typeInfo, false, false)}*)args[{i}]);");
}
// Invoke RPC
- contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);");
+ contents.AppendLine($" return NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);");
contents.AppendLine(" }");
}
contents.AppendLine();
@@ -780,7 +780,7 @@ namespace Flax.Build.Plugins
private static void GenerateCallINetworkSerializable(ref DotnetContext context, TypeDefinition type, string name, MethodDefinition method)
{
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, context.VoidType);
- m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, context.NetworkStreamType));
+ m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, type.Module.ImportReference(context.NetworkStreamType)));
ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
@@ -1304,7 +1304,10 @@ namespace Flax.Build.Plugins
il.Emit(jmp4);
valueContext.Load(ref il);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
- il.Emit(OpCodes.Ldelem_Ref);
+ if (elementType.IsValueType)
+ il.Emit(OpCodes.Ldelem_Any, elementType);
+ else
+ il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Stloc, varStart + 2); //
// Serialize item value
@@ -1753,10 +1756,10 @@ namespace Flax.Build.Plugins
if (jumpBodyEnd == null)
throw new Exception("Missing IL Return op code in method " + method.Name);
il.Emit(OpCodes.Ldloc, varsStart + 0);
- il.Emit(OpCodes.Brfalse_S, jumpIf2Start);
+ il.Emit(OpCodes.Brfalse, jumpIf2Start);
il.Emit(OpCodes.Ldloc, varsStart + 2);
il.Emit(OpCodes.Ldc_I4_2);
- il.Emit(OpCodes.Beq_S, jumpIfBodyStart);
+ il.Emit(OpCodes.Beq, jumpIfBodyStart);
// ||
il.Emit(jumpIf2Start);
il.Emit(OpCodes.Ldloc, varsStart + 1);
@@ -1812,35 +1815,12 @@ namespace Flax.Build.Plugins
else
il.Emit(OpCodes.Ldnull);
var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 5);
+ if (endInvokeRPC.ReturnType.FullName != boolType.FullName)
+ throw new Exception("Invalid EndInvokeRPC return type. Remove any 'Binaries' folders to force project recompile.");
il.Emit(OpCodes.Call, module.ImportReference(endInvokeRPC));
- // if (server && networkMode == NetworkManagerMode.Client) return;
- if (methodRPC.IsServer)
- {
- il.Emit(OpCodes.Nop);
- il.Emit(OpCodes.Ldloc, varsStart + 2);
- il.Emit(OpCodes.Ldc_I4_2);
- var tmp = ilp.Create(OpCodes.Nop);
- il.Emit(OpCodes.Beq_S, tmp);
- il.Emit(OpCodes.Br, jumpBodyStart);
- il.Emit(tmp);
- //il.Emit(OpCodes.Ret);
- il.Emit(OpCodes.Br, jumpBodyEnd);
- }
-
- // if (client && networkMode == NetworkManagerMode.Server) return;
- if (methodRPC.IsClient)
- {
- il.Emit(OpCodes.Nop);
- il.Emit(OpCodes.Ldloc, varsStart + 2);
- il.Emit(OpCodes.Ldc_I4_1);
- var tmp = ilp.Create(OpCodes.Nop);
- il.Emit(OpCodes.Beq_S, tmp);
- il.Emit(OpCodes.Br, jumpBodyStart);
- il.Emit(tmp);
- //il.Emit(OpCodes.Ret);
- il.Emit(OpCodes.Br, jumpBodyEnd);
- }
+ // if (EndInvokeRPC) return
+ il.Emit(OpCodes.Brtrue, jumpBodyEnd);
// Continue to original method body
il.Emit(jumpBodyStart);
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
index ed8222fb7..efce04776 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
@@ -88,7 +88,7 @@ namespace Flax.Build.Projects.VisualStudio
csProjectFileContent.AppendLine(" net7.0");
csProjectFileContent.AppendLine(" disable");
- csProjectFileContent.AppendLine(" annotations");
+ csProjectFileContent.AppendLine(string.Format(" {0}", baseConfiguration.TargetBuildOptions.ScriptingAPI.CSharpNullableReferences.ToString().ToLowerInvariant()));
csProjectFileContent.AppendLine(" false");
csProjectFileContent.AppendLine(" false");
csProjectFileContent.AppendLine(" false");
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs
index fb5c90872..1955a57ba 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs
@@ -547,6 +547,7 @@ namespace Flax.Build.Projects.VisualStudio
if (project.Type == TargetType.DotNetCore)
{
var path = Path.Combine(Path.GetDirectoryName(project.Path), "Properties/launchSettings.json");
+ path = Utilities.NormalizePath(path);
if (profiles.ContainsKey(path))
profile.AppendLine(",");
profile.AppendLine($" \"{project.BaseName}\": {{");
@@ -568,6 +569,7 @@ namespace Flax.Build.Projects.VisualStudio
var folder = Path.GetDirectoryName(e.Key);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
+ profile.Clear();
profile.AppendLine("{");
profile.AppendLine(" \"profiles\": {");
profile.AppendLine(e.Value);
diff --git a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs
index 0301e33d2..188327931 100644
--- a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs
+++ b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs
@@ -249,7 +249,7 @@ namespace Flax.Build
public static void GetType(this ModuleDefinition module, string fullName, out TypeReference type)
{
- if (!module.TryGetTypeReference(fullName, out type))
+ //if (!module.TryGetTypeReference(fullName, out type)) // TODO: this seams to return 'FlaxEngine.Networking.NetworkManagerMode' as a Class instead of Enum
{
// Do manual search
foreach (var a in module.AssemblyReferences)