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

# Conflicts:
#	Source/Engine/Level/Scene/SceneRendering.cpp
#	Source/Engine/Physics/Colliders/Collider.cpp
#	Source/Engine/Physics/Colliders/Collider.h
This commit is contained in:
Wojtek Figat
2025-09-02 22:23:45 +02:00
53 changed files with 899 additions and 287 deletions

View File

@@ -749,6 +749,15 @@ namespace FlaxEditor.CustomEditors
} }
} }
private Actor FindActor(CustomEditor editor)
{
if (editor.Values[0] is Actor actor)
return actor;
if (editor.ParentEditor != null)
return FindActor(editor.ParentEditor);
return null;
}
private Actor FindPrefabRoot(CustomEditor editor) private Actor FindPrefabRoot(CustomEditor editor)
{ {
if (editor.Values[0] is Actor actor) if (editor.Values[0] is Actor actor)
@@ -767,32 +776,35 @@ namespace FlaxEditor.CustomEditors
return FindPrefabRoot(actor.Parent); return FindPrefabRoot(actor.Parent);
} }
private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId, Actor endPoint)
{ {
var visited = new HashSet<Actor>();
return FindObjectWithPrefabObjectId(actor, ref prefabObjectId, endPoint, visited);
}
private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId, Actor endPoint, HashSet<Actor> visited)
{
if (visited.Contains(actor) || actor is Scene || actor == endPoint)
return null;
if (actor.PrefabObjectID == prefabObjectId) if (actor.PrefabObjectID == prefabObjectId)
return actor; return actor;
for (int i = 0; i < actor.ScriptsCount; i++) for (int i = 0; i < actor.ScriptsCount; i++)
{ {
if (actor.GetScript(i).PrefabObjectID == prefabObjectId) var script = actor.GetScript(i);
{ if (script != null && script.PrefabObjectID == prefabObjectId)
var a = actor.GetScript(i); return script;
if (a != null)
return a;
}
} }
for (int i = 0; i < actor.ChildrenCount; i++) for (int i = 0; i < actor.ChildrenCount; i++)
{ {
if (actor.GetChild(i).PrefabObjectID == prefabObjectId) var child = actor.GetChild(i);
{ if (child != null && child.PrefabObjectID == prefabObjectId)
var a = actor.GetChild(i); return child;
if (a != null)
return a;
}
} }
return null; // Go up in the hierarchy
return FindObjectWithPrefabObjectId(actor.Parent, ref prefabObjectId, endPoint, visited);
} }
/// <summary> /// <summary>
@@ -826,7 +838,7 @@ namespace FlaxEditor.CustomEditors
} }
var prefabObjectId = referenceSceneObject.PrefabObjectID; var prefabObjectId = referenceSceneObject.PrefabObjectID;
var prefabInstanceRef = FindObjectWithPrefabObjectId(prefabInstanceRoot, ref prefabObjectId); var prefabInstanceRef = FindObjectWithPrefabObjectId(FindActor(this), ref prefabObjectId, prefabInstanceRoot);
if (prefabInstanceRef == null) if (prefabInstanceRef == null)
{ {
Editor.LogWarning("Missing prefab instance reference in the prefab instance. Cannot revert to it."); Editor.LogWarning("Missing prefab instance reference in the prefab instance. Cannot revert to it.");

View File

@@ -63,6 +63,11 @@ namespace FlaxEditor.CustomEditors
/// Indication of if the properties window is locked on specific objects. /// Indication of if the properties window is locked on specific objects.
/// </summary> /// </summary>
public bool LockSelection { get; set; } public bool LockSelection { get; set; }
/// <summary>
/// Gets the scene editing context.
/// </summary>
public ISceneEditingContext SceneContext { get; }
} }
/// <summary> /// <summary>

View File

@@ -1,8 +1,5 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Actions; using FlaxEditor.Actions;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
@@ -10,12 +7,14 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace FlaxEditor.CustomEditors.Dedicated namespace FlaxEditor.CustomEditors.Dedicated
{ {
@@ -240,6 +239,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
node.TextColor = Color.OrangeRed; node.TextColor = Color.OrangeRed;
node.Text = Utilities.Utils.GetPropertyNameUI(removed.PrefabObject.GetType().Name); node.Text = Utilities.Utils.GetPropertyNameUI(removed.PrefabObject.GetType().Name);
} }
// Removed Actor
else if (editor is RemovedActorDummy removedActor)
{
node.TextColor = Color.OrangeRed;
node.Text = $"{removedActor.PrefabObject.Name} ({Utilities.Utils.GetPropertyNameUI(removedActor.PrefabObject.GetType().Name)})";
}
// Actor or Script // Actor or Script
else if (editor.Values[0] is SceneObject sceneObject) else if (editor.Values[0] is SceneObject sceneObject)
{ {
@@ -295,16 +300,40 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Not used // Not used
} }
} }
private class RemovedActorDummy : CustomEditor
{
/// <summary>
/// The removed prefab object (from the prefab default instance).
/// </summary>
public Actor PrefabObject;
/// <summary>
/// The prefab instance's parent.
/// </summary>
public Actor ParentActor;
/// <summary>
/// The order of the removed actor in the parent.
/// </summary>
public int OrderInParent;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
// Not used
}
}
private TreeNode ProcessDiff(CustomEditor editor, bool skipIfNotModified = true) private TreeNode ProcessDiff(CustomEditor editor, bool skipIfNotModified = true)
{ {
// Special case for new Script added to actor // Special case for new Script or child actor added to actor
if (editor.Values[0] is Script script && !script.HasPrefabLink) if ((editor.Values[0] is Script script && !script.HasPrefabLink) || (editor.Values[0] is Actor a && !a.HasPrefabLink))
return CreateDiffNode(editor); return CreateDiffNode(editor);
// Skip if no change detected // Skip if no change detected
var isRefEdited = editor.Values.IsReferenceValueModified; var isRefEdited = editor.Values.IsReferenceValueModified;
if (!isRefEdited && skipIfNotModified) if (!isRefEdited && skipIfNotModified && editor is not ScriptsEditor)
return null; return null;
TreeNode result = null; TreeNode result = null;
@@ -359,6 +388,44 @@ namespace FlaxEditor.CustomEditors.Dedicated
} }
} }
// Compare child actors for removed actors.
if (editor is ActorEditor && editor.Values.HasReferenceValue && editor.Values.ReferenceValue is Actor prefabObjectActor)
{
var thisActor = editor.Values[0] as Actor;
for (int i = 0; i < prefabObjectActor.ChildrenCount; i++)
{
var prefabActorChild = prefabObjectActor.Children[i];
if (thisActor == null)
continue;
bool isRemoved = true;
for (int j = 0; j < thisActor.ChildrenCount; j++)
{
var actorChild = thisActor.Children[j];
if (actorChild.PrefabObjectID == prefabActorChild.PrefabObjectID)
{
isRemoved = false;
break;
}
}
if (isRemoved)
{
var dummy = new RemovedActorDummy
{
PrefabObject = prefabActorChild,
ParentActor = thisActor,
OrderInParent = prefabActorChild.OrderInParent,
};
var child = CreateDiffNode(dummy);
if (result == null)
result = CreateDiffNode(editor);
result.AddChild(child);
}
}
}
if (editor is ScriptsEditor && result != null && result.ChildrenCount == 0)
return null;
return result; return result;
} }
@@ -438,6 +505,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
Presenter.BuildLayoutOnUpdate(); Presenter.BuildLayoutOnUpdate();
} }
private static void GetAllPrefabObjects(List<object> objects, Actor actor)
{
objects.Add(actor);
objects.AddRange(actor.Scripts);
var children = actor.Children;
foreach (var child in children)
GetAllPrefabObjects(objects, child);
}
private void OnDiffRevert(CustomEditor editor) private void OnDiffRevert(CustomEditor editor)
{ {
// Special case for removed Script from actor // Special case for removed Script from actor
@@ -459,6 +535,22 @@ namespace FlaxEditor.CustomEditors.Dedicated
return; return;
} }
// Special case for reverting removed Actors
if (editor is RemovedActorDummy removedActor)
{
Editor.Log("Reverting removed actor changes to prefab (adding it)");
var parentActor = removedActor.ParentActor;
var restored = parentActor.AddChild(removedActor.PrefabObject.GetType());
var prefabId = parentActor.PrefabID;
var prefabObjectId = removedActor.PrefabObject.PrefabObjectID;
string data = JsonSerializer.Serialize(removedActor.PrefabObject);
JsonSerializer.Deserialize(restored, data);
Presenter.Owner.SceneContext.Spawn(restored, parentActor, removedActor.OrderInParent);
Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(restored), ref prefabId, ref prefabObjectId);
return;
}
// Special case for new Script added to actor // Special case for new Script added to actor
if (editor.Values[0] is Script script && !script.HasPrefabLink) if (editor.Values[0] is Script script && !script.HasPrefabLink)
{ {
@@ -470,8 +562,37 @@ namespace FlaxEditor.CustomEditors.Dedicated
return; return;
} }
// Special case for new Actor added to actor
if (editor.Values[0] is Actor a && !a.HasPrefabLink)
{
Editor.Log("Reverting added actor changes to prefab (removing it)");
editor.RevertToReferenceValue(); // TODO: Keep previous selection.
var context = Presenter.Owner.SceneContext;
context.Select(SceneGraph.SceneGraphFactory.FindNode(a.ID));
context.DeleteSelection();
return;
}
if (Presenter.Undo != null && Presenter.Undo.Enabled)
{
var thisActor = (Actor)Values[0];
var rootActor = thisActor.IsPrefabRoot ? thisActor : thisActor.GetPrefabRoot();
var prefabObjects = new List<object>();
GetAllPrefabObjects(prefabObjects, rootActor);
using (new UndoMultiBlock(Presenter.Undo, prefabObjects, "Revert to Prefab"))
{
editor.RevertToReferenceValue();
editor.RefreshInternal();
}
}
else
{
editor.RevertToReferenceValue();
editor.RefreshInternal();
}
} }
} }
} }

View File

@@ -70,7 +70,9 @@ namespace FlaxEditor.CustomEditors.Editors
menu.ItemsContainer.RemoveChildren(); menu.ItemsContainer.RemoveChildren();
menu.AddButton("Copy", linkedEditor.Copy); menu.AddButton("Copy", linkedEditor.Copy);
var b = menu.AddButton("Paste", linkedEditor.Paste); var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
b = menu.AddButton("Paste", linkedEditor.Paste);
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
menu.AddSeparator(); menu.AddSeparator();
@@ -404,8 +406,10 @@ namespace FlaxEditor.CustomEditors.Editors
var menu = new ContextMenu(); var menu = new ContextMenu();
menu.AddButton("Copy", linkedEditor.Copy); menu.AddButton("Copy", linkedEditor.Copy);
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
var paste = menu.AddButton("Paste", linkedEditor.Paste); var paste = menu.AddButton("Paste", linkedEditor.Paste);
paste.Enabled = linkedEditor.CanPaste; paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
if (_canReorder) if (_canReorder)
{ {
@@ -741,6 +745,34 @@ namespace FlaxEditor.CustomEditors.Editors
cloned[srcIndex] = tmp; cloned[srcIndex] = tmp;
SetValue(cloned); SetValue(cloned);
} }
/// <summary>
/// Duplicates the list item.
/// </summary>
/// <param name="index">The index to duplicate.</param>
public void Duplicate(int index)
{
if (IsSetBlocked)
return;
var count = Count;
var newValues = Allocate(count + 1);
var oldValues = (IList)Values[0];
for (int i = 0; i <= index; i++)
{
newValues[i] = oldValues[i];
}
newValues[index + 1] = Utilities.Utils.CloneValue(oldValues[index]);
for (int i = index + 1; i < count; i++)
{
newValues[i + 1] = oldValues[i];
}
SetValue(newValues);
}
/// <summary> /// <summary>
/// Shifts the specified item at the given index and moves it through the list to the other item. It supports undo. /// Shifts the specified item at the given index and moves it through the list to the other item. It supports undo.

View File

@@ -254,18 +254,29 @@ namespace FlaxEditor.Modules
PrefabApplying?.Invoke(prefab, instance); PrefabApplying?.Invoke(prefab, instance);
// When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff) // When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff)
Actor prefabRoot = null;
var originalTransform = instance.LocalTransform; var originalTransform = instance.LocalTransform;
var originalName = instance.Name; var originalName = instance.Name;
if (instance.IsPrefabRoot && instance.HasScene) if (instance.HasScene)
{ {
instance.LocalTransform = prefab.GetDefaultInstance().Transform; prefabRoot = instance.GetPrefabRoot();
instance.Name = prefab.GetDefaultInstance().Name; if (prefabRoot != null && prefabRoot.IsPrefabRoot && instance.HasScene)
{
var defaultInstance = prefab.GetDefaultInstance();
originalTransform = prefabRoot.LocalTransform;
originalName = prefabRoot.Name;
prefabRoot.LocalTransform = defaultInstance.Transform;
prefabRoot.Name = defaultInstance.Name;
}
} }
// Call backend // Call backend
var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)); var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance));
instance.LocalTransform = originalTransform; if (prefabRoot != null)
instance.Name = originalName; {
prefabRoot.LocalTransform = originalTransform;
prefabRoot.Name = originalName;
}
if (failed) if (failed)
throw new Exception("Failed to apply the prefab. See log to learn more."); throw new Exception("Failed to apply the prefab. See log to learn more.");

View File

@@ -569,6 +569,10 @@ namespace FlaxEditor.Modules
return; return;
} }
// Skip if already added
if (SceneGraphFactory.Nodes.ContainsKey(actor.ID))
return;
var node = SceneGraphFactory.BuildActorNode(actor); var node = SceneGraphFactory.BuildActorNode(actor);
if (node != null) if (node != null)
{ {

View File

@@ -141,6 +141,8 @@ namespace FlaxEditor.SceneGraph.Actors
b.TooltipText = "Add a box collider to every selected model that will auto resize based on the model bounds."; b.TooltipText = "Add a box collider to every selected model that will auto resize based on the model bounds.";
b = menu.ContextMenu.AddButton("Sphere", () => OnAddCollider(window, CreateSphere)); b = menu.ContextMenu.AddButton("Sphere", () => OnAddCollider(window, CreateSphere));
b.TooltipText = "Add a sphere collider to every selected model that will auto resize based on the model bounds."; b.TooltipText = "Add a sphere collider to every selected model that will auto resize based on the model bounds.";
b = menu.ContextMenu.AddButton("Capsule", () => OnAddCollider(window, CreateCapsule));
b.TooltipText = "Add a capsule collider to every selected model that will auto resize based on the model bounds.";
b = menu.ContextMenu.AddButton("Convex", () => OnAddCollider(window, CreateConvex)); b = menu.ContextMenu.AddButton("Convex", () => OnAddCollider(window, CreateConvex));
b.TooltipText = "Generate and add a convex collider for every selected model."; b.TooltipText = "Generate and add a convex collider for every selected model.";
b = menu.ContextMenu.AddButton("Triangle Mesh", () => OnAddCollider(window, CreateTriangle)); b = menu.ContextMenu.AddButton("Triangle Mesh", () => OnAddCollider(window, CreateTriangle));
@@ -267,6 +269,20 @@ namespace FlaxEditor.SceneGraph.Actors
spawner(collider); spawner(collider);
} }
private void CreateCapsule(StaticModel actor, Spawner spawner, bool singleNode)
{
var collider = new CapsuleCollider
{
Transform = actor.Transform,
Position = actor.Box.Center,
// Size the capsule to best fit the actor
Radius = (float)actor.Sphere.Radius / Mathf.Max((float)actor.Scale.MaxValue, 0.0001f) * 0.707f,
Height = 100f,
};
spawner(collider);
}
private void CreateConvex(StaticModel actor, Spawner spawner, bool singleNode) private void CreateConvex(StaticModel actor, Spawner spawner, bool singleNode)
{ {
CreateMeshCollider(actor, spawner, singleNode, CollisionDataType.ConvexMesh); CreateMeshCollider(actor, spawner, singleNode, CollisionDataType.ConvexMesh);

View File

@@ -1,8 +1,9 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport; using FlaxEditor.Viewport;
using FlaxEngine;
using System.Collections.Generic;
namespace FlaxEditor namespace FlaxEditor
{ {
@@ -39,9 +40,23 @@ namespace FlaxEditor
/// </summary> /// </summary>
void RenameSelection(); void RenameSelection();
/// <summary>
/// Deletes selected objects.
/// </summary>
void DeleteSelection();
/// <summary> /// <summary>
/// Focuses selected objects. /// Focuses selected objects.
/// </summary> /// </summary>
void FocusSelection(); void FocusSelection();
/// <summary>
/// Spawns the specified actor to the game (with undo).
/// </summary>
/// <param name="actor">The actor.</param>
/// <param name="parent">The parent actor. Set null as default.</param>
/// <param name="orderInParent">The order under the parent to put the spawned actor.</param>
/// <param name="autoSelect">True if automatically select the spawned actor, otherwise false.</param>
void Spawn(Actor actor, Actor parent = null, int orderInParent = -1, bool autoSelect = true);
} }
} }

View File

@@ -181,7 +181,7 @@ namespace FlaxEditor.SceneGraph
public List<SceneGraphNode> Selection => SceneContext.Selection; public List<SceneGraphNode> Selection => SceneContext.Selection;
/// <summary> /// <summary>
/// Gets the list of selected scene graph nodes in the editor context. /// Gets the scene editing context.
/// </summary> /// </summary>
public abstract ISceneEditingContext SceneContext { get; } public abstract ISceneEditingContext SceneContext { get; }
} }

View File

@@ -61,6 +61,7 @@ namespace FlaxEditor.Utilities
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public void SetMemberValue(object instance, object value) public void SetMemberValue(object instance, object value)
{ {
var originalInstance = instance;
var finalMember = MemberPath.GetLastMember(ref instance); var finalMember = MemberPath.GetLastMember(ref instance);
var type = finalMember.Type; var type = finalMember.Type;
@@ -92,6 +93,12 @@ namespace FlaxEditor.Utilities
} }
finalMember.SetValue(instance, value); finalMember.SetValue(instance, value);
if (instance != originalInstance && finalMember.Index != null)
{
// Set collection back to the parent object (in case of properties that always return a new object like 'Spline.SplineKeyframes')
finalMember.Member.SetValue(originalInstance, instance);
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -668,10 +668,7 @@ namespace FlaxEditor.Viewport
if ((view.Flags & ViewFlags.PhysicsDebug) != 0 || view.Mode == ViewMode.PhysicsColliders) if ((view.Flags & ViewFlags.PhysicsDebug) != 0 || view.Mode == ViewMode.PhysicsColliders)
{ {
foreach (var actor in _debugDrawActors) foreach (var actor in _debugDrawActors)
{ DebugDraw.DrawDebugPhysics(actor, renderContext.View);
if (actor is Collider c && c.IsActiveInHierarchy)
DebugDraw.DrawColliderDebugPhysics(c, renderContext.View);
}
} }
// Draw lights debug // Draw lights debug

View File

@@ -641,5 +641,8 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc /> /// <inheritdoc />
public bool LockSelection { get; set; } public bool LockSelection { get; set; }
/// <inheritdoc />
public ISceneEditingContext SceneContext => null;
} }
} }

View File

@@ -7,6 +7,7 @@ using FlaxEditor.Content.Create;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEngine; using FlaxEngine;
@@ -171,13 +172,49 @@ namespace FlaxEditor.Windows.Assets
} }
private readonly SplitPanel _split; private readonly SplitPanel _split;
private readonly ModelBasePreview _preview; private readonly CollisionDataPreview _preview;
private readonly CustomEditorPresenter _propertiesPresenter; private readonly CustomEditorPresenter _propertiesPresenter;
private readonly PropertiesProxy _properties; private readonly PropertiesProxy _properties;
private Model _collisionWiresModel; private Model _collisionWiresModel;
private StaticModel _collisionWiresShowActor; private StaticModel _collisionWiresShowActor;
private bool _updateWireMesh; private bool _updateWireMesh;
private class CollisionDataPreview : ModelBasePreview
{
public bool ShowCollisionData = false;
private int _verticesCount = 0;
private int _trianglesCount = 0;
public void SetVerticesAndTriangleCount(int verticesCount, int triangleCount)
{
_verticesCount = verticesCount;
_trianglesCount = triangleCount;
}
/// <inheritdoc />
public CollisionDataPreview(bool useWidgets)
: base(useWidgets)
{
ViewportCamera = new FPSCamera();
Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom & ~ViewFlags.EyeAdaptation;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
if (ShowCollisionData)
{
var text = string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}\nMemory Size: {2}", _trianglesCount, _verticesCount, Utilities.Utils.FormatBytesCount(Asset.MemoryUsage));
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public CollisionDataWindow(Editor editor, AssetItem item) public CollisionDataWindow(Editor editor, AssetItem item)
: base(editor, item) : base(editor, item)
@@ -185,6 +222,12 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip // Toolstrip
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision"); _toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision");
var infoButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64).LinkTooltip("Show Collision Data");
infoButton.Clicked += () =>
{
_preview.ShowCollisionData = !_preview.ShowCollisionData;
infoButton.Checked = _preview.ShowCollisionData;
};
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more");
// Split Panel // Split Panel
@@ -197,12 +240,10 @@ namespace FlaxEditor.Windows.Assets
}; };
// Model preview // Model preview
_preview = new ModelBasePreview(true) _preview = new CollisionDataPreview(true)
{ {
ViewportCamera = new FPSCamera(),
Parent = _split.Panel1 Parent = _split.Panel1
}; };
_preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom & ~ViewFlags.EyeAdaptation;
// Asset properties // Asset properties
_propertiesPresenter = new CustomEditorPresenter(null); _propertiesPresenter = new CustomEditorPresenter(null);
@@ -240,7 +281,7 @@ namespace FlaxEditor.Windows.Assets
_collisionWiresModel = FlaxEngine.Content.CreateVirtualAsset<Model>(); _collisionWiresModel = FlaxEngine.Content.CreateVirtualAsset<Model>();
_collisionWiresModel.SetupLODs(new[] { 1 }); _collisionWiresModel.SetupLODs(new[] { 1 });
} }
Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices, out var _, out var _); Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices, out var triangleCount, out var indicesCount);
if (triangles != null && indices != null) if (triangles != null && indices != null)
_collisionWiresModel.LODs[0].Meshes[0].UpdateMesh(triangles, indices); _collisionWiresModel.LODs[0].Meshes[0].UpdateMesh(triangles, indices);
else else
@@ -252,6 +293,7 @@ namespace FlaxEditor.Windows.Assets
} }
_collisionWiresShowActor.Model = _collisionWiresModel; _collisionWiresShowActor.Model = _collisionWiresModel;
_collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial)); _collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial));
_preview.SetVerticesAndTriangleCount(triangleCount, indicesCount / 3);
_preview.Asset = FlaxEngine.Content.LoadAsync<ModelBase>(_asset.Options.Model); _preview.Asset = FlaxEngine.Content.LoadAsync<ModelBase>(_asset.Options.Model);
} }

View File

@@ -430,6 +430,12 @@ namespace FlaxEditor.Windows.Assets
} }
} }
/// <inheritdoc />
public void DeleteSelection()
{
Delete();
}
/// <inheritdoc /> /// <inheritdoc />
public void FocusSelection() public void FocusSelection()
{ {
@@ -488,7 +494,8 @@ namespace FlaxEditor.Windows.Assets
/// <param name="actor">The actor.</param> /// <param name="actor">The actor.</param>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
/// <param name="orderInParent">The order of the actor under the parent.</param> /// <param name="orderInParent">The order of the actor under the parent.</param>
public void Spawn(Actor actor, Actor parent, int orderInParent = -1) /// <param name="autoSelect">True if automatically select the spawned actor, otherwise false.</param>
public void Spawn(Actor actor, Actor parent, int orderInParent = -1, bool autoSelect = true)
{ {
if (actor == null) if (actor == null)
throw new ArgumentNullException(nameof(actor)); throw new ArgumentNullException(nameof(actor));
@@ -514,8 +521,11 @@ namespace FlaxEditor.Windows.Assets
// Create undo action // Create undo action
var action = new CustomDeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true); var action = new CustomDeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true);
Undo.AddAction(action); Undo.AddAction(action);
Focus(); if (autoSelect)
Select(actorNode); {
Focus();
Select(actorNode);
}
} }
private void OnTreeRightClick(TreeNode node, Float2 location) private void OnTreeRightClick(TreeNode node, Float2 location)

View File

@@ -91,6 +91,9 @@ namespace FlaxEditor.Windows.Assets
} }
} }
/// <inheritdoc />
public ISceneEditingContext SceneContext => this;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto). /// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto).
/// </summary> /// </summary>

View File

@@ -58,6 +58,9 @@ namespace FlaxEditor.Windows
} }
} }
/// <inheritdoc />
public ISceneEditingContext SceneContext => Editor.Windows.EditWin;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PropertiesWindow"/> class. /// Initializes a new instance of the <see cref="PropertiesWindow"/> class.
/// </summary> /// </summary>

View File

@@ -26,12 +26,24 @@ namespace FlaxEditor.Windows
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
} }
/// <inheritdoc />
public void DeleteSelection()
{
Editor.SceneEditing.Delete();
}
/// <inheritdoc /> /// <inheritdoc />
public void FocusSelection() public void FocusSelection()
{ {
Editor.Windows.EditWin.Viewport.FocusSelection(); Editor.Windows.EditWin.Viewport.FocusSelection();
} }
/// <inheritdoc />
public void Spawn(Actor actor, Actor parent = null, int orderInParent = -1, bool autoSelect = true)
{
Editor.SceneEditing.Spawn(actor, parent, orderInParent, autoSelect);
}
/// <inheritdoc /> /// <inheritdoc />
public EditorViewport Viewport => Editor.Windows.EditWin.Viewport; public EditorViewport Viewport => Editor.Windows.EditWin.Viewport;

View File

@@ -417,7 +417,7 @@ public:
template<typename ItemType> template<typename ItemType>
bool Add(const ItemType& item) bool Add(const ItemType& item)
{ {
Bucket* bucket = Base::OnAdd(item, false); Bucket* bucket = Base::OnAdd(item, false, true);
if (bucket) if (bucket)
bucket->Occupy(item); bucket->Occupy(item);
return bucket != nullptr; return bucket != nullptr;
@@ -430,7 +430,7 @@ public:
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns> /// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
bool Add(T&& item) bool Add(T&& item)
{ {
Bucket* bucket = Base::OnAdd(item, false); Bucket* bucket = Base::OnAdd(item, false, true);
if (bucket) if (bucket)
bucket->Occupy(MoveTemp(item)); bucket->Occupy(MoveTemp(item));
return bucket != nullptr; return bucket != nullptr;

View File

@@ -365,7 +365,7 @@ protected:
} }
template<typename KeyComparableType> template<typename KeyComparableType>
BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true) BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true, bool nullIfNonUnique = false)
{ {
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity) // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount * HASH_SET_DEFAULT_SLACK_SCALE > _size) if (_deletedCount * HASH_SET_DEFAULT_SLACK_SCALE > _size)
@@ -386,6 +386,8 @@ protected:
Platform::CheckFailed("That key has been already added to the collection.", __FILE__, __LINE__); Platform::CheckFailed("That key has been already added to the collection.", __FILE__, __LINE__);
return nullptr; return nullptr;
} }
if (nullIfNonUnique)
return nullptr;
return &_allocation.Get()[pos.ObjectIndex]; return &_allocation.Get()[pos.ObjectIndex];
} }

View File

@@ -30,6 +30,7 @@
#include "Editor/Editor.h" #include "Editor/Editor.h"
#include "Engine/Level/Actors/Light.h" #include "Engine/Level/Actors/Light.h"
#include "Engine/Physics/Colliders/Collider.h" #include "Engine/Physics/Colliders/Collider.h"
#include "Engine/Physics/Actors/IPhysicsDebug.h"
#endif #endif
// Debug draw service configuration // Debug draw service configuration
@@ -1041,11 +1042,12 @@ void DebugDraw::DrawActorsTree(Actor* actor)
#if USE_EDITOR #if USE_EDITOR
void DebugDraw::DrawColliderDebugPhysics(Collider* collider, RenderView& view) void DebugDraw::DrawDebugPhysics(Actor* actor, RenderView& view)
{ {
if (!collider) if (!actor || !actor->IsActiveInHierarchy())
return; return;
collider->DrawPhysicsDebug(view); if (auto* physicsDebug = dynamic_cast<IPhysicsDebug*>(actor))
physicsDebug->DrawPhysicsDebug(view);
} }
void DebugDraw::DrawLightDebug(Light* light, RenderView& view) void DebugDraw::DrawLightDebug(Light* light, RenderView& view)

View File

@@ -107,11 +107,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
#if USE_EDITOR #if USE_EDITOR
/// <summary> /// <summary>
/// Draws the physics debug shapes for the given collider. Editor Only /// Draws the physics debug shapes for the given actor. Editor Only
/// </summary> /// </summary>
/// <param name="collider">The collider to draw.</param> /// <param name="actor">The actor to draw.</param>
/// <param name="view">The render view to draw in.</param> /// <param name="view">The render view to draw in.</param>
API_FUNCTION() static void DrawColliderDebugPhysics(Collider* collider, RenderView& view); API_FUNCTION() static void DrawDebugPhysics(Actor* actor, RenderView& view);
/// <summary> /// <summary>
/// Draws the light debug shapes for the given light. Editor Only /// Draws the light debug shapes for the given light. Editor Only

View File

@@ -304,6 +304,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
{ {
for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++) for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++)
{ {
PROFILE_CPU_NAMED("Instance");
auto& instance = prefabInstancesData[instanceIndex]; auto& instance = prefabInstancesData[instanceIndex];
ISerializeModifierCacheType modifier = Cache::ISerializeModifier.Get(); ISerializeModifierCacheType modifier = Cache::ISerializeModifier.Get();
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
@@ -377,6 +378,10 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
sceneObjects->Add(obj); sceneObjects->Add(obj);
} }
// Generate nested prefab instances to properly handle Ids Mapping within each nested prefab
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, instance.Data, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
// Apply modifications // Apply modifications
for (int32 i = existingObjectsCount - 1; i >= 0; i--) for (int32 i = existingObjectsCount - 1; i >= 0; i--)
{ {
@@ -388,6 +393,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
if (prefabObjectIdToDiffData.TryGet(obj->GetPrefabObjectID(), data)) if (prefabObjectIdToDiffData.TryGet(obj->GetPrefabObjectID(), data))
{ {
// Apply prefab changes // Apply prefab changes
context.SetupIdsMapping(obj, modifier.Value);
obj->Deserialize(*(ISerializable::DeserializeStream*)data, modifier.Value); obj->Deserialize(*(ISerializable::DeserializeStream*)data, modifier.Value);
} }
else else
@@ -424,7 +430,6 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
for (int32 i = 0; i < sceneObjects->Count(); i++) for (int32 i = 0; i < sceneObjects->Count(); i++)
{ {
SceneObject* obj = sceneObjects->At(i); SceneObject* obj = sceneObjects->At(i);
int32 dataIndex; int32 dataIndex;
if (instance.PrefabInstanceIdToDataIndex.TryGet(obj->GetSceneObjectId(), dataIndex)) if (instance.PrefabInstanceIdToDataIndex.TryGet(obj->GetSceneObjectId(), dataIndex))
{ {
@@ -440,6 +445,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
data.RemoveMember("ParentID"); data.RemoveMember("ParentID");
#endif #endif
context.SetupIdsMapping(obj, modifier.Value);
obj->Deserialize(data, modifier.Value); obj->Deserialize(data, modifier.Value);
// Preserve order in parent (values from prefab are used) // Preserve order in parent (values from prefab are used)
@@ -527,6 +533,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
{ {
if (prefabInstancesData.IsEmpty()) if (prefabInstancesData.IsEmpty())
return false; return false;
PROFILE_CPU();
// Fully serialize default instance scene objects (accumulate all prefab and nested prefabs changes into a single linear list of objects) // Fully serialize default instance scene objects (accumulate all prefab and nested prefabs changes into a single linear list of objects)
rapidjson_flax::Document defaultInstanceData; rapidjson_flax::Document defaultInstanceData;
@@ -926,7 +933,6 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
// TODO: what if user applied prefab with references to the other objects from scene? clear them or what? // TODO: what if user applied prefab with references to the other objects from scene? clear them or what?
JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId); JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId);
} }
dataBuffer.Clear();
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
// Destroy default instance and some cache data in Prefab // Destroy default instance and some cache data in Prefab
@@ -1002,6 +1008,32 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
obj->RegisterObject(); obj->RegisterObject();
} }
// Generate nested prefab instances to properly handle Ids Mapping within each nested prefab
rapidjson_flax::Document targetDataDocument;
if (NestedPrefabs.HasItems())
{
targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize());
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
if (context.Instances.HasItems())
{
// Only main prefab instance is allowed (in case nested prefab was added to this prefab)
for (auto i = context.ObjectToInstance.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value != 0)
context.ObjectToInstance.Remove(i);
}
context.Instances.Resize(1);
// Trash object mapping to prevent messing up prefab structure when applying hierarchy changes (only nested instances are used)
context.Instances[0].IdsMapping.Clear();
}
}
dataBuffer.Clear();
auto originalIdsMapping = modifier.Value->IdsMapping;
// Deserialize prefab objects and apply modifications // Deserialize prefab objects and apply modifications
for (int32 i = 0; i < ObjectsCount; i++) for (int32 i = 0; i < ObjectsCount; i++)
{ {
@@ -1037,6 +1069,9 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
} }
} }
// Use the initial Ids Mapping (SetupIdsMapping overrides it for instanced prefabs)
modifier.Value->IdsMapping = originalIdsMapping;
// Deserialize new prefab objects // Deserialize new prefab objects
newPrefabInstanceIdToDataIndexCounter = 0; newPrefabInstanceIdToDataIndexCounter = 0;
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
@@ -1283,6 +1318,10 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
ObjectsDataCache.Add(objectId, &objData); ObjectsDataCache.Add(objectId, &objData);
ObjectsCount++; ObjectsCount++;
Guid parentID;
if (JsonTools::GetGuidIfValid(parentID, objData, "ParentID"))
ObjectsHierarchyCache[parentID].Add(objectId);
Guid prefabId = JsonTools::GetGuid(objData, "PrefabID"); Guid prefabId = JsonTools::GetGuid(objData, "PrefabID");
if (prefabId.IsValid() && !NestedPrefabs.Contains(prefabId)) if (prefabId.IsValid() && !NestedPrefabs.Contains(prefabId))
{ {

View File

@@ -172,7 +172,9 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options)
SceneObjectsFactory::HandleObjectDeserializationError(stream); SceneObjectsFactory::HandleObjectDeserializationError(stream);
} }
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value);
if (options.WithSync) bool withSync = options.WithSync || prefab->NestedPrefabs.HasItems(); // Nested prefabs needs prefab instances generation for correct IdsMapping if the same prefab exists multiple times
// TODO: let prefab check if has multiple nested prefabs at cook time?
if (withSync)
{ {
// Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it) // Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it)
// TODO: resave and force sync prefabs during game cooking so this step could be skipped in game // TODO: resave and force sync prefabs during game cooking so this step could be skipped in game
@@ -187,14 +189,14 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options)
if (obj) if (obj)
SceneObjectsFactory::Deserialize(context, obj, stream); SceneObjectsFactory::Deserialize(context, obj, stream);
} }
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (options.WithSync) if (withSync)
{ {
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game // TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
} }
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Pick prefab root object // Pick prefab root object
Actor* root = nullptr; Actor* root = nullptr;

View File

@@ -7,6 +7,7 @@
#include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderView.h"
#include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/RenderList.h"
#include "Engine/Threading/JobSystem.h" #include "Engine/Threading/JobSystem.h"
#include "Engine/Physics/Actors/IPhysicsDebug.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Profiler/ProfilerMemory.h"
#if !BUILD_RELEASE #if !BUILD_RELEASE
@@ -107,10 +108,10 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
if (EnumHasAnyFlags(view.Flags, ViewFlags::PhysicsDebug) || view.Mode == ViewMode::PhysicsColliders) if (EnumHasAnyFlags(view.Flags, ViewFlags::PhysicsDebug) || view.Mode == ViewMode::PhysicsColliders)
{ {
PROFILE_CPU_NAMED("PhysicsDebug"); PROFILE_CPU_NAMED("PhysicsDebug");
const PhysicsDebugCallback* physicsDebugData = PhysicsDebug.Get(); const auto* physicsDebugData = PhysicsDebug.Get();
for (int32 i = 0; i < PhysicsDebug.Count(); i++) for (int32 i = 0; i < PhysicsDebug.Count(); i++)
{ {
physicsDebugData[i](view); physicsDebugData[i]->DrawPhysicsDebug(view);
} }
} }

View File

@@ -11,6 +11,7 @@
class SceneRenderTask; class SceneRenderTask;
class SceneRendering; class SceneRendering;
class IPhysicsDebug;
struct PostProcessSettings; struct PostProcessSettings;
struct RenderContext; struct RenderContext;
struct RenderContextBatch; struct RenderContextBatch;
@@ -74,7 +75,6 @@ public:
class FLAXENGINE_API SceneRendering class FLAXENGINE_API SceneRendering
{ {
#if USE_EDITOR #if USE_EDITOR
typedef Function<void(RenderView&)> PhysicsDebugCallback;
typedef Function<void(RenderView&)> LightsDebugCallback; typedef Function<void(RenderView&)> LightsDebugCallback;
friend class ViewportIconsRendererService; friend class ViewportIconsRendererService;
#endif #endif
@@ -106,7 +106,7 @@ public:
private: private:
#if USE_EDITOR #if USE_EDITOR
Array<PhysicsDebugCallback> PhysicsDebug; Array<IPhysicsDebug*> PhysicsDebug;
Array<LightsDebugCallback> LightsDebug; Array<LightsDebugCallback> LightsDebug;
Array<Actor*> ViewportIcons; Array<Actor*> ViewportIcons;
#endif #endif
@@ -150,20 +150,14 @@ public:
} }
#if USE_EDITOR #if USE_EDITOR
template<class T, void(T::*Method)(RenderView&)> FORCE_INLINE void AddPhysicsDebug(IPhysicsDebug* obj)
FORCE_INLINE void AddPhysicsDebug(T* obj)
{ {
PhysicsDebugCallback f; PhysicsDebug.Add(obj);
f.Bind<T, Method>(obj);
PhysicsDebug.Add(f);
} }
template<class T, void(T::*Method)(RenderView&)> FORCE_INLINE void RemovePhysicsDebug(IPhysicsDebug* obj)
void RemovePhysicsDebug(T* obj)
{ {
PhysicsDebugCallback f; PhysicsDebug.Remove(obj);
f.Bind<T, Method>(obj);
PhysicsDebug.Remove(f);
} }
template<class T, void(T::*Method)(RenderView&)> template<class T, void(T::*Method)(RenderView&)>

View File

@@ -101,13 +101,25 @@ ISerializeModifier* SceneObjectsFactory::Context::GetModifier()
void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj, ISerializeModifier* modifier) const void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj, ISerializeModifier* modifier) const
{ {
int32 instanceIndex; int32 instanceIndex;
if (ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != modifier->CurrentInstance) const Guid id = obj->GetID();
if (ObjectToInstance.TryGet(id, instanceIndex))
{ {
// Apply the current prefab instance objects ids table to resolve references inside a prefab properly // Apply the current prefab instance objects ids table to resolve references inside a prefab properly
modifier->CurrentInstance = instanceIndex;
const auto& instance = Instances[instanceIndex]; const auto& instance = Instances[instanceIndex];
for (const auto& e : instance.IdsMapping) if (instanceIndex != modifier->CurrentInstance)
modifier->IdsMapping[e.Key] = e.Value; {
modifier->CurrentInstance = instanceIndex;
for (const auto& e : instance.IdsMapping)
modifier->IdsMapping[e.Key] = e.Value;
}
int32 nestedIndex;
if (instance.ObjectToNested.TryGet(id, nestedIndex))
{
// Each nested prefab has own object ids mapping that takes precedence
const auto& nested = instance.Nested[nestedIndex];
for (const auto& e : nested.IdsMapping)
modifier->IdsMapping[e.Key] = e.Value;
}
} }
} }
@@ -499,6 +511,34 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
prefab = Content::LoadAsync<Prefab>(prefabId); prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab && !prefab->WaitForLoaded()) if (prefab && !prefab->WaitForLoaded())
{ {
// If prefab instance contains multiple nested prefabs, then need to build separate Ids remapping for each of them to prevent collisions
int32 nestedIndex = -1;
if (prefabInstance.Nested.HasItems())
{
// Check only the last instance if the current object belongs to it
for (int32 j = prefabInstance.Nested.Count() - 1; j >= 0; j--)
{
const auto& e = prefabInstance.Nested[j];
if (e.Prefab == prefab && e.RootObjectId != prefabObjectId)
{
nestedIndex = j;
break;
}
}
}
if (nestedIndex == -1)
{
nestedIndex = prefabInstance.Nested.Count();
auto& e = prefabInstance.Nested.AddOne();
e.Prefab = prefab;
e.RootObjectId = prefabObjectId;
}
// Map this object into this instance to inherit all objects from it when looking up via IdsMapping
prefabInstance.ObjectToNested[id] = nestedIndex;
auto& nestedInstance = prefabInstance.Nested[nestedIndex];
nestedInstance.IdsMapping[prefabObjectId] = id;
// Map prefab object ID to the deserialized instance ID // Map prefab object ID to the deserialized instance ID
prefabInstance.IdsMapping[prefabObjectId] = id; prefabInstance.IdsMapping[prefabObjectId] = id;
goto NESTED_PREFAB_WALK; goto NESTED_PREFAB_WALK;
@@ -794,23 +834,20 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
if (spawned) if (spawned)
continue; continue;
// Map prefab object ID to this actor's prefab instance so the new objects gets added to it // Map prefab object ID to this actor's prefab instance so the new objects get added to it
context.SetupIdsMapping(actor, data.Modifier); context.SetupIdsMapping(actor, data.Modifier);
data.Modifier->IdsMapping[actorPrefabObjectId] = actorId; data.Modifier->IdsMapping[actorPrefabObjectId] = actorId;
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Create instance (including all children) // Create instance (including all children)
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId); SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId, actor->GetID());
} }
} }
void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId) void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId, const Guid& nestedInstanceId)
{ {
PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance"); PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance");
// Missing object found!
LOG(Info, "Actor {0} has missing child object (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", actor->ToString(), prefabObjectId, prefab->GetID(), prefab->GetPath());
// Get prefab object data from the prefab // Get prefab object data from the prefab
const ISerializable::DeserializeStream* prefabData; const ISerializable::DeserializeStream* prefabData;
if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData)) if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData))
@@ -830,6 +867,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS
LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId); LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId);
return; return;
} }
LOG(Info, "Actor {0} has missing child object '{4}' (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", actor->ToString(), prefabObjectId, prefab->GetID(), prefab->GetPath(), child->GetType().ToString());
// Register object // Register object
child->RegisterObject(); child->RegisterObject();
@@ -846,17 +884,51 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS
context.ObjectToInstance[id] = instanceIndex; context.ObjectToInstance[id] = instanceIndex;
auto& prefabInstance = context.Instances[instanceIndex]; auto& prefabInstance = context.Instances[instanceIndex];
prefabInstance.IdsMapping[prefabObjectId] = id; prefabInstance.IdsMapping[prefabObjectId] = id;
// Check if it's a nested prefab
const ISerializable::DeserializeStream* nestedPrefabData;
Guid nestedPrefabObjectId;
if (prefab->ObjectsDataCache.TryGet(prefabObjectId, nestedPrefabData) && JsonTools::GetGuidIfValid(nestedPrefabObjectId, *nestedPrefabData, "PrefabObjectID"))
{
// Try reusing parent nested instance (or make a new one)
int32 nestedIndex = -1;
if (!prefabInstance.ObjectToNested.TryGet(nestedInstanceId, nestedIndex))
{
if (auto* nestedPrefab = Content::LoadAsync<Prefab>(JsonTools::GetGuid(*prefabData, "PrefabID")))
{
if (prefabInstance.Nested.HasItems())
{
// Check only the last instance if the current object belongs to it
const auto& e = prefabInstance.Nested.Last();
if (e.Prefab == nestedPrefab && e.RootObjectId != nestedPrefabObjectId)
nestedIndex = prefabInstance.Nested.Count() - 1;
}
if (nestedIndex == -1)
{
nestedIndex = prefabInstance.Nested.Count();
auto& e = prefabInstance.Nested.AddOne();
e.Prefab = nestedPrefab;
e.RootObjectId = nestedPrefabObjectId;
}
}
}
if (nestedIndex != -1)
{
// Insert into nested instance
prefabInstance.ObjectToNested[id] = nestedIndex;
auto& nestedInstance = prefabInstance.Nested[nestedIndex];
nestedInstance.IdsMapping[nestedPrefabObjectId] = id;
}
}
} }
// Use loop to add even more objects to added objects (prefab can have one new object that has another child, we need to add that child) // Use loop to add even more objects to added objects (prefab can have one new object that has another child, we need to add that child)
// TODO: prefab could cache lookup object id -> children ids const auto* hierarchy = prefab->ObjectsHierarchyCache.TryGet(prefabObjectId);
for (auto q = prefab->ObjectsDataCache.Begin(); q.IsNotEnd(); ++q) if (hierarchy)
{ {
Guid qParentId; for (const Guid& e : *hierarchy)
if (JsonTools::GetGuidIfValid(qParentId, *q->Value, "ParentID") && qParentId == prefabObjectId)
{ {
const Guid qPrefabObjectId = JsonTools::GetGuid(*q->Value, "ID"); SynchronizeNewPrefabInstance(context, data, prefab, actor, e, id);
SynchronizeNewPrefabInstance(context, data, prefab, actor, qPrefabObjectId);
} }
} }
} }

View File

@@ -16,6 +16,13 @@
class FLAXENGINE_API SceneObjectsFactory class FLAXENGINE_API SceneObjectsFactory
{ {
public: public:
struct NestedPrefabInstance
{
Prefab* Prefab;
Guid RootObjectId;
Dictionary<Guid, Guid> IdsMapping;
};
struct PrefabInstance struct PrefabInstance
{ {
int32 StatIndex; int32 StatIndex;
@@ -24,6 +31,8 @@ public:
Prefab* Prefab; Prefab* Prefab;
bool FixRootParent = false; bool FixRootParent = false;
Dictionary<Guid, Guid> IdsMapping; Dictionary<Guid, Guid> IdsMapping;
Array<NestedPrefabInstance> Nested;
Dictionary<Guid, int32> ObjectToNested;
}; };
struct Context struct Context
@@ -136,5 +145,5 @@ public:
private: private:
static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream); static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream);
static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId); static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId, const Guid& nestedInstanceId);
}; };

View File

@@ -462,7 +462,7 @@ void Cloth::OnEnable()
{ {
GetSceneRendering()->AddActor(this, _sceneRenderingKey); GetSceneRendering()->AddActor(this, _sceneRenderingKey);
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<Cloth, &Cloth::DrawPhysicsDebug>(this); GetSceneRendering()->AddPhysicsDebug(this);
#endif #endif
#if WITH_CLOTH #if WITH_CLOTH
if (_cloth) if (_cloth)
@@ -481,7 +481,7 @@ void Cloth::OnDisable()
PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
#endif #endif
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<Cloth, &Cloth::DrawPhysicsDebug>(this); GetSceneRendering()->RemovePhysicsDebug(this);
#endif #endif
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
} }

View File

@@ -4,6 +4,7 @@
#include "Engine/Level/Actor.h" #include "Engine/Level/Actor.h"
#include "Engine/Level/Actors/ModelInstanceActor.h" #include "Engine/Level/Actors/ModelInstanceActor.h"
#include "IPhysicsDebug.h"
// Used internally to validate cloth data against invalid nan/inf values // Used internally to validate cloth data against invalid nan/inf values
#define USE_CLOTH_SANITY_CHECKS 0 #define USE_CLOTH_SANITY_CHECKS 0
@@ -12,6 +13,9 @@
/// Physical simulation actor for cloth objects made of vertices that are simulated as cloth particles with physical properties, forces, and constraints to affect cloth behavior. /// Physical simulation actor for cloth objects made of vertices that are simulated as cloth particles with physical properties, forces, and constraints to affect cloth behavior.
/// </summary> /// </summary>
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor
#if USE_EDITOR
, public IPhysicsDebug
#endif
{ {
DECLARE_SCENE_OBJECT(Cloth); DECLARE_SCENE_OBJECT(Cloth);
@@ -364,9 +368,7 @@ protected:
void OnPhysicsSceneChanged(PhysicsScene* previous) override; void OnPhysicsSceneChanged(PhysicsScene* previous) override;
private: private:
#if USE_EDITOR ImplementPhysicsDebug;
void DrawPhysicsDebug(RenderView& view);
#endif
bool CreateCloth(); bool CreateCloth();
void DestroyCloth(); void DestroyCloth();
void CalculateInvMasses(Array<float>& invMasses); void CalculateInvMasses(Array<float>& invMasses);

View File

@@ -0,0 +1,18 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/BaseTypes.h"
#if USE_EDITOR
class FLAXENGINE_API IPhysicsDebug
{
public:
virtual void DrawPhysicsDebug(struct RenderView& view)
{
}
};
#define ImplementPhysicsDebug void DrawPhysicsDebug(RenderView& view)
#else
#define ImplementPhysicsDebug
#endif

View File

@@ -356,19 +356,19 @@ void WheeledVehicle::Setup()
void WheeledVehicle::DrawPhysicsDebug(RenderView& view) void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
{ {
// Wheels shapes // Wheels shapes
for (const auto& data : _wheelsData) for (const auto& wheel : _wheels)
{ {
int32 wheelIndex = 0;
for (; wheelIndex < _wheels.Count(); wheelIndex++)
{
if (_wheels[wheelIndex].Collider == data.Collider)
break;
}
if (wheelIndex == _wheels.Count())
break;
const auto& wheel = _wheels[wheelIndex];
if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger())
{ {
WheelData data = { wheel.Collider, wheel.Collider->GetLocalOrientation() };
for (auto& e : _wheelsData)
{
if (e.Collider == data.Collider)
{
data = e;
break;
}
}
const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90); const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
@@ -387,25 +387,28 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
void WheeledVehicle::OnDebugDrawSelected() void WheeledVehicle::OnDebugDrawSelected()
{ {
// Wheels shapes // Wheels shapes
for (const auto& data : _wheelsData) for (const auto& wheel : _wheels)
{ {
int32 wheelIndex = 0;
for (; wheelIndex < _wheels.Count(); wheelIndex++)
{
if (_wheels[wheelIndex].Collider == data.Collider)
break;
}
if (wheelIndex == _wheels.Count())
break;
const auto& wheel = _wheels[wheelIndex];
if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger())
{ {
WheelData data = { wheel.Collider, wheel.Collider->GetLocalOrientation() };
for (auto& e : _wheelsData)
{
if (e.Collider == data.Collider)
{
data = e;
break;
}
}
const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90); const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
Transform actorPose = Transform::Identity, shapePose = Transform::Identity; Transform actorPose = GetTransform(), shapePose = wheel.Collider->GetLocalTransform();
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation); actorPose.Scale = Float3::One;
PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation); if (_actor)
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation);
if (wheel.Collider->GetPhysicsShape())
PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false);
@@ -561,14 +564,14 @@ void WheeledVehicle::BeginPlay(SceneBeginData* data)
#endif #endif
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<WheeledVehicle, &WheeledVehicle::DrawPhysicsDebug>(this); GetSceneRendering()->AddPhysicsDebug(this);
#endif #endif
} }
void WheeledVehicle::EndPlay() void WheeledVehicle::EndPlay()
{ {
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<WheeledVehicle, &WheeledVehicle::DrawPhysicsDebug>(this); GetSceneRendering()->RemovePhysicsDebug(this);
#endif #endif
#if WITH_VEHICLE #if WITH_VEHICLE

View File

@@ -5,12 +5,16 @@
#include "Engine/Physics/Actors/RigidBody.h" #include "Engine/Physics/Actors/RigidBody.h"
#include "Engine/Physics/Colliders/Collider.h" #include "Engine/Physics/Colliders/Collider.h"
#include "Engine/Scripting/ScriptingObjectReference.h" #include "Engine/Scripting/ScriptingObjectReference.h"
#include "IPhysicsDebug.h"
/// <summary> /// <summary>
/// Representation of the car vehicle that uses wheels. Built on top of the RigidBody with collider representing its chassis shape and wheels. /// Representation of the car vehicle that uses wheels. Built on top of the RigidBody with collider representing its chassis shape and wheels.
/// </summary> /// </summary>
/// <seealso cref="RigidBody" /> /// <seealso cref="RigidBody" />
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Wheeled Vehicle\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API WheeledVehicle : public RigidBody API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Wheeled Vehicle\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API WheeledVehicle : public RigidBody
#if USE_EDITOR
, public IPhysicsDebug
#endif
{ {
friend class PhysicsBackend; friend class PhysicsBackend;
friend struct ScenePhysX; friend struct ScenePhysX;
@@ -644,13 +648,9 @@ public:
/// </summary> /// </summary>
API_FUNCTION() void Setup(); API_FUNCTION() void Setup();
private:
#if USE_EDITOR
void DrawPhysicsDebug(RenderView& view);
#endif
public: public:
// [Vehicle] // [Vehicle]
ImplementPhysicsDebug;
#if USE_EDITOR #if USE_EDITOR
void OnDebugDrawSelected() override; void OnDebugDrawSelected() override;
#endif #endif

View File

@@ -58,9 +58,7 @@ public:
protected: protected:
// [Collider] // [Collider]
ImplementPhysicsDebug;
void UpdateBounds() override; void UpdateBounds() override;
void GetGeometry(CollisionShape& collision) override; void GetGeometry(CollisionShape& collision) override;
#if USE_EDITOR
void DrawPhysicsDebug(RenderView& view) override;
#endif
}; };

View File

@@ -62,9 +62,7 @@ public:
protected: protected:
// [Collider] // [Collider]
ImplementPhysicsDebug;
void UpdateBounds() override; void UpdateBounds() override;
void GetGeometry(CollisionShape& collision) override; void GetGeometry(CollisionShape& collision) override;
#if USE_EDITOR
void DrawPhysicsDebug(RenderView& view) override;
#endif
}; };

View File

@@ -267,13 +267,11 @@ public:
protected: protected:
// [PhysicsActor] // [PhysicsActor]
ImplementPhysicsDebug;
void UpdateGeometry() override; void UpdateGeometry() override;
void GetGeometry(CollisionShape& collision) override; void GetGeometry(CollisionShape& collision) override;
void BeginPlay(SceneBeginData* data) override; void BeginPlay(SceneBeginData* data) override;
void EndPlay() override; void EndPlay() override;
#if USE_EDITOR
void DrawPhysicsDebug(RenderView& view) override;
#endif
void OnActiveInTreeChanged() override; void OnActiveInTreeChanged() override;
void OnEnable() override; void OnEnable() override;
void OnDisable() override; void OnDisable() override;

View File

@@ -145,7 +145,7 @@ void Collider::OnEnable()
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger) if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger)
GetScene()->Navigation.Actors.Add(this); GetScene()->Navigation.Actors.Add(this);
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<Collider, &Collider::DrawPhysicsDebug>(this); GetSceneRendering()->AddPhysicsDebug(this);
#endif #endif
PhysicsColliderActor::OnEnable(); PhysicsColliderActor::OnEnable();
@@ -158,7 +158,7 @@ void Collider::OnDisable()
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger) if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger)
GetScene()->Navigation.Actors.Remove(this); GetScene()->Navigation.Actors.Remove(this);
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<Collider, &Collider::DrawPhysicsDebug>(this); GetSceneRendering()->RemovePhysicsDebug(this);
#endif #endif
} }
@@ -284,14 +284,6 @@ void Collider::RemoveStaticActor()
_staticActor = nullptr; _staticActor = nullptr;
} }
#if USE_EDITOR
void Collider::DrawPhysicsDebug(RenderView& view)
{
}
#endif
void Collider::BeginPlay(SceneBeginData* data) void Collider::BeginPlay(SceneBeginData* data)
{ {
// Check if has no shape created (it means no rigidbody requested it but also collider may be spawned at runtime) // Check if has no shape created (it means no rigidbody requested it but also collider may be spawned at runtime)

View File

@@ -6,6 +6,7 @@
#include "Engine/Content/JsonAsset.h" #include "Engine/Content/JsonAsset.h"
#include "Engine/Content/JsonAssetReference.h" #include "Engine/Content/JsonAssetReference.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h"
#include "Engine/Physics/Actors/IPhysicsDebug.h"
struct RayCastHit; struct RayCastHit;
class RigidBody; class RigidBody;
@@ -16,6 +17,9 @@ class RigidBody;
/// <seealso cref="Actor" /> /// <seealso cref="Actor" />
/// <seealso cref="PhysicsColliderActor" /> /// <seealso cref="PhysicsColliderActor" />
API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor, protected IAssetReference API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor, protected IAssetReference
#if USE_EDITOR
, public IPhysicsDebug
#endif
{ {
API_AUTO_SERIALIZATION(); API_AUTO_SERIALIZATION();
DECLARE_SCENE_OBJECT_ABSTRACT(Collider); DECLARE_SCENE_OBJECT_ABSTRACT(Collider);
@@ -162,10 +166,6 @@ public:
void ClosestPoint(const Vector3& point, Vector3& result) const final; void ClosestPoint(const Vector3& point, Vector3& result) const final;
bool ContainsPoint(const Vector3& point) const final; bool ContainsPoint(const Vector3& point) const final;
#if USE_EDITOR
virtual void DrawPhysicsDebug(RenderView& view);
#endif
protected: protected:
// [PhysicsColliderActor] // [PhysicsColliderActor]
void OnEnable() override; void OnEnable() override;

View File

@@ -37,9 +37,7 @@ public:
protected: protected:
// [Collider] // [Collider]
#if USE_EDITOR ImplementPhysicsDebug;
void DrawPhysicsDebug(RenderView& view) override;
#endif
void UpdateBounds() override; void UpdateBounds() override;
void GetGeometry(CollisionShape& collision) override; void GetGeometry(CollisionShape& collision) override;
void OnAssetChanged(Asset* asset, void* caller) override; void OnAssetChanged(Asset* asset, void* caller) override;

View File

@@ -42,9 +42,7 @@ public:
protected: protected:
// [Collider] // [Collider]
#if USE_EDITOR ImplementPhysicsDebug;
void DrawPhysicsDebug(RenderView& view) override;
#endif
void UpdateBounds() override; void UpdateBounds() override;
void GetGeometry(CollisionShape& collision) override; void GetGeometry(CollisionShape& collision) override;
}; };

View File

@@ -67,9 +67,7 @@ public:
protected: protected:
// [Collider] // [Collider]
#if USE_EDITOR ImplementPhysicsDebug;
void DrawPhysicsDebug(RenderView& view) override;
#endif
void UpdateBounds() override; void UpdateBounds() override;
void GetGeometry(CollisionShape& collision) override; void GetGeometry(CollisionShape& collision) override;
}; };

View File

@@ -298,7 +298,7 @@ void Joint::EndPlay()
void Joint::OnEnable() void Joint::OnEnable()
{ {
GetSceneRendering()->AddPhysicsDebug<Joint, &Joint::DrawPhysicsDebug>(this); GetSceneRendering()->AddPhysicsDebug(this);
// Base // Base
Actor::OnEnable(); Actor::OnEnable();
@@ -306,7 +306,7 @@ void Joint::OnEnable()
void Joint::OnDisable() void Joint::OnDisable()
{ {
GetSceneRendering()->RemovePhysicsDebug<Joint, &Joint::DrawPhysicsDebug>(this); GetSceneRendering()->RemovePhysicsDebug(this);
// Base // Base
Actor::OnDisable(); Actor::OnDisable();

View File

@@ -5,6 +5,7 @@
#include "Engine/Level/Actor.h" #include "Engine/Level/Actor.h"
#include "Engine/Physics/Types.h" #include "Engine/Physics/Types.h"
#include "Engine/Scripting/ScriptingObjectReference.h" #include "Engine/Scripting/ScriptingObjectReference.h"
#include "Engine/Physics/Actors/IPhysicsDebug.h"
class IPhysicsActor; class IPhysicsActor;
@@ -17,6 +18,9 @@ class IPhysicsActor;
/// </remarks> /// </remarks>
/// <seealso cref="Actor" /> /// <seealso cref="Actor" />
API_CLASS(Abstract) class FLAXENGINE_API Joint : public Actor API_CLASS(Abstract) class FLAXENGINE_API Joint : public Actor
#if USE_EDITOR
, public IPhysicsDebug
#endif
{ {
DECLARE_SCENE_OBJECT_ABSTRACT(Joint); DECLARE_SCENE_OBJECT_ABSTRACT(Joint);
protected: protected:
@@ -174,9 +178,7 @@ protected:
Vector3 GetTargetPosition() const; Vector3 GetTargetPosition() const;
Quaternion GetTargetOrientation() const; Quaternion GetTargetOrientation() const;
virtual void* CreateJoint(const struct PhysicsJointDesc& desc) = 0; virtual void* CreateJoint(const struct PhysicsJointDesc& desc) = 0;
#if USE_EDITOR ImplementPhysicsDebug;
virtual void DrawPhysicsDebug(RenderView& view);
#endif
private: private:
void Delete(); void Delete();

View File

@@ -1366,10 +1366,11 @@ RETRY_ATLAS_SETUP:
tile.LinkedRectTile = nullptr; tile.LinkedRectTile = nullptr;
auto& linkedTile = linkedAtlasLight->Tiles[tileIndex]; auto& linkedTile = linkedAtlasLight->Tiles[tileIndex];
// Check if both lights use the same projections // Link tile and use its projection
if (tile.WorldToShadow == linkedTile.WorldToShadow && linkedTile.RectTile) if (linkedTile.RectTile)
{ {
tile.LinkedRectTile = linkedTile.RectTile; tile.LinkedRectTile = linkedTile.RectTile;
tile.WorldToShadow = linkedTile.WorldToShadow;
} }
} }
} }

View File

@@ -790,14 +790,14 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Matrix
DESERIALIZE_HELPER(stream, "M44", v.M44, 0); DESERIALIZE_HELPER(stream, "M44", v.M44, 0);
} }
bool Serialization::ShouldSerialize(const SceneObject* v, const SceneObject* other) bool Serialization::ShouldSerializeRef(const SceneObject* v, const SceneObject* other)
{ {
bool result = v != other; bool result = v != other;
if (result && v && other && v->HasPrefabLink() && other->HasPrefabLink()) if (result && v && other && v->HasPrefabLink() && other->HasPrefabLink())
{ {
// Special case when saving reference to prefab object and the objects are different but the point to the same prefab object // Special case when saving reference to prefab object and the objects are different but the point to the same prefab object
// In that case, skip saving reference as it's defined in prefab (will be populated via IdsMapping during deserialization) // In that case, skip saving reference as it's defined in prefab (will be populated via IdsMapping during deserialization)
result &= v->GetPrefabObjectID() != other->GetPrefabObjectID(); result = v->GetPrefabObjectID() != other->GetPrefabObjectID();
} }
return result; return result;
} }

View File

@@ -448,15 +448,20 @@ namespace Serialization
// Scripting Object // Scripting Object
FLAXENGINE_API bool ShouldSerialize(const SceneObject* v, const SceneObject* other); inline bool ShouldSerializeRef(const ScriptingObject* v, const ScriptingObject* other)
{
return v != other;
}
FLAXENGINE_API bool ShouldSerializeRef(const SceneObject* v, const SceneObject* other);
template<typename T> template<typename T>
inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj) inline typename TEnableIf<TAnd<TIsBaseOf<ScriptingObject, T>, TNot<TIsBaseOf<SceneObject, T>>>::Value, bool>::Type ShouldSerialize(const T* v, const void* otherObj)
{ {
return !otherObj || v != *(const T**)otherObj; return !otherObj || v != *(const T**)otherObj;
} }
template<typename T> template<typename T>
inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T*& v, const void* otherObj) inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T* v, const void* otherObj)
{ {
stream.Guid(v ? v->GetID() : Guid::Empty); stream.Guid(v ? v->GetID() : Guid::Empty);
} }
@@ -470,9 +475,9 @@ namespace Serialization
} }
template<typename T> template<typename T>
inline typename TEnableIf<TIsBaseOf<SceneObject, T>::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj) inline typename TEnableIf<TIsBaseOf<SceneObject, T>::Value, bool>::Type ShouldSerialize(const T* v, const void* otherObj)
{ {
return !otherObj || ShouldSerialize((const SceneObject*)v, *(const SceneObject**)otherObj); return !otherObj || ShouldSerializeRef((const SceneObject*)v, *(const SceneObject**)otherObj);
} }
// Scripting Object Reference // Scripting Object Reference
@@ -480,7 +485,7 @@ namespace Serialization
template<typename T> template<typename T>
inline bool ShouldSerialize(const ScriptingObjectReference<T>& v, const void* otherObj) inline bool ShouldSerialize(const ScriptingObjectReference<T>& v, const void* otherObj)
{ {
return !otherObj || ShouldSerialize(v.Get(), ((ScriptingObjectReference<T>*)otherObj)->Get()); return !otherObj || ShouldSerializeRef(v.Get(), ((ScriptingObjectReference<T>*)otherObj)->Get());
} }
template<typename T> template<typename T>
inline void Serialize(ISerializable::SerializeStream& stream, const ScriptingObjectReference<T>& v, const void* otherObj) inline void Serialize(ISerializable::SerializeStream& stream, const ScriptingObjectReference<T>& v, const void* otherObj)
@@ -501,7 +506,7 @@ namespace Serialization
template<typename T> template<typename T>
inline bool ShouldSerialize(const SoftObjectReference<T>& v, const void* otherObj) inline bool ShouldSerialize(const SoftObjectReference<T>& v, const void* otherObj)
{ {
return !otherObj || ShouldSerialize(v.Get(), ((SoftObjectReference<T>*)otherObj)->Get()); return !otherObj || ShouldSerializeRef(v.Get(), ((SoftObjectReference<T>*)otherObj)->Get());
} }
template<typename T> template<typename T>
inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectReference<T>& v, const void* otherObj) inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectReference<T>& v, const void* otherObj)

View File

@@ -80,7 +80,7 @@ class ISerializeModifier;
// Explicit auto-cast for object pointer // Explicit auto-cast for object pointer
#define SERIALIZE_OBJ(name) \ #define SERIALIZE_OBJ(name) \
if (Serialization::ShouldSerialize((const ScriptingObject*&)name, other ? &other->name : nullptr)) \ if (Serialization::ShouldSerialize(name, decltype(name)(other ? &other->name : nullptr))) \
{ \ { \
stream.JKEY(#name); \ stream.JKEY(#name); \
Serialization::Serialize(stream, (const ScriptingObject*&)name, other ? &other->name : nullptr); \ Serialization::Serialize(stream, (const ScriptingObject*&)name, other ? &other->name : nullptr); \

View File

@@ -852,7 +852,7 @@ void Terrain::OnEnable()
GetScene()->Navigation.Actors.Add(this); GetScene()->Navigation.Actors.Add(this);
GetSceneRendering()->AddActor(this, _sceneRenderingKey); GetSceneRendering()->AddActor(this, _sceneRenderingKey);
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
GetSceneRendering()->AddPhysicsDebug<Terrain, &Terrain::DrawPhysicsDebug>(this); GetSceneRendering()->AddPhysicsDebug(this);
#endif #endif
void* scene = GetPhysicsScene()->GetPhysicsScene(); void* scene = GetPhysicsScene()->GetPhysicsScene();
for (int32 i = 0; i < _patches.Count(); i++) for (int32 i = 0; i < _patches.Count(); i++)
@@ -873,7 +873,7 @@ void Terrain::OnDisable()
GetScene()->Navigation.Actors.Remove(this); GetScene()->Navigation.Actors.Remove(this);
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
GetSceneRendering()->RemovePhysicsDebug<Terrain, &Terrain::DrawPhysicsDebug>(this); GetSceneRendering()->RemovePhysicsDebug(this);
#endif #endif
void* scene = GetPhysicsScene()->GetPhysicsScene(); void* scene = GetPhysicsScene()->GetPhysicsScene();
for (int32 i = 0; i < _patches.Count(); i++) for (int32 i = 0; i < _patches.Count(); i++)

View File

@@ -5,6 +5,7 @@
#include "Engine/Content/JsonAssetReference.h" #include "Engine/Content/JsonAssetReference.h"
#include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/Assets/MaterialBase.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h"
#include "Engine/Physics/Actors/IPhysicsDebug.h"
class Terrain; class Terrain;
class TerrainChunk; class TerrainChunk;
@@ -38,6 +39,9 @@ struct RenderView;
/// <seealso cref="Actor" /> /// <seealso cref="Actor" />
/// <seealso cref="PhysicsColliderActor" /> /// <seealso cref="PhysicsColliderActor" />
API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor
#if USE_EDITOR
, public IPhysicsDebug
#endif
{ {
DECLARE_SCENE_OBJECT(Terrain); DECLARE_SCENE_OBJECT(Terrain);
friend Terrain; friend Terrain;
@@ -441,9 +445,7 @@ public:
API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const; API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const;
private: private:
#if TERRAIN_USE_PHYSICS_DEBUG ImplementPhysicsDebug;
void DrawPhysicsDebug(RenderView& view);
#endif
bool DrawSetup(RenderContext& renderContext); bool DrawSetup(RenderContext& renderContext);
void DrawImpl(RenderContext& renderContext, HashSet<TerrainChunk*, class RendererAllocation>& drawnChunks); void DrawImpl(RenderContext& renderContext, HashSet<TerrainChunk*, class RendererAllocation>& drawnChunks);

View File

@@ -687,6 +687,171 @@ TEST_CASE("Prefabs")
instanceA->DeleteObject(); instanceA->DeleteObject();
instanceB->DeleteObject(); instanceB->DeleteObject();
instanceC->DeleteObject(); instanceC->DeleteObject();
}
SECTION("Test Loading Nested Prefab With Multiple Instances of Nested Prefab")
{
// https://github.com/FlaxEngine/FlaxEngine/issues/3255
// Create Prefab C with basic cross-object references
AssetReference<Prefab> prefabC = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabC);
Guid id;
Guid::Parse("cccbe4b0416be0777a6ce59e8788b10f", id);
prefabC->ChangeID(id);
auto prefabCInit = prefabC->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
"\"TypeName\": \"FlaxEngine.ExponentialHeightFog\","
"\"Name\": \"Prefab C.Root\","
"\"DirectionalInscatteringLight\": \"bbb6b9644492fbca1a6ab0a7904a557e\""
"},"
"{"
"\"ID\": \"bbb6b9644492fbca1a6ab0a7904a557e\","
"\"TypeName\": \"FlaxEngine.DirectionalLight\","
"\"ParentID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
"\"Name\": \"Prefab C.Light\""
"}"
"]");
REQUIRE(!prefabCInit);
// Create Prefab B with two nested Prefab C attached to the root
AssetReference<Prefab> prefabB = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabB);
SCOPE_EXIT{ Content::DeleteAsset(prefabB); };
Guid::Parse("bbb744714f746e31855f41815612d14b", id);
prefabB->ChangeID(id);
auto prefabBInit = prefabB->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"244274a04cc60d56a2f024bfeef5772d\","
"\"TypeName\": \"FlaxEngine.SpotLight\","
"\"Name\": \"Prefab B.Root\""
"},"
"{"
"\"ID\": \"1111f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
"\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\""
"},"
"{"
"\"ID\": \"2221f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"bbb6b9644492fbca1a6ab0a7904a557e\","
"\"ParentID\": \"1111f1094f430733333f8280e78dfcc3\""
"},"
"{"
"\"ID\": \"3331f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
"\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\""
"},"
"{"
"\"ID\": \"4441f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"bbb6b9644492fbca1a6ab0a7904a557e\","
"\"ParentID\": \"3331f1094f430733333f8280e78dfcc3\""
"}"
"]");
REQUIRE(!prefabBInit);
// Create Prefab A as variant of Prefab B (no local changes, just object remapped)
AssetReference<Prefab> prefabA = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabA);
SCOPE_EXIT{ Content::DeleteAsset(prefabA); };
Guid::Parse("aaa744714f746e31855f41815612d14b", id);
prefabA->ChangeID(id);
auto prefabAInit = prefabA->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"123274a04cc60d56a2f024bfeef5772d\","
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
"\"PrefabObjectID\": \"244274a04cc60d56a2f024bfeef5772d\","
"\"Name\": \"Prefab A.Root\""
"},"
"{"
"\"ID\": \"1211f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
"\"PrefabObjectID\": \"1111f1094f430733333f8280e78dfcc3\","
"\"ParentID\": \"123274a04cc60d56a2f024bfeef5772d\""
"},"
"{"
"\"ID\": \"4221f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
"\"PrefabObjectID\": \"2221f1094f430733333f8280e78dfcc3\","
"\"ParentID\": \"1211f1094f430733333f8280e78dfcc3\""
"},"
"{"
"\"ID\": \"3131f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
"\"PrefabObjectID\": \"3331f1094f430733333f8280e78dfcc3\","
"\"ParentID\": \"123274a04cc60d56a2f024bfeef5772d\""
"},"
"{"
"\"ID\": \"5441f1094f430733333f8280e78dfcc3\","
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
"\"PrefabObjectID\": \"4441f1094f430733333f8280e78dfcc3\","
"\"ParentID\": \"3131f1094f430733333f8280e78dfcc3\""
"}"
"]");
REQUIRE(!prefabAInit);
// Spawn test instances of both prefabs
ScriptingObjectReference<Actor> instanceA = PrefabManager::SpawnPrefab(prefabA);
ScriptingObjectReference<Actor> instanceB = PrefabManager::SpawnPrefab(prefabB);
ScriptingObjectReference<Actor> instanceC = PrefabManager::SpawnPrefab(prefabC);
// Check state of objects
REQUIRE(instanceC);
REQUIRE(instanceC->Is<ExponentialHeightFog>());
REQUIRE(instanceC->Children.Count() == 1);
CHECK(instanceC.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceC->Children[0]);
REQUIRE(instanceB);
REQUIRE(instanceB->Children.Count() == 2);
ScriptingObjectReference<Actor> instanceB1 = instanceB->Children[0];
ScriptingObjectReference<Actor> instanceB2 = instanceB->Children[1];
REQUIRE(instanceB1->Is<ExponentialHeightFog>());
REQUIRE(instanceB1->Children.Count() == 1);
CHECK(instanceB1.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceB1->Children[0]);
REQUIRE(instanceB2->Is<ExponentialHeightFog>());
REQUIRE(instanceB2->Children.Count() == 1);
CHECK(instanceB2.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceB2->Children[0]);
REQUIRE(instanceA);
REQUIRE(instanceA->Children.Count() == 2);
ScriptingObjectReference<Actor> instanceA1 = instanceA->Children[0];
ScriptingObjectReference<Actor> instanceA2 = instanceA->Children[1];
REQUIRE(instanceA1->Is<ExponentialHeightFog>());
REQUIRE(instanceA1->Children.Count() == 1);
CHECK(instanceA1.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA1->Children[0]);
REQUIRE(instanceA2->Is<ExponentialHeightFog>());
REQUIRE(instanceA1->Children.Count() == 1);
CHECK(instanceA2.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA2->Children[0]);
// Add instance of Prefab C to Prefab B
instanceC->SetName(StringView(TEXT("New")));
instanceC->SetParent(instanceB);
bool applyResult = PrefabManager::ApplyAll(instanceB);
REQUIRE(!applyResult);
// Check if Prefab A reflects that change
REQUIRE(instanceA);
REQUIRE(instanceA->Children.Count() == 3);
instanceA1 = instanceA->Children[0];
instanceA2 = instanceA->Children[1];
ScriptingObjectReference<Actor> instanceA3 = instanceA->Children[2];
REQUIRE(instanceA1->Is<ExponentialHeightFog>());
REQUIRE(instanceA1->Children.Count() == 1);
CHECK(instanceA1.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA1->Children[0]);
REQUIRE(instanceA2->Is<ExponentialHeightFog>());
REQUIRE(instanceA2->Children.Count() == 1);
CHECK(instanceA2.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA2->Children[0]);
REQUIRE(instanceA3->Is<ExponentialHeightFog>());
REQUIRE(instanceA3->Children.Count() == 1);
CHECK(instanceA3.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA3->Children[0]);
// Cleanup
instanceA->DeleteObject();
instanceB->DeleteObject();
instanceC->DeleteObject();
} }
} }

View File

@@ -172,7 +172,7 @@ void TextRender::UpdateLayout()
// Pick a font (remove DPI text scale as the text is being placed in the world) // Pick a font (remove DPI text scale as the text is being placed in the world)
auto font = Font->CreateFont(_size); auto font = Font->CreateFont(_size);
float scale = 1.0f / FontManager::FontScale; float scale = _layoutOptions.Scale / FontManager::FontScale;
// Prepare // Prepare
FontTextureAtlas* fontAtlas = nullptr; FontTextureAtlas* fontAtlas = nullptr;

View File

@@ -122,10 +122,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f
// Calculate shadow // Calculate shadow
ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask); ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask);
#if !LIGHTING_NO_DIRECTIONAL
// Directional shadowing
shadow.SurfaceShadow *= NoL;
#endif
// Calculate attenuation // Calculate attenuation
if (isRadial) if (isRadial)
@@ -139,6 +135,11 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f
shadow.TransmissionShadow *= attenuation; shadow.TransmissionShadow *= attenuation;
} }
#if !LIGHTING_NO_DIRECTIONAL
// Directional shadowing
shadow.SurfaceShadow *= NoL;
#endif
BRANCH BRANCH
if (shadow.SurfaceShadow + shadow.TransmissionShadow > 0) if (shadow.SurfaceShadow + shadow.TransmissionShadow > 0)
{ {

View File

@@ -13,6 +13,7 @@ namespace Flax.Build.Bindings
public FileInfo File; public FileInfo File;
public Tokenizer Tokenizer; public Tokenizer Tokenizer;
public ApiTypeInfo ScopeInfo; public ApiTypeInfo ScopeInfo;
public NativeCpp.BuildOptions ModuleOptions;
public AccessLevel CurrentAccessLevel; public AccessLevel CurrentAccessLevel;
public Stack<ApiTypeInfo> ScopeTypeStack; public Stack<ApiTypeInfo> ScopeTypeStack;
public Stack<AccessLevel> ScopeAccessStack; public Stack<AccessLevel> ScopeAccessStack;
@@ -534,6 +535,12 @@ namespace Flax.Build.Bindings
} }
if (token.Type == TokenType.LeftCurlyBrace) if (token.Type == TokenType.LeftCurlyBrace)
break; break;
if (token.Type == TokenType.Preprocessor)
{
OnPreProcessorToken(ref context, ref token);
while (token.Type == TokenType.Newline || token.Value == "endif")
token = context.Tokenizer.NextToken();
}
if (token.Type == TokenType.Colon) if (token.Type == TokenType.Colon)
{ {
token = context.Tokenizer.ExpectToken(TokenType.Colon); token = context.Tokenizer.ExpectToken(TokenType.Colon);

View File

@@ -245,6 +245,7 @@ namespace Flax.Build.Bindings
File = fileInfo, File = fileInfo,
Tokenizer = tokenizer, Tokenizer = tokenizer,
ScopeInfo = null, ScopeInfo = null,
ModuleOptions = moduleOptions,
CurrentAccessLevel = AccessLevel.Public, CurrentAccessLevel = AccessLevel.Public,
ScopeTypeStack = new Stack<ApiTypeInfo>(), ScopeTypeStack = new Stack<ApiTypeInfo>(),
ScopeAccessStack = new Stack<AccessLevel>(), ScopeAccessStack = new Stack<AccessLevel>(),
@@ -380,110 +381,7 @@ namespace Flax.Build.Bindings
// Handle preprocessor blocks // Handle preprocessor blocks
if (token.Type == TokenType.Preprocessor) if (token.Type == TokenType.Preprocessor)
{ {
token = tokenizer.NextToken(); OnPreProcessorToken(ref context, ref token);
switch (token.Value)
{
case "define":
{
token = tokenizer.NextToken();
var name = token.Value;
var value = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
value += token.Value;
token = tokenizer.NextToken(true);
}
value = value.Trim();
context.PreprocessorDefines[name] = value;
break;
}
case "if":
case "elif":
{
// Parse condition
var condition = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
var tokenValue = token.Value.Trim();
if (tokenValue.Length == 0)
{
token = tokenizer.NextToken(true);
continue;
}
// Very simple defines processing
var negate = tokenValue[0] == '!';
if (negate)
tokenValue = tokenValue.Substring(1);
tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines);
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions);
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions);
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions);
tokenValue = tokenValue.Replace("false", "0");
tokenValue = tokenValue.Replace("true", "1");
tokenValue = tokenValue.Replace("||", "|");
if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|")
tokenValue = "0";
if (negate)
tokenValue = tokenValue == "1" ? "0" : "1";
condition += tokenValue;
token = tokenizer.NextToken(true);
}
// Filter condition
bool modified;
do
{
modified = false;
if (condition.Contains("1|1"))
{
condition = condition.Replace("1|1", "1");
modified = true;
}
if (condition.Contains("1|0"))
{
condition = condition.Replace("1|0", "1");
modified = true;
}
if (condition.Contains("0|1"))
{
condition = condition.Replace("0|1", "1");
modified = true;
}
} while (modified);
// Skip chunk of code of condition fails
if (condition != "1")
{
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
}
break;
}
case "ifdef":
{
// Parse condition
var define = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
define += token.Value;
token = tokenizer.NextToken(true);
}
// Check condition
define = define.Trim();
if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define))
{
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
}
break;
}
}
} }
// Scope tracking // Scope tracking
@@ -514,6 +412,120 @@ namespace Flax.Build.Bindings
} }
} }
private static void OnPreProcessorToken(ref ParsingContext context, ref Token token)
{
var tokenizer = context.Tokenizer;
token = tokenizer.NextToken();
switch (token.Value)
{
case "define":
{
token = tokenizer.NextToken();
var name = token.Value;
var value = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
value += token.Value;
token = tokenizer.NextToken(true);
}
value = value.Trim();
context.PreprocessorDefines[name] = value;
break;
}
case "if":
case "elif":
{
// Parse condition
var condition = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
var tokenValue = token.Value.Trim();
if (tokenValue.Length == 0)
{
token = tokenizer.NextToken(true);
continue;
}
// Very simple defines processing
var negate = tokenValue[0] == '!';
if (negate)
tokenValue = tokenValue.Substring(1);
tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines);
tokenValue = ReplacePreProcessorDefines(tokenValue, context.ModuleOptions.PublicDefinitions);
tokenValue = ReplacePreProcessorDefines(tokenValue, context.ModuleOptions.PrivateDefinitions);
tokenValue = ReplacePreProcessorDefines(tokenValue, context.ModuleOptions.CompileEnv.PreprocessorDefinitions);
tokenValue = tokenValue.Replace("false", "0");
tokenValue = tokenValue.Replace("true", "1");
tokenValue = tokenValue.Replace("||", "|");
if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|")
tokenValue = "0";
if (negate)
tokenValue = tokenValue == "1" ? "0" : "1";
condition += tokenValue;
token = tokenizer.NextToken(true);
}
// Filter condition
bool modified;
do
{
modified = false;
if (condition.Contains("1|1"))
{
condition = condition.Replace("1|1", "1");
modified = true;
}
if (condition.Contains("1|0"))
{
condition = condition.Replace("1|0", "1");
modified = true;
}
if (condition.Contains("0|1"))
{
condition = condition.Replace("0|1", "1");
modified = true;
}
} while (modified);
// Skip chunk of code of condition fails
if (condition != "1")
{
ParsePreprocessorIf(context.File, tokenizer, ref token);
}
break;
}
case "ifdef":
{
// Parse condition
var define = string.Empty;
token = tokenizer.NextToken(true);
while (token.Type != TokenType.Newline)
{
define += token.Value;
token = tokenizer.NextToken(true);
}
// Check condition
define = define.Trim();
if (!context.PreprocessorDefines.ContainsKey(define) && !context.ModuleOptions.CompileEnv.PreprocessorDefinitions.Contains(define))
{
ParsePreprocessorIf(context.File, tokenizer, ref token);
}
break;
}
case "endif":
{
token = tokenizer.NextToken(true);
break;
}
}
}
private static string ReplacePreProcessorDefines(string text, IEnumerable<string> defines) private static string ReplacePreProcessorDefines(string text, IEnumerable<string> defines)
{ {
foreach (var define in defines) foreach (var define in defines)