From 633f2fa901aeca1c67c6f102937194a17568f7ee Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Apr 2026 12:49:43 +0200 Subject: [PATCH] Fix prefab preview to wait for the assets streaming --- Source/Editor/Content/Proxy/PrefabProxy.cs | 4 +- .../Content/Thumbnails/ThumbnailsModule.cs | 45 +++++++++++++++++++ Source/Engine/Level/Actors/AnimatedModel.cpp | 5 +++ Source/Engine/Level/Actors/AnimatedModel.h | 1 + .../Engine/Level/Actors/ModelInstanceActor.h | 5 +++ Source/Engine/Level/Actors/SplineModel.cpp | 5 +++ Source/Engine/Level/Actors/SplineModel.h | 1 + Source/Engine/Level/Actors/StaticModel.cpp | 5 +++ Source/Engine/Level/Actors/StaticModel.h | 1 + 9 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index c01fc9ebf..0fb247605 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -122,9 +122,7 @@ namespace FlaxEditor.Content return false; // Check if asset is streamed enough - var asset = (Prefab)request.Asset; - // TODO: check all prefab actors to have loaded resources (models/textures/etc.) - return asset.IsLoaded; + return ThumbnailsModule.HasMinimumQuality((Prefab)request.Asset); } private void Prepare(Actor actor, ref BoundingBox bounds) diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs index 75ab79c4d..dbc970b05 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs @@ -181,6 +181,14 @@ namespace FlaxEditor.Content.Thumbnails return baseMaterial == null || HasMinimumQualityInternal(baseMaterial); } + internal static bool HasMinimumQuality(Prefab asset) + { + if (!asset.IsLoaded) + return false; + var defaultInstance = asset.GetDefaultInstance(); + return defaultInstance == null || HasMinimumQualityInternal(defaultInstance); + } + private static bool HasMinimumQualityInternal(MaterialBase asset) { if (!asset.IsLoaded) @@ -194,6 +202,43 @@ namespace FlaxEditor.Content.Thumbnails return true; } + private static bool HasMinimumQualityInternal(Actor actor) + { + if (!actor.IsActive) + return true; + + if (actor is ModelInstanceActor modelInstance) + { + var model = modelInstance.GetModel(); + if (model && !HasMinimumQuality(model)) + return false; + var slots = modelInstance.MaterialSlots; + foreach (var slot in slots) + { + if (slot.Material && !HasMinimumQuality(slot.Material)) + return false; + } + } + if (actor is SpriteRender spriteRender) + { + if (spriteRender.Material && !HasMinimumQuality(spriteRender.Material)) + return false; + } + if (actor is TextRender textRender) + { + if (textRender.Material && !HasMinimumQuality(textRender.Material)) + return false; + } + + for (int i = 0; i < actor.ChildrenCount; i++) + { + if (!HasMinimumQualityInternal(actor.GetChild(i))) + return false; + } + + return true; + } + #region IContentItemOwner /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 1f441b6b0..ce106dc83 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -1332,6 +1332,11 @@ MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex) return material; } +ModelBase* AnimatedModel::GetModel() +{ + return SkinnedModel.Get(); +} + bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) { auto model = SkinnedModel.Get(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index f9182d115..5e549e4a8 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -476,6 +476,7 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; const Span GetMaterialSlots() const override; MaterialBase* GetMaterial(int32 entryIndex) override; + ModelBase* GetModel() override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index d553d132a..05b6c7f91 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -66,6 +66,11 @@ public: /// The material slot entry index. API_FUNCTION(Sealed) virtual MaterialBase* GetMaterial(int32 entryIndex) = 0; + /// + /// Gets the model (base class). + /// + API_FUNCTION(Sealed) virtual ModelBase* GetModel() = 0; + /// /// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot. /// diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 42b2dc620..0da4a3d70 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -367,6 +367,11 @@ MaterialBase* SplineModel::GetMaterial(int32 entryIndex) return material; } +ModelBase* SplineModel::GetModel() +{ + return Model.Get(); +} + void SplineModel::UpdateBounds() { OnSplineUpdated(); diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index a63d4f721..a9b105a8e 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -117,6 +117,7 @@ public: void OnParentChanged() override; const Span GetMaterialSlots() const override; MaterialBase* GetMaterial(int32 entryIndex) override; + ModelBase* GetModel() override; void UpdateBounds() override; protected: diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index fad068bf0..1fba1ba2e 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -599,6 +599,11 @@ MaterialBase* StaticModel::GetMaterial(int32 entryIndex) return material; } +ModelBase* StaticModel::GetModel() +{ + return Model.Get(); +} + bool StaticModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) { auto model = Model.Get(); diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 063638e14..3a8c391f6 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -178,6 +178,7 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; const Span GetMaterialSlots() const override; MaterialBase* GetMaterial(int32 entryIndex) override; + ModelBase* GetModel() override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;