Files
FlaxEngine/Source/Engine/Level/Actors/StaticModel.cpp
T
luchu1993 9943959e76 Fix model material access with empty entries
Allow model instance material APIs to fall back to asset material slots when instance entries have not been initialized yet. Lazily initialize entries before writing material overrides so imported prefab StaticModel instances can call GetMaterial and SetMaterial without assertion failures.\n\nFixes #3801.
2026-06-20 10:22:51 +08:00

730 lines
23 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#include "StaticModel.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/GPUBufferDescription.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h"
#include "Engine/Utilities/Encryption.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
StaticModel::StaticModel(const SpawnParams& params)
: ModelInstanceActor(params)
, _scaleInLightmap(1.0f)
, _boundsScale(1.0f)
, _lodBias(0)
, _forcedLod(-1)
, _vertexColorsDirty(false)
, _vertexColorsCount(0)
, _sortOrder(0)
, Model(this)
{
_drawCategory = SceneRendering::SceneDrawAsync;
}
StaticModel::~StaticModel()
{
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
SAFE_DELETE_GPU_RESOURCE(_vertexColorsBuffer[lodIndex]);
if (_deformation)
Delete(_deformation);
}
float StaticModel::GetScaleInLightmap() const
{
return _scaleInLightmap;
}
void StaticModel::SetScaleInLightmap(float value)
{
_scaleInLightmap = value;
}
float StaticModel::GetBoundsScale() const
{
return _boundsScale;
}
void StaticModel::SetBoundsScale(float value)
{
if (_boundsScale == value)
return;
_boundsScale = value;
UpdateBounds();
}
DrawPass StaticModel::GetDrawModes() const
{
return _drawModes;
}
void StaticModel::SetDrawModes(DrawPass value)
{
if (_drawModes == value)
return;
_drawModes = value;
if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes);
}
int32 StaticModel::GetLODBias() const
{
return _lodBias;
}
void StaticModel::SetLODBias(int32 value)
{
_lodBias = static_cast<char>(Math::Clamp(value, -100, 100));
}
int32 StaticModel::GetForcedLOD() const
{
return _forcedLod;
}
void StaticModel::SetForcedLOD(int32 value)
{
_forcedLod = static_cast<char>(Math::Clamp(value, -1, 100));
}
int32 StaticModel::GetSortOrder() const
{
return _sortOrder;
}
void StaticModel::SetSortOrder(int32 value)
{
_sortOrder = (int8)Math::Clamp<int32>(value, MIN_int8, MAX_int8);
}
bool StaticModel::HasLightmap() const
{
return Lightmap.TextureIndex != INVALID_INDEX;
}
void StaticModel::RemoveLightmap()
{
Lightmap.TextureIndex = INVALID_INDEX;
}
MaterialBase* StaticModel::GetMaterial(int32 meshIndex, int32 lodIndex) const
{
auto model = Model.Get();
ASSERT(model &&
Math::IsInRange(lodIndex, 0, model->GetLODsCount()) &&
Math::IsInRange(meshIndex, 0, model->LODs[lodIndex].Meshes.Count()));
const auto& mesh = model->LODs[lodIndex].Meshes[meshIndex];
const auto materialSlotIndex = mesh.GetMaterialSlotIndex();
MaterialBase* material = materialSlotIndex < Entries.Count() ? Entries[materialSlotIndex].Material.Get() : nullptr;
return material ? material : model->MaterialSlots[materialSlotIndex].Material.Get();
}
Color32 StaticModel::GetVertexColor(int32 lodIndex, int32 meshIndex, int32 vertexIndex) const
{
if (Model && !Model->WaitForLoaded() && _vertexColorsCount == Model->GetLODsCount())
{
if (lodIndex < 0 || lodIndex >= Model->GetLODsCount())
{
LOG(Warning, "Specified model LOD index {0} was out of range.", lodIndex);
return Color32::Black;
}
int32 index = 0;
const ModelLOD& lod = Model->LODs[lodIndex];
auto& vertexColorsData = _vertexColorsData[lodIndex];
if (vertexColorsData.Count() != lod.GetVertexCount())
return Color32::Black;
for (int32 i = 0; i < lod.Meshes.Count(); i++)
{
const Mesh& mesh = lod.Meshes[i];
if (i == meshIndex)
{
if (vertexIndex < 0 || vertexIndex >= mesh.GetVertexCount())
{
LOG(Warning, "Specified vertex index {3} was out of range. LOD{0} mesh {1} has {2}.", lodIndex, meshIndex, mesh.GetVertexCount(), vertexIndex);
return Color32::Black;
}
index += vertexIndex;
return _vertexColorsData[lodIndex][index];
}
index += mesh.GetVertexCount();
}
LOG(Warning, "Specified model mesh index was out of range. LOD{0} mesh {1}.", lodIndex, meshIndex);
}
return Color32::Black;
}
void StaticModel::SetVertexColor(int32 lodIndex, int32 meshIndex, int32 vertexIndex, const Color32& color)
{
if (!Model || Model->WaitForLoaded())
{
LOG(Warning, "Cannot set vertex color if model is missing or failed to load.");
return;
}
if (lodIndex < 0 || lodIndex >= Model->GetLODsCount())
{
LOG(Warning, "Specified model LOD index {0} was out of range.", lodIndex);
return;
}
if (_vertexColorsCount != Model->GetLODsCount())
{
// Initialize vertex colors data for all LODs
RemoveVertexColors();
_vertexColorsCount = Model->GetLODsCount();
for (int32 i = 0; i < _vertexColorsCount; i++)
_vertexColorsBuffer[i] = nullptr;
_vertexColorsDirty = false;
}
int32 index = 0;
const ModelLOD& lod = Model->LODs[lodIndex];
auto& vertexColorsData = _vertexColorsData[lodIndex];
if (vertexColorsData.Count() != lod.GetVertexCount())
{
vertexColorsData.Resize(lod.GetVertexCount());
vertexColorsData.SetAll(Color32::Black);
}
for (int32 i = 0; i < lod.Meshes.Count(); i++)
{
const Mesh& mesh = lod.Meshes[i];
if (i == meshIndex)
{
if (vertexIndex < 0 || vertexIndex >= mesh.GetVertexCount())
{
LOG(Warning, "Specified vertex index {3} was out of range. LOD{0} mesh {1} has {2}.", lodIndex, meshIndex, mesh.GetVertexCount(), vertexIndex);
return;
}
index += vertexIndex;
vertexColorsData[index] = color;
_vertexColorsDirty = true;
return;
}
index += mesh.GetVertexCount();
}
LOG(Warning, "Specified model mesh index was out of range. LOD{0} mesh {1}.", lodIndex, meshIndex);
}
bool StaticModel::HasVertexColors() const
{
return _vertexColorsCount != 0;
}
void StaticModel::RemoveVertexColors()
{
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
_vertexColorsData[lodIndex].Resize(0);
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
SAFE_DELETE_GPU_RESOURCE(_vertexColorsBuffer[lodIndex]);
_vertexColorsCount = 0;
_vertexColorsDirty = false;
}
void StaticModel::OnAssetChanged(Asset* asset, void* caller)
{
if (_residencyChangedModel)
{
_residencyChangedModel = nullptr;
Model->ResidencyChanged.Unbind<StaticModel, &StaticModel::OnModelResidencyChanged>(this);
}
RemoveVertexColors();
Entries.Release();
if (Model && !Model->IsLoaded())
UpdateBounds();
if (_deformation)
_deformation->Clear();
else if (!Model && _sceneRenderingKey != -1)
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
}
void StaticModel::OnAssetLoaded(Asset* asset, void* caller)
{
Entries.SetupIfInvalid(Model);
UpdateBounds();
if (_sceneRenderingKey == -1 && _scene && _isActiveInHierarchy && _isEnabled && !_residencyChangedModel)
{
// Register for rendering but once the model has any LOD loaded
if (Model->GetLoadedLODs() == 0)
{
_residencyChangedModel = Model;
_residencyChangedModel->ResidencyChanged.Bind<StaticModel, &StaticModel::OnModelResidencyChanged>(this);
}
else
{
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
}
}
}
void StaticModel::OnModelResidencyChanged()
{
if (_sceneRenderingKey == -1 && _scene && Model && Model->GetLoadedLODs() > 0 && _residencyChangedModel)
{
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
_residencyChangedModel->ResidencyChanged.Unbind<StaticModel, &StaticModel::OnModelResidencyChanged>(this);
_residencyChangedModel = nullptr;
}
}
void StaticModel::UpdateBounds()
{
const auto model = Model.Get();
if (model && model->IsLoaded() && model->LODs.Count() != 0)
{
Transform transform = _transform;
transform.Scale *= _boundsScale;
_box = model->LODs[0].GetBox(transform, _deformation);
}
else
{
_box = BoundingBox(_transform.Translation);
}
BoundingSphere::FromBox(_box, _sphere);
if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Bounds);
}
void StaticModel::FlushVertexColors()
{
RenderContext::GPULocker.Lock();
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
{
auto& vertexColorsData = _vertexColorsData[lodIndex];
auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex];
if (vertexColorsData.HasItems())
{
const uint32 size = vertexColorsData.Count() * sizeof(Color32);
if (!vertexColorsBuffer)
vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors"));
if (vertexColorsBuffer->GetSize() != size)
{
auto layout = GPUVertexLayout::Get({{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }});
if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(layout, sizeof(Color32), vertexColorsData.Count(), nullptr)))
break;
}
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
}
else
{
SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer);
}
}
RenderContext::GPULocker.Unlock();
}
void StaticModel::OnAssetUnloaded(Asset* asset, void* caller)
{
}
bool StaticModel::HasContentLoaded() const
{
return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded();
}
void StaticModel::Draw(RenderContext& renderContext)
{
if (!Model || !Model->IsLoaded() || !Model->CanBeRendered())
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
{
if (EnumHasAnyFlags(_drawModes, DrawPass::GlobalSDF) && Model->SDF.Texture)
GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _transform, _box);
return;
}
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
{
if (EnumHasAnyFlags(_drawModes, DrawPass::GlobalSurfaceAtlas) && Model->SDF.Texture)
GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _transform, Model->LODs.Last().GetBox());
return;
}
ACTOR_GET_WORLD_MATRIX(this, view, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
if (_vertexColorsDirty)
FlushVertexColors();
Mesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
draw.DrawModes = _drawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
draw.LODBias = _lodBias;
draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
draw.SetStencilValue(_layer);
#if USE_EDITOR
if (HasStaticFlag(StaticFlags::Lightmap))
draw.LightmapScale = _scaleInLightmap;
#endif
Model->Draw(renderContext, draw);
GEOMETRY_DRAW_STATE_EVENT_END(_drawState, world);
}
void StaticModel::Draw(RenderContextBatch& renderContextBatch)
{
if (!Model || !Model->IsLoaded())
return;
const RenderContext& renderContext = renderContextBatch.GetMainContext();
ACTOR_GET_WORLD_MATRIX(this, view, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
if (_vertexColorsDirty)
FlushVertexColors();
Mesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
draw.DrawModes = _drawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
draw.LODBias = _lodBias;
draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
draw.SetStencilValue(_layer);
#if USE_EDITOR
if (HasStaticFlag(StaticFlags::Lightmap))
draw.LightmapScale = _scaleInLightmap;
#endif
Model->Draw(renderContextBatch, draw);
GEOMETRY_DRAW_STATE_EVENT_END(_drawState, world);
}
bool StaticModel::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
{
bool result = false;
if (Model != nullptr && Model->IsLoaded())
{
Mesh* mesh;
Matrix world;
GetLocalToWorldMatrix(world);
result = Model->Intersects(ray, world, distance, normal, &mesh);
}
return result;
}
void StaticModel::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
ModelInstanceActor::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(StaticModel);
SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
SERIALIZE_MEMBER(BoundsScale, _boundsScale);
SERIALIZE(Model);
SERIALIZE_MEMBER(LODBias, _lodBias);
SERIALIZE_MEMBER(ForcedLOD, _forcedLod);
SERIALIZE_MEMBER(SortOrder, _sortOrder);
SERIALIZE_MEMBER(DrawModes, _drawModes);
if (HasLightmap()
#if USE_EDITOR
&& !PrefabManager::IsCreatingPrefab
#endif
)
{
stream.JKEY("LightmapIndex");
stream.Int(Lightmap.TextureIndex);
stream.JKEY("LightmapArea");
stream.Rectangle(Lightmap.UVsArea);
}
SERIALIZE_MEMBER(Buffer, Entries);
if (_vertexColorsCount)
{
stream.JKEY("VertexColors");
stream.StartArray();
Array<char> encodedData;
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
{
auto& vertexColorsData = _vertexColorsData[lodIndex];
if (vertexColorsData.HasItems())
{
const int32 size = vertexColorsData.Count() * sizeof(Color32);
Encryption::Base64Encode((byte*)vertexColorsData.Get(), size, encodedData);
stream.String(encodedData.Get(), encodedData.Count());
}
else
{
stream.String("", 0);
}
}
stream.EndArray();
}
}
void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
ModelInstanceActor::Deserialize(stream, modifier);
DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
DESERIALIZE_MEMBER(BoundsScale, _boundsScale);
DESERIALIZE(Model);
DESERIALIZE_MEMBER(LODBias, _lodBias);
DESERIALIZE_MEMBER(ForcedLOD, _forcedLod);
DESERIALIZE_MEMBER(SortOrder, _sortOrder);
DESERIALIZE_MEMBER(DrawModes, _drawModes);
DESERIALIZE_MEMBER(LightmapIndex, Lightmap.TextureIndex);
DESERIALIZE_MEMBER(LightmapArea, Lightmap.UVsArea);
DESERIALIZE_MEMBER(Buffer, Entries);
{
const auto member = stream.FindMember("VertexColors");
if (member != stream.MemberEnd() && member->value.IsArray())
{
// TODO: don't stall but just check the length of the loaded vertex colors arrays size later when asset gets loaded
if (Model && !Model->WaitForLoaded())
{
RemoveVertexColors();
auto& array = member->value;
_vertexColorsCount = array.Size();
Array<byte> decodedData;
if (_vertexColorsCount == Model->GetLODsCount())
{
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
{
_vertexColorsBuffer[lodIndex] = nullptr;
auto& vertexColorsData = _vertexColorsData[lodIndex];
vertexColorsData.Clear();
auto& v = array[lodIndex];
if (v.IsString())
{
Encryption::Base64Decode(v.GetString(), v.GetStringLength(), decodedData);
const int32 length = decodedData.Count() / sizeof(Color32);
vertexColorsData.Resize(length);
Platform::MemoryCopy(vertexColorsData.Get(), decodedData.Get(), decodedData.Count());
}
}
}
else
{
LOG(Error, "Loaded vertex colors data for {0} has different size than the model {1} LODs count.", ToString(), Model->ToString());
}
_vertexColorsDirty = true;
}
}
}
// [Deprecated on 11.10.2019, expires on 11.10.2020]
if (modifier->EngineBuild <= 6187)
{
const auto member = stream.FindMember("HiddenShadow");
if (member != stream.MemberEnd() && member->value.IsBool() && member->value.GetBool())
{
MARK_CONTENT_DEPRECATED();
_drawModes = DrawPass::Depth;
}
}
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
{
MARK_CONTENT_DEPRECATED();
_drawModes |= DrawPass::GlobalSDF;
}
// [Deprecated on 27.04.2022, expires on 27.04.2024]
if (modifier->EngineBuild <= 6331)
{
MARK_CONTENT_DEPRECATED();
_drawModes |= DrawPass::GlobalSurfaceAtlas;
}
{
const auto member = stream.FindMember("RenderPasses");
if (member != stream.MemberEnd() && member->value.IsInt())
{
_drawModes = (DrawPass)member->value.GetInt();
}
}
}
const Span<MaterialSlot> StaticModel::GetMaterialSlots() const
{
const auto model = Model.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
{
if (!Model || Model->WaitForLoaded())
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Model->MaterialSlots.Count(), nullptr);
MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr;
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultMaterial();
}
return material;
}
ModelBase* StaticModel::GetModel()
{
return Model.Get();
}
bool StaticModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{
auto model = Model.Get();
if (!model || !model->IsInitialized() || model->GetLoadedLODs() == 0)
return false;
// Find mesh in the highest loaded LOD that is using the given material slot index and ray hits it
auto& meshes = model->LODs[model->HighestResidentLODIndex()].Meshes;
for (int32 i = 0; i < meshes.Count(); i++)
{
const auto& mesh = meshes[i];
if (mesh.GetMaterialSlotIndex() == entryIndex && mesh.Intersects(ray, _transform, distance, normal))
return true;
}
distance = 0;
normal = Vector3::Up;
return false;
}
bool StaticModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex)
{
auto model = Model.Get();
if (!model || !model->IsInitialized() || model->GetLoadedLODs() == 0)
return false;
// Find mesh in the highest loaded LOD that is using the given material slot index and ray hits it
bool result = false;
Real closest = MAX_Real;
Vector3 closestNormal = Vector3::Up;
int32 closestEntry = -1;
auto& meshes = model->LODs[model->HighestResidentLODIndex()].Meshes;
for (int32 i = 0; i < meshes.Count(); i++)
{
// Test intersection with mesh and check if is closer than previous
const auto& mesh = meshes[i];
Real dst;
Vector3 nrm;
if (mesh.Intersects(ray, _transform, dst, nrm) && dst < closest)
{
result = true;
closest = dst;
closestNormal = nrm;
closestEntry = mesh.GetMaterialSlotIndex();
}
}
distance = closest;
normal = closestNormal;
entryIndex = closestEntry;
return result;
}
bool StaticModel::GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const
{
count = 0;
if (ref.LODIndex < 0 || ref.MeshIndex < 0)
return true;
const auto model = Model.Get();
if (!model || model->WaitForLoaded())
return true;
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
return mesh.DownloadDataCPU(type, result, count, layout);
}
MeshBase* StaticModel::GetMesh(const MeshReference& ref) const
{
const auto model = Model.Get();
if (!model || model->WaitForLoaded())
return nullptr;
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
return &mesh;
}
MeshDeformation* StaticModel::GetMeshDeformation() const
{
if (!_deformation)
_deformation = New<MeshDeformation>();
return _deformation;
}
void StaticModel::OnEnable()
{
// If model is set and loaded but we still don't have residency registered do it here (eg. model is streaming LODs right now)
if (_scene && _sceneRenderingKey == -1 && !_residencyChangedModel && Model && Model->IsLoaded())
{
// Register for rendering but once the model has any LOD loaded
if (Model->GetLoadedLODs() == 0)
{
_residencyChangedModel = Model;
_residencyChangedModel->ResidencyChanged.Bind<StaticModel, &StaticModel::OnModelResidencyChanged>(this);
}
else
{
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
}
}
// Skip ModelInstanceActor (add to SceneRendering manually)
Actor::OnEnable();
}
void StaticModel::OnDisable()
{
// Skip ModelInstanceActor (add to SceneRendering manually)
Actor::OnDisable();
if (_sceneRenderingKey != -1)
{
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
}
if (_residencyChangedModel)
{
_residencyChangedModel->ResidencyChanged.Unbind<StaticModel, &StaticModel::OnModelResidencyChanged>(this);
_residencyChangedModel = nullptr;
}
}
void StaticModel::WaitForModelLoad()
{
if (Model)
Model->WaitForLoaded();
}