Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -0,0 +1,268 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/vector.h>
namespace MCore
{
/**
* A dynamic 2D array template.
* This would be a better solution than "Array< Array< T > >", because the Array inside Array will perform many allocations,
* while this specialized 2D array will only perform two similar allocations.
* What it does is keep one big array of data elements, and maintain a table that indices inside this big array.
* We advise you to call the Shrink function after you performed a number of operations on the array, to maximize its memory usage efficiency.
*
* The layout of the array is as following:
*
* <pre>
*
* [ROW0]: [E0][E1][E2]
* [ROW1]: [E0][E1]
* [ROW2]: [E0][E1][E2][E3]
* [ROW3]: [E0]
*
* </pre>
*
* Where E0, E1, E2, etc are elements of the specified type T.
* Each row can have a different amount of elements that can be added or removed dynamically. Also rows can be deleted
* or added when desired.
*/
template <class T>
class Array2D
{
public:
/**
* An index table entry.
* Each row in the 2D array will get a table entry, which tells us where in the data array
* the element data starts for the given row, and how many elements will follow for the given row.
*/
struct TableEntry
{
size_t mStartIndex; /**< The index offset where the data for this row starts. */
size_t mNumElements; /**< The number of elements to follow. */
};
/**
* The default constructor.
* The number of pre-cached/allocated elements per row is set to a value of 2 on default.
* You can use the SetNumPreCachedElements(...) method to adjust this value. Make sure you adjust this value
* before you call the Resize method though, otherwise it will have no immediate effect.
*/
Array2D() = default;
/**
* Extended constructor which will automatically initialize the array dimensions.
* Basically this will initialize the array dimensions at (numRows x numPreAllocatedElemsPerRow) elements.
* Please note though, that this will NOT add actual elements. So you can't get values from the elements yet.
* This would just pre-allocate data. You have to use the Add method to actually fill the items.
* @param numRows The number of rows the array should have.
* @param numPreAllocatedElemsPerRow The number of pre-cached/allocated elements per row.
*
*/
Array2D(size_t numRows, size_t numPreAllocatedElemsPerRow = 2)
: mNumPreCachedElements(numPreAllocatedElemsPerRow) { Resize(numRows); }
/**
* Resize the array in one dimension (the number of rows).
* Rows that will be added willl automatically get [n] number of elements pre-allocated.
* The number of [n] can be set with the SetNumPreCachedElements(...) method.
* Please note that the pre-allocated/cached elements are not valid to be used yet. You have to use the Add method first.
* @param numRows The number of rows you wish to init for.
* @param autoShrink When set to true, after execution of this method the Shrink method will automatically be called in order
* to optimize the memory usage. This only happens when resizing to a lower amount of rows, so when making the array smaller.
*/
void Resize(size_t numRows, bool autoShrink = false);
/**
* Add an element to the list of elements in a given row.
* @param rowIndex The row number to add the element to.
* @param element The value of the element to add.
*/
void Add(size_t rowIndex, const T& element);
/**
* Remove an element from the array.
* @param rowIndex The row number where the element is stored.
* @param elementIndex The element number inside this row to remove.
*/
void Remove(size_t rowIndex, size_t elementIndex);
/**
* Remove a given row, including all its elements.
* This will decrease the number of rows.
* @param rowIndex The row number to remove.
* @param autoShrink When set to true, the array's memory usage will be optimized and minimized as much as possible.
*/
void RemoveRow(size_t rowIndex, bool autoShrink = false);
/**
* Remove a given range of rows and all their elements.
* All rows from the specified start row until the end row will be removed, with the start and end rows included.
* @param startRow The start row number to start removing from (so this one will also be removed).
* @param endRow The end row number (which will also be removed).
* @param autoShrink When set to true, the array's memory usage will be optimized and minimized as much as possible.
*/
void RemoveRows(size_t startRow, size_t endRow, bool autoShrink = false);
/**
* Optimize (minimize) the memory usage of the array.
* This will move all elements around, removing all gaps and unused pre-cached/allocated items.
* It is advised to call this method after you applied some heavy modifications to the array, such as
* removing rows or many elements. When your array data is fairly static, and you won't be adding or removing
* data from it very frequently, you should definitely call this method after you have filled the array with data.
*/
void Shrink();
/**
* Set the number of elements per row that should be pre-allocated/cached when creating / adding new rows.
* This doesn't actually increase the number of elements for a given row, but just reserves memory for the elements, which can
* speedup adding of new elements and prevent memory reallocs. The default value is set to 2 when creating an array, unless specified differently.
* @param numElemsPerRow The number of elements per row that should be pre-allocated.
*/
void SetNumPreCachedElements(size_t numElemsPerRow) { mNumPreCachedElements = numElemsPerRow; }
/**
* Get the number of pre-cached/allocated elements per row, when creating new rows.
* See the SetNumPreCachedElements for more information.
* @result The number of elements per row that will be pre-allocated/cached when adding a new row.
* @see SetNumPreCachedElements.
*/
size_t GetNumPreCachedElements() const { return mNumPreCachedElements; }
/**
* Get the number of stored elements inside a given row.
* @param rowIndex The row number.
* @result The number of elements stored inside this row.
*/
size_t GetNumElements(size_t rowIndex) const { return mIndexTable[rowIndex].mNumElements; }
/**
* Get a pointer to the element data stored in a given row.
* Use this method with care as it can easily overwrite data from other elements.
* All element data for a given row is stored sequential, so right after eachother in one continuous piece of memory.
* The next row's element data however might not be connected to the memory of row before that!
* Also only use this method when the GetNumElements(...) method for this row returns a value greater than zero.
* @param rowIndex the row number.
* @result A pointer to the element data for the given row.
*/
T* GetElements(size_t rowIndex) { return &mData[ mIndexTable[rowIndex].mStartIndex ]; }
/**
* Get the data of a given element.
* @param rowIndex The row number where the element is stored.
* @param elementNr The element number inside this row to retrieve.
* @result A reference to the element data.
*/
T& GetElement(size_t rowIndex, size_t elementNr) { return mData[ mIndexTable[rowIndex].mStartIndex + elementNr ]; }
/**
* Get the data of a given element.
* @param rowIndex The row number where the element is stored.
* @param elementNr The element number inside this row to retrieve.
* @result A const reference to the element data.
*/
const T& GetElement(size_t rowIndex, size_t elementNr) const { return mData[ mIndexTable[rowIndex].mStartIndex + elementNr ]; }
/**
* Set the value for a given element in the array.
* @param rowIndex The row where the element is stored in.
* @param elementNr The element number to set the value for.
* @param value The value to set the element to.
*/
void SetElement(size_t rowIndex, size_t elementNr, const T& value) { MCORE_ASSERT(rowIndex < mIndexTable.GetLength()); MCORE_ASSERT(elementNr < mIndexTable[rowIndex].mNumElements); mData[ mIndexTable[rowIndex].mStartIndex + elementNr ] = value; }
/**
* Get the number of rows in the 2D array.
* @result The number of rows.
*/
size_t GetNumRows() const { return mIndexTable.size(); }
/**
* Calculate the percentage of memory that is filled with element data.
* When this is 100%, then all allocated element data is filled and used.
* When it would be 25% then only 25% of all allocated element data is used. This is an indication that
* you should most likely use the Shrink method, which will ensure that the memory usage will become 100% again, which
* would be most optimal.
* @result The percentage (in range of 0..100) of used element memory.
*/
float CalcUsedElementMemoryPercentage() const { return (mData.GetLength() ? (CalcTotalNumElements() / (float)mData.GetLength()) * 100.0f : 0); }
/**
* Swap the element data of two rows.
* Beware, this is pretty slow!
* @param rowA The first row.
* @param rowB The second row.
*/
void Swap(size_t rowA, size_t rowB);
/**
* Calculate the total number of used elements.
* A used element is an element that has been added and that has a valid value stored.
* This excludes pre-allocated/cached elements.
* @result The total number of elements stored in the array.
*/
size_t CalcTotalNumElements() const;
/**
* Clear all contents.
* This deletes all rows and clears all their their elements as well.
* Please keep in mind though, that when you have an array of pointers to objects you allocated, that
* you still have to delete those objects by hand! The Clear function will not delete those.
* @param freeMem When set to true, all memory used by the array internally will be deleted. If set to false, the memory
* will not be deleted and can be reused later on again without doing any memory realloc when possible.
*/
void Clear(bool freeMem = true)
{
mIndexTable.clear();
mData.clear();
if (freeMem)
{
mIndexTable.shrink_to_fit();
mData.shrink_to_fit();
}
}
/**
* Log all array contents.
* This will log the number of rows, number of elements, used element memory percentage, as well
* as some details about each row.
*/
void LogContents();
/**
* Get the index table.
* This table describes for each row the start index and number of elements for the row.
* The length of the array equals the value returned by GetNumRows().
* @result The array of index table entries, which specify the start indices and number of entries per row.
*/
AZStd::vector<TableEntry>& GetIndexTable() { return mIndexTable; }
/**
* Get the data array.
* This contains the data array in which the index table points.
* Normally you shouldn't be using this method. However it is useful in some specific cases.
* @result The data array that contains all elements.
*/
AZStd::vector<T>& GetData() { return mData; }
private:
AZStd::vector<T> mData; /**< The element data. */
AZStd::vector<TableEntry> mIndexTable; /**< The index table that let's us know where what data is inside the element data array. */
size_t mNumPreCachedElements = 2; /**< The number of elements per row to pre-allocate when resizing this array. This prevents some re-allocs. */
};
// include inline code
#include "Array2D.inl"
} // namespace MCore
@@ -0,0 +1,293 @@
/*
* 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.
*
*/
// resize the array's number of rows
template <class T>
void Array2D<T>::Resize(size_t numRows, bool autoShrink)
{
// get the current (old) number of rows
const size_t oldNumRows = mIndexTable.size();
// don't do anything when we don't need to
if (numRows == oldNumRows)
{
return;
}
// resize the index table
mIndexTable.resize(numRows);
// check if we decreased the number of rows or not
if (numRows < oldNumRows)
{
// pack memory as tight as possible
if (autoShrink)
{
Shrink();
}
}
else // we added new entries
{
// init the new table entries
for (size_t i = oldNumRows; i < numRows; ++i)
{
mIndexTable[i].mStartIndex = mData.size() + (i * mNumPreCachedElements);
mIndexTable[i].mNumElements = 0;
}
// grow the data array
const size_t numNewRows = numRows - oldNumRows;
mData.resize(mData.size() + numNewRows * mNumPreCachedElements);
}
}
// add an element
template <class T>
void Array2D<T>::Add(size_t rowIndex, const T& element)
{
AZ_Assert(rowIndex < mIndexTable.size(), "Array index out of bounds");
// find the insert location inside the data array
size_t insertPos = mIndexTable[rowIndex].mStartIndex + mIndexTable[rowIndex].mNumElements;
if (insertPos >= mData.size())
{
mData.resize(insertPos + 1);
}
// check if we need to insert for real
bool needRealInsert = true;
if (rowIndex < mIndexTable.size() - 1) // if there are still entries coming after the one we have to add to
{
if (insertPos < mIndexTable[rowIndex + 1].mStartIndex) // if basically there are empty unused element we can use
{
needRealInsert = false; // then we don't need to do any reallocs
}
}
else
{
// if we're dealing with the last row
if (rowIndex == mIndexTable.size() - 1)
{
if (insertPos < mData.size()) // if basically there are empty unused element we can use
{
needRealInsert = false;
}
}
}
// perform the insertion
if (needRealInsert)
{
// insert the element inside the data array
mData.insert(AZStd::next(begin(mData), insertPos), element);
// adjust the index table entries
const size_t numRows = mIndexTable.size();
for (size_t i = rowIndex + 1; i < numRows; ++i)
{
mIndexTable[i].mStartIndex++;
}
}
else
{
mData[insertPos] = element;
}
// increase the number of elements in the index table
mIndexTable[rowIndex].mNumElements++;
}
// remove a given element
template <class T>
void Array2D<T>::Remove(size_t rowIndex, size_t elementIndex)
{
AZ_Assert(rowIndex < mIndexTable.size(), "Array2D<>::Remove: array index out of bounds");
AZ_Assert(elementIndex < mIndexTable[rowIndex].mNumElements, "Array2D<>::Remove: element index out of bounds");
AZ_Assert(mIndexTable[rowIndex].mNumElements > 0, "Array2D<>::Remove: array index out of bounds");
const size_t startIndex = mIndexTable[rowIndex].mStartIndex;
const size_t maxElementIndex = mIndexTable[rowIndex].mNumElements - 1;
// swap the last element with the one to be removed
if (elementIndex != maxElementIndex)
{
mData[startIndex + elementIndex] = mData[startIndex + maxElementIndex];
}
// decrease the number of elements
mIndexTable[rowIndex].mNumElements--;
}
// remove a given row
template <class T>
void Array2D<T>::RemoveRow(size_t rowIndex, bool autoShrink)
{
AZ_Assert(rowIndex < mIndexTable.GetLength(), "Array2D<>::RemoveRow: rowIndex out of bounds");
mIndexTable.Remove(rowIndex);
// optimize memory usage when desired
if (autoShrink)
{
Shrink();
}
}
// remove a set of rows
template <class T>
void Array2D<T>::RemoveRows(size_t startRow, size_t endRow, bool autoShrink)
{
AZ_Assert(startRow < mIndexTable.size(), "Array2D<>::RemoveRows: startRow out of bounds");
AZ_Assert(endRow < mIndexTable.size(), "Array2D<>::RemoveRows: endRow out of bounds");
// check if the start row is smaller than the end row
if (startRow < endRow)
{
const size_t numToRemove = (endRow - startRow) + 1;
mIndexTable.erase(AZStd::next(begin(mIndexTable), startRow), AZStd::next(AZStd::next(begin(mIndexTable), startRow), numToRemove));
}
else // if the end row is smaller than the start row
{
const size_t numToRemove = (startRow - endRow) + 1;
mIndexTable.erase(AZStd::next(begin(mIndexTable), endRow), AZStd::next(AZStd::next(begin(mIndexTable), endRow), numToRemove));
}
// optimize memory usage when desired
if (autoShrink)
{
Shrink();
}
}
// optimize memory usage
template <class T>
void Array2D<T>::Shrink()
{
// for all attributes, except for the last one
const size_t numRows = mIndexTable.size();
if (numRows == 0)
{
return;
}
// remove all unused items between the rows (unused element data per row)
const size_t numRowsMinusOne = numRows - 1;
for (size_t a = 0; a < numRowsMinusOne; ++a)
{
const size_t firstUnusedIndex = mIndexTable[a ].mStartIndex + mIndexTable[a].mNumElements;
const size_t numUnusedElements = mIndexTable[a + 1].mStartIndex - firstUnusedIndex;
// if we have pre-cached/unused elements, remove those by moving memory to remove the "holes"
if (numUnusedElements > 0)
{
// remove the unused elements from the array
mData.erase(AZStd::next(begin(mData), firstUnusedIndex), AZStd::next(AZStd::next(begin(mData), firstUnusedIndex), numUnusedElements));
// change the start indices for all the rows coming after the current one
const size_t numTotalRows = mIndexTable.size();
for (size_t i = a + 1; i < numTotalRows; ++i)
{
mIndexTable[i].mStartIndex -= numUnusedElements;
}
}
}
// now move all start index values and all data to the front of the data array as much as possible
// like data on row 0 starting at data element 7, would be moved to data element 0
size_t dataPos = 0;
for (size_t row = 0; row < numRows; ++row)
{
// if the data starts after the place where it could start, move it to the place where it could start
if (mIndexTable[row].mStartIndex > dataPos)
{
AZStd::move(AZStd::next(begin(mData), this->mIndexTable[row].mStartIndex), AZStd::next(AZStd::next(begin(mData), this->mIndexTable[row].mStartIndex), this->mIndexTable[row].mNumElements), AZStd::next(begin(mData), dataPos));
mIndexTable[row].mStartIndex = dataPos;
}
// increase the data pos
dataPos += mIndexTable[row].mNumElements;
}
// remove all unused data items
if (dataPos < mData.size())
{
mData.erase(AZStd::next(begin(mData), dataPos), end(mData));
}
// shrink the arrays
mData.shrink_to_fit();
mIndexTable.shrink_to_fit();
}
// calculate the number of used elements
template <class T>
size_t Array2D<T>::CalcTotalNumElements() const
{
size_t totalElements = 0;
// add all number of row elements together
const size_t numRows = mIndexTable.size();
for (size_t i = 0; i < numRows; ++i)
{
totalElements += mIndexTable[i].mNumElements;
}
return totalElements;
}
// swap the contents of two rows
template <class T>
void Array2D<T>::Swap(size_t rowA, size_t rowB)
{
// get the original number of elements from both rows
const size_t numElementsA = mIndexTable[rowA].mNumElements;
const size_t numElementsB = mIndexTable[rowB].mNumElements;
// move the element data of rowA into a temp buffer
AZStd::vector<T> tempData(numElementsA);
AZStd::move(
AZStd::next(mData.begin(), mIndexTable[rowA].mStartIndex),
AZStd::next(mData.begin(), mIndexTable[rowA].mStartIndex + numElementsA),
tempData.begin()
);
// remove the elements from rowA
while (GetNumElements(rowA))
{
Remove(rowA, 0);
}
// add all elements of row B
size_t i;
for (i = 0; i < numElementsB; ++i)
{
Add(rowA, GetElement(rowB, i));
}
// remove all elements from B
while (GetNumElements(rowB))
{
Remove(rowB, 0);
}
// add all elements from the original A
for (i = 0; i < numElementsA; ++i)
{
Add(rowB, tempData[i]);
}
}
@@ -0,0 +1,369 @@
/*
* 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 <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/numeric.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Jobs/JobCompletion.h>
#include <AzCore/Jobs/JobContext.h>
#include "MeshBuilder.h"
#include "MeshBuilderSkinningInfo.h"
#include "MeshBuilderSubMesh.h"
namespace AZ::MeshBuilder
{
AZ_CLASS_ALLOCATOR_IMPL(MeshBuilder, AZ::SystemAllocator, 0)
MeshBuilder::MeshBuilder(size_t numOrgVerts, bool optimizeDuplicates)
: MeshBuilder(numOrgVerts, s_defaultMaxBonesPerSubMesh, s_defaultMaxSubMeshVertices, optimizeDuplicates)
{
}
MeshBuilder::MeshBuilder(size_t numOrgVerts, size_t maxBonesPerSubMesh, size_t maxSubMeshVertices, bool optimizeDuplicates)
: m_vertices(numOrgVerts)
, m_maxBonesPerSubMesh(AZ::GetMax<size_t>(1, maxBonesPerSubMesh))
, m_maxSubMeshVertices(AZ::GetMax<size_t>(1, maxSubMeshVertices))
, m_numOrgVerts(numOrgVerts)
, m_optimizeDuplicates(optimizeDuplicates)
{
}
const MeshBuilderVertexLookup MeshBuilder::FindMatchingDuplicate(size_t orgVertexNr) const
{
// check with all vertex duplicates
const size_t numDuplicates = m_layers[0]->GetNumDuplicates(orgVertexNr);
for (size_t d = 0; d < numDuplicates; ++d)
{
// check if the submitted vertex data is equal in all layers for the current duplicate
bool allDataEqual = true;
const size_t numLayers = m_layers.size();
for (size_t layer = 0; layer < numLayers && allDataEqual; ++layer)
{
if (m_layers[layer]->CheckIfIsVertexEqual(orgVertexNr, d) == false)
{
allDataEqual = false;
}
}
// if so, we have found a matching vertex!
if (allDataEqual)
{
return MeshBuilderVertexLookup(orgVertexNr, d);
}
}
// no matching vertex found
return {};
}
const MeshBuilderVertexLookup MeshBuilder::AddVertex(const size_t orgVertexNr)
{
// when there are no layers, there is nothing to do
if (m_layers.empty())
{
return {};
}
// try to find a matching duplicate number for the current vertex
const MeshBuilderVertexLookup index = m_optimizeDuplicates ? FindMatchingDuplicate(orgVertexNr) : MeshBuilderVertexLookup{};
if (index.mOrgVtx != InvalidIndex)
{
return index;
}
// if there isn't a similar vertex, we have to submit it to all layers
for (AZStd::unique_ptr<MeshBuilderVertexAttributeLayer>& layer : m_layers)
{
layer->AddVertex(orgVertexNr);
}
return {orgVertexNr, m_layers.back()->GetNumDuplicates(orgVertexNr) - 1};
}
// find the index value for the current set vertex
const MeshBuilderVertexLookup MeshBuilder::FindVertexIndex(size_t orgVertexNr) const
{
// if there are no layers, we can't find a valid index
if (m_layers.empty())
{
return {};
}
// try to locate a matching duplicate
return FindMatchingDuplicate(orgVertexNr);
}
void MeshBuilder::BeginPolygon(size_t materialIndex)
{
m_materialIndex = materialIndex;
m_polyIndices.clear();
m_polyOrgVertexNumbers.clear();
}
void MeshBuilder::AddPolygonVertex(size_t orgVertexNr)
{
m_polyIndices.emplace_back(AddVertex(orgVertexNr));
m_polyOrgVertexNumbers.emplace_back(orgVertexNr);
}
void MeshBuilder::EndPolygon()
{
AZ_Assert(m_polyIndices.size() >= 3, "Polygon should at least have three vertices.");
// add the triangle
AddPolygon(m_polyIndices, m_polyOrgVertexNumbers, m_materialIndex);
}
const MeshBuilderSubMesh* MeshBuilder::FindSubMeshForPolygon(const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex) const
{
// collect all bones that influence the given polygon
AZStd::vector<size_t> polyJointList;
ExtractBonesForPolygon(orgVertexNumbers, polyJointList);
// create our list of possible submeshes, start value are all submeshes available
AZStd::vector<MeshBuilderSubMesh*> possibleSubMeshes;
possibleSubMeshes.reserve(m_subMeshes.size());
for (const AZStd::unique_ptr<MeshBuilderSubMesh>& subMesh : m_subMeshes)
{
possibleSubMeshes.emplace_back(subMesh.get());
}
while (!possibleSubMeshes.empty())
{
size_t maxMatchings = 0;
size_t foundSubMeshNr = InvalidIndex;
const size_t numPossibleSubMeshes = possibleSubMeshes.size();
// iterate over all submeshes and find the one with the most similar bones
for (size_t i = 0; i < numPossibleSubMeshes; ++i)
{
// get the number of matching bones from the current submesh and the given polygon
const size_t currentNumMatches = possibleSubMeshes[i]->CalcNumSimilarJoints(polyJointList);
// if the current submesh has more similar bones than the current maximum we found a better one
if (currentNumMatches > maxMatchings)
{
// check if this submesh already our perfect match
if (currentNumMatches == polyJointList.size())
{
// check if the submesh which has the most common bones with the given polygon can handle it
if (possibleSubMeshes[i]->CanHandlePolygon(orgVertexNumbers, materialIndex, polyJointList))
{
return possibleSubMeshes[i];
}
}
maxMatchings = currentNumMatches;
foundSubMeshNr = i;
}
}
// if we cannot find a submesh return nullptr and create a new one
if (foundSubMeshNr == InvalidIndex)
{
for (MeshBuilderSubMesh* possibleSubMesh : possibleSubMeshes)
{
// check if the submesh which has the most common bones with the given polygon can handle it
if (possibleSubMesh->CanHandlePolygon(orgVertexNumbers, materialIndex, polyJointList))
{
return possibleSubMesh;
}
}
return nullptr;
}
// check if the submesh which has the most common bones with the given polygon can handle it
if (possibleSubMeshes[foundSubMeshNr]->CanHandlePolygon(orgVertexNumbers, materialIndex, polyJointList))
{
return possibleSubMeshes[foundSubMeshNr];
}
// remove the found submesh from the possible submeshes directly so that we can don't find the same one in the next iteration again
possibleSubMeshes.erase(possibleSubMeshes.begin() + foundSubMeshNr);
}
return nullptr;
}
void MeshBuilder::AddPolygon(const AZStd::vector<MeshBuilderVertexLookup>& indices, const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex)
{
// add the polygon to the list of poly vertex counts
const size_t numPolyVerts = indices.size();
AZ_Assert(numPolyVerts <= 255, "Polygon has too many vertices.");
m_polyVertexCounts.emplace_back(static_cast<AZ::u8>(numPolyVerts));
// try to find a submesh where we can add it
MeshBuilderSubMesh* subMesh = FindSubMeshForPolygon(orgVertexNumbers, materialIndex);
// if there is none where we can add it to, create a new one
if (!subMesh)
{
m_subMeshes.emplace_back(AZStd::make_unique<MeshBuilderSubMesh>(materialIndex, this));
subMesh = m_subMeshes.back().get();
}
// add the polygon to the submesh
ExtractBonesForPolygon(orgVertexNumbers, m_polyJointList);
subMesh->AddPolygon(indices, m_polyJointList);
}
void MeshBuilder::OptimizeTriangleList()
{
if (!CheckIfIsTriangleMesh())
{
return;
}
for (const AZStd::unique_ptr<MeshBuilderSubMesh>& subMesh : m_subMeshes)
{
subMesh->Optimize();
}
}
void MeshBuilder::ExtractBonesForPolygon(const AZStd::vector<size_t>& orgVertexNumbers, AZStd::vector<size_t>& outPolyJointList) const
{
// get rid of existing data
outPolyJointList.clear();
// get the skinning info, if there is any
const MeshBuilderSkinningInfo* skinningInfo = GetSkinningInfo();
if (skinningInfo == nullptr)
{
return;
}
// for all 3 vertices of the polygon
for (size_t orgVtxNr : orgVertexNumbers)
{
// traverse all influences for this vertex
const size_t numInfluences = skinningInfo->GetNumInfluences(orgVtxNr);
for (size_t n = 0; n < numInfluences; ++n)
{
const size_t nodeNr = skinningInfo->GetInfluence(orgVtxNr, n).mNodeNr;
// if it isn't yet in the output array with bones, add it
if (AZStd::find(outPolyJointList.begin(), outPolyJointList.end(), nodeNr) == outPolyJointList.end())
{
outPolyJointList.emplace_back(nodeNr);
}
}
}
}
size_t MeshBuilder::CalcNumIndices() const
{
size_t totalIndices = 0;
for (const AZStd::unique_ptr<MeshBuilderSubMesh>& subMesh : m_subMeshes)
{
totalIndices += subMesh->GetNumIndices();
}
return totalIndices;
}
size_t MeshBuilder::CalcNumVertices() const
{
size_t total = 0;
for (const AZStd::unique_ptr<MeshBuilderSubMesh>& subMesh : m_subMeshes)
{
total += subMesh->GetNumVertices();
}
return total;
}
void MeshBuilder::GenerateSubMeshVertexOrders()
{
AZ::JobCompletion jobCompletion;
for (const AZStd::unique_ptr<MeshBuilderSubMesh>& subMesh : m_subMeshes)
{
AZ::JobContext* jobContext = nullptr;
AZ::Job* job = AZ::CreateJobFunction([&subMesh]()
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Animation, "MeshBuilder::GenerateSubMeshVertexOrders::SubMeshJob");
subMesh->GenerateVertexOrder();
}, true, jobContext);
job->SetDependent(&jobCompletion);
job->Start();
}
jobCompletion.StartAndWaitForCompletion();
}
bool MeshBuilder::CheckIfIsTriangleMesh() const
{
return AZStd::all_of(begin(m_polyVertexCounts), end(m_polyVertexCounts), [](AZ::u8 count) { return count == 3; });
}
bool MeshBuilder::CheckIfIsQuadMesh() const
{
return AZStd::all_of(begin(m_polyVertexCounts), end(m_polyVertexCounts), [](AZ::u8 count) { return count == 4; });
}
void MeshBuilder::SetSkinningInfo(AZStd::unique_ptr<MeshBuilderSkinningInfo> skinningInfo)
{
m_skinningInfo = AZStd::move(skinningInfo);
}
size_t MeshBuilder::FindRealVertexNr(const MeshBuilderSubMesh* subMesh, size_t orgVtx, size_t dupeNr) const
{
for (const SubMeshVertex& subMeshVertex : m_vertices[orgVtx])
{
if (subMeshVertex.m_subMesh == subMesh && subMeshVertex.m_dupeNr == dupeNr)
{
return subMeshVertex.m_realVertexNr;
}
}
return InvalidIndex;
}
const MeshBuilder::SubMeshVertex* MeshBuilder::FindSubMeshVertex(MeshBuilderSubMesh* subMesh, size_t orgVtx, size_t dupeNr) const
{
for (const SubMeshVertex& subMeshVertex : m_vertices[orgVtx])
{
if (subMeshVertex.m_subMesh == subMesh && subMeshVertex.m_dupeNr == dupeNr)
{
return &subMeshVertex;
}
}
return nullptr;
}
size_t MeshBuilder::CalcNumVertexDuplicates(MeshBuilderSubMesh* subMesh, size_t orgVtx) const
{
size_t numDupes = 0;
for (const SubMeshVertex& subMeshVertex : m_vertices[orgVtx])
{
if (subMeshVertex.m_subMesh == subMesh)
{
numDupes++;
}
}
return numDupes;
}
size_t MeshBuilder::GetNumSubMeshVertices(size_t orgVtx) const
{
return m_vertices[orgVtx].size();
}
void MeshBuilder::AddSubMeshVertex(size_t orgVtx, SubMeshVertex&& vtx)
{
m_vertices[orgVtx].emplace_back(vtx);
}
} // namespace AZ::MeshBuilder
@@ -0,0 +1,140 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/tuple.h>
#include <AzCore/std/typetraits/is_convertible.h>
#include <AzCore/std/typetraits/is_same.h>
#include "MeshBuilderVertexAttributeLayers.h"
#include "MeshBuilderSubMesh.h"
#include "MeshBuilderSkinningInfo.h"
namespace AZ::MeshBuilder
{
/*
* Small usage tutorial:
*
* For all your vertex data types (position, normal, uvs etc)
* AddLayer( layer );
*
* For all polygons inside a mesh you want to export
* BeginPolygon( polyMaterialIndex )
* For all added layers you added to the mesh
* layer->SetCurrentVertexValue( )
* AddPolygonVertex( originalVertexNr )
* EndPolygon()
*
* OptimizeTriangleList()
*/
class MeshBuilder
{
friend class MeshBuilderSubMesh;
public:
AZ_CLASS_ALLOCATOR_DECL
explicit MeshBuilder(size_t numOrgVerts, bool optimizeDuplicates = true);
explicit MeshBuilder(size_t numOrgVerts, size_t maxBonesPerSubMesh, size_t maxSubMeshVertices, bool optimizeDuplicates = true);
template <class LayerType, class... Args>
AZStd::enable_if_t<AZStd::is_convertible_v<LayerType*, MeshBuilderVertexAttributeLayer*>, LayerType*> AddLayer(Args&&... args)
{
if constexpr (AZStd::is_same_v<AZStd::tuple_element_t<0, AZStd::tuple<Args...>>, AZStd::unique_ptr<LayerType>>)
{
// If the thing we were passed is already a unique_ptr, just move it in
m_layers.emplace_back(AZStd::move(args)...);
}
else
{
m_layers.emplace_back(AZStd::make_unique<LayerType>(AZStd::forward<Args>(args)...));
}
return static_cast<LayerType*>(m_layers.back().get());
}
void BeginPolygon(size_t materialIndex); // begin a poly
void AddPolygonVertex(size_t orgVertexNr); // add a vertex to it (do this n-times, for an n-gon)
void EndPolygon(); // end the polygon, after adding all polygon vertices
size_t CalcNumIndices() const; // calculate the number of indices in the mesh
size_t CalcNumVertices() const; // calculate the number of vertices in the mesh
void OptimizeTriangleList(); // call this in order to optimize the index buffers on cache efficiency
bool CheckIfIsTriangleMesh() const;
bool CheckIfIsQuadMesh() const;
size_t GetNumOrgVerts() const { return m_numOrgVerts; }
void SetSkinningInfo(AZStd::unique_ptr<MeshBuilderSkinningInfo> skinningInfo);
const MeshBuilderSkinningInfo* GetSkinningInfo() const { return m_skinningInfo.get(); }
MeshBuilderSkinningInfo* GetSkinningInfo() { return m_skinningInfo.get(); }
size_t GetMaxBonesPerSubMesh() const { return m_maxBonesPerSubMesh; }
size_t GetMaxVerticesPerSubMesh() const { return m_maxSubMeshVertices; }
void SetMaxBonesPerSubMesh(size_t maxBones) { m_maxBonesPerSubMesh = maxBones; }
size_t GetNumLayers() const { return m_layers.size(); }
size_t GetNumSubMeshes() const { return m_subMeshes.size(); }
const MeshBuilderSubMesh* GetSubMesh(size_t index) const { return m_subMeshes[index].get(); }
const MeshBuilderVertexAttributeLayer* GetLayer(size_t index) const { return m_layers[index].get(); }
size_t GetNumPolygons() const { return m_polyVertexCounts.size(); }
struct SubMeshVertex
{
size_t m_realVertexNr;
size_t m_dupeNr;
MeshBuilderSubMesh* m_subMesh;
};
size_t FindRealVertexNr(const MeshBuilderSubMesh* subMesh, size_t orgVtx, size_t dupeNr) const;
const SubMeshVertex* FindSubMeshVertex(MeshBuilderSubMesh* subMesh, size_t orgVtx, size_t dupeNr) const;
size_t CalcNumVertexDuplicates(MeshBuilderSubMesh* subMesh, size_t orgVtx) const;
void GenerateSubMeshVertexOrders();
void AddSubMeshVertex(size_t orgVtx, SubMeshVertex&& vtx);
size_t GetNumSubMeshVertices(size_t orgVtx) const;
const SubMeshVertex& GetSubMeshVertex(size_t orgVtx, size_t index) const { return m_vertices[orgVtx][index]; }
private:
static constexpr inline int s_defaultMaxBonesPerSubMesh = 512;
static constexpr inline int s_defaultMaxSubMeshVertices = 65535;
AZStd::vector<AZStd::unique_ptr<MeshBuilderSubMesh>> m_subMeshes;
AZStd::vector<AZStd::unique_ptr<MeshBuilderVertexAttributeLayer>> m_layers;
AZStd::vector<AZStd::vector<SubMeshVertex>> m_vertices;
AZStd::vector<size_t> m_polyJointList;
AZStd::unique_ptr<MeshBuilderSkinningInfo> m_skinningInfo{};
AZStd::vector<MeshBuilderVertexLookup> m_polyIndices;
AZStd::vector<size_t> m_polyOrgVertexNumbers;
AZStd::vector<AZ::u8> m_polyVertexCounts;
size_t m_materialIndex = 0;
size_t m_maxBonesPerSubMesh = s_defaultMaxBonesPerSubMesh;
size_t m_maxSubMeshVertices = s_defaultMaxSubMeshVertices;
size_t m_numOrgVerts = 0;
bool m_optimizeDuplicates = true;
const MeshBuilderVertexLookup FindMatchingDuplicate(size_t orgVertexNr) const;
const MeshBuilderVertexLookup AddVertex(size_t orgVertexNr);
const MeshBuilderVertexLookup FindVertexIndex(size_t orgVertexNr) const;
const MeshBuilderSubMesh* FindSubMeshForPolygon(const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex) const;
MeshBuilderSubMesh* FindSubMeshForPolygon(const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex)
{
return const_cast<MeshBuilderSubMesh*>(static_cast<const MeshBuilder*>(this)->FindSubMeshForPolygon(orgVertexNumbers, materialIndex));
}
void ExtractBonesForPolygon(const AZStd::vector<size_t>& orgVertexNumbers, AZStd::vector<size_t>& outPolyJointList) const;
void AddPolygon(const AZStd::vector<MeshBuilderVertexLookup>& indices, const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex);
};
} // namespace AZ::MeshBuilder
@@ -0,0 +1,22 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/base.h>
namespace AZ::MeshBuilder
{
template<class IndexType>
inline static constexpr IndexType InvalidIndexT = static_cast<IndexType>(-1);
inline static constexpr size_t InvalidIndex = InvalidIndexT<size_t>;
} // namespace AZ::MeshBuilder
@@ -0,0 +1,143 @@
/*
* 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 the required headers
#include <AzCore/std/sort.h>
#include <AzCore/Memory/SystemAllocator.h>
#include "MeshBuilderSkinningInfo.h"
#include "MeshBuilderSubMesh.h"
#include "MeshBuilder.h"
#include "MeshBuilderVertexAttributeLayers.h"
namespace AZ::MeshBuilder
{
AZ_CLASS_ALLOCATOR_IMPL(MeshBuilderSkinningInfo, AZ::SystemAllocator, 0)
// constructor
MeshBuilderSkinningInfo::MeshBuilderSkinningInfo(size_t numOrgVertices)
{
mInfluences.SetNumPreCachedElements(4); // TODO: verify if this is the fastest
mInfluences.Resize(numOrgVertices);
}
// optimize weights
void MeshBuilderSkinningInfo::OptimizeSkinningInfluences(AZStd::vector<Influence>& influences, float tolerance, size_t maxWeights)
{
if (influences.empty())
{
return; // vertex has no weights, so nothing to optimize
}
// remove all weights below the tolerance
// at least keep one weight left after this optimization
for (auto it = begin(influences); it != end(influences);)
{
if (influences.size() == 1)
{
break;
}
float weight = it->mWeight;
if (weight < tolerance)
{
influences.erase(it);
}
else
{
++it;
}
}
// reduce number of weights when needed
while (influences.size() > maxWeights)
{
float minWeight = FLT_MAX;
AZStd::vector<Influence>::iterator minInfluence = end(influences);
// find the smallest weight
for (auto it = begin(influences); it != end(influences); ++it)
{
if (it->mWeight < minWeight)
{
minWeight = it->mWeight;
minInfluence = it;
}
}
// remove this smallest weight
influences.erase(minInfluence);
}
// calculate the total weight
float totalWeight = 0;
for (const Influence& influence : influences)
{
totalWeight += influence.mWeight;
}
// normalize
for (Influence& influence : influences)
{
influence.mWeight /= totalWeight;
}
}
// sort influences on weights, from big to small
void MeshBuilderSkinningInfo::SortInfluences(AZStd::vector<Influence>& influences)
{
AZStd::sort(begin(influences), end(influences), [](const auto& lhs, const auto& rhs)
{
return lhs.mWeight > rhs.mWeight;
});
}
// optimize the weight data
void MeshBuilderSkinningInfo::Optimize(AZ::u32 maxNumWeightsPerVertex, float weightThreshold)
{
AZStd::vector<Influence> influences;
// for all vertices
const size_t numOrgVerts = GetNumOrgVertices();
for (size_t v = 0; v < numOrgVerts; ++v)
{
// gather all weights
const size_t numInfluences = GetNumInfluences(v);
influences.resize(numInfluences);
for (size_t i = 0; i < numInfluences; ++i)
{
influences[i] = GetInfluence(v, i);
}
// optimize the weights and sort them from big to small weight
OptimizeSkinningInfluences(influences, weightThreshold, maxNumWeightsPerVertex);
SortInfluences(influences);
// remove all influences
for (size_t i = 0; i < numInfluences; ++i)
{
mInfluences.Remove(v, 0);
}
// re-add them
for (const Influence& influence : influences)
{
mInfluences.Add(v, influence);
}
}
}
} // namespace AZ::MeshBuilder
@@ -0,0 +1,63 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/base.h>
#include "MeshBuilderInvalidIndex.h"
#include "Array2D.h"
namespace AZ::MeshBuilder
{
class MeshBuilderSkinningInfo
{
public:
AZ_CLASS_ALLOCATOR_DECL
struct Influence
{
size_t mNodeNr = InvalidIndex;
float mWeight = 1.0f;
Influence() = default;
Influence(size_t nodeNr, float weight)
: mNodeNr(nodeNr)
, mWeight(weight)
{}
};
MeshBuilderSkinningInfo(size_t numOrgVertices);
void AddInfluence(size_t orgVtxNr, size_t nodeNr, float weight) { AddInfluence(orgVtxNr, {nodeNr, weight}); }
void AddInfluence(size_t orgVtxNr, const Influence& influence) { mInfluences.Add(orgVtxNr, influence); }
void RemoveInfluence(size_t orgVtxNr, size_t influenceNr) { mInfluences.Remove(orgVtxNr, influenceNr); }
const Influence& GetInfluence(size_t orgVtxNr, size_t influenceNr) const { return mInfluences.GetElement(orgVtxNr, influenceNr); }
size_t GetNumInfluences(size_t orgVtxNr) const { return mInfluences.GetNumElements(orgVtxNr); }
size_t GetNumOrgVertices() const { return mInfluences.GetNumRows(); }
void OptimizeMemoryUsage() { mInfluences.Shrink(); }
size_t CalcTotalNumInfluences() const { return mInfluences.CalcTotalNumElements(); }
// optimize the weight data
void Optimize(AZ::u32 maxNumWeightsPerVertex = 4, float weightThreshold = 0.0001f);
// optimize weights
static void OptimizeSkinningInfluences(AZStd::vector<Influence>& influences, float tolerance, size_t maxWeights);
// sort the influences, starting with the biggest weight
static void SortInfluences(AZStd::vector<Influence>& influences);
public:
MCore::Array2D<Influence> mInfluences;
};
} // namespace AZ::MeshBuilder
@@ -0,0 +1,195 @@
/*
* 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 <AzCore/Memory/SystemAllocator.h>
#include "MeshBuilder.h"
#include "MeshBuilderSkinningInfo.h"
#include "MeshBuilderSubMesh.h"
#include "MeshBuilderVertexAttributeLayers.h"
namespace AZ::MeshBuilder
{
AZ_CLASS_ALLOCATOR_IMPL(MeshBuilderSubMesh, AZ::SystemAllocator, 0)
MeshBuilderSubMesh::MeshBuilderSubMesh(size_t materialIndex, MeshBuilder* mesh)
: m_materialIndex(materialIndex)
, m_mesh(mesh)
{
}
void MeshBuilderSubMesh::Optimize()
{
if (m_vertexOrder.size() != m_numVertices)
{
GenerateVertexOrder();
}
}
// map the vertices to their original vertex and dupe numbers
void MeshBuilderSubMesh::GenerateVertexOrder()
{
// create our vertex order array and allocate numVertices lookups
m_vertexOrder.resize(m_numVertices);
const size_t numOrgVertices = m_mesh->GetNumOrgVerts();
for (size_t orgVertexNr = 0; orgVertexNr < numOrgVertices; ++orgVertexNr)
{
const size_t numSubMeshVertices = m_mesh->GetNumSubMeshVertices(orgVertexNr);
for (size_t i = 0; i < numSubMeshVertices; ++i)
{
const MeshBuilder::SubMeshVertex& subMeshVertex = m_mesh->GetSubMeshVertex(orgVertexNr, i);
if (subMeshVertex.m_subMesh == this && subMeshVertex.m_realVertexNr != InvalidIndex)
{
const size_t realVertexNr = subMeshVertex.m_realVertexNr;
m_vertexOrder[realVertexNr].mOrgVtx = orgVertexNr;
m_vertexOrder[realVertexNr].mDuplicateNr = subMeshVertex.m_dupeNr;
}
}
}
}
// add a polygon to the submesh
void MeshBuilderSubMesh::AddPolygon(const AZStd::vector<MeshBuilderVertexLookup>& indices, const AZStd::vector<size_t>& jointList)
{
// pre-allocate
if (m_indices.size() % 10000 == 0)
{
m_indices.reserve(m_indices.size() + 10000);
}
// for all vertices in the poly
m_polyVertexCounts.emplace_back(aznumeric_caster(indices.size()));
for (const MeshBuilderVertexLookup& index : indices)
{
// add unused vertices to the list
if (CheckIfHasVertex(index) == false)
{
const size_t numDupes = m_mesh->CalcNumVertexDuplicates(this, index.mOrgVtx);
const ptrdiff_t numToAdd = (index.mDuplicateNr - numDupes) + 1;
for (ptrdiff_t j = 0; j < numToAdd; ++j)
{
m_mesh->AddSubMeshVertex(index.mOrgVtx, {
/* .m_realVertexNr = */ m_numVertices,
/* .m_dupeNr = */ numDupes + j,
/* .m_subMesh = */ this,
});
++m_numVertices;
}
}
// an index in the local vertices array
m_indices.emplace_back(index);
}
// add the new joints
for (size_t jointIndex : jointList)
{
if (AZStd::find(m_jointList.begin(), m_jointList.end(), jointIndex) == m_jointList.end())
{
m_jointList.emplace_back(jointIndex);
}
}
}
// check if we can handle a given poly inside the submesh
bool MeshBuilderSubMesh::CanHandlePolygon(const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex, AZStd::vector<size_t>& outJointList) const
{
// if the material isn't the same, we can't handle it
if (m_materialIndex != materialIndex)
{
return false;
}
// check if there is still space for the poly vertices (worst case scenario), and if this won't go over the 16 bit index buffer limit
const size_t numPolyVerts = orgVertexNumbers.size();
if (m_numVertices + numPolyVerts > m_mesh->m_maxSubMeshVertices)
{
return false;
}
const MeshBuilderSkinningInfo* skinningInfo = m_mesh->GetSkinningInfo();
if (skinningInfo)
{
// get the maximum number of allowed bones per submesh
const size_t maxNumBones = m_mesh->GetMaxBonesPerSubMesh();
// extract the list of bones used by this poly
m_mesh->ExtractBonesForPolygon(orgVertexNumbers, outJointList);
// check if worst case scenario would be allowed
// this is when we have to add all triangle bones to the bone list
if (m_jointList.size() + outJointList.size() > maxNumBones)
{
return false;
}
// calculate the real number of extra bones needed
size_t numExtraNeeded = 0;
const size_t numPolyBones = outJointList.size();
for (size_t i = 0; i < numPolyBones; ++i)
{
if (AZStd::find(m_jointList.begin(), m_jointList.end(), outJointList.at(i)) == m_jointList.end())
{
numExtraNeeded++;
}
}
// if we can't add the extra required bones to the list, because it would result in more than the
// allowed number of bones, then return that we can't add this triangle to this submesh
if (m_jointList.size() + numExtraNeeded > maxNumBones)
{
return false;
}
}
// yeah, we can add this triangle to the submesh
return true;
}
bool MeshBuilderSubMesh::CheckIfHasVertex(const MeshBuilderVertexLookup& vertex)
{
if (m_mesh->CalcNumVertexDuplicates(this, vertex.mOrgVtx) <= vertex.mDuplicateNr)
{
return false;
}
return (m_mesh->FindRealVertexNr(this, vertex.mOrgVtx, vertex.mDuplicateNr) != InvalidIndex);
}
size_t MeshBuilderSubMesh::GetIndex(size_t index) const
{
return m_mesh->FindRealVertexNr(this, m_indices[index].mOrgVtx, m_indices[index].mDuplicateNr);
}
size_t MeshBuilderSubMesh::CalcNumSimilarJoints(const AZStd::vector<size_t>& jointList) const
{
// reset our similar bones counter and get the number of bones from the input and submesh bone lists
size_t numMatches = 0;
// iterate through all bones from the input bone list and find the matching bones
for (size_t inputJointIndex : jointList)
{
for (size_t jointIndex : jointList)
{
if (jointIndex == inputJointIndex)
{
numMatches++;
break;
}
}
}
return numMatches;
}
} // namespace AZ::MeshBuilder
@@ -0,0 +1,65 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/vector.h>
#include "MeshBuilderVertexAttributeLayers.h"
namespace AZ::MeshBuilder
{
class MeshBuilder;
class MeshBuilderSubMesh
{
public:
AZ_CLASS_ALLOCATOR_DECL
MeshBuilderSubMesh(size_t materialIndex, MeshBuilder* mesh);
size_t GetNumIndices() const { return m_indices.size(); }
size_t GetNumPolygons() const { return m_polyVertexCounts.size(); }
size_t GetNumJoints() const { return m_jointList.size(); }
size_t GetMaterialIndex() const { return m_materialIndex; }
size_t GetNumVertices() const { return m_numVertices; }
size_t GetJoint(size_t index) const { return m_jointList[index]; }
AZ::u8 GetPolygonVertexCount(size_t polyIndex) const { return m_polyVertexCounts[polyIndex]; }
const MeshBuilderVertexLookup& GetVertex(size_t index) const
{
AZ_Assert(m_vertexOrder.size() == m_numVertices, "Call GenerateVertexOrder() first")
return m_vertexOrder[index];
}
const MeshBuilder* GetMesh() const { return m_mesh; }
size_t GetIndex(size_t index) const;
void GenerateVertexOrder();
void SetJoints(const AZStd::vector<size_t>& jointList) { m_jointList = jointList; }
const AZStd::vector<size_t>& GetJoints() const { return m_jointList; }
void Optimize();
void AddPolygon(const AZStd::vector<MeshBuilderVertexLookup>& indices, const AZStd::vector<size_t>& jointList);
bool CanHandlePolygon(const AZStd::vector<size_t>& orgVertexNumbers, size_t materialIndex, AZStd::vector<size_t>& outJointList) const;
size_t CalcNumSimilarJoints(const AZStd::vector<size_t>& jointList) const;
private:
AZStd::vector<MeshBuilderVertexLookup> m_indices;
AZStd::vector<MeshBuilderVertexLookup> m_vertexOrder;
AZStd::vector<size_t> m_jointList;
AZStd::vector<AZ::u8> m_polyVertexCounts;
size_t m_materialIndex = InvalidIndex;
size_t m_numVertices = 0;
MeshBuilder* m_mesh = nullptr;
bool CheckIfHasVertex(const MeshBuilderVertexLookup& vertex);
};
} // namespace AZ::MeshBuilder
@@ -0,0 +1,28 @@
/*
* 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 the required headers
#include "MeshBuilderVertexAttributeLayers.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
namespace AZ::MeshBuilder
{
AZ_CLASS_ALLOCATOR_IMPL(MeshBuilderVertexAttributeLayer, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerFloat, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerUInt32, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerVector2, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerVector3, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerVector4, AZ::SystemAllocator, 0)
} // namespace AZ::MeshBuilder
@@ -0,0 +1,150 @@
/*
* 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.
*
*/
#pragma once
// include required headers
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/typetraits/is_floating_point.h>
#include <AzCore/std/typetraits/is_integral.h>
#include "AzCore/std/numeric.h"
#include "MeshBuilderInvalidIndex.h"
namespace AZ::SceneAPI::DataTypes{ struct Color; }
namespace AZ::MeshBuilder
{
struct MeshBuilderVertexLookup
{
size_t mOrgVtx = InvalidIndex;
size_t mDuplicateNr = InvalidIndex;
MeshBuilderVertexLookup() = default;
MeshBuilderVertexLookup(size_t orgVtx, size_t duplicateNr)
: mOrgVtx(orgVtx)
, mDuplicateNr(duplicateNr)
{}
};
class MeshBuilderVertexAttributeLayer
{
public:
AZ_CLASS_ALLOCATOR_DECL
MeshBuilderVertexAttributeLayer(bool isScale = false, bool isDeformable = false)
: mIsScale(isScale)
, mDeformable(isDeformable)
{
}
virtual ~MeshBuilderVertexAttributeLayer() = default;
bool GetIsScale() const { return mIsScale; }
bool GetIsDeformable() const { return mDeformable; }
void SetName(AZStd::string name) { mName = AZStd::move(name); }
const AZStd::string& GetName() const { return mName; }
virtual size_t GetAttributeSizeInBytes() const = 0;
virtual size_t GetNumOrgVertices() const = 0;
virtual size_t GetNumDuplicates(size_t orgVertexNr) const = 0;
virtual size_t CalcLayerSizeInBytes() const { return GetAttributeSizeInBytes() * CalcNumVertices(); }
virtual size_t CalcNumVertices() const = 0;
virtual bool CheckIfIsVertexEqual(size_t orgVtx, size_t duplicate) const = 0;
virtual void AddVertex(size_t orgVertexNr) = 0;
protected:
AZStd::string mName;
bool mIsScale;
bool mDeformable;
};
template<class AttribType>
class MeshBuilderVertexAttributeLayerT : public MeshBuilderVertexAttributeLayer
{
public:
AZ_CLASS_ALLOCATOR_DECL
struct Vertex
{
AttribType mValue{};
size_t mOrgVertex = InvalidIndex;
Vertex() = default;
Vertex(const AttribType& value, size_t orgVtx)
: mValue(value)
, mOrgVertex(orgVtx)
{}
};
MeshBuilderVertexAttributeLayerT(size_t numOrgVerts, bool isScale = false, bool isDeformable = false)
: MeshBuilderVertexAttributeLayer(isScale, isDeformable)
, mVertices(numOrgVerts)
{}
size_t GetAttributeSizeInBytes() const override { return sizeof(AttribType); }
size_t GetNumOrgVertices() const override { return mVertices.size(); }
size_t GetNumDuplicates(size_t orgVertexNr) const override { return mVertices[orgVertexNr].size(); }
size_t CalcNumVertices() const override
{
return AZStd::accumulate(mVertices.begin(), mVertices.end(), size_t(0), [](size_t sum, const auto& vertsPerPolygon) { return sum + vertsPerPolygon.size(); });
}
bool CheckIfIsVertexEqual(size_t orgVtx, size_t duplicate) const override
{
if constexpr (AZStd::is_integral_v<AttribType>)
{
return mVertices[orgVtx][duplicate].mValue == mVertexValue;
}
else if constexpr (AZStd::is_floating_point_v<AttribType>)
{
return AZ::IsClose(mVertices[orgVtx][duplicate].mValue, mVertexValue);
}
else
{
return mVertices[orgVtx][duplicate].mValue.IsClose(mVertexValue);
}
}
void SetCurrentVertexValue(const AttribType& value) { mVertexValue = value; }
const AttribType& GetCurrentVertexValue() const { return mVertexValue; }
void AddVertex(size_t orgVertexNr) override
{
mVertices[orgVertexNr].emplace_back(mVertexValue, orgVertexNr);
}
void AddVertexValue(size_t orgVertexNr, const AttribType& value)
{
mVertices[orgVertexNr].emplace_back(value, orgVertexNr);
}
const AttribType& GetVertexValue(size_t orgVertexNr, size_t duplicateNr) const
{
return mVertices[orgVertexNr][duplicateNr].mValue;
}
private:
AZStd::vector<AZStd::vector<Vertex>> mVertices;
AttribType mVertexValue;
};
// some standard layer types
using MeshBuilderVertexAttributeLayerVector2 = MeshBuilderVertexAttributeLayerT<AZ::Vector2>;
using MeshBuilderVertexAttributeLayerVector3 = MeshBuilderVertexAttributeLayerT<AZ::Vector3>;
using MeshBuilderVertexAttributeLayerVector4 = MeshBuilderVertexAttributeLayerT<AZ::Vector4>;
using MeshBuilderVertexAttributeLayerUInt32 = MeshBuilderVertexAttributeLayerT<AZ::u32>;
using MeshBuilderVertexAttributeLayerFloat = MeshBuilderVertexAttributeLayerT<float>;
} // namespace AZ::MeshBuilder
@@ -0,0 +1,520 @@
/*
* 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 <Generation/Components/MeshOptimizer/MeshOptimizerComponent.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/base.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/list.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/iterator.h>
#include <AzCore/std/limits.h>
#include <AzCore/std/reference_wrapper.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/std/typetraits/add_pointer.h>
#include <AzCore/std/typetraits/remove_cvref.h>
#include <AzCore/std/utils.h>
#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneCore/Containers/Views/ConvertIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/FilterIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/View.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBlendShapeData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexColorData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexUVData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
#include <SceneAPI/SceneCore/DataTypes/ManifestBase/ISceneNodeSelectionList.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h>
#include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexColorData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexTangentData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.h>
#include <SceneAPI/SceneData/GraphData/SkinWeightData.h>
#include <Generation/Components/MeshOptimizer/MeshBuilder.h>
#include <Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h>
#include <Generation/Components/MeshOptimizer/MeshBuilderVertexAttributeLayers.h>
namespace AZ { class ReflectContext; }
namespace AZ::MeshBuilder
{
using MeshBuilderVertexAttributeLayerColor = MeshBuilderVertexAttributeLayerT<AZ::SceneAPI::DataTypes::Color>;
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerColor, AZ::SystemAllocator, 0)
} // namespace AZ::MeshBuilder
namespace AZ::SceneGenerationComponents
{
using AZ::SceneAPI::Containers::SceneGraph;
using AZ::SceneAPI::DataTypes::IBlendShapeData;
using AZ::SceneAPI::DataTypes::ILodRule;
using AZ::SceneAPI::DataTypes::IMeshData;
using AZ::SceneAPI::DataTypes::IMeshGroup;
using AZ::SceneAPI::DataTypes::IMeshVertexBitangentData;
using AZ::SceneAPI::DataTypes::IMeshVertexTangentData;
using AZ::SceneAPI::DataTypes::IMeshVertexUVData;
using AZ::SceneAPI::DataTypes::IMeshVertexColorData;
using AZ::SceneAPI::DataTypes::ISkinWeightData;
using AZ::SceneAPI::Events::ProcessingResult;
using AZ::SceneAPI::Events::GenerateSimplificationEventContext;
using AZ::SceneAPI::SceneCore::GenerationComponent;
using AZ::SceneData::GraphData::MeshData;
using AZ::SceneData::GraphData::MeshVertexBitangentData;
using AZ::SceneData::GraphData::MeshVertexColorData;
using AZ::SceneData::GraphData::MeshVertexTangentData;
using AZ::SceneData::GraphData::MeshVertexUVData;
using AZ::SceneData::GraphData::SkinWeightData;
using NodeIndex = AZ::SceneAPI::Containers::SceneGraph::NodeIndex;
namespace Containers = AZ::SceneAPI::Containers;
namespace Views = Containers::Views;
MeshOptimizerComponent::MeshOptimizerComponent()
{
}
void MeshOptimizerComponent::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<MeshOptimizerComponent, GenerationComponent>()->Version(1);
}
}
template<class SkinWeightDataView>
static AZStd::unique_ptr<AZ::MeshBuilder::MeshBuilderSkinningInfo> ExtractSkinningInfo(
const IMeshData* meshData,
const SkinWeightDataView& skinWeights,
AZ::u32 maxWeightsPerVertex,
float weightThreshold)
{
if (skinWeights.empty())
{
return {};
}
const size_t usedControlPointCount = meshData->GetUsedControlPointCount();
auto skinningInfo = AZStd::make_unique<AZ::MeshBuilder::MeshBuilderSkinningInfo>(aznumeric_cast<AZ::u32>(usedControlPointCount));
for (const auto& skinData : skinWeights)
{
for (size_t controlPointIndex = 0; controlPointIndex < skinData.get().GetVertexCount(); ++controlPointIndex)
{
const int usedPointIndex = meshData->GetUsedPointIndexForControlPoint(aznumeric_caster(controlPointIndex));
const size_t linkCount = skinData.get().GetLinkCount(controlPointIndex);
if (usedPointIndex < 0 || linkCount == 0)
{
continue;
}
for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex)
{
const ISkinWeightData::Link& link = skinData.get().GetLink(controlPointIndex, linkIndex);
skinningInfo->AddInfluence(usedPointIndex, {aznumeric_caster(link.boneId), link.weight});
}
}
}
if (skinningInfo)
{
skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold);
}
return skinningInfo;
}
// Recurse through the SceneAPI's iterator types, extracting the real underlying iterator.
struct ConvertToHierarchyIterator
{
template<typename T, typename U>
static auto Unwrap(const Containers::Views::ConvertIterator<T, U>& it)
{
return Unwrap(it.GetBaseIterator());
}
template<typename T, typename U>
static auto Unwrap(const Containers::Views::FilterIterator<T, U>& it)
{
return Unwrap(it.GetBaseIterator());
}
template<typename T>
static auto Unwrap(const Containers::Views::SceneGraphChildIterator<T>& it)
{
return it.GetHierarchyIterator();
}
};
bool MeshOptimizerComponent::HasAnyBlendShapeChild(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex)
{
return !Containers::MakeDerivedFilterView<IBlendShapeData>(
Views::MakeSceneGraphChildView(graph, nodeIndex, graph.GetContentStorage().cbegin(), true)
).empty();
}
ProcessingResult MeshOptimizerComponent::OptimizeMeshes(GenerateSimplificationEventContext& context) const
{
// Iterate over all graph content and filter out all meshes.
SceneGraph& graph = context.GetScene().GetGraph();
// Build a list of mesh data nodes.
const AZStd::vector<AZStd::pair<const IMeshData*, NodeIndex>> meshes = [](const SceneGraph& graph)
{
AZStd::vector<AZStd::pair<const IMeshData*, NodeIndex>> meshes;
for (auto it = graph.GetContentStorage().cbegin(); it != graph.GetContentStorage().cend(); ++it)
{
// Skip anything that isn't a mesh.
const auto* mesh = azdynamic_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(it->get());
if (!mesh)
{
continue;
}
// Get the mesh data and node index and store them in the vector as a pair, so we can iterate over them later.
meshes.emplace_back(mesh, graph.ConvertToNodeIndex(it));
}
return meshes;
}(graph);
const auto meshGroups = Containers::MakeDerivedFilterView<IMeshGroup>(context.GetScene().GetManifest().GetValueStorage());
const AZStd::unordered_map<const IMeshGroup*, AZStd::vector<AZStd::string_view>> selectedNodes = [&meshGroups]
{
AZStd::unordered_map<const IMeshGroup*, AZStd::vector<AZStd::string_view>> selectedNodes;
const auto addSelectionListToMap = [&selectedNodes](const IMeshGroup& meshGroup, const SceneAPI::DataTypes::ISceneNodeSelectionList& selectionList)
{
for (size_t selectedNodeIndex = 0; selectedNodeIndex < selectionList.GetSelectedNodeCount(); ++selectedNodeIndex)
{
selectedNodes[&meshGroup].emplace_back(selectionList.GetSelectedNode(selectedNodeIndex));
}
};
for (const IMeshGroup& meshGroup : meshGroups)
{
addSelectionListToMap(meshGroup, meshGroup.GetSceneNodeSelectionList());
const ILodRule* lodRule = meshGroup.GetRuleContainerConst().FindFirstByType<SceneAPI::DataTypes::ILodRule>().get();
if (lodRule)
{
for (size_t lod = 0; lod < lodRule->GetLodCount(); ++lod)
{
addSelectionListToMap(meshGroup, lodRule->GetSceneNodeSelectionList(lod));
}
}
}
return selectedNodes;
}();
const auto childNodes = [&graph](NodeIndex nodeIndex) { return Views::MakeSceneGraphChildView(graph, nodeIndex, graph.GetContentStorage().cbegin(), true); };
const auto nodeIndexes = [&graph](const auto& view)
{
AZStd::vector<NodeIndex> indexes;
indexes.reserve(AZStd::distance(view.begin(), view.end()));
for (auto it = view.begin(); it != view.end(); ++it)
{
indexes.emplace_back(graph.ConvertToNodeIndex(ConvertToHierarchyIterator::Unwrap(it)));
}
return indexes;
};
// Iterate over them. We had to build the array before as this method can insert new nodes, so using the iterator directly would fail.
for (const auto& [mesh, nodeIndex] : meshes)
{
// A Mesh can have multiple child nodes that contain other data streams, like uvs and tangents
const auto uvDatasView = Containers::MakeDerivedFilterView<IMeshVertexUVData>(childNodes(nodeIndex));
const auto tangentDatasView = Containers::MakeDerivedFilterView<IMeshVertexTangentData>(childNodes(nodeIndex));
const auto bitangentDatasView = Containers::MakeDerivedFilterView<IMeshVertexBitangentData>(childNodes(nodeIndex));
const auto skinWeightDatasView = Containers::MakeDerivedFilterView<ISkinWeightData>(childNodes(nodeIndex));
const auto colorDatasView = Containers::MakeDerivedFilterView<IMeshVertexColorData>(childNodes(nodeIndex));
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexUVData>> uvDatas(uvDatasView.begin(), uvDatasView.end());
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexTangentData>> tangentDatas(tangentDatasView.begin(), tangentDatasView.end());
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexBitangentData>> bitangentDatas(bitangentDatasView.begin(), bitangentDatasView.end());
const AZStd::vector<AZStd::reference_wrapper<const ISkinWeightData>> skinWeightDatas(skinWeightDatasView.begin(), skinWeightDatasView.end());
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexColorData>> colorDatas(colorDatasView.begin(), colorDatasView.end());
const AZStd::string_view nodePath(graph.GetNodeName(nodeIndex).GetPath(), graph.GetNodeName(nodeIndex).GetPathLength());
for (const IMeshGroup& meshGroup : meshGroups)
{
// Skip meshes that are not used by this mesh group
if (AZStd::find(selectedNodes.at(&meshGroup).cbegin(), selectedNodes.at(&meshGroup).cend(), nodePath) == selectedNodes.at(&meshGroup).cend())
{
continue;
}
const AZStd::string name =
AZStd::string(graph.GetNodeName(nodeIndex).GetName(), graph.GetNodeName(nodeIndex).GetNameLength())
+ "_optimized";
if (graph.Find(name).IsValid())
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Optimized mesh already exists at '%s', there must be multiple mesh groups that have selected this mesh. Skipping the additional ones.", name.c_str());
continue;
}
const bool hasBlendShapes = HasAnyBlendShapeChild(graph, nodeIndex);
auto [optimizedMesh, optimizedUVs, optimizedTangents, optimizedBitangents, optimizedVertexColors, optimizedSkinWeights] = OptimizeMesh(mesh, uvDatas, tangentDatas, bitangentDatas, colorDatas, skinWeightDatas, meshGroup, hasBlendShapes);
const NodeIndex optimizedMeshNodeIndex = graph.AddChild(graph.GetNodeParent(nodeIndex), name.c_str(), AZStd::move(optimizedMesh));
auto addOptimizedNodes = [&graph, &optimizedMeshNodeIndex](const auto& originalNodeIndexes, auto& optimizedNodes)
{
AZ_PUSH_DISABLE_WARNING(, "-Wrange-loop-analysis") // remove when we upgrade from clang 6.0
for (const auto& [originalNodeIndex, optimizedNode] : Containers::Views::MakePairView(originalNodeIndexes, optimizedNodes))
AZ_POP_DISABLE_WARNING
{
const AZStd::string optimizedName {graph.GetNodeName(originalNodeIndex).GetName(), graph.GetNodeName(originalNodeIndex).GetNameLength()};
const NodeIndex optimizedNodeIndex = graph.AddChild(optimizedMeshNodeIndex, optimizedName.c_str(), AZStd::move(optimizedNode));
graph.MakeEndPoint(optimizedNodeIndex);
}
};
addOptimizedNodes(nodeIndexes(Containers::MakeDerivedFilterView<IMeshVertexUVData>(childNodes(nodeIndex))), optimizedUVs);
addOptimizedNodes(nodeIndexes(Containers::MakeDerivedFilterView<IMeshVertexTangentData>(childNodes(nodeIndex))), optimizedTangents);
addOptimizedNodes(nodeIndexes(Containers::MakeDerivedFilterView<IMeshVertexBitangentData>(childNodes(nodeIndex))), optimizedBitangents);
addOptimizedNodes(nodeIndexes(Containers::MakeDerivedFilterView<IMeshVertexColorData>(childNodes(nodeIndex))), optimizedVertexColors);
if (optimizedSkinWeights)
{
const NodeIndex optimizedSkinNodeIndex = graph.AddChild(optimizedMeshNodeIndex, "skinWeights", AZStd::move(optimizedSkinWeights));
graph.MakeEndPoint(optimizedSkinNodeIndex);
}
const AZStd::array optimizedChildTypes {
azrtti_typeid<IMeshVertexUVData>(),
azrtti_typeid<IMeshVertexTangentData>(),
azrtti_typeid<IMeshVertexBitangentData>(),
azrtti_typeid<IMeshVertexColorData>(),
azrtti_typeid<ISkinWeightData>(),
};
for (const NodeIndex& childNodeIndex : nodeIndexes(childNodes(nodeIndex)))
{
const AZStd::shared_ptr<SceneAPI::DataTypes::IGraphObject>& childNode = graph.GetNodeContent(childNodeIndex);
if (!AZStd::any_of(optimizedChildTypes.begin(), optimizedChildTypes.end(), [&childNode](const AZ::Uuid& typeId) { return AZ::RttiIsTypeOf(typeId, childNode.get()); }))
{
const AZStd::string optimizedName {graph.GetNodeName(childNodeIndex).GetName(), graph.GetNodeName(childNodeIndex).GetNameLength()};
const NodeIndex optimizedNodeIndex = graph.AddChild(optimizedMeshNodeIndex, optimizedName.c_str(), childNode);
if (graph.IsNodeEndPoint(nodeIndex))
{
graph.MakeEndPoint(optimizedNodeIndex);
}
}
}
}
}
return ProcessingResult::Success;
}
template<class DataNodeType, class MeshBuilderLayerType>
AZStd::vector<AZStd::unique_ptr<DataNodeType>> makeSceneGraphNodesForMeshBuilderLayers(const MeshBuilderLayerType& meshBuilderLayers)
{
AZStd::vector<AZStd::unique_ptr<DataNodeType>> layers(meshBuilderLayers.size());
AZStd::generate(layers.begin(), layers.end(), []
{
return AZStd::make_unique<DataNodeType>();
});
return layers;
};
AZStd::tuple<
AZStd::unique_ptr<AZ::SceneAPI::DataTypes::IMeshData>,
AZStd::vector<AZStd::unique_ptr<MeshVertexUVData>>,
AZStd::vector<AZStd::unique_ptr<MeshVertexTangentData>>,
AZStd::vector<AZStd::unique_ptr<MeshVertexBitangentData>>,
AZStd::vector<AZStd::unique_ptr<MeshVertexColorData>>,
AZStd::unique_ptr<AZ::SceneAPI::DataTypes::ISkinWeightData>
> MeshOptimizerComponent::OptimizeMesh(
const IMeshData* meshData,
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexUVData>>& uvs,
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexTangentData>>& tangents,
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexBitangentData>>& bitangents,
const AZStd::vector<AZStd::reference_wrapper<const IMeshVertexColorData>>& vertexColors,
const AZStd::vector<AZStd::reference_wrapper<const ISkinWeightData>>& skinWeights,
const AZ::SceneAPI::DataTypes::IMeshGroup& /*meshGroup*/,
const bool hasBlendShapes)
{
const size_t vertexCount = meshData->GetUsedControlPointCount();
AZ::MeshBuilder::MeshBuilder meshBuilder(vertexCount, AZStd::numeric_limits<size_t>::max(), AZStd::numeric_limits<size_t>::max(), /*optimizeDuplicates=*/ hasBlendShapes);
// Make the layers to hold the vertex data
auto* orgVtxLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerUInt32>(vertexCount);
auto* posLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerVector3>(vertexCount, false, true);
auto* normalsLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerVector3>(vertexCount, false, true);
const auto makeLayersForData = [&meshBuilder, vertexCount](const auto& dataView)
{
using InputDataType = typename AZStd::remove_cvref_t<decltype(*dataView.begin())>::type;
// Determine the layer data type to use in the mesh builder based on the type of scene graph node
// IMeshVertexUVData -> MeshBuilderVertexAttributeLayerVector2
// IMeshVertexTangentData -> MeshBuilderVertexAttributeLayerVector4
// IMeshVertexBitangentData -> MeshBuilderVertexAttributeLayerVector3
// IMeshVertexColorData -> MeshBuilderVertexAttributeLayerColor
struct ViewTypeToLayerType
{
static constexpr auto type(const IMeshVertexUVData*) -> MeshBuilder::MeshBuilderVertexAttributeLayerVector2;
static constexpr auto type(const IMeshVertexTangentData*) -> MeshBuilder::MeshBuilderVertexAttributeLayerVector4;
static constexpr auto type(const IMeshVertexBitangentData*) -> MeshBuilder::MeshBuilderVertexAttributeLayerVector3;
static constexpr auto type(const IMeshVertexColorData*) -> MeshBuilder::MeshBuilderVertexAttributeLayerColor;
};
using ResultingLayerType = decltype(ViewTypeToLayerType::type(AZStd::add_pointer_t<InputDataType>{}));
// the views provided by SceneAPI do not have a size() method, so compute it
const size_t layerCount = AZStd::distance(dataView.begin(), dataView.end());
AZStd::vector<ResultingLayerType*> layers(layerCount);
AZStd::generate(layers.begin(), layers.end(), [&meshBuilder, vertexCount]
{
return meshBuilder.AddLayer<ResultingLayerType>(vertexCount);
});
return layers;
};
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerVector2*> uvLayers = makeLayersForData(uvs);
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerVector4*> tangentLayers = makeLayersForData(tangents);
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerVector3*> bitangentLayers = makeLayersForData(bitangents);
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerColor*> vertexColorLayers = makeLayersForData(vertexColors);
const AZ::u32 maxWeightsPerVertex = 4;
const float weightThreshold = 0.001f;
meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold));
// Add the vertex data to all the layers
const AZ::u32 faceCount = meshData->GetFaceCount();
for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
{
meshBuilder.BeginPolygon(meshData->GetFaceMaterialId(faceIndex));
for (const AZ::u32 vertexIndex : meshData->GetFaceInfo(faceIndex).vertexIndex)
{
const int orgVertexNumber = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(vertexIndex));
AZ_Assert(orgVertexNumber >= 0, "Invalid vertex number");
orgVtxLayer->SetCurrentVertexValue(orgVertexNumber);
posLayer->SetCurrentVertexValue(meshData->GetPosition(vertexIndex));
normalsLayer->SetCurrentVertexValue(meshData->GetNormal(vertexIndex));
AZ_PUSH_DISABLE_WARNING(, "-Wrange-loop-analysis") // remove when we upgrade from clang 6.0
for (const auto& [uvData, uvLayer] : Containers::Views::MakePairView(uvs, uvLayers))
{
uvLayer->SetCurrentVertexValue(uvData.get().GetUV(vertexIndex));
}
for (const auto& [tangentData, tangentLayer] : Containers::Views::MakePairView(tangents, tangentLayers))
{
tangentLayer->SetCurrentVertexValue(tangentData.get().GetTangent(vertexIndex));
}
for (const auto& [bitangentData, bitangentLayer] : Containers::Views::MakePairView(bitangents, bitangentLayers))
{
bitangentLayer->SetCurrentVertexValue(bitangentData.get().GetBitangent(vertexIndex));
}
for (const auto& [vertexColorData, vertexColorLayer] : Containers::Views::MakePairView(vertexColors, vertexColorLayers))
{
vertexColorLayer->SetCurrentVertexValue(vertexColorData.get().GetColor(vertexIndex));
}
AZ_POP_DISABLE_WARNING
meshBuilder.AddPolygonVertex(orgVertexNumber);
}
meshBuilder.EndPolygon();
}
meshBuilder.OptimizeTriangleList();
meshBuilder.GenerateSubMeshVertexOrders();
// Create the resulting nodes
auto optimizedMesh = AZStd::make_unique<MeshData>();
optimizedMesh->SetUnitSizeInMeters(meshData->GetUnitSizeInMeters());
optimizedMesh->SetOriginalUnitSizeInMeters(meshData->GetOriginalUnitSizeInMeters());
AZStd::vector<AZStd::unique_ptr<MeshVertexUVData>> optimizedUVs = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexUVData>(uvLayers);
AZStd::vector<AZStd::unique_ptr<MeshVertexTangentData>> optimizedTangents = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexTangentData>(tangentLayers);
AZStd::vector<AZStd::unique_ptr<MeshVertexBitangentData>> optimizedBitangents = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexBitangentData>(bitangentLayers);
AZStd::vector<AZStd::unique_ptr<MeshVertexColorData>> optimizedVertexColors = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexColorData>(vertexColorLayers);
auto optimizedSkinWeights = AZStd::make_unique<SkinWeightData>();
for (size_t subMeshIndex = 0; subMeshIndex < meshBuilder.GetNumSubMeshes(); ++subMeshIndex)
{
const AZ::MeshBuilder::MeshBuilderSubMesh* subMesh = meshBuilder.GetSubMesh(subMeshIndex);
for (size_t vertexIndex = 0; vertexIndex < subMesh->GetNumVertices(); ++vertexIndex)
{
const AZ::MeshBuilder::MeshBuilderVertexLookup& vertexLookup = subMesh->GetVertex(vertexIndex);
optimizedMesh->AddPosition(posLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr));
optimizedMesh->AddNormal(normalsLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr));
optimizedMesh->SetVertexIndexToControlPointIndexMap(
aznumeric_caster(optimizedMesh->GetVertexCount()),
orgVtxLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr)
);
for (auto [uvLayer, optimizedUVNode] : Containers::Views::MakePairView(uvLayers, optimizedUVs))
{
optimizedUVNode->AppendUV(uvLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr));
}
for (auto [tangentLayer, optimizedTangentNode] : Containers::Views::MakePairView(tangentLayers, optimizedTangents))
{
optimizedTangentNode->AppendTangent(tangentLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr));
}
for (auto [bitangentLayer, optimizedBitangentNode] : Containers::Views::MakePairView(bitangentLayers, optimizedBitangents))
{
optimizedBitangentNode->AppendBitangent(bitangentLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr));
}
for (auto [vertexColorLayer, optimizedVertexColorNode] : Containers::Views::MakePairView(vertexColorLayers, optimizedVertexColors))
{
optimizedVertexColorNode->AppendColor(vertexColorLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr));
}
}
for (size_t polygonIndex = 0; polygonIndex < subMesh->GetNumPolygons(); ++polygonIndex)
{
optimizedMesh->AddFace(
{
aznumeric_caster(subMesh->GetIndex(polygonIndex * 3 + 0)),
aznumeric_caster(subMesh->GetIndex(polygonIndex * 3 + 1)),
aznumeric_caster(subMesh->GetIndex(polygonIndex * 3 + 2)),
},
aznumeric_caster(subMesh->GetMaterialIndex())
);
}
}
return AZStd::make_tuple(
AZStd::move(optimizedMesh),
AZStd::move(optimizedUVs),
AZStd::move(optimizedTangents),
AZStd::move(optimizedBitangents),
AZStd::move(optimizedVertexColors),
AZStd::move(optimizedSkinWeights)
);
}
} // namespace AZ::SceneGenerationComponents
@@ -0,0 +1,73 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/tuple.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
namespace AZ { class ReflectContext; }
namespace AZ::SceneAPI::DataTypes { class IMeshData; }
namespace AZ::SceneAPI::DataTypes { class IMeshGroup; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexUVData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexTangentData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexBitangentData; }
namespace AZ::SceneAPI::DataTypes { class ISkinWeightData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexColorData; }
namespace AZ::SceneAPI::Events { class GenerateSimplificationEventContext; }
namespace AZ::SceneData::GraphData { class MeshVertexBitangentData; }
namespace AZ::SceneData::GraphData { class MeshVertexColorData; }
namespace AZ::SceneData::GraphData { class MeshVertexTangentData; }
namespace AZ::SceneData::GraphData { class MeshVertexUVData; }
namespace AZ::SceneAPI::Containers { class SceneGraph; }
namespace AZ::SceneGenerationComponents
{
class MeshOptimizerComponent
: public AZ::SceneAPI::SceneCore::GenerationComponent
{
public:
AZ_COMPONENT(MeshOptimizerComponent, "{05791580-A464-436C-B3EA-36AD68A42BC8}", AZ::SceneAPI::SceneCore::GenerationComponent)
MeshOptimizerComponent();
static void Reflect(AZ::ReflectContext* context);
AZ::SceneAPI::Events::ProcessingResult OptimizeMeshes(AZ::SceneAPI::Events::GenerateSimplificationEventContext& context) const;
static bool HasAnyBlendShapeChild(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex);
private:
static AZStd::tuple<
AZStd::unique_ptr<AZ::SceneAPI::DataTypes::IMeshData>,
AZStd::vector<AZStd::unique_ptr<AZ::SceneData::GraphData::MeshVertexUVData>>,
AZStd::vector<AZStd::unique_ptr<AZ::SceneData::GraphData::MeshVertexTangentData>>,
AZStd::vector<AZStd::unique_ptr<AZ::SceneData::GraphData::MeshVertexBitangentData>>,
AZStd::vector<AZStd::unique_ptr<AZ::SceneData::GraphData::MeshVertexColorData>>,
AZStd::unique_ptr<AZ::SceneAPI::DataTypes::ISkinWeightData>
> OptimizeMesh(
const AZ::SceneAPI::DataTypes::IMeshData* meshData,
const AZStd::vector<AZStd::reference_wrapper<const AZ::SceneAPI::DataTypes::IMeshVertexUVData>>& uvs,
const AZStd::vector<AZStd::reference_wrapper<const AZ::SceneAPI::DataTypes::IMeshVertexTangentData>>& tangents,
const AZStd::vector<AZStd::reference_wrapper<const AZ::SceneAPI::DataTypes::IMeshVertexBitangentData>>& bitangents,
const AZStd::vector<AZStd::reference_wrapper<const AZ::SceneAPI::DataTypes::IMeshVertexColorData>>& vertexColors,
const AZStd::vector<AZStd::reference_wrapper<const AZ::SceneAPI::DataTypes::ISkinWeightData>>& skinWeights,
const AZ::SceneAPI::DataTypes::IMeshGroup& meshGroup,
bool hasBlendShapes);
};
} // namespace AZ::SceneGenerationComponents
@@ -0,0 +1,313 @@
/*
* 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 <Generation/Components/TangentGenerator/TangentGenerateComponent.h>
#include <Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IGroup.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexUVData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h>
#include <SceneAPI/SceneData/Rules/TangentsRule.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/ConvertIterator.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneCore/DataTypes/DataTypeUtilities.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexTangentData.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ::SceneGenerationComponents
{
TangentGenerateComponent::TangentGenerateComponent()
{
BindToCall(&TangentGenerateComponent::GenerateTangentData);
}
void TangentGenerateComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<TangentGenerateComponent, AZ::SceneAPI::SceneCore::GenerationComponent>()->Version(1);
}
}
AZStd::vector<AZ::SceneAPI::DataTypes::TangentSpace> TangentGenerateComponent::CollectRequiredTangentSpaces(const AZ::SceneAPI::Containers::Scene& scene) const
{
AZStd::vector<AZ::SceneAPI::DataTypes::TangentSpace> result;
for (const auto& object : scene.GetManifest().GetValueStorage())
{
if (object->RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::IGroup::TYPEINFO_Uuid()))
{
const AZ::SceneAPI::DataTypes::IGroup* group = azrtti_cast<const AZ::SceneAPI::DataTypes::IGroup*>(object.get());
const AZ::SceneAPI::SceneData::TangentsRule* rule = group->GetRuleContainerConst().FindFirstByType<AZ::SceneAPI::SceneData::TangentsRule>().get();
if (rule)
{
if (AZStd::find(result.begin(), result.end(), rule->GetTangentSpace()) == result.end())
{
result.emplace_back(rule->GetTangentSpace());
}
}
}
}
return result;
}
AZ::SceneAPI::Events::ProcessingResult TangentGenerateComponent::GenerateTangentData(TangentGenerateContext& context)
{
// Iterate over all graph content and filter out all meshes.
AZ::SceneAPI::Containers::SceneGraph& graph = context.GetScene().GetGraph();
AZ::SceneAPI::Containers::SceneGraph::ContentStorageData graphContent = graph.GetContentStorage();
// Build a list of mesh data nodes.
AZStd::vector<AZStd::pair<AZ::SceneAPI::DataTypes::IMeshData*, AZ::SceneAPI::Containers::SceneGraph::NodeIndex> > meshes;
for (auto item = graphContent.begin(); item != graphContent.end(); ++item)
{
// Skip anything that isn't a mesh.
if (!(*item) || !(*item)->RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::IMeshData::TYPEINFO_Uuid()))
{
continue;
}
// Get the mesh data and node index and store them in the vector as a pair, so we can iterate over them later.
auto* mesh = static_cast<AZ::SceneAPI::DataTypes::IMeshData*>(item->get());
AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.ConvertToNodeIndex(item);
meshes.emplace_back(mesh, nodeIndex);
}
// Iterate over them. We had to build the array before as this method can insert new nodes, so using the iterator directly would fail.
for (auto& [mesh, nodeIndex] : meshes)
{
// Generate tangents for the mesh (if this is desired or needed).
if (!GenerateTangentsForMesh(context.GetScene(), nodeIndex, mesh))
{
return AZ::SceneAPI::Events::ProcessingResult::Failure;
}
// Now that we have the tangents and bitangents, calculate the tangent w values for the ones that we imported from Fbx, as they only have xyz.
UpdateFbxTangentWValues(graph, nodeIndex, mesh);
}
return AZ::SceneAPI::Events::ProcessingResult::Success;
}
void TangentGenerateComponent::UpdateFbxTangentWValues(AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, const AZ::SceneAPI::DataTypes::IMeshData* meshData)
{
// Iterate over all UV sets.
AZ::SceneAPI::DataTypes::IMeshVertexUVData* uvData = AZ::SceneAPI::SceneData::TangentsRule::FindUVData(graph, nodeIndex, 0);
size_t uvSetIndex = 0;
while (uvData)
{
// Get the tangents and bitangents from Fbx.
AZ::SceneAPI::DataTypes::IMeshVertexTangentData* fbxTangentData = AZ::SceneAPI::SceneData::TangentsRule::FindTangentData(graph, nodeIndex, uvSetIndex, AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* fbxBitangentData = AZ::SceneAPI::SceneData::TangentsRule::FindBitangentData(graph, nodeIndex, uvSetIndex, AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
if (fbxTangentData && fbxBitangentData)
{
const size_t numVerts = uvData->GetCount();
AZ_Assert((numVerts == fbxTangentData->GetCount()) && (numVerts == fbxBitangentData->GetCount()), "Number of vertices inside UV set is not the same as number of tangents and bitangents.");
for (size_t i = 0; i < numVerts; ++i)
{
// This code calculates the best tangent.w value, which is either -1 or +1, depending on the bitangent being mirrored or not.
// We determine this by checking the angle between the generated tangent by doing a cross product between the tangent and normal, and the actual real bitangent.
// It is no guarantee that using "cross(normal, tangent.xyz)* tangent.w" will result in the right bitangent, as the basis might not be orthogonal.
// But we still go for the best guess.
AZ::Vector4 tangent = fbxTangentData->GetTangent(i);
AZ::Vector3 tangentDir = tangent.GetAsVector3();
tangentDir.NormalizeSafe();
AZ::Vector3 normal = meshData->GetNormal(static_cast<AZ::u32>(i));
normal.NormalizeSafe();
AZ::Vector3 generatedBitangent = normal.Cross(tangentDir);
float dot = fbxBitangentData->GetBitangent(i).Dot(generatedBitangent);
dot = AZ::GetMax(dot, -1.0f);
dot = AZ::GetMin(dot, 1.0f);
const float angle = acosf(dot);
if (angle > AZ::Constants::HalfPi)
{
tangent = fbxTangentData->GetTangent(i);
tangent.SetW(-1.0f);
}
else
{
tangent = fbxTangentData->GetTangent(i);
tangent.SetW(1.0f);
}
fbxTangentData->SetTangent(i, tangent);
}
}
// Find the next UV set.
uvData = AZ::SceneAPI::SceneData::TangentsRule::FindUVData(graph, nodeIndex, ++uvSetIndex);
}
}
bool TangentGenerateComponent::GenerateTangentsForMesh(AZ::SceneAPI::Containers::Scene& scene, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::SceneAPI::DataTypes::IMeshData* meshData)
{
AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
// Check if we have any UV data, if not, we cannot possibly generate the tangents.
AZ::SceneAPI::DataTypes::IMeshVertexUVData* uvData = AZ::SceneAPI::SceneData::TangentsRule::FindUVData(graph, nodeIndex, 0);
if (!uvData)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow, "We cannot generate tangents for this mesh, as it has no UV coordinates!\n");
return true; // No fatal error
}
// Check if we had tangents inside the Fbx file.
AZ::SceneAPI::DataTypes::IMeshVertexTangentData* fbxTangentData = AZ::SceneAPI::SceneData::TangentsRule::FindTangentData(graph, nodeIndex, 0, AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* fbxBitangentData = AZ::SceneAPI::SceneData::TangentsRule::FindBitangentData(graph, nodeIndex, 0, AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
// Check what tangent spaces we need.
AZStd::vector<AZ::SceneAPI::DataTypes::TangentSpace> requiredSpaces = CollectRequiredTangentSpaces(scene);
// If we have no tangent rules, so if the required spaces is empty.
if (requiredSpaces.empty())
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Mesh '%s' has no tangents rule, assuming MikkT tangent space on UV set 0, using normalized tangents and orthogonal bitangents!\n", scene.GetGraph().GetNodeName(nodeIndex).GetName());
requiredSpaces.emplace_back(AZ::SceneAPI::DataTypes::TangentSpace::MikkT);
}
// If all we need is import from FBX, and we have tangent data from Fbx already, then skip generating.
if ((requiredSpaces.size() == 1 && requiredSpaces[0] == AZ::SceneAPI::DataTypes::TangentSpace::FromFbx) && fbxTangentData && fbxBitangentData)
{
return true;
}
// Generate all the tangent spaces we need.
// Do this for every UV set.
bool allSuccess = true;
size_t uvSetIndex = 0;
while (uvData)
{
for (AZ::SceneAPI::DataTypes::TangentSpace space : requiredSpaces)
{
switch (space)
{
// If we want Fbx tangents, we don't need to do anything for that.
case AZ::SceneAPI::DataTypes::TangentSpace::FromFbx:
{
allSuccess &= true;
}
break;
// Generate using MikkT space.
case AZ::SceneAPI::DataTypes::TangentSpace::MikkT:
{
allSuccess &= AZ::TangentGeneration::MikkT::GenerateTangents(scene.GetManifest(), graph, nodeIndex, const_cast<AZ::SceneAPI::DataTypes::IMeshData*>(meshData), uvSetIndex);
}
break;
// If we use EMotion FX calculated tangents, we don't need to generate this here.
case AZ::SceneAPI::DataTypes::TangentSpace::EMotionFX:
allSuccess &= true;
break;
default:
{
AZ_Assert(false, "Unknown tangent space selected (spaceID=%d) for UV set %d, cannot generate tangents!\n", static_cast<AZ::u32>(space), uvSetIndex);
allSuccess = false;
}
}
}
// Try to find the next UV set.
uvData = AZ::SceneAPI::SceneData::TangentsRule::FindUVData(graph, nodeIndex, ++uvSetIndex);
}
return allSuccess;
}
bool TangentGenerateComponent::CreateTangentBitangentLayers(AZ::SceneAPI::Containers::SceneManifest& manifest, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, size_t numVerts, size_t uvSetIndex, AZ::SceneAPI::DataTypes::TangentSpace tangentSpace, const char* spaceName, AZ::SceneAPI::Containers::SceneGraph& graph, AZ::SceneAPI::DataTypes::IMeshVertexTangentData** outTangentData, AZ::SceneAPI::DataTypes::IMeshVertexBitangentData** outBitangentData)
{
*outTangentData = nullptr;
*outBitangentData = nullptr;
//-------------------------------------------------------------
// Create tangent layer.
//-------------------------------------------------------------
AZStd::shared_ptr<SceneData::GraphData::MeshVertexTangentData> tangentData = AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexTangentData>();
tangentData->Resize(numVerts);
AZ_Assert(tangentData, "Failed to allocate tangent data for scene graph.");
if (!tangentData)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to allocate tangent data.\n");
return false;
}
tangentData->SetTangentSetIndex(uvSetIndex);
tangentData->SetTangentSpace(tangentSpace);
const AZStd::string tangentGeneratedName = AZStd::string::format("TangentSet_%s_%zu", spaceName, uvSetIndex);
const AZStd::string tangentSetName = AZ::SceneAPI::DataTypes::Utilities::CreateUniqueName<SceneData::GraphData::MeshVertexBitangentData>(tangentGeneratedName, manifest);
AZ::SceneAPI::Containers::SceneGraph::NodeIndex newIndex = graph.AddChild(nodeIndex, tangentSetName.c_str(), tangentData);
AZ_Assert(newIndex.IsValid(), "Failed to create SceneGraph node for tangent attribute.");
if (!newIndex.IsValid())
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to create node in scene graph that stores tangent data.\n");
return false;
}
graph.MakeEndPoint(newIndex);
//-------------------------------------------------------------
// Create bitangent layer.
//-------------------------------------------------------------
AZStd::shared_ptr<AZ::SceneData::GraphData::MeshVertexBitangentData> bitangentData = AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexBitangentData>();
bitangentData->Resize(numVerts);
AZ_Assert(bitangentData, "Failed to allocate bitangent data for scene graph.");
if (!bitangentData)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to allocate bitangent data.\n");
return false;
}
bitangentData->SetBitangentSetIndex(uvSetIndex);
bitangentData->SetTangentSpace(tangentSpace);
const AZStd::string bitangentGeneratedName = AZStd::string::format("BitangentSet_%s_%zu", spaceName, uvSetIndex);
const AZStd::string bitangentSetName = AZ::SceneAPI::DataTypes::Utilities::CreateUniqueName<SceneData::GraphData::MeshVertexBitangentData>(bitangentGeneratedName, manifest);
newIndex = graph.AddChild(nodeIndex, bitangentSetName.c_str(), bitangentData);
AZ_Assert(newIndex.IsValid(), "Failed to create SceneGraph node for bitangent attribute.");
if (!newIndex.IsValid())
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to create node in scene graph that stores bitangent data.\n");
return false;
}
graph.MakeEndPoint(newIndex);
*outTangentData = tangentData.get();
*outBitangentData = bitangentData.get();
return true;
}
} // namespace AZ::SceneGenerationComponents
@@ -0,0 +1,63 @@
/*
* 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.
*
*/
#pragma once
#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <AzCore/RTTI/RTTI.h>
namespace AZ::SceneAPI::DataTypes { class IMeshData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexUVData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexTangentData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexBitangentData; }
namespace AZ::SceneAPI::DataTypes { enum class TangentSpace; }
namespace AZ::SceneGenerationComponents
{
struct TangentGenerateContext
: public AZ::SceneAPI::Events::ICallContext
{
AZ_RTTI(TangentGenerateContext, "{E836F8F8-5A66-497C-89CC-2D37D741CCAA}", AZ::SceneAPI::Events::ICallContext)
TangentGenerateContext(AZ::SceneAPI::Containers::Scene& scene)
: m_scene(scene) {}
TangentGenerateContext& operator=(const TangentGenerateContext& other) = delete;
AZ::SceneAPI::Containers::Scene& GetScene() { return m_scene; }
const AZ::SceneAPI::Containers::Scene& GetScene() const { return m_scene; }
private:
AZ::SceneAPI::Containers::Scene& m_scene;
};
class TangentGenerateComponent
: public AZ::SceneAPI::SceneCore::GenerationComponent
{
public:
AZ_COMPONENT(TangentGenerateComponent, "{57743E6F-8718-491C-8A82-24A6763904F5}", AZ::SceneAPI::SceneCore::GenerationComponent);
TangentGenerateComponent();
static void Reflect(AZ::ReflectContext* context);
static bool CreateTangentBitangentLayers(AZ::SceneAPI::Containers::SceneManifest& manifest, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, size_t numVerts, size_t uvSetIndex, AZ::SceneAPI::DataTypes::TangentSpace tangentSpace,
const char* spaceName, AZ::SceneAPI::Containers::SceneGraph& graph, AZ::SceneAPI::DataTypes::IMeshVertexTangentData** outTangentData, AZ::SceneAPI::DataTypes::IMeshVertexBitangentData** outBitangentData);
AZ::SceneAPI::Events::ProcessingResult GenerateTangentData(TangentGenerateContext& context);
private:
bool GenerateTangentsForMesh(AZ::SceneAPI::Containers::Scene& scene, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::SceneAPI::DataTypes::IMeshData* meshData);
void UpdateFbxTangentWValues(AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, const AZ::SceneAPI::DataTypes::IMeshData* meshData);
AZStd::vector<AZ::SceneAPI::DataTypes::TangentSpace> CollectRequiredTangentSpaces(const AZ::SceneAPI::Containers::Scene& scene) const;
};
} // namespace AZ::SceneGenerationComponents
@@ -0,0 +1,168 @@
/*
* 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 <Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.h>
#include <Generation/Components/TangentGenerator/TangentGenerateComponent.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexUVData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexTangentData.h>
#include <SceneAPI/SceneData/Rules/TangentsRule.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <mikkelsen/mikktspace.h>
namespace AZ::TangentGeneration::MikkT
{
// Returns the number of triangles in the mesh.
int GetNumFaces(const SMikkTSpaceContext* context)
{
MikktCustomData* customData = static_cast<MikktCustomData*>(context->m_pUserData);
return customData->m_meshData->GetFaceCount();
}
int GetNumVerticesOfFace(const SMikkTSpaceContext* context, const int face)
{
AZ_UNUSED(context);
AZ_UNUSED(face);
return 3;
}
void GetPosition(const SMikkTSpaceContext* context, float posOut[], const int face, const int vert)
{
MikktCustomData* customData = static_cast<MikktCustomData*>(context->m_pUserData);
const AZ::u32 vertexIndex = customData->m_meshData->GetVertexIndex(face, vert);
const AZ::Vector3& pos = customData->m_meshData->GetPosition(vertexIndex);
posOut[0] = pos.GetX();
posOut[1] = pos.GetY();
posOut[2] = pos.GetZ();
}
void GetNormal(const SMikkTSpaceContext* context, float normOut[], const int face, const int vert)
{
MikktCustomData* customData = static_cast<MikktCustomData*>(context->m_pUserData);
const AZ::u32 vertexIndex = customData->m_meshData->GetVertexIndex(face, vert);
const AZ::Vector3 normal = customData->m_meshData->GetNormal(vertexIndex).GetNormalizedSafe();
normOut[0] = normal.GetX();
normOut[1] = normal.GetY();
normOut[2] = normal.GetZ();
}
void GetTexCoord(const SMikkTSpaceContext* context, float texOut[], const int face, const int vert)
{
MikktCustomData* customData = static_cast<MikktCustomData*>(context->m_pUserData);
const AZ::u32 vertexIndex = customData->m_meshData->GetVertexIndex(face, vert);
const AZ::Vector2& uv = customData->m_uvData->GetUV(vertexIndex);
texOut[0] = uv.GetX();
texOut[1] = uv.GetY();
}
// This function is used to return the tangent and signValue to the application.
// tangent is a unit length vector.
// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
// bitangent = signValue * cross(vN, tangent);
// Note that the results are returned unindexed. It is possible to generate a new index list
void SetTSpaceBasic(const SMikkTSpaceContext* context, const float tangent[], const float signValue, const int face, const int vert)
{
MikktCustomData* customData = static_cast<MikktCustomData*>(context->m_pUserData);
const AZ::u32 vertexIndex = customData->m_meshData->GetVertexIndex(face, vert);
AZ::Vector3 tangentVec3(tangent[0], tangent[1], tangent[2]);
tangentVec3.NormalizeSafe();
AZ::Vector3 normal = customData->m_meshData->GetNormal(vertexIndex);
normal.NormalizeSafe();
const AZ::Vector3 bitangent = normal.Cross(tangentVec3) * signValue;
customData->m_tangentData->SetTangent(vertexIndex, AZ::Vector4(tangentVec3.GetX(), tangentVec3.GetY(), tangentVec3.GetZ(), signValue));
customData->m_bitangentData->SetBitangent(vertexIndex, bitangent);
}
// This function is used to return tangent space results to the application.
// tangent and bitangent are unit length vectors and magS and magT are their
// true magnitudes which can be used for relief mapping effects.
// bitangent is the "real" bitangent and thus may not be perpendicular to tangent.
// However, both are perpendicular to the vertex normal.
// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
// signValue = isOrientationPreserving ? 1.0f : -1.0f;
// bitangent = signValue * cross(vN, tangent);
void SetTSpace(const SMikkTSpaceContext* context, const float tangent[], const float bitangent[], const float magS, const float magT, const tbool isOrientationPreserving, const int face, const int vert)
{
MikktCustomData* customData = static_cast<MikktCustomData*>(context->m_pUserData);
const AZ::u32 vertexIndex = customData->m_meshData->GetVertexIndex(face, vert);
const float flipSign = isOrientationPreserving ? 1.0f : -1.0f;
const AZ::Vector4 tangentVec(tangent[0]*magS, tangent[1]*magS, tangent[2]*magS, flipSign);
const AZ::Vector3 bitangentVec(bitangent[0]*magT, bitangent[1]*magT, bitangent[2]*magT);
customData->m_tangentData->SetTangent(vertexIndex, tangentVec);
customData->m_bitangentData->SetBitangent(vertexIndex, bitangentVec);
}
bool GenerateTangents(AZ::SceneAPI::Containers::SceneManifest& manifest, AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::SceneAPI::DataTypes::IMeshData* meshData, size_t uvSet)
{
// Create tangent and bitangent data sets and relate them to the given UV set.
AZ::SceneAPI::DataTypes::IMeshVertexUVData* uvData = AZ::SceneAPI::SceneData::TangentsRule::FindUVData(graph, nodeIndex, uvSet);
AZ::SceneAPI::DataTypes::IMeshVertexTangentData* tangentData = nullptr;
AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* bitangentData = nullptr;
if (!uvData)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Cannot find UV data (set index=%d) to generate tangents and bitangents from in MikkT generator!\n", uvSet);
return false;
}
if (!AZ::SceneGenerationComponents::TangentGenerateComponent::CreateTangentBitangentLayers(manifest, nodeIndex, meshData->GetVertexCount(), uvSet, AZ::SceneAPI::DataTypes::TangentSpace::MikkT, "MikkT", graph, &tangentData, &bitangentData))
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to create tangents and bitangents data sets inside MikkT generator!\n");
return false;
}
//----------------------------------
// Provide the MikkT interface.
SMikkTSpaceInterface mikkInterface;
mikkInterface.m_getNumFaces = GetNumFaces;
mikkInterface.m_getNormal = GetNormal;
mikkInterface.m_getPosition = GetPosition;
mikkInterface.m_getTexCoord = GetTexCoord;
mikkInterface.m_setTSpace = SetTSpace;
mikkInterface.m_setTSpaceBasic = nullptr;//SetTSpaceBasic;
mikkInterface.m_getNumVerticesOfFace= GetNumVerticesOfFace;
// Set the MikkT custom data.
MikktCustomData customData;
customData.m_meshData = meshData;
customData.m_uvData = uvData;
customData.m_tangentData = tangentData;
customData.m_bitangentData = bitangentData;
// Generate the tangents.
SMikkTSpaceContext mikkContext;
mikkContext.m_pInterface = &mikkInterface;
mikkContext.m_pUserData = &customData;
if (genTangSpaceDefault(&mikkContext) == 0)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to generate tangents and bitangents using MikkT, because MikkT reported failure!\n");
return false;
}
return true;
}
} // namespace AZ::TangentGeneration::MikkT
@@ -0,0 +1,34 @@
/*
* 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.
*
*/
#pragma once
#include <SceneAPI/SceneCore/Containers/Scene.h>
namespace AZ::SceneAPI::DataTypes { class IMeshData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexUVData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexTangentData; }
namespace AZ::SceneAPI::DataTypes { class IMeshVertexBitangentData; }
namespace AZ::TangentGeneration::MikkT
{
struct MikktCustomData
{
AZ::SceneAPI::DataTypes::IMeshData* m_meshData;
AZ::SceneAPI::DataTypes::IMeshVertexUVData* m_uvData;
AZ::SceneAPI::DataTypes::IMeshVertexTangentData* m_tangentData;
AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* m_bitangentData;
};
// The main generation method.
bool GenerateTangents(AZ::SceneAPI::Containers::SceneManifest& manifest, AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::SceneAPI::DataTypes::IMeshData* meshData, size_t uvSet);
} // namespace AZ::TangentGeneration::MikkT
@@ -0,0 +1,44 @@
/*
* 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 <Generation/Components/TangentGenerator/TangentPreExportComponent.h>
#include <Generation/Components/TangentGenerator/TangentGenerateComponent.h>
#include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
namespace AZ::SceneGenerationComponents
{
namespace SceneEvents = AZ::SceneAPI::Events;
TangentPreExportComponent::TangentPreExportComponent()
{
BindToCall(&TangentPreExportComponent::Register);
}
void TangentPreExportComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<TangentPreExportComponent, AZ::SceneAPI::SceneCore::GenerationComponent>()->Version(1);
}
}
AZ::SceneAPI::Events::ProcessingResult TangentPreExportComponent::Register(AZ::SceneAPI::Events::GenerateAdditionEventContext& context)
{
SceneEvents::ProcessingResultCombiner result;
TangentGenerateContext tangentGenerateContext(context.GetScene());
result += SceneEvents::Process<TangentGenerateContext>(tangentGenerateContext);
return SceneEvents::ProcessingResult::Success;
}
} // namespace AZ::SceneGenerationComponents
@@ -0,0 +1,38 @@
/*
* 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.
*
*/
#pragma once
#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <RC/ResourceCompilerScene/Common/ExportContextGlobal.h>
#include <AzCore/RTTI/RTTI.h>
namespace AZ::SceneAPI::Events { class GenerateAdditionEventContext; }
namespace AZ::SceneGenerationComponents
{
class TangentPreExportComponent
: public AZ::SceneAPI::SceneCore::GenerationComponent
{
public:
AZ_COMPONENT(TangentPreExportComponent, "{BFFE114A-2FC6-42F1-92C4-61329CC54A2B}", AZ::SceneAPI::SceneCore::GenerationComponent)
TangentPreExportComponent();
static void Reflect(AZ::ReflectContext* context);
AZ::SceneAPI::Events::ProcessingResult Register(AZ::SceneAPI::Events::GenerateAdditionEventContext& context);
};
} // namespace AZ::SceneGenerationComponents