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

# Conflicts:
#	Flax.flaxproj
#	Source/Engine/Level/Actors/StaticModel.cpp
#	Source/Engine/Level/Prefabs/Prefab.cpp
#	Source/Engine/Tools/ModelTool/ModelTool.cpp
This commit is contained in:
2026-06-03 17:15:38 +02:00
91 changed files with 1290 additions and 579 deletions
+3 -7
View File
@@ -820,8 +820,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
void AnimatedModel::BeginPlay(SceneBeginData* data)
{
if (SkinnedModel && SkinnedModel->IsLoaded())
PreInitSkinningData();
PreInitSkinningData();
// Base
ModelInstanceActor::BeginPlay(data);
@@ -1263,9 +1262,7 @@ void AnimatedModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
SERIALIZE(RootMotionTarget);
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
}
void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -1290,8 +1287,7 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DESERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
DESERIALIZE(RootMotionTarget);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
+27 -17
View File
@@ -98,99 +98,99 @@ public:
/// <summary>
/// The skinned model asset used for rendering.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skeleton\")")
AssetReference<SkinnedModel> SkinnedModel;
/// <summary>
/// The animation graph asset used for the skinned mesh skeleton bones evaluation (controls the animation).
/// </summary>
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skeleton\")")
AssetReference<AnimationGraph> AnimationGraph;
/// <summary>
/// If true, use per-bone motion blur on this skeletal model. It requires additional rendering, can be disabled to save performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Drawing\")")
bool PerBoneMotionBlur = true;
/// <summary>
/// If true, animation speed will be affected by the global timescale parameter.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Updating\")")
bool UseTimeScale = true;
/// <summary>
/// If true, the animation will be updated even when an actor cannot be seen by any camera. Otherwise, the animations themselves will also stop running when the actor is off-screen.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Updating\")")
bool UpdateWhenOffscreen = false;
/// <summary>
/// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Updating\")")
float UpdateSpeed = 1.0f;
/// <summary>
/// The animation update mode. Can be used to optimize the performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Updating\")")
AnimationUpdateMode UpdateMode = AnimationUpdateMode::Auto;
/// <summary>
/// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Drawing\")")
float BoundsScale = 1.5f;
/// <summary>
/// The custom bounds(in actor local space). If set to empty bounds then source skinned model bind pose bounds will be used.
/// </summary>
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Drawing\")")
BoundingBox CustomBounds = BoundingBox::Zero;
/// <summary>
/// The model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
/// </summary>
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"LOD Bias\")")
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Drawing\", \"LOD Bias\")")
int32 LODBias = 0;
/// <summary>
/// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature.
/// </summary>
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"Forced LOD\")")
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Drawing\", \"Forced LOD\")")
int32 ForcedLOD = -1;
/// <summary>
/// The draw passes to use for rendering this object.
/// </summary>
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Drawing\")")
DrawPass DrawModes = DrawPass::Default;
/// <summary>
/// The object sort order key used when sorting drawable objects during rendering. Use lower values to draw object before others, higher values are rendered later (on top). Can be used to control transparency drawing.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Skinned Model\"), EditorOrder(110), DefaultValue(0)")
API_FIELD(Attributes="EditorOrder(110), DefaultValue(0), EditorDisplay(\"Drawing\")")
int8 SortOrder = 0;
/// <summary>
/// The shadows casting mode.
/// [Deprecated on 26.10.2022, expires on 26.10.2024]
/// </summary>
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Drawing\")")
DEPRECATED() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
/// <summary>
/// The animation root motion apply target. If not specified the animated model will apply it itself.
/// </summary>
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skeleton\")")
ScriptingObjectReference<Actor> RootMotionTarget;
#if USE_EDITOR
/// <summary>
/// If checked, the skeleton pose will be shawn during debug shapes drawing.
/// If checked, the skeleton pose will be shown during debug shapes drawing.
/// </summary>
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skeleton\"), VisibleIf(nameof(ShowDebugDrawOptions))") bool ShowDebugDrawSkeleton = false;
#endif
public:
@@ -440,6 +440,16 @@ public:
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
private:
#if USE_EDITOR
/// <summary>
/// Used to hide <see cref="ShowDebugDrawSkeleton"/> options if when the skinned model or animation graph is null.
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetShowDebugDrawOptions() const
{
return SkinnedModel != nullptr && AnimationGraph != nullptr;
}
#endif
void ApplyRootMotion(const Transform& rootMotionDelta);
void SyncParameters();
void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
+2 -5
View File
@@ -497,9 +497,7 @@ void SplineModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(PreTransform, _preTransform)
SERIALIZE(Model);
SERIALIZE(DrawModes);
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
}
void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -514,8 +512,7 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(PreTransform, _preTransform);
DESERIALIZE(Model);
DESERIALIZE(DrawModes);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
+2 -3
View File
@@ -486,8 +486,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj)
Lightmap.Serialize(stream);
}
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
if (_vertexColorsCount)
{
@@ -525,7 +524,7 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(SortOrder, _sortOrder);
DESERIALIZE_MEMBER(DrawModes, _drawModes);
Lightmap.Deserialize(stream);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
{
const auto member = stream.FindMember("VertexColors");
+35 -25
View File
@@ -24,6 +24,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Editor/Editor.h"
#include "FlaxEngine.Gen.h"
// Apply flow:
// - collect all prefabs using this prefab (load and create default instances)
@@ -772,7 +773,13 @@ bool Prefab::ApplyAll(Actor* targetActor)
if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData))
return true;
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
// Sync nested prefabs
if (allPrefabs.HasItems())
{
LOG(Info, "Updating referencing prefabs");
HashSet<Guid> synced;
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
}
const auto endTime = DateTime::NowUTC();
LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds());
@@ -1027,8 +1034,14 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
rapidjson_flax::Document targetDataDocument;
if (NestedPrefabs.HasItems())
{
// Use initial data buffer (unstripped) but reorder objects to match the sequence (eg. when new object was added to the nested prefab)
targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize());
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value);
Array<SceneObject*> reorderedObjects = *sceneObjects.Value;
newPrefabInstanceIdToDataIndexCounter = 0;
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
reorderedObjects.Insert(i->Value, sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++));
reorderedObjects.Resize(sceneObjects.Value->Count()); // reorderedObjects matches order in targetDataDocument
SceneObjectsFactory::PrefabSyncData prefabSyncData(reorderedObjects, targetDataDocument, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
if (context.Instances.HasItems())
@@ -1236,7 +1249,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
{
return Init(TypeName, StringAnsiView(tmpBuffer.GetString(), (int32)tmpBuffer.GetSize()));
}
#if 1 // Set to 0 to use memory-only reload that does not modifies the source file - useful for testing and debugging prefabs apply
#if 1 // Set to 0 to use memory-only reload that does not modify the source file - useful for testing and debugging prefabs apply
#if COMPILE_WITH_ASSETS_IMPORTER
Locker.Unlock();
@@ -1295,7 +1308,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
_defaultInstance->DeleteObject();
_defaultInstance = nullptr;
}
_isLoaded = false;
_loadState = 0;
// Update prefab data manually (to prevent updating source asset file - just for testing)
Document.Parse(buffer.GetString(), buffer.GetSize());
@@ -1348,7 +1361,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
NestedPrefabs.Add(prefabId);
}
}
_isLoaded = true;
_loadState = 1;
}
#endif
@@ -1395,34 +1408,31 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
return ApplyAllInternal(targetActor, false, prefabInstancesData);
}
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid>& synced) const
{
PROFILE_CPU();
LOG(Info, "Updating referencing prefabs");
// TODO: this may not work well for very complex prefab nesting -> loop order matters, maybe build a graph of dependencies?
// Call recursive for all referencing prefab assets to refresh nested prefabs
for (int32 i = 0; i < allPrefabs.Count(); i++)
{
auto nestedPrefab = allPrefabs[i].Get();
if (nestedPrefab)
Prefab* nestedPrefab = allPrefabs[i].Get();
if (!nestedPrefab || synced.Contains(nestedPrefab->GetID()))
continue;
if (nestedPrefab->WaitForLoaded())
{
if (nestedPrefab->WaitForLoaded())
{
LOG(Warning, "Waiting for prefab asset load failed.");
continue;
}
LOG(Warning, "Waiting for '{}' load failed.", nestedPrefab->ToString());
continue;
}
// Sync only if prefab is used by this prefab (directly) and it has been captured before
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
if (nestedPrefabIndex != -1)
{
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
continue;
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
ObjectsRemovalService::Flush();
}
// Sync only if prefab is used by this prefab (directly) and it has been captured before
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
if (nestedPrefabIndex != -1)
{
synced.Add(nestedPrefab->GetID());
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
continue;
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
ObjectsRemovalService::Flush();
}
}
}
+4 -1
View File
@@ -7,6 +7,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Actor.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Scripting/Scripting.h"
@@ -22,6 +23,7 @@ Prefab::Prefab(const SpawnParams& params, const AssetInfo* info)
Guid Prefab::GetRootObjectId() const
{
PROFILE_CPU();
ASSERT(IsLoaded());
ScopeLock lock(Locker);
@@ -57,11 +59,12 @@ Actor* Prefab::GetDefaultInstance()
// Skip if already created (reuse cached result)
if (_defaultInstance)
return _defaultInstance;
PROFILE_CPU();
// Skip if not loaded
if (!IsLoaded())
{
LOG(Warning, "Cannot instantiate object from not loaded prefab asset '{}'", GetPath());
LOG(Warning, "Cannot instantiate object from not loaded prefab asset ({}, {})", GetPath(), GetID());
return nullptr;
}
+1 -1
View File
@@ -104,7 +104,7 @@ private:
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData);
bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer);
bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData);
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const;
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid, HeapAllocation>& synced) const;
#endif
void DeleteDefaultInstance();
+1 -1
View File
@@ -752,7 +752,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
obj->SetOrderInParent(order);
}
// Setup hierarchy for the prefab instances (ensure any new objects are connected)
// Setup hierarchy for the prefab instances (after adding new objects to ensure they are connected, eg. when reparenting existing prefab into a new root)
for (const auto& instance : context.Instances)
{
const auto& prefabStartData = data.Data[instance.StatIndex];