Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,300 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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/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;
}
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& dir, float& distance) 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 kdtree 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 false;
}
else
{
return m_kdTree->RayIntersection(rayStart, dir, distance);
}
}
return BruteForceRayIntersect(rayStart, dir, distance);
}
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& dir, float& distance) const
{
// brute force - check every triangle
if (GetLodAssets().empty() == false)
{
// intersect against the highest level of detail
if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
{
float shortestDistance = std::numeric_limits<float>::max();
bool anyHit = false;
for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
{
if (LocalRayIntersectionAgainstMesh(mesh, rayStart, dir, distance))
{
anyHit = true;
shortestDistance = AZ::GetMin(distance, shortestDistance);
}
}
if (anyHit)
{
distance = shortestDistance;
}
return anyHit;
}
}
return false;
}
bool ModelAsset::LocalRayIntersectionAgainstMesh(const ModelLodAsset::Mesh& mesh, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance) 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();
float closestNormalizedDistance = 1.f;
bool anyHit = false;
const AZ::Vector3 rayEnd = rayStart + dir * distance;
AZ::Vector3 a, b, c;
AZ::Vector3 normal;
float normalizedDistance = 1.f;
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));
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, normal, normalizedDistance))
{
closestNormalizedDistance = AZ::GetMin(closestNormalizedDistance, normalizedDistance);
anyHit = true;
}
}
if (anyHit)
{
distance = closestNormalizedDistance * distance;
}
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;
}
} //namespace RPI
} // namespace AZ
@@ -0,0 +1,68 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
#include <AzCore/Asset/AssetManager.h>
namespace AZ
{
namespace RPI
{
void ModelAssetCreator::Begin(const Data::AssetId& assetId)
{
BeginCommon(assetId);
m_modelAabb = Aabb::CreateNull();
}
void ModelAssetCreator::SetName(AZStd::string_view name)
{
if (ValidateIsReady())
{
m_asset->m_name = name;
}
}
void ModelAssetCreator::AddLodAsset(Data::Asset<ModelLodAsset>&& lodAsset)
{
if (ValidateIsReady())
{
m_asset->m_lodAssets.push_back(AZStd::move(lodAsset));
m_modelAabb.AddAabb(m_asset->m_lodAssets.back()->GetAabb());
}
}
bool ModelAssetCreator::End(Data::Asset<ModelAsset>& result)
{
if (!ValidateIsReady())
{
return false;
}
if (m_asset->GetLodCount() == 0)
{
ReportError("No valid ModelLodAssets have been added to this ModelAsset.");
return false;
}
// Create Model Aabb as it wraps all ModelLod Aabbs
for (const auto& modelLodAsset : m_asset->GetLodAssets())
{
m_asset->m_aabb.AddAabb(modelLodAsset->GetAabb());
}
m_asset->SetReady();
return EndCommon(result);
}
} // namespace RPI
} // namespace AZ
@@ -0,0 +1,331 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Model/ModelKdTree.h>
#include <AzCore/Math/IntersectSegment.h>
namespace AZ
{
namespace RPI
{
ModelKdTree::ESplitAxis ModelKdTree::SearchForBestSplitAxis(const AZ::Aabb& aabb, float& splitPosition)
{
const float xsize = aabb.GetXExtent();
const float ysize = aabb.GetYExtent();
const float zsize = aabb.GetZExtent();
ModelKdTree::ESplitAxis axis;
if (xsize >= ysize && xsize >= zsize)
{
axis = ModelKdTree::eSA_X;
splitPosition = aabb.GetMin().GetX() + xsize * 0.5f;
}
else if (ysize >= zsize && ysize >= xsize)
{
axis = ModelKdTree::eSA_Y;
splitPosition = aabb.GetMin().GetY() + ysize * 0.5f;
}
else
{
axis = ModelKdTree::eSA_Z;
splitPosition = aabb.GetMin().GetZ() + zsize * 0.5f;
}
return axis;
}
bool ModelKdTree::SplitNode(const AZ::Aabb& boundbox, const AZStd::vector<AZ::u32>& indices, ModelKdTree::ESplitAxis splitAxis, float splitPos, SSplitInfo& outInfo)
{
if (splitAxis != ModelKdTree::eSA_X && splitAxis != ModelKdTree::eSA_Y && splitAxis != ModelKdTree::eSA_Z)
{
return false;
}
outInfo.m_aboveBoundbox = boundbox;
outInfo.m_belowBoundbox = boundbox;
{
Vector3 maxBound = outInfo.m_aboveBoundbox.GetMax();
maxBound.SetElement(splitAxis, splitPos);
outInfo.m_aboveBoundbox.SetMax(maxBound);
}
{
Vector3 minBound = outInfo.m_belowBoundbox.GetMin();
minBound.SetElement(splitAxis, splitPos);
outInfo.m_belowBoundbox.SetMin(minBound);
}
const AZ::u32 iIndexSize = aznumeric_cast<AZ::u32>(indices.size());
outInfo.m_aboveIndices.reserve(iIndexSize);
outInfo.m_belowIndices.reserve(iIndexSize);
AZStd::array<AZ::Vector3, 3> triangleVertex;
for (AZ::u32 i = 0; i <= iIndexSize - 3; i += 3)
{
const AZ::u32 nObjIndex = (indices[i] & 0xFF000000) >> 24; // asuming that all 3 verices belong to the same triangle from the same object
const AZ::u32 nVertexIndices[3] = { indices[i] & 0xFFFFFF, indices[i + 1] & 0xFFFFFF, indices[i + 2] & 0xFFFFFF };
const AZStd::array_view<float>& positionBuffer = m_meshes[nObjIndex].m_vertexData;
if (positionBuffer.empty() == false)
{
for (AZStd::size_t triangleVertexIndex = 0; triangleVertexIndex < triangleVertex.size(); ++triangleVertexIndex)
{
triangleVertex[triangleVertexIndex].Set(const_cast<float*>(positionBuffer.data() + 3 * nVertexIndices[triangleVertexIndex]));
}
}
else
{
continue;
}
if (triangleVertex[0].GetElement(splitAxis) < splitPos || triangleVertex[1].GetElement(splitAxis) < splitPos || triangleVertex[2].GetElement(splitAxis) < splitPos)
{
outInfo.m_aboveIndices.push_back(indices[i + 0]);
outInfo.m_aboveIndices.push_back(indices[i + 1]);
outInfo.m_aboveIndices.push_back(indices[i + 2]);
}
if (triangleVertex[0].GetElement(splitAxis) >= splitPos || triangleVertex[1].GetElement(splitAxis) >= splitPos || triangleVertex[2].GetElement(splitAxis) >= splitPos)
{
outInfo.m_belowIndices.push_back(indices[i + 0]);
outInfo.m_belowIndices.push_back(indices[i + 1]);
outInfo.m_belowIndices.push_back(indices[i + 2]);
}
}
if (indices.size() == outInfo.m_aboveIndices.size() || indices.size() == outInfo.m_belowIndices.size())
{
// triangles are too close to cut any further
return false;
}
return true;
}
bool ModelKdTree::Build(const ModelAsset* model)
{
if (model == nullptr)
{
return false;
}
ConstructMeshList(model, AZ::Transform::CreateIdentity());
AZ::Aabb entireBoundBox;
entireBoundBox.SetNull();
// indices with object ids
AZStd::vector<AZ::u32> indices;
int totalSizeNeed = 0;
for (const MeshData& data : m_meshes)
{
totalSizeNeed += data.m_mesh->GetVertexCount();
}
indices.reserve(totalSizeNeed);
AZ::Vector3 vertex;
for (AZ::u32 meshIndex = 0, meshCount = aznumeric_cast<AZ::u32>(m_meshes.size()); meshIndex < meshCount; ++meshIndex)
{
AZStd::array_view<float> positionBuffer = m_meshes[meshIndex].m_vertexData;
if (positionBuffer.empty() == false)
{
const int nVertexCount = m_meshes[meshIndex].m_mesh->GetVertexCount();
for (int k = 0; k < nVertexCount; ++k)
{
vertex.Set(const_cast<float*>((positionBuffer.data() + 3 * k)));
entireBoundBox.AddPoint(vertex);
indices.push_back((meshIndex << 24) | k);
}
}
}
m_pRootNode = AZStd::make_unique<ModelKdTreeNode>();
BuildRecursively(m_pRootNode.get(), entireBoundBox, indices);
return true;
}
AZStd::array_view<float> ModelKdTree::GetPositionsBuffer(const ModelLodAsset::Mesh& mesh)
{
const AZStd::array_view<uint8_t> positionRawBuffer = mesh.GetSemanticBuffer(m_positionName);
if (positionRawBuffer.empty() == false)
{
AZStd::array_view<float> floatBuffer(reinterpret_cast<const float*>(positionRawBuffer.data()), positionRawBuffer.size() / 12);
return floatBuffer;
}
AZ_Warning("ModelKdTree", false, "Could not find position buffers in a mesh");
return {};
}
void ModelKdTree::BuildRecursively(ModelKdTreeNode* pNode, const AZ::Aabb& boundbox, AZStd::vector<AZ::u32>& indices)
{
pNode->SetBoundBox(boundbox);
if (indices.size() <= s_MinimumVertexSizeInLeafNode)
{
pNode->SetVertexIndexBuffer(AZStd::move(indices));
return;
}
float splitPos(0);
const ESplitAxis splitAxis = SearchForBestSplitAxis(boundbox, splitPos);
pNode->SetSplitAxis(splitAxis);
pNode->SetSplitPos(splitPos);
SSplitInfo splitInfo;
if (!SplitNode(boundbox, indices, splitAxis, splitPos, splitInfo))
{
pNode->SetVertexIndexBuffer(AZStd::move(indices));
return;
}
if (splitInfo.m_aboveIndices.empty() || splitInfo.m_belowIndices.empty())
{
pNode->SetVertexIndexBuffer(AZStd::move(indices));
return;
}
pNode->SetChild(0, AZStd::make_unique<ModelKdTreeNode>());
pNode->SetChild(1, AZStd::make_unique<ModelKdTreeNode>());
BuildRecursively(pNode->GetChild(0), splitInfo.m_aboveBoundbox, splitInfo.m_aboveIndices);
BuildRecursively(pNode->GetChild(1), splitInfo.m_belowBoundbox, splitInfo.m_belowIndices);
}
void ModelKdTree::ConstructMeshList(const ModelAsset* model, [[maybe_unused]] const AZ::Transform& matParent)
{
if (model == nullptr)
{
return;
}
if (model->GetLodAssets().empty() == false)
{
if (ModelLodAsset* loadAssetPtr = model->GetLodAssets()[0].Get())
{
for (const ModelLodAsset::Mesh& data : loadAssetPtr->GetMeshes())
{
m_meshes.push_back({ &data, GetPositionsBuffer(data) });
}
}
}
}
bool ModelKdTree::RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance) const
{
return RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, distance);
}
bool ModelKdTree::RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance) const
{
if (!pNode)
{
return false;
}
float start, end;
if (AZ::Intersect::IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == Intersect::ISECT_RAY_AABB_NONE)
{
return false;
}
if (pNode->IsLeaf())
{
if (m_meshes.empty())
{
return false;
}
const AZ::u32 nVBuffSize = pNode->GetVertexBufferSize();
if (nVBuffSize == 0)
{
return false;
}
AZ::Vector3 ignoreNormal;
float hitDistanceNormalized;
const float maxDist(FLT_MAX);
float nearestDist = maxDist;
for (AZ::u32 i = 0; i <= nVBuffSize - 3; i += 3)
{
const AZ::u32 nVertexIndex = pNode->GetVertexIndex(i);
const AZ::u32 nObjIndex = pNode->GetObjIndex(i);
AZStd::array_view<float> positionBuffer = m_meshes[nObjIndex].m_vertexData;
AZStd::array<AZ::Vector3, 3> trianglePoints;
if (positionBuffer.empty() == false)
{
trianglePoints[0].Set(const_cast<float*>(positionBuffer.data() + 3 * nVertexIndex));
trianglePoints[1].Set(const_cast<float*>(positionBuffer.data() + 3 * pNode->GetVertexIndex(i + 1)));
trianglePoints[2].Set(const_cast<float*>(positionBuffer.data() + 3 * pNode->GetVertexIndex(i + 2)));
}
else
{
continue;
}
const AZ::Vector3 rayEnd = raySrc + rayDir * distance;
if (AZ::Intersect::IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2],
ignoreNormal, hitDistanceNormalized) != Intersect::ISECT_RAY_AABB_NONE)
{
float hitDistance = hitDistanceNormalized * distance;
nearestDist = AZStd::GetMin(nearestDist, hitDistance);
}
}
if (nearestDist < maxDist)
{
distance = AZStd::GetMin(distance, nearestDist);
return true;
}
return false;
}
// running both sides to find the closest intersection
const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distance);
const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distance);
return bFoundChild0 || bFoundChild1;
}
void ModelKdTree::GetPenetratedBoxes(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector<AZ::Aabb>& outBoxes)
{
GetPenetratedBoxesRecursively(m_pRootNode.get(), raySrc, rayDir, outBoxes);
}
void ModelKdTree::GetPenetratedBoxesRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector<AZ::Aabb>& outBoxes)
{
AZ::Vector3 ignoreNormal;
float ignore;
if (!pNode || (!pNode->GetBoundBox().Contains(raySrc) &&
(AZ::Intersect::IntersectRayAABB(raySrc, rayDir, rayDir.GetReciprocal(), pNode->GetBoundBox(),
ignore, ignore, ignoreNormal)) == Intersect::ISECT_RAY_AABB_NONE))
{
return;
}
outBoxes.push_back(pNode->GetBoundBox());
GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes);
GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes);
}
}
}
@@ -0,0 +1,159 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace RPI
{
const char* ModelLodAsset::DisplayName = "ModelLodAsset";
const char* ModelLodAsset::Group = "Model";
const char* ModelLodAsset::Extension = "azlod";
void ModelLodAsset::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ModelLodAsset>()
->Version(0)
->Field("Meshes", &ModelLodAsset::m_meshes)
->Field("Aabb", &ModelLodAsset::m_aabb)
;
}
Mesh::Reflect(context);
}
void ModelLodAsset::Mesh::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ModelLodAsset::Mesh>()
->Version(0)
->Field("Material", &ModelLodAsset::Mesh::m_materialAsset)
->Field("Name", &ModelLodAsset::Mesh::m_name)
->Field("AABB", &ModelLodAsset::Mesh::m_aabb)
->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView)
->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo)
;
}
StreamBufferInfo::Reflect(context);
}
void ModelLodAsset::Mesh::StreamBufferInfo::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ModelLodAsset::Mesh::StreamBufferInfo>()
->Version(1)
->Field("Semantic", &ModelLodAsset::Mesh::StreamBufferInfo::m_semantic)
->Field("CustomName", &ModelLodAsset::Mesh::StreamBufferInfo::m_customName)
->Field("BufferAssetView", &ModelLodAsset::Mesh::StreamBufferInfo::m_bufferAssetView)
;
}
}
uint32_t ModelLodAsset::Mesh::GetVertexCount() const
{
// Index 0 here is not special. All stream buffer views owned by this mesh should
// view the same number of vertices. It doesn't make sense to be viewing 30 positions
// but only 20 normals since we're using an index buffer model.
return m_streamBufferInfo[0].m_bufferAssetView.GetBufferViewDescriptor().m_elementCount;
}
uint32_t ModelLodAsset::Mesh::GetIndexCount() const
{
return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount;
}
const Data::Asset <MaterialAsset>& ModelLodAsset::Mesh::GetMaterialAsset() const
{
return m_materialAsset;
}
const AZ::Name& ModelLodAsset::Mesh::GetName() const
{
return m_name;
}
const AZ::Aabb& ModelLodAsset::Mesh::GetAabb() const
{
return m_aabb;
}
const BufferAssetView& ModelLodAsset::Mesh::GetIndexBufferAssetView() const
{
return m_indexBufferAssetView;
}
AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo> ModelLodAsset::Mesh::GetStreamBufferInfoList() const
{
return AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>(m_streamBufferInfo);
}
void ModelLodAsset::AddMesh(const Mesh& mesh)
{
m_meshes.push_back(mesh);
Aabb meshAabb = mesh.GetAabb();
m_aabb.AddAabb(meshAabb);
}
AZStd::array_view<ModelLodAsset::Mesh> ModelLodAsset::GetMeshes() const
{
return AZStd::array_view<ModelLodAsset::Mesh>(m_meshes);
}
const AZ::Aabb& ModelLodAsset::GetAabb() const
{
return m_aabb;
}
const BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const
{
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = GetStreamBufferInfoList();
for (const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo : streamBufferList)
{
if (streamBufferInfo.m_semantic.m_name == semantic)
{
return &streamBufferInfo.m_bufferAssetView;
}
}
return nullptr;
}
AZStd::array_view<uint8_t> ModelLodAsset::Mesh::GetSemanticBuffer(const AZ::Name& semantic) const
{
if (const BufferAssetView* bufferAssetView = GetSemanticBufferAssetView(semantic))
{
if (const BufferAsset* bufferAsset = bufferAssetView->GetBufferAsset().Get())
{
return bufferAsset->GetBuffer();
}
}
return {};
}
void ModelLodAsset::SetReady()
{
m_status = Data::AssetData::AssetStatus::Ready;
}
} // namespace RPI
} // namespace AZ
@@ -0,0 +1,237 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
#include <AzCore/Asset/AssetManager.h>
namespace AZ
{
namespace RPI
{
void ModelLodAssetCreator::Begin(const Data::AssetId& assetId)
{
BeginCommon(assetId);
}
void ModelLodAssetCreator::SetLodIndexBuffer(const Data::Asset<BufferAsset>& bufferAsset)
{
if (ValidateIsReady())
{
m_asset->m_indexBuffer = AZStd::move(bufferAsset);
}
}
void ModelLodAssetCreator::AddLodStreamBuffer(const Data::Asset<BufferAsset>& bufferAsset)
{
if (ValidateIsReady())
{
m_asset->m_streamBuffers.push_back(AZStd::move(bufferAsset));
}
}
void ModelLodAssetCreator::BeginMesh()
{
if (ValidateIsReady())
{
m_currentMesh = ModelLodAsset::Mesh();
m_meshBegan = true;
}
}
void ModelLodAssetCreator::SetMeshName(const AZ::Name& name)
{
if (ValidateIsMeshReady())
{
m_currentMesh.m_name = name;
}
}
void ModelLodAssetCreator::SetMeshAabb(AZ::Aabb&& aabb)
{
if (ValidateIsMeshReady())
{
m_currentMesh.m_aabb = AZStd::move(aabb);
}
}
void ModelLodAssetCreator::SetMeshMaterialAsset(const Data::Asset<MaterialAsset>& materialAsset)
{
if (ValidateIsMeshReady())
{
m_currentMesh.m_materialAsset = materialAsset;
}
}
void ModelLodAssetCreator::SetMeshIndexBuffer(const BufferAssetView& bufferAssetView)
{
if (!ValidateIsMeshReady())
{
return;
}
if (m_currentMesh.m_indexBufferAssetView.GetBufferAsset().Get() != nullptr)
{
ReportError("The current mesh has already had an index buffer set.");
return;
}
m_currentMesh.m_indexBufferAssetView = AZStd::move(bufferAssetView);
}
void ModelLodAssetCreator::AddMeshStreamBuffer(
const RHI::ShaderSemantic& streamSemantic,
const AZ::Name& customName,
const BufferAssetView& bufferAssetView)
{
if (!ValidateIsMeshReady())
{
return;
}
// If this streamId already exists throw an error
for (const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo : m_currentMesh.m_streamBufferInfo)
{
if (streamBufferInfo.m_semantic == streamSemantic || (!streamBufferInfo.m_customName.IsEmpty() && streamBufferInfo.m_customName == customName))
{
ReportError("Failed to add Stream Buffer. Buffer with this streamId or name already exists.");
return;
}
}
ModelLodAsset::Mesh::StreamBufferInfo streamBufferInfo;
streamBufferInfo.m_semantic = streamSemantic;
streamBufferInfo.m_customName = customName;
streamBufferInfo.m_bufferAssetView = AZStd::move(bufferAssetView);
m_currentMesh.m_streamBufferInfo.push_back(AZStd::move(streamBufferInfo));
}
void ModelLodAssetCreator::AddMeshStreamBuffer(
const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo)
{
if (!ValidateIsMeshReady())
{
return;
}
// If this semantic already exists throw an error
for (const ModelLodAsset::Mesh::StreamBufferInfo& existingInfo : m_currentMesh.m_streamBufferInfo)
{
if (existingInfo.m_semantic == streamBufferInfo.m_semantic || (!existingInfo.m_customName.IsEmpty() && existingInfo.m_customName == streamBufferInfo.m_customName))
{
ReportError("Failed to add Stream Buffer. Buffer with this semantic or name already exists.");
return;
}
}
m_currentMesh.m_streamBufferInfo.push_back(AZStd::move(streamBufferInfo));
}
void ModelLodAssetCreator::EndMesh()
{
if (ValidateIsMeshReady() && ValidateMesh(m_currentMesh))
{
m_asset->AddMesh(AZStd::move(m_currentMesh));
m_meshBegan = false;
}
}
bool ModelLodAssetCreator::End(Data::Asset<ModelLodAsset>& result)
{
if (ValidateIsReady() && ValidateIsMeshEnded() && ValidateLod())
{
m_asset->SetReady();
return EndCommon(result);
}
return false;
}
bool ModelLodAssetCreator::ValidateIsMeshReady()
{
if (!ValidateIsReady())
{
return false;
}
if (!m_meshBegan)
{
AZ_Assert(false, "BeginMesh() was not called");
return false;
}
return true;
}
bool ModelLodAssetCreator::ValidateIsMeshEnded()
{
if (m_meshBegan)
{
AZ_Assert(false, "MeshEnd() was not called");
return false;
}
return true;
}
bool ModelLodAssetCreator::ValidateLod()
{
if (m_asset->GetMeshes().empty())
{
ReportError("No meshes have been provided for this LOD");
return false;
}
return true;
}
bool ModelLodAssetCreator::ValidateMesh(const ModelLodAsset::Mesh& mesh)
{
if (mesh.GetVertexCount() == 0)
{
ReportError("Mesh has a vertex count of 0");
return false;
}
if (mesh.GetIndexCount() == 0)
{
ReportError("Mesh has an index count of 0");
return false;
}
if (!mesh.GetAabb().IsValid())
{
ReportError("Mesh does not have a valid Aabb");
return false;
}
if (mesh.m_indexBufferAssetView.GetBufferAsset().Get() == nullptr)
{
ReportError("Mesh does not have a valid index buffer");
return false;
}
for (const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo : mesh.m_streamBufferInfo)
{
if (streamBufferInfo.m_bufferAssetView.GetBufferAsset().Get() == nullptr)
{
ReportError("Mesh has an invalid stream buffer");
return false;
}
}
return true;
}
} // namespace RPI
} // namespace AZ