277 lines
9.2 KiB
C++
277 lines
9.2 KiB
C++
#pragma once
|
|
#include "../../core/Defines.h"
|
|
#include "../../core/Serializer.h"
|
|
#include "../../core/CollectionHelper.h"
|
|
#include "MaterialLibraryPointer.h"
|
|
#include "../../inc/tbb/parallel_sort.h"
|
|
#include <vector>
|
|
#include <iterator>
|
|
|
|
// T should be some type with a method called "Serialize()".
|
|
// This method should return some type that can be iterated over using operator [], and each item of the iteration should be an unsigned char (unsigned8) to put in the texture colors.
|
|
// If sizeof(result) for the value is used, it should give the number of bytes needed to store in pixels.
|
|
// An example of a working return type is std::vector<unsigned8>, glm::u8vec3, and unsigned char[].
|
|
template<typename T, typename Comparer = std::less<T>, unsigned8 channelsPerPixel = 3>
|
|
class MaterialLibrary
|
|
{
|
|
private:
|
|
std::map<T, MaterialLibraryPointer, Comparer> mMaterialPointers;
|
|
bool finalized = false;
|
|
unsigned short mTextureSize;
|
|
|
|
protected:
|
|
const size_t SIZE_OF_MATERIAL = sizeof(T);
|
|
const unsigned32 PIXELS_PER_MATERIAL = (unsigned32)(SIZE_OF_MATERIAL / channelsPerPixel + (SIZE_OF_MATERIAL % channelsPerPixel == 0 ? 0 : 1));
|
|
|
|
std::vector<T>* mMaterials;
|
|
|
|
inline MaterialLibraryPointer WrapSetIndex(size_t setIndex) const
|
|
{
|
|
assert(finalized);
|
|
setIndex *= PIXELS_PER_MATERIAL;
|
|
return MaterialLibraryPointer(
|
|
(unsigned16)(setIndex % (size_t)mTextureSize),
|
|
(unsigned16)(setIndex / (size_t)mTextureSize)
|
|
);
|
|
}
|
|
|
|
inline size_t GetSetIndex(MaterialLibraryPointer textureIndex) const { assert(finalized); return (textureIndex.x + textureIndex.y * mTextureSize) / PIXELS_PER_MATERIAL; }
|
|
|
|
inline void RebuildMaterialPointers()
|
|
{
|
|
assert(finalized);
|
|
mMaterialPointers.clear();
|
|
// Build the result material pointers map:
|
|
for (size_t i = 0; i < mMaterials->size(); i++)
|
|
mMaterialPointers.insert(std::pair<T, MaterialLibraryPointer>(mMaterials->at(i), WrapSetIndex(i)));
|
|
}
|
|
public:
|
|
MaterialLibrary() :
|
|
mMaterialPointers(std::map<T, MaterialLibraryPointer, Comparer>()),
|
|
finalized(false),
|
|
mTextureSize(0),
|
|
mMaterials(new std::vector<T>())
|
|
{}
|
|
|
|
MaterialLibrary(std::vector<unsigned char> texture, unsigned short textureSize, MaterialLibraryPointer highestMaterialIndex) : MaterialLibrary()
|
|
{
|
|
ReadTexture(texture, textureSize, highestMaterialIndex);
|
|
}
|
|
|
|
// Copy constructor
|
|
MaterialLibrary(const MaterialLibrary& other) :
|
|
mMaterialPointers(std::map<T, MaterialLibraryPointer, Comparer>(other.mMaterialPointers)),
|
|
finalized(other.finalized),
|
|
mTextureSize(other.mTextureSize),
|
|
mMaterials(new std::vector<T>(*other.mMaterials))
|
|
{}
|
|
|
|
~MaterialLibrary() { delete mMaterials; }
|
|
|
|
void Serialize(std::ostream& file)
|
|
{
|
|
unsigned16 materialTextureSize = GetTextureSize();
|
|
MaterialLibraryPointer maxTextureIndex = GetMaxTextureIndex();
|
|
std::vector<unsigned8> texture = GetTexture();
|
|
|
|
// The material texture size and max texture index used to be stored in one 32 bit unsigned integer.
|
|
// However, this caused problems if the material texture contained more then 1024 colors.
|
|
// Therefore, we now use 48 bits for this, precluded by an empty header in the old style, to ensure that the new style can be recognized correctly.
|
|
Serializer<unsigned32>::Serialize(0, file);
|
|
Serializer<unsigned16>::Serialize(materialTextureSize, file);
|
|
Serializer<MaterialLibraryPointer>::Serialize(maxTextureIndex, file);
|
|
Serializer<unsigned8*>::Serialize(&texture[0], (size_t)materialTextureSize * (size_t)materialTextureSize * (size_t)channelsPerPixel, file);
|
|
}
|
|
|
|
void Deserialize(std::istream& file)
|
|
{
|
|
unsigned16 materialTextureSize = 0;
|
|
MaterialLibraryPointer maxTextureIndex = MaterialLibraryPointer(0);
|
|
|
|
// If the first unsigned32 of a material library is 0, then the new style information is placed after it.
|
|
// If it isn't 0, then use the old style
|
|
unsigned materialTextureSizeSummary;
|
|
Serializer<unsigned>::Deserialize(materialTextureSizeSummary, file);
|
|
if (materialTextureSizeSummary == 0)
|
|
{
|
|
// New style (supports bigger libraries)
|
|
Serializer<unsigned16>::Deserialize(materialTextureSize, file);
|
|
Serializer<MaterialLibraryPointer>::Deserialize(maxTextureIndex, file);
|
|
}
|
|
else
|
|
{
|
|
// Old style
|
|
unsigned mask1 = BitHelper::GetLSMask<unsigned32>(20, 30);
|
|
unsigned mask2 = BitHelper::GetLSMask<unsigned32>(10, 20);
|
|
unsigned mask3 = BitHelper::GetLSMask<unsigned32>(0, 10);
|
|
maxTextureIndex.x = (mask1 & materialTextureSizeSummary) >> 20;
|
|
maxTextureIndex.y = (mask2 & materialTextureSizeSummary) >> 10;
|
|
materialTextureSize = BitHelper::CeilToNearestPowerOfTwo(mask3 & materialTextureSizeSummary);
|
|
}
|
|
|
|
size_t textureArraySize = (size_t)materialTextureSize * (size_t)materialTextureSize * (size_t)channelsPerPixel;
|
|
std::vector<unsigned8> texture(textureArraySize);
|
|
Serializer<unsigned8*>::Deserialize(&texture[0], textureArraySize, file);
|
|
ReadTexture(texture, materialTextureSize, maxTextureIndex);
|
|
}
|
|
|
|
|
|
|
|
void AddMaterial(const T& material) {
|
|
if(!finalized) mMaterials->push_back(material);
|
|
}
|
|
void RemoveMaterial(const T& material) { if (!finalized) mMaterials->erase(material); }
|
|
void Finalize(bool filterUnique = true)
|
|
{
|
|
if (finalized) return;
|
|
|
|
if (filterUnique)
|
|
CollectionHelper::Unique(*mMaterials, Comparer());
|
|
|
|
mTextureSize = GetTextureSize();
|
|
finalized = true;
|
|
|
|
RebuildMaterialPointers();
|
|
}
|
|
bool IsFinalized() const { return finalized; }
|
|
std::vector<T> GetMaterials() const
|
|
{
|
|
std::vector<T> materials(mMaterials->size());
|
|
tbb::parallel_for(size_t(0), mMaterials->size(), [&](size_t i)
|
|
{
|
|
materials[i] = mMaterials->at(i);
|
|
});
|
|
return materials;
|
|
}
|
|
|
|
T GetNearestMaterial(T material) const {
|
|
return NearestFinder<T>()(material, *mMaterials);
|
|
}
|
|
T GetMaterial(MaterialLibraryPointer materialPointer)
|
|
{
|
|
assert(finalized);
|
|
return mMaterials->at(GetSetIndex(materialPointer));
|
|
}
|
|
std::vector<std::pair<T, MaterialLibraryPointer>> GetMaterialTextureIndices() const
|
|
{
|
|
assert(finalized);
|
|
std::vector<std::pair<T, MaterialLibraryPointer>> materials;
|
|
size_t setIndex = 0;
|
|
for (T material : (*mMaterials))
|
|
{
|
|
auto textureIndex = WrapSetIndex(setIndex++);
|
|
materials.push_back(std::pair<T, MaterialLibraryPointer>(material, textureIndex));
|
|
}
|
|
return materials;
|
|
}
|
|
MaterialLibraryPointer GetTextureIndex(const T& material) const
|
|
{
|
|
assert(finalized);
|
|
auto materialIt = mMaterialPointers.find(material);
|
|
if (materialIt == mMaterialPointers.end())
|
|
return GetTextureIndex(GetNearestMaterial(material));
|
|
return materialIt->second;
|
|
}
|
|
|
|
bool Contains(const T& material) const
|
|
{
|
|
auto materialIt = mMaterialPointers.find(material);
|
|
return materialIt != mMaterialPointers.end();
|
|
}
|
|
|
|
MaterialLibraryPointer GetMaxTextureIndex() const
|
|
{
|
|
return WrapSetIndex(mMaterials->size());
|
|
}
|
|
|
|
unsigned short GetTextureSize() const {
|
|
if (finalized) return mTextureSize;
|
|
if (mMaterials->empty())
|
|
return 0;
|
|
size_t requiredPixels = PIXELS_PER_MATERIAL * mMaterials->size();
|
|
if (requiredPixels < 4) requiredPixels = 4; // Make sure the texture is always 2x2
|
|
unsigned16 v = (unsigned16)std::ceil(std::sqrt(double(requiredPixels)));
|
|
return BitHelper::CeilToNearestPowerOfTwo(v);
|
|
}
|
|
|
|
std::vector<unsigned8> GetTexture() const
|
|
{
|
|
std::vector<unsigned8> texture(mTextureSize * mTextureSize * channelsPerPixel);
|
|
size_t i = 0;
|
|
for (T material : (*mMaterials))
|
|
{
|
|
auto materialProperties = material.Serialize();
|
|
unsigned props = (unsigned)materialProperties.size();
|
|
unsigned prop = 0;
|
|
assert(i + channelsPerPixel * PIXELS_PER_MATERIAL <= texture.size());
|
|
|
|
for (unsigned pixel = 0; pixel < PIXELS_PER_MATERIAL; pixel++)
|
|
{
|
|
for (unsigned8 channel = 0; channel < channelsPerPixel; channel++)
|
|
{
|
|
if (prop < props)
|
|
texture[i + channel] = materialProperties[prop];
|
|
prop++;
|
|
}
|
|
i += channelsPerPixel;
|
|
}
|
|
}
|
|
return texture;
|
|
}
|
|
|
|
void ReadTexture(std::vector<unsigned char> texture, unsigned textureSize, MaterialLibraryPointer maxTextureIndex)
|
|
{
|
|
mTextureSize = textureSize;
|
|
unsigned props = PIXELS_PER_MATERIAL * channelsPerPixel;
|
|
std::vector<unsigned8> curMatProps(SIZE_OF_MATERIAL);
|
|
unsigned maxIndex = maxTextureIndex.x + maxTextureIndex.y * textureSize;
|
|
bool endFound = maxIndex != 0;
|
|
bool firstEmptyMaterialFound = false;
|
|
size_t i = 0;
|
|
while (!(endFound && i >= maxIndex * channelsPerPixel))
|
|
{
|
|
// Read the materials from the current pixels
|
|
for (size_t j = 0; j < SIZE_OF_MATERIAL; j++)
|
|
curMatProps[j] = texture[i + j];
|
|
if (!endFound)
|
|
{
|
|
bool allZero = true;
|
|
for (unsigned char prop : curMatProps)
|
|
if (prop != 0)
|
|
{
|
|
allZero = false;
|
|
break;
|
|
}
|
|
if (firstEmptyMaterialFound && allZero) endFound = true;
|
|
firstEmptyMaterialFound |= allZero;
|
|
}
|
|
|
|
T curMat;
|
|
curMat.Deserialize(curMatProps);
|
|
AddMaterial(curMat);
|
|
|
|
i += props;
|
|
}
|
|
finalized = true;
|
|
RebuildMaterialPointers();
|
|
}
|
|
|
|
bool operator==(const MaterialLibrary& other) const
|
|
{
|
|
// Check if the material count and finalized state are equal
|
|
if (this->IsFinalized() != other.IsFinalized()
|
|
|| this->mMaterials->size() != other.mMaterials->size())
|
|
return false;
|
|
|
|
// Check if the materials are equal
|
|
for (size_t i = 0; i < mMaterials->size(); i++)
|
|
if (this->mMaterials->at(i) != other.mMaterials->at(i))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool operator!=(const MaterialLibrary& other) const
|
|
{
|
|
return !(*this == other);
|
|
}
|
|
}; |