/* * 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 #include #include #include #include #include #include // 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::FindOrCreate(const Data::Asset& modelAsset) { return Data::InstanceDatabase::Instance().FindOrCreate( Data::InstanceId::CreateFromAssetId(modelAsset.GetId()), modelAsset); } size_t Model::GetLodCount() const { return m_lods.size(); } AZStd::array_view> Model::GetLods() const { return m_lods; } Data::Instance Model::CreateInternal(const Data::Asset& modelAsset) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); Data::Instance 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) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); m_lods.resize(modelAsset->GetLodAssets().size()); for (size_t lodIndex = 0; lodIndex < m_lods.size(); ++lodIndex) { const Data::Asset& lodAsset = modelAsset->GetLodAssets()[lodIndex]; if (!lodAsset) { AZ_Error("Model", false, "Invalid Operation: Last ModelLod is not loaded."); return RHI::ResultCode::Fail; } Data::Instance 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_STALL_DYNAMIC(Debug::ProfileCategory::AzRender, "Model::WaitForUpload - %s", GetDatabaseName()); for (const Data::Instance& lod : m_lods) { lod->WaitForUpload(); } m_isUploadPending = false; } } bool Model::IsUploadPending() const { return m_isUploadPending; } const Data::Asset& 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(Debug::ProfileCategory::AzRender); 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(Debug::ProfileCategory::AzRender); 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& Model::GetUvNames() const { return m_uvNames; } } // namespace RPI } // namespace AZ