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.Public/Model/Model.cpp

197 lines
6.9 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.Public/Model/Model.h>
#include <Atom/RHI/Factory.h>
#include <AzCore/Debug/EventTrace.h>
#include <AtomCore/Instance/InstanceDatabase.h>
#include <AzCore/Debug/Timer.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Math/IntersectSegment.h>
// Enable to show profile logs of how long it takes to raycast against models in the Editor
//#define AZ_RPI_PROFILE_RAYCASTING_AGAINST_MODELS
namespace AZ
{
namespace RPI
{
Data::Instance<Model> Model::FindOrCreate(const Data::Asset<ModelAsset>& modelAsset)
{
return Data::InstanceDatabase<Model>::Instance().FindOrCreate(
Data::InstanceId::CreateFromAssetId(modelAsset.GetId()),
modelAsset);
}
size_t Model::GetLodCount() const
{
return m_lods.size();
}
AZStd::array_view<Data::Instance<ModelLod>> Model::GetLods() const
{
return m_lods;
}
Data::Instance<Model> Model::CreateInternal(const Data::Asset<ModelAsset>& modelAsset)
{
AZ_PROFILE_FUNCTION(RPI);
Data::Instance<Model> model = aznew Model();
const RHI::ResultCode resultCode = model->Init(modelAsset);
if (resultCode == RHI::ResultCode::Success)
{
return model;
}
return nullptr;
}
RHI::ResultCode Model::Init(const Data::Asset<ModelAsset>& modelAsset)
{
AZ_PROFILE_FUNCTION(RPI);
m_lods.resize(modelAsset->GetLodAssets().size());
for (size_t lodIndex = 0; lodIndex < m_lods.size(); ++lodIndex)
{
const Data::Asset<ModelLodAsset>& lodAsset = modelAsset->GetLodAssets()[lodIndex];
if (!lodAsset)
{
AZ_Error("Model", false, "Invalid Operation: Last ModelLod is not loaded.");
return RHI::ResultCode::Fail;
}
Data::Instance<ModelLod> lodInstance = ModelLod::FindOrCreate(lodAsset, modelAsset);
if (lodInstance == nullptr)
{
return RHI::ResultCode::Fail;
}
for (const AZ::RPI::ModelLod::Mesh& mesh : lodInstance->GetMeshes())
{
for (const AZ::RPI::ModelLod::StreamBufferInfo& stream : mesh.m_streamInfo)
{
if (stream.m_semantic.m_name.GetStringView().starts_with(RHI::ShaderSemantic::UvStreamSemantic))
{
// For unnamed UVs, use the semantic instead.
if (stream.m_customName.IsEmpty())
{
m_uvNames.insert(AZ::Name(stream.m_semantic.ToString()));
}
else
{
m_uvNames.insert(stream.m_customName);
}
}
}
}
m_lods[lodIndex] = AZStd::move(lodInstance);
}
m_modelAsset = modelAsset;
m_isUploadPending = true;
return RHI::ResultCode::Success;
}
void Model::WaitForUpload()
{
if (m_isUploadPending)
{
AZ_PROFILE_SCOPE(RPI, "Model::WaitForUpload - %s", GetDatabaseName());
for (const Data::Instance<ModelLod>& lod : m_lods)
{
lod->WaitForUpload();
}
m_isUploadPending = false;
}
}
bool Model::IsUploadPending() const
{
return m_isUploadPending;
}
const Data::Asset<ModelAsset>& Model::GetModelAsset() const
{
return m_modelAsset;
}
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(RPI);
if (!GetModelAsset())
{
AZ_Assert(false, "Invalid Model - not created from a ModelAsset?");
return false;
}
float start;
float end;
const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), GetModelAsset()->GetAabb(), start, end);
if (Intersect::ISECT_RAY_AABB_NONE != result)
{
if (ModelAsset* modelAssetPtr = m_modelAsset.Get())
{
#if defined(AZ_RPI_PROFILE_RAYCASTING_AGAINST_MODELS)
AZ::Debug::Timer timer;
timer.Stamp();
#endif
constexpr bool AllowBruteForce = false;
const bool hit = modelAssetPtr->LocalRayIntersectionAgainstModel(
rayStart, rayDir, AllowBruteForce, distanceNormalized, normal);
#if defined(AZ_RPI_PROFILE_RAYCASTING_AGAINST_MODELS)
if (hit)
{
AZ_Printf("Model", "Model::LocalRayIntersection took %.2f ms", timer.StampAndGetDeltaTimeInSeconds() * 1000.f);
}
#endif
return hit;
}
}
return false;
}
bool Model::RayIntersection(
const AZ::Transform& modelTransform,
const AZ::Vector3& nonUniformScale,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(RPI);
const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale));
const AZ::Transform inverseTM = modelTransform.GetInverse();
const AZ::Vector3 raySrcLocal = inverseTM.TransformPoint(rayStart) / clampedScale;
// Instead of just rotating 'rayDir' we need it to be scaled too, so that 'distanceNormalized' will be in the target units rather
// than object local units.
const AZ::Vector3 rayDest = rayStart + rayDir;
const AZ::Vector3 rayDestLocal = inverseTM.TransformPoint(rayDest) / clampedScale;
const AZ::Vector3 rayDirLocal = rayDestLocal - raySrcLocal;
const bool result = LocalRayIntersection(raySrcLocal, rayDirLocal, distanceNormalized, normal);
normal = (normal * clampedScale).GetNormalized();
return result;
}
const AZStd::unordered_set<AZ::Name>& Model::GetUvNames() const
{
return m_uvNames;
}
} // namespace RPI
} // namespace AZ