#pragma once #include "../../core/Defines.h" #include "../../core/Serializer.h" #include "../../core/CollectionHelper.h" #include "MaterialLibraryPointer.h" #include "../../inc/tbb/parallel_sort.h" #include #include // 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, glm::u8vec3, and unsigned char[]. template, unsigned8 channelsPerPixel = 3> class MaterialLibrary { private: std::map 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* 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(mMaterials->at(i), WrapSetIndex(i))); } public: MaterialLibrary() : mMaterialPointers(std::map()), finalized(false), mTextureSize(0), mMaterials(new std::vector()) {} MaterialLibrary(std::vector texture, unsigned short textureSize, MaterialLibraryPointer highestMaterialIndex) : MaterialLibrary() { ReadTexture(texture, textureSize, highestMaterialIndex); } // Copy constructor MaterialLibrary(const MaterialLibrary& other) : mMaterialPointers(std::map(other.mMaterialPointers)), finalized(other.finalized), mTextureSize(other.mTextureSize), mMaterials(new std::vector(*other.mMaterials)) {} ~MaterialLibrary() { delete mMaterials; } void Serialize(std::ostream& file) { unsigned16 materialTextureSize = GetTextureSize(); MaterialLibraryPointer maxTextureIndex = GetMaxTextureIndex(); std::vector 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::Serialize(0, file); Serializer::Serialize(materialTextureSize, file); Serializer::Serialize(maxTextureIndex, file); Serializer::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::Deserialize(materialTextureSizeSummary, file); if (materialTextureSizeSummary == 0) { // New style (supports bigger libraries) Serializer::Deserialize(materialTextureSize, file); Serializer::Deserialize(maxTextureIndex, file); } else { // Old style unsigned mask1 = BitHelper::GetLSMask(20, 30); unsigned mask2 = BitHelper::GetLSMask(10, 20); unsigned mask3 = BitHelper::GetLSMask(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 texture(textureArraySize); Serializer::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 GetMaterials() const { std::vector 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()(material, *mMaterials); } T GetMaterial(MaterialLibraryPointer materialPointer) { assert(finalized); return mMaterials->at(GetSetIndex(materialPointer)); } std::vector> GetMaterialTextureIndices() const { assert(finalized); std::vector> materials; size_t setIndex = 0; for (T material : (*mMaterials)) { auto textureIndex = WrapSetIndex(setIndex++); materials.push_back(std::pair(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 GetTexture() const { std::vector 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 texture, unsigned textureSize, MaterialLibraryPointer maxTextureIndex) { mTextureSize = textureSize; unsigned props = PIXELS_PER_MATERIAL * channelsPerPixel; std::vector 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); } };