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)
{
if (editor.Values[0] is Actor actor)
@@ -767,32 +776,35 @@ namespace FlaxEditor.CustomEditors
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)
return actor;
for (int i = 0; i < actor.ScriptsCount; i++)
{
if (actor.GetScript(i).PrefabObjectID == prefabObjectId)
{
var a = actor.GetScript(i);
if (a != null)
return a;
}
var script = actor.GetScript(i);
if (script != null && script.PrefabObjectID == prefabObjectId)
return script;
}
for (int i = 0; i < actor.ChildrenCount; i++)
{
if (actor.GetChild(i).PrefabObjectID == prefabObjectId)
{
var a = actor.GetChild(i);
if (a != null)
return a;
}
var child = actor.GetChild(i);
if (child != null && child.PrefabObjectID == prefabObjectId)
return child;
}
return null;
// Go up in the hierarchy
return FindObjectWithPrefabObjectId(actor.Parent, ref prefabObjectId, endPoint, visited);
}
/// <summary>
@@ -826,7 +838,7 @@ namespace FlaxEditor.CustomEditors
}
var prefabObjectId = referenceSceneObject.PrefabObjectID;
var prefabInstanceRef = FindObjectWithPrefabObjectId(prefabInstanceRoot, ref prefabObjectId);
var prefabInstanceRef = FindObjectWithPrefabObjectId(FindActor(this), ref prefabObjectId, prefabInstanceRoot);
if (prefabInstanceRef == null)
{
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.
/// </summary>
public bool LockSelection { get; set; }
/// <summary>
/// Gets the scene editing context.
/// </summary>
public ISceneEditingContext SceneContext { get; }
}
/// <summary>

View File

@@ -1,8 +1,5 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements;
@@ -10,12 +7,14 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -240,6 +239,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
node.TextColor = Color.OrangeRed;
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
else if (editor.Values[0] is SceneObject sceneObject)
{
@@ -295,16 +300,40 @@ namespace FlaxEditor.CustomEditors.Dedicated
// 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)
{
// Special case for new Script added to actor
if (editor.Values[0] is Script script && !script.HasPrefabLink)
// Special case for new Script or child actor added to actor
if ((editor.Values[0] is Script script && !script.HasPrefabLink) || (editor.Values[0] is Actor a && !a.HasPrefabLink))
return CreateDiffNode(editor);
// Skip if no change detected
var isRefEdited = editor.Values.IsReferenceValueModified;
if (!isRefEdited && skipIfNotModified)
if (!isRefEdited && skipIfNotModified && editor is not ScriptsEditor)
return 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;
}
@@ -438,6 +505,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
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)
{
// Special case for removed Script from actor
@@ -459,6 +535,22 @@ namespace FlaxEditor.CustomEditors.Dedicated
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
if (editor.Values[0] is Script script && !script.HasPrefabLink)
{
@@ -470,8 +562,37 @@ namespace FlaxEditor.CustomEditors.Dedicated
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.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;
menu.AddSeparator();
@@ -404,8 +406,10 @@ namespace FlaxEditor.CustomEditors.Editors
var menu = new ContextMenu();
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);
paste.Enabled = linkedEditor.CanPaste;
paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
if (_canReorder)
{
@@ -741,6 +745,34 @@ namespace FlaxEditor.CustomEditors.Editors
cloned[srcIndex] = tmp;
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>
/// 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);
// 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 originalName = instance.Name;
if (instance.IsPrefabRoot && instance.HasScene)
if (instance.HasScene)
{
instance.LocalTransform = prefab.GetDefaultInstance().Transform;
instance.Name = prefab.GetDefaultInstance().Name;
prefabRoot = instance.GetPrefabRoot();
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
var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance));
instance.LocalTransform = originalTransform;
instance.Name = originalName;
if (prefabRoot != null)
{
prefabRoot.LocalTransform = originalTransform;
prefabRoot.Name = originalName;
}
if (failed)
throw new Exception("Failed to apply the prefab. See log to learn more.");

View File

@@ -569,6 +569,10 @@ namespace FlaxEditor.Modules
return;
}
// Skip if already added
if (SceneGraphFactory.Nodes.ContainsKey(actor.ID))
return;
var node = SceneGraphFactory.BuildActorNode(actor);
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 = 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 = 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.TooltipText = "Generate and add a convex collider for every selected model.";
b = menu.ContextMenu.AddButton("Triangle Mesh", () => OnAddCollider(window, CreateTriangle));
@@ -267,6 +269,20 @@ namespace FlaxEditor.SceneGraph.Actors
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)
{
CreateMeshCollider(actor, spawner, singleNode, CollisionDataType.ConvexMesh);

View File

@@ -1,8 +1,9 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport;
using FlaxEngine;
using System.Collections.Generic;
namespace FlaxEditor
{
@@ -39,9 +40,23 @@ namespace FlaxEditor
/// </summary>
void RenameSelection();
/// <summary>
/// Deletes selected objects.
/// </summary>
void DeleteSelection();
/// <summary>
/// Focuses selected objects.
/// </summary>
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;
/// <summary>
/// Gets the list of selected scene graph nodes in the editor context.
/// Gets the scene editing context.
/// </summary>
public abstract ISceneEditingContext SceneContext { get; }
}

View File

@@ -61,6 +61,7 @@ namespace FlaxEditor.Utilities
/// <param name="value">The value.</param>
public void SetMemberValue(object instance, object value)
{
var originalInstance = instance;
var finalMember = MemberPath.GetLastMember(ref instance);
var type = finalMember.Type;
@@ -92,6 +93,12 @@ namespace FlaxEditor.Utilities
}
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 />

View File

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

View File

@@ -641,5 +641,8 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
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.Editors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
@@ -171,13 +172,49 @@ namespace FlaxEditor.Windows.Assets
}
private readonly SplitPanel _split;
private readonly ModelBasePreview _preview;
private readonly CollisionDataPreview _preview;
private readonly CustomEditorPresenter _propertiesPresenter;
private readonly PropertiesProxy _properties;
private Model _collisionWiresModel;
private StaticModel _collisionWiresShowActor;
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 />
public CollisionDataWindow(Editor editor, AssetItem item)
: base(editor, item)
@@ -185,6 +222,12 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_toolstrip.AddSeparator();
_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");
// Split Panel
@@ -197,12 +240,10 @@ namespace FlaxEditor.Windows.Assets
};
// Model preview
_preview = new ModelBasePreview(true)
_preview = new CollisionDataPreview(true)
{
ViewportCamera = new FPSCamera(),
Parent = _split.Panel1
};
_preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom & ~ViewFlags.EyeAdaptation;
// Asset properties
_propertiesPresenter = new CustomEditorPresenter(null);
@@ -240,7 +281,7 @@ namespace FlaxEditor.Windows.Assets
_collisionWiresModel = FlaxEngine.Content.CreateVirtualAsset<Model>();
_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)
_collisionWiresModel.LODs[0].Meshes[0].UpdateMesh(triangles, indices);
else
@@ -252,6 +293,7 @@ namespace FlaxEditor.Windows.Assets
}
_collisionWiresShowActor.Model = _collisionWiresModel;
_collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial));
_preview.SetVerticesAndTriangleCount(triangleCount, indicesCount / 3);
_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 />
public void FocusSelection()
{
@@ -488,7 +494,8 @@ namespace FlaxEditor.Windows.Assets
/// <param name="actor">The actor.</param>
/// <param name="parent">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)
throw new ArgumentNullException(nameof(actor));
@@ -514,8 +521,11 @@ namespace FlaxEditor.Windows.Assets
// Create undo action
var action = new CustomDeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true);
Undo.AddAction(action);
Focus();
Select(actorNode);
if (autoSelect)
{
Focus();
Select(actorNode);
}
}
private void OnTreeRightClick(TreeNode node, Float2 location)

View File

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

View File

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

View File

@@ -26,12 +26,24 @@ namespace FlaxEditor.Windows
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
/// <inheritdoc />
public void DeleteSelection()
{
Editor.SceneEditing.Delete();
}
/// <inheritdoc />
public void 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 />
public EditorViewport Viewport => Editor.Windows.EditWin.Viewport;

View File

@@ -417,7 +417,7 @@ public:
template<typename ItemType>
bool Add(const ItemType& item)
{
Bucket* bucket = Base::OnAdd(item, false);
Bucket* bucket = Base::OnAdd(item, false, true);
if (bucket)
bucket->Occupy(item);
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>
bool Add(T&& item)
{
Bucket* bucket = Base::OnAdd(item, false);
Bucket* bucket = Base::OnAdd(item, false, true);
if (bucket)
bucket->Occupy(MoveTemp(item));
return bucket != nullptr;

View File

@@ -365,7 +365,7 @@ protected:
}
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)
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__);
return nullptr;
}
if (nullIfNonUnique)
return nullptr;
return &_allocation.Get()[pos.ObjectIndex];
}

View File

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

View File

@@ -107,11 +107,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
#if USE_EDITOR
/// <summary>
/// Draws the physics debug shapes for the given collider. Editor Only
/// Draws the physics debug shapes for the given actor. Editor Only
/// </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>
API_FUNCTION() static void DrawColliderDebugPhysics(Collider* collider, RenderView& view);
API_FUNCTION() static void DrawDebugPhysics(Actor* actor, RenderView& view);
/// <summary>
/// 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++)
{
PROFILE_CPU_NAMED("Instance");
auto& instance = prefabInstancesData[instanceIndex];
ISerializeModifierCacheType modifier = Cache::ISerializeModifier.Get();
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
@@ -377,6 +378,10 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
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
for (int32 i = existingObjectsCount - 1; i >= 0; i--)
{
@@ -388,6 +393,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
if (prefabObjectIdToDiffData.TryGet(obj->GetPrefabObjectID(), data))
{
// Apply prefab changes
context.SetupIdsMapping(obj, modifier.Value);
obj->Deserialize(*(ISerializable::DeserializeStream*)data, modifier.Value);
}
else
@@ -424,7 +430,6 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects->At(i);
int32 dataIndex;
if (instance.PrefabInstanceIdToDataIndex.TryGet(obj->GetSceneObjectId(), dataIndex))
{
@@ -440,6 +445,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
data.RemoveMember("ParentID");
#endif
context.SetupIdsMapping(obj, modifier.Value);
obj->Deserialize(data, modifier.Value);
// Preserve order in parent (values from prefab are used)
@@ -527,6 +533,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
{
if (prefabInstancesData.IsEmpty())
return false;
PROFILE_CPU();
// Fully serialize default instance scene objects (accumulate all prefab and nested prefabs changes into a single linear list of objects)
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?
JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId);
}
dataBuffer.Clear();
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
// Destroy default instance and some cache data in Prefab
@@ -1002,6 +1008,32 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
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
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
newPrefabInstanceIdToDataIndexCounter = 0;
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
@@ -1283,6 +1318,10 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
ObjectsDataCache.Add(objectId, &objData);
ObjectsCount++;
Guid parentID;
if (JsonTools::GetGuidIfValid(parentID, objData, "ParentID"))
ObjectsHierarchyCache[parentID].Add(objectId);
Guid prefabId = JsonTools::GetGuid(objData, "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::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)
// 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)
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)
if (options.WithSync)
if (withSync)
{
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
}
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Pick prefab root object
Actor* root = nullptr;

View File

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

View File

@@ -11,6 +11,7 @@
class SceneRenderTask;
class SceneRendering;
class IPhysicsDebug;
struct PostProcessSettings;
struct RenderContext;
struct RenderContextBatch;
@@ -74,7 +75,6 @@ public:
class FLAXENGINE_API SceneRendering
{
#if USE_EDITOR
typedef Function<void(RenderView&)> PhysicsDebugCallback;
typedef Function<void(RenderView&)> LightsDebugCallback;
friend class ViewportIconsRendererService;
#endif
@@ -106,7 +106,7 @@ public:
private:
#if USE_EDITOR
Array<PhysicsDebugCallback> PhysicsDebug;
Array<IPhysicsDebug*> PhysicsDebug;
Array<LightsDebugCallback> LightsDebug;
Array<Actor*> ViewportIcons;
#endif
@@ -150,20 +150,14 @@ public:
}
#if USE_EDITOR
template<class T, void(T::*Method)(RenderView&)>
FORCE_INLINE void AddPhysicsDebug(T* obj)
FORCE_INLINE void AddPhysicsDebug(IPhysicsDebug* obj)
{
PhysicsDebugCallback f;
f.Bind<T, Method>(obj);
PhysicsDebug.Add(f);
PhysicsDebug.Add(obj);
}
template<class T, void(T::*Method)(RenderView&)>
void RemovePhysicsDebug(T* obj)
FORCE_INLINE void RemovePhysicsDebug(IPhysicsDebug* obj)
{
PhysicsDebugCallback f;
f.Bind<T, Method>(obj);
PhysicsDebug.Remove(f);
PhysicsDebug.Remove(obj);
}
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
{
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
modifier->CurrentInstance = instanceIndex;
const auto& instance = Instances[instanceIndex];
for (const auto& e : instance.IdsMapping)
modifier->IdsMapping[e.Key] = e.Value;
if (instanceIndex != modifier->CurrentInstance)
{
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);
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
prefabInstance.IdsMapping[prefabObjectId] = id;
goto NESTED_PREFAB_WALK;
@@ -794,23 +834,20 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
if (spawned)
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);
data.Modifier->IdsMapping[actorPrefabObjectId] = actorId;
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// 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");
// 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
const ISerializable::DeserializeStream* 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);
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
child->RegisterObject();
@@ -846,17 +884,51 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS
context.ObjectToInstance[id] = instanceIndex;
auto& prefabInstance = context.Instances[instanceIndex];
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)
// TODO: prefab could cache lookup object id -> children ids
for (auto q = prefab->ObjectsDataCache.Begin(); q.IsNotEnd(); ++q)
const auto* hierarchy = prefab->ObjectsHierarchyCache.TryGet(prefabObjectId);
if (hierarchy)
{
Guid qParentId;
if (JsonTools::GetGuidIfValid(qParentId, *q->Value, "ParentID") && qParentId == prefabObjectId)
for (const Guid& e : *hierarchy)
{
const Guid qPrefabObjectId = JsonTools::GetGuid(*q->Value, "ID");
SynchronizeNewPrefabInstance(context, data, prefab, actor, qPrefabObjectId);
SynchronizeNewPrefabInstance(context, data, prefab, actor, e, id);
}
}
}

View File

@@ -16,6 +16,13 @@
class FLAXENGINE_API SceneObjectsFactory
{
public:
struct NestedPrefabInstance
{
Prefab* Prefab;
Guid RootObjectId;
Dictionary<Guid, Guid> IdsMapping;
};
struct PrefabInstance
{
int32 StatIndex;
@@ -24,6 +31,8 @@ public:
Prefab* Prefab;
bool FixRootParent = false;
Dictionary<Guid, Guid> IdsMapping;
Array<NestedPrefabInstance> Nested;
Dictionary<Guid, int32> ObjectToNested;
};
struct Context
@@ -136,5 +145,5 @@ public:
private:
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);
#if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<Cloth, &Cloth::DrawPhysicsDebug>(this);
GetSceneRendering()->AddPhysicsDebug(this);
#endif
#if WITH_CLOTH
if (_cloth)
@@ -481,7 +481,7 @@ void Cloth::OnDisable()
PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
#endif
#if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<Cloth, &Cloth::DrawPhysicsDebug>(this);
GetSceneRendering()->RemovePhysicsDebug(this);
#endif
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
}

View File

@@ -4,6 +4,7 @@
#include "Engine/Level/Actor.h"
#include "Engine/Level/Actors/ModelInstanceActor.h"
#include "IPhysicsDebug.h"
// Used internally to validate cloth data against invalid nan/inf values
#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.
/// </summary>
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);
@@ -364,9 +368,7 @@ protected:
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
private:
#if USE_EDITOR
void DrawPhysicsDebug(RenderView& view);
#endif
ImplementPhysicsDebug;
bool CreateCloth();
void DestroyCloth();
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)
{
// 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())
{
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 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);
@@ -387,25 +387,28 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
void WheeledVehicle::OnDebugDrawSelected()
{
// 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())
{
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 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
Transform actorPose = Transform::Identity, shapePose = Transform::Identity;
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation);
PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation);
Transform actorPose = GetTransform(), shapePose = wheel.Collider->GetLocalTransform();
actorPose.Scale = Float3::One;
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(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);
@@ -561,14 +564,14 @@ void WheeledVehicle::BeginPlay(SceneBeginData* data)
#endif
#if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<WheeledVehicle, &WheeledVehicle::DrawPhysicsDebug>(this);
GetSceneRendering()->AddPhysicsDebug(this);
#endif
}
void WheeledVehicle::EndPlay()
{
#if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<WheeledVehicle, &WheeledVehicle::DrawPhysicsDebug>(this);
GetSceneRendering()->RemovePhysicsDebug(this);
#endif
#if WITH_VEHICLE

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,7 +145,7 @@ void Collider::OnEnable()
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger)
GetScene()->Navigation.Actors.Add(this);
#if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<Collider, &Collider::DrawPhysicsDebug>(this);
GetSceneRendering()->AddPhysicsDebug(this);
#endif
PhysicsColliderActor::OnEnable();
@@ -158,7 +158,7 @@ void Collider::OnDisable()
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger)
GetScene()->Navigation.Actors.Remove(this);
#if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<Collider, &Collider::DrawPhysicsDebug>(this);
GetSceneRendering()->RemovePhysicsDebug(this);
#endif
}
@@ -284,14 +284,6 @@ void Collider::RemoveStaticActor()
_staticActor = nullptr;
}
#if USE_EDITOR
void Collider::DrawPhysicsDebug(RenderView& view)
{
}
#endif
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1366,10 +1366,11 @@ RETRY_ATLAS_SETUP:
tile.LinkedRectTile = nullptr;
auto& linkedTile = linkedAtlasLight->Tiles[tileIndex];
// Check if both lights use the same projections
if (tile.WorldToShadow == linkedTile.WorldToShadow && linkedTile.RectTile)
// Link tile and use its projection
if (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);
}
bool Serialization::ShouldSerialize(const SceneObject* v, const SceneObject* other)
bool Serialization::ShouldSerializeRef(const SceneObject* v, const SceneObject* other)
{
bool result = v != other;
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
// 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;
}

View File

@@ -448,15 +448,20 @@ namespace Serialization
// 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>
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;
}
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);
}
@@ -470,9 +475,9 @@ namespace Serialization
}
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
@@ -480,7 +485,7 @@ namespace Serialization
template<typename T>
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>
inline void Serialize(ISerializable::SerializeStream& stream, const ScriptingObjectReference<T>& v, const void* otherObj)
@@ -501,7 +506,7 @@ namespace Serialization
template<typename T>
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>
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
#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); \
Serialization::Serialize(stream, (const ScriptingObject*&)name, other ? &other->name : nullptr); \

View File

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

View File

@@ -5,6 +5,7 @@
#include "Engine/Content/JsonAssetReference.h"
#include "Engine/Content/Assets/MaterialBase.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h"
#include "Engine/Physics/Actors/IPhysicsDebug.h"
class Terrain;
class TerrainChunk;
@@ -38,6 +39,9 @@ struct RenderView;
/// <seealso cref="Actor" />
/// <seealso cref="PhysicsColliderActor" />
API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor
#if USE_EDITOR
, public IPhysicsDebug
#endif
{
DECLARE_SCENE_OBJECT(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;
private:
#if TERRAIN_USE_PHYSICS_DEBUG
void DrawPhysicsDebug(RenderView& view);
#endif
ImplementPhysicsDebug;
bool DrawSetup(RenderContext& renderContext);
void DrawImpl(RenderContext& renderContext, HashSet<TerrainChunk*, class RendererAllocation>& drawnChunks);

View File

@@ -687,6 +687,171 @@ TEST_CASE("Prefabs")
instanceA->DeleteObject();
instanceB->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)
auto font = Font->CreateFont(_size);
float scale = 1.0f / FontManager::FontScale;
float scale = _layoutOptions.Scale / FontManager::FontScale;
// Prepare
FontTextureAtlas* fontAtlas = nullptr;

View File

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

View File

@@ -13,6 +13,7 @@ namespace Flax.Build.Bindings
public FileInfo File;
public Tokenizer Tokenizer;
public ApiTypeInfo ScopeInfo;
public NativeCpp.BuildOptions ModuleOptions;
public AccessLevel CurrentAccessLevel;
public Stack<ApiTypeInfo> ScopeTypeStack;
public Stack<AccessLevel> ScopeAccessStack;
@@ -534,6 +535,12 @@ namespace Flax.Build.Bindings
}
if (token.Type == TokenType.LeftCurlyBrace)
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)
{
token = context.Tokenizer.ExpectToken(TokenType.Colon);

View File

@@ -245,6 +245,7 @@ namespace Flax.Build.Bindings
File = fileInfo,
Tokenizer = tokenizer,
ScopeInfo = null,
ModuleOptions = moduleOptions,
CurrentAccessLevel = AccessLevel.Public,
ScopeTypeStack = new Stack<ApiTypeInfo>(),
ScopeAccessStack = new Stack<AccessLevel>(),
@@ -380,110 +381,7 @@ namespace Flax.Build.Bindings
// Handle preprocessor blocks
if (token.Type == TokenType.Preprocessor)
{
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, 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;
}
}
OnPreProcessorToken(ref context, ref token);
}
// 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)
{
foreach (var define in defines)