Integrating latest 47acbe8
This commit is contained in:
@@ -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
|
||||
+22
@@ -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
|
||||
+143
@@ -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
|
||||
+63
@@ -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
|
||||
+195
@@ -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
|
||||
+65
@@ -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
|
||||
+28
@@ -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
|
||||
+150
@@ -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
|
||||
+520
@@ -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
|
||||
+73
@@ -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
|
||||
+313
@@ -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
|
||||
+63
@@ -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
|
||||
+168
@@ -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
|
||||
+34
@@ -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
|
||||
+44
@@ -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
|
||||
+38
@@ -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
|
||||
Reference in New Issue
Block a user