683 lines
22 KiB
C++
683 lines
22 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "StaticModel.h"
|
|
#include "Engine/Engine/Engine.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/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)
|
|
{
|
|
_drawCategory = SceneRendering::SceneDrawAsync;
|
|
Model.Changed.Bind<StaticModel, &StaticModel::OnModelChanged>(this);
|
|
Model.Loaded.Bind<StaticModel, &StaticModel::OnModelLoaded>(this);
|
|
}
|
|
|
|
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 (Math::NearEqual(_boundsScale, value))
|
|
return;
|
|
|
|
_boundsScale = value;
|
|
UpdateBounds();
|
|
}
|
|
|
|
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 = (int16)Math::Clamp<int32>(value, MIN_int16, MAX_int16);
|
|
}
|
|
|
|
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 = Entries[materialSlotIndex].Material.Get();
|
|
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::OnModelChanged()
|
|
{
|
|
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::OnModelLoaded()
|
|
{
|
|
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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count())))
|
|
break;
|
|
}
|
|
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
|
|
}
|
|
else
|
|
{
|
|
SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer);
|
|
}
|
|
}
|
|
RenderContext::GPULocker.Unlock();
|
|
}
|
|
|
|
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))
|
|
GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _transform, _box);
|
|
return;
|
|
}
|
|
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
|
|
{
|
|
if (EnumHasAnyFlags(DrawModes, DrawPass::GlobalSurfaceAtlas))
|
|
GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _transform, Model->LODs.Last().GetBox());
|
|
return;
|
|
}
|
|
Matrix world;
|
|
GetLocalToWorldMatrix(world);
|
|
renderContext.View.GetWorldMatrix(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;
|
|
|
|
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();
|
|
Matrix world;
|
|
GetLocalToWorldMatrix(world);
|
|
renderContext.View.GetWorldMatrix(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;
|
|
|
|
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(DrawModes);
|
|
|
|
if (HasLightmap()
|
|
#if USE_EDITOR
|
|
&& !PrefabManager::IsCreatingPrefab
|
|
#endif
|
|
)
|
|
{
|
|
stream.JKEY("LightmapIndex");
|
|
stream.Int(Lightmap.TextureIndex);
|
|
|
|
stream.JKEY("LightmapArea");
|
|
stream.Rectangle(Lightmap.UVsArea);
|
|
}
|
|
|
|
stream.JKEY("Buffer");
|
|
stream.Object(&Entries, other ? &other->Entries : nullptr);
|
|
|
|
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(DrawModes);
|
|
DESERIALIZE_MEMBER(LightmapIndex, Lightmap.TextureIndex);
|
|
DESERIALIZE_MEMBER(LightmapArea, Lightmap.UVsArea);
|
|
|
|
Entries.DeserializeIfExists(stream, "Buffer", modifier);
|
|
|
|
{
|
|
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())
|
|
{
|
|
DrawModes = DrawPass::Depth;
|
|
}
|
|
}
|
|
// [Deprecated on 07.02.2022, expires on 07.02.2024]
|
|
if (modifier->EngineBuild <= 6330)
|
|
DrawModes |= DrawPass::GlobalSDF;
|
|
// [Deprecated on 27.04.2022, expires on 27.04.2024]
|
|
if (modifier->EngineBuild <= 6331)
|
|
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 < Entries.Count(), nullptr);
|
|
MaterialBase* material = Entries[entryIndex].Material.Get();
|
|
if (!material && entryIndex < Model->MaterialSlots.Count())
|
|
{
|
|
material = Model->MaterialSlots[entryIndex].Material.Get();
|
|
if (!material)
|
|
material = GPUDevice::Instance->GetDefaultMaterial();
|
|
}
|
|
return material;
|
|
}
|
|
|
|
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& mesh, MeshBufferType type, BytesContainer& result, int32& count) const
|
|
{
|
|
count = 0;
|
|
if (mesh.LODIndex < 0 || mesh.MeshIndex < 0)
|
|
return true;
|
|
const auto model = Model.Get();
|
|
if (!model || model->WaitForLoaded())
|
|
return true;
|
|
auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)];
|
|
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
|
|
}
|
|
|
|
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();
|
|
}
|