You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp

362 lines
14 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Atom/RPI.Reflect/Model/ModelKdTree.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzCore/std/limits.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace RPI
{
const char* ModelAsset::DisplayName = "ModelAsset";
const char* ModelAsset::Group = "Model";
const char* ModelAsset::Extension = "azmodel";
void ModelAsset::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ModelAsset, Data::AssetData>()
->Version(0)
->Field("Name", &ModelAsset::m_name)
->Field("Aabb", &ModelAsset::m_aabb)
->Field("LodAssets", &ModelAsset::m_lodAssets)
;
}
}
ModelAsset::ModelAsset()
{
// c-tor and d-tor have to be defined in .cpp in order to have AZStd::unique_ptr<ModelKdTree> without having to include the header of KDTree
}
ModelAsset::~ModelAsset()
{
// c-tor and d-tor have to be defined in .cpp in order to have AZStd::unique_ptr<ModelKdTree> without having to include the header of KDTree
}
const Name& ModelAsset::GetName() const
{
return m_name;
}
const Aabb& ModelAsset::GetAabb() const
{
return m_aabb;
}
RPI::ModelMaterialSlotMap ModelAsset::GetModelMaterialSlots() const
{
RPI::ModelMaterialSlotMap slotMap;
for (const Data::Asset<AZ::RPI::ModelLodAsset>& lod : GetLodAssets())
{
for (const AZ::RPI::ModelMaterialSlot& materialSlot : lod->GetMaterialSlots())
{
auto iter = slotMap.find(materialSlot.m_stableId);
if (iter == slotMap.end())
{
slotMap.emplace(materialSlot.m_stableId, materialSlot);
}
else
{
AZ_Assert(materialSlot.m_displayName == iter->second.m_displayName && materialSlot.m_defaultMaterialAsset.GetId() == iter->second.m_defaultMaterialAsset.GetId(),
"Multiple LODs have mismatched data for the same material slot.");
}
}
}
return slotMap;
}
size_t ModelAsset::GetLodCount() const
{
return m_lodAssets.size();
}
AZStd::array_view<Data::Asset<ModelLodAsset>> ModelAsset::GetLodAssets() const
{
return AZStd::array_view<Data::Asset<ModelLodAsset>>(m_lodAssets);
}
void ModelAsset::SetReady()
{
m_status = Data::AssetData::AssetStatus::Ready;
}
bool ModelAsset::LocalRayIntersectionAgainstModel(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, bool allowBruteForce,
float& distanceNormalized, AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender);
if (!m_modelTriangleCount)
{
// [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
m_modelTriangleCount = CalculateTriangleCount();
}
// check the total vertex count for this model and skip kd-tree if the model is simple enough
if (*m_modelTriangleCount > s_minimumModelTriangleCountToOptimize)
{
if (!m_kdTree)
{
BuildKdTree();
AZ_WarningOnce("Model", false, "ray intersection against a model that is still creating spatial information");
return allowBruteForce ? BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal) : false;
}
else
{
return m_kdTree->RayIntersection(rayStart, rayDir, distanceNormalized, normal);
}
}
return BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal);
}
void ModelAsset::BuildKdTree() const
{
AZStd::lock_guard<AZStd::mutex> lock(m_kdTreeLock);
if (m_isKdTreeCalculationRunning == false)
{
m_isKdTreeCalculationRunning = true;
// ModelAsset can go away while the job is queued up or is in progress, keep it alive until the job is done
const_cast<ModelAsset*>(this)->Acquire();
// [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
// This is a temporary workaround to enable interactive Editor experience.
// For runtime approach is to do this during asset processing and serialized spatial information alongside with mesh model assets
const auto jobLambda = [&]() -> void
{
AZ_TRACE_METHOD();
AZStd::unique_ptr<ModelKdTree> tree = AZStd::make_unique<ModelKdTree>();
tree->Build(this);
AZStd::lock_guard<AZStd::mutex> jobLock(m_kdTreeLock);
m_isKdTreeCalculationRunning = false;
m_kdTree = AZStd::move(tree);
const_cast<ModelAsset*>(this)->Release();
};
Job* executeGroupJob = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
executeGroupJob->Start();
}
}
bool ModelAsset::BruteForceRayIntersect(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
// brute force - check every triangle
if (GetLodAssets().empty() == false)
{
// intersect against the highest level of detail
if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
{
bool anyHit = false;
AZ::Vector3 intersectionNormal;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
{
float currentDistanceNormalized;
if (LocalRayIntersectionAgainstMesh(mesh, rayStart, rayDir, currentDistanceNormalized, intersectionNormal))
{
anyHit = true;
if (currentDistanceNormalized < shortestDistanceNormalized)
{
normal = intersectionNormal;
shortestDistanceNormalized = currentDistanceNormalized;
}
}
}
if (anyHit)
{
distanceNormalized = shortestDistanceNormalized;
}
return anyHit;
}
}
return false;
}
bool ModelAsset::LocalRayIntersectionAgainstMesh(
const ModelLodAsset::Mesh& mesh,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const
{
const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView();
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
// find position semantic
const ModelLodAsset::Mesh::StreamBufferInfo* positionBuffer = nullptr;
for (const ModelLodAsset::Mesh::StreamBufferInfo& bufferInfo : streamBufferList)
{
if (bufferInfo.m_semantic.m_name == m_positionName)
{
positionBuffer = &bufferInfo;
break;
}
}
if (positionBuffer && positionBuffer->m_bufferAssetView.GetBufferAsset().Get())
{
BufferAsset* bufferAssetViewPtr = positionBuffer->m_bufferAssetView.GetBufferAsset().Get();
BufferAsset* indexAssetViewPtr = indexBufferView.GetBufferAsset().Get();
if (!bufferAssetViewPtr || !indexAssetViewPtr)
{
return false;
}
RHI::BufferViewDescriptor positionBufferViewDesc = bufferAssetViewPtr->GetBufferViewDescriptor();
AZStd::array_view<uint8_t> positionRawBuffer = bufferAssetViewPtr->GetBuffer();
const uint32_t positionElementSize = positionBufferViewDesc.m_elementSize;
const uint32_t positionElementCount = positionBufferViewDesc.m_elementCount;
// Position is 3 floats
if (positionElementSize != sizeof(float) * 3)
{
AZ_Warning("ModelAsset", false, "unsupported mesh posiiton format, only full 3 floats per vertex are supported at the moment");
return false;
}
AZStd::array_view<uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer();
RHI::BufferViewDescriptor indexRawDesc = indexAssetViewPtr->GetBufferViewDescriptor();
bool anyHit = false;
const AZ::Vector3 rayEnd = rayStart + rayDir;
AZ::Vector3 a, b, c;
AZ::Vector3 intersectionNormal;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(indexRawBuffer.data());
for (uint32_t indexIter = 0; indexIter <= indexRawDesc.m_elementCount - 3; indexIter += 3, indexPtr += 3)
{
AZ::u32 index0 = indexPtr[0];
AZ::u32 index1 = indexPtr[1];
AZ::u32 index2 = indexPtr[2];
if (index0 >= positionElementCount || index1 >= positionElementCount || index2 >= positionElementCount)
{
AZ_Warning("ModelAsset", false, "mesh has a bad vertex index");
return false;
}
const float* p = reinterpret_cast<const float*>(&positionRawBuffer[index0 * positionElementSize]);
a.Set(const_cast<float*>(p)); // faster than AZ::Vector3 c-tor
p = reinterpret_cast<const float*>(&positionRawBuffer[index1 * positionElementSize]);
b.Set(const_cast<float*>(p));
p = reinterpret_cast<const float*>(&positionRawBuffer[index2 * positionElementSize]);
c.Set(const_cast<float*>(p));
float currentDistanceNormalized;
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, intersectionNormal, currentDistanceNormalized))
{
anyHit = true;
if (currentDistanceNormalized < shortestDistanceNormalized)
{
normal = intersectionNormal;
shortestDistanceNormalized = currentDistanceNormalized;
}
}
}
if (anyHit)
{
distanceNormalized = shortestDistanceNormalized;
}
return anyHit;
}
return false;
}
AZStd::size_t ModelAsset::CalculateTriangleCount() const
{
AZStd::size_t modelTriangleCount = 0;
if (GetLodAssets().empty() == false)
{
if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
{
for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
{
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
// find position semantic
const ModelLodAsset::Mesh::StreamBufferInfo* positionBuffer = nullptr;
for (const ModelLodAsset::Mesh::StreamBufferInfo& bufferInfo : streamBufferList)
{
if (bufferInfo.m_semantic.m_name == m_positionName)
{
positionBuffer = &bufferInfo;
break;
}
}
if (positionBuffer)
{
const RHI::BufferViewDescriptor& desc = positionBuffer->m_bufferAssetView.GetBufferViewDescriptor();
modelTriangleCount += desc.m_elementCount / 3;
}
}
}
}
AZ_Warning("Model", modelTriangleCount < ((2<<23) / 3), "Model has too many vertices for the spatial optimization. Currently only up to 16,777,216 is supported");
return modelTriangleCount;
}
bool ModelAssetHandler::HasConflictingProducts(const AZStd::vector<AZ::Data::AssetType>& productAssetTypes) const
{
size_t modelAssetCount = 0;
size_t actorAssetCount = 0;
for (const AZ::Data::AssetType& assetType : productAssetTypes)
{
if (assetType == azrtti_typeid<ModelAsset>())
{
modelAssetCount++;
}
else if (assetType == AZ::Data::AssetType("{F67CC648-EA51-464C-9F5D-4A9CE41A7F86}")) // ActorAsset
{
actorAssetCount++;
}
}
// When dropping a well-defined character, consisting of a mesh and a skeleton/actor,
// do not create an entity with a mesh component.
return modelAssetCount == 1 && actorAssetCount == 1;
}
} // namespace RPI
} // namespace AZ