#pragma once #include "Tree.h" #include "MaterialNode.h" #include "IMaterialTexture.h" #include "../../inc/tbb/parallel_for_each.h" #include "../../inc/tbb/concurrent_queue.h" #include "../Material/MaterialLibrary.h" #include "../../core/BitHelper.h" #include #include #include typedef MaterialNode MaterialLibraryNode; template, unsigned8 channelsPerPixel = 3> class MaterialLibraryTree : public Tree, public IMaterialTexture { protected: MaterialLibrary* mMaterialLibrary; bool mOwnsLibrary; std::vector mMaterialTexture; MaterialLibraryPointer mMaxTextureIndex; unsigned short mMaterialTextureSize; std::unordered_map leafMap; inline void WriteMaterialTexture(std::ostream& file) { assert(mMaterialLibrary != NULL); mMaterialLibrary->Serialize(file); } inline void ReadMaterialTexture(std::istream& file) { if (mMaterialLibrary == NULL) mMaterialLibrary = new MaterialLibrary(); mMaterialLibrary->Deserialize(file); GetMaterialTexture(); } inline void CreateAllMaterialLeafs() { std::vector> materialsAndIndices = mMaterialLibrary->GetMaterialTextureIndices(); for (auto materialAndIndex : materialsAndIndices) { T material = materialAndIndex.first; MaterialLibraryPointer textureIndex = materialAndIndex.second; MaterialLibraryNode* leaf = (MaterialLibraryNode*)Create(GetMaxLevel()); leaf->SetMaterial(textureIndex); leafMap.insert(std::pair(material, leaf)); } } public: // Creates a material library tree. MaterialLibraryTree(unsigned8 maxLevel) : Tree(maxLevel), mOwnsLibrary(true), mMaterialLibrary(new MaterialLibrary()) { mLeafsAreEqual = false; } // Creates a material library tree with the given (finalized!) material library. The library is not owned by this tree and therefore will not // be deleted when this tree is deleted. MaterialLibraryTree(unsigned8 maxLevel, MaterialLibrary* materialLibrary) : Tree(maxLevel), mOwnsLibrary(false), mMaterialLibrary(materialLibrary) { mLeafsAreEqual = false; CreateAllMaterialLeafs(); } ~MaterialLibraryTree() override { if(mOwnsLibrary) delete mMaterialLibrary; } MaterialLibrary* GetMaterialLibrary() { return mMaterialLibrary; } static void PassLibraryOwnership(MaterialLibraryTree* tree1, MaterialLibraryTree* tree2) { tree1->mOwnsLibrary = false; tree2->mOwnsLibrary = true; } void AddMaterial(T material) { if (mMaterialLibrary->IsFinalized()) return; mMaterialLibrary->AddMaterial(material); } void FinalizeMaterials() { mMaterialLibrary->Finalize(); CreateAllMaterialLeafs(); } // Assuming all leaf nodes contain pointers to materials, propagates those materials up in the tree, leaving the average material everywhere. // It is advised to call this method after DAG conversion, as it is still valid at that point, and it will be cheaper. void PropagateMaterials() { // Bottom up go through the nodes to propagate the materials auto levelIndices = SortOnLevel(); // Set node weights for weighted average calculation std::vector lastLevelNodeWeights; std::vector perfectMaterialPerNode(GetNodeCount()); std::vector levelNodeWeights; auto leafStart = levelIndices[GetMaxLevel()]; auto leafEnd = levelIndices[GetMaxLevel() + 1]; lastLevelNodeWeights.resize(leafEnd - leafStart, 1.f); // Initialize the vectors for leaf nodes, assuming leaf nodes have weight 1 and perfect materials for (auto i = leafStart; i != leafEnd; i++) perfectMaterialPerNode[i] = mMaterialLibrary->GetMaterial(GetTypedNode(i)->GetMaterial()); // Bottom-up calculate the weighted average material for each node in the tree, and store them in perfectMaterialPerNode for (unsigned8 level = GetMaxLevel(); level-- > 0;) { unsigned32 levelStart = levelIndices[level]; unsigned32 levelEnd = levelIndices[level + 1]; levelNodeWeights = std::vector(levelEnd - levelStart); tbb::parallel_for(levelStart, levelEnd, [&](unsigned32 i) { // Get the current node MaterialLibraryNode* node = GetTypedNode(i); std::vector childMaterials; std::vector childWeights; float nodeWeight = 0; // Find all materials the children use unsigned32* children = node->GetChildren(); unsigned8 childCount = node->GetChildCount(); for (ChildIndex c = 0; c < childCount; c++) { unsigned32 i = children[c]; MaterialLibraryNode* child = GetTypedNode(i); MaterialLibraryPointer matPtr = child->GetMaterial(); float childWeight = lastLevelNodeWeights[i - levelEnd]; childMaterials.push_back(perfectMaterialPerNode[i]); childWeights.push_back(childWeight); nodeWeight += childWeight; } // Calculate the average material and retrieve the closest material to that from the library T nodeMaterial = T::WeightedAverage(childMaterials, childWeights); // Store the weighted average in perfectMaterialPerNode perfectMaterialPerNode[i] = nodeMaterial; levelNodeWeights[i - levelStart] = nodeWeight; }); // Update the last level node weights to the node weights of the current level lastLevelNodeWeights = std::move(levelNodeWeights); } // Find all materials required for this scene std::vector perfectMaterials(GetNodeCount()); tbb::parallel_for((size_t)0, perfectMaterialPerNode.size(), [&](size_t i) { perfectMaterials[i] = perfectMaterialPerNode[i]; }); // Reduce the materials by only using unique ones tbb::parallel_sort(perfectMaterials, Comparer()); perfectMaterials.erase(std::unique(perfectMaterials.begin(), perfectMaterials.end()), perfectMaterials.end()); perfectMaterials.shrink_to_fit(); // Create a new material library based on these materials if (mOwnsLibrary) delete mMaterialLibrary; mMaterialTexture.clear(); mMaterialLibrary = new MaterialLibrary(); for (auto material : perfectMaterials) mMaterialLibrary->AddMaterial(material); mMaterialLibrary->Finalize(); // Update all nodes in the tree to point to the new material library tbb::parallel_for((unsigned32)0, GetNodeCount(), [&](unsigned32 i) { MaterialLibraryPointer materialPointer = mMaterialLibrary->GetTextureIndex(perfectMaterialPerNode[i]); auto node = GetTypedNode(i); node->SetMaterial(materialPointer); }); // Update the material texture GetMaterialTexture(); } std::vector GetMaterialTexture() override { if (!mMaterialTexture.empty()) return mMaterialTexture; assert(mMaterialLibrary->IsFinalized()); mMaterialTextureSize = mMaterialLibrary->GetTextureSize(); mMaterialTexture = mMaterialLibrary->GetTexture(); mMaxTextureIndex = mMaterialLibrary->GetMaxTextureIndex(); return mMaterialTexture; } unsigned GetMaterialTextureSize() override { GetMaterialTexture(); return mMaterialTextureSize; } unsigned8 GetMaterialTextureChannelsPerPixel() override { return channelsPerPixel; } std::vector GetLeafMaterials() { auto levelIndices = SortOnLevel(); unsigned32 leafsStart = levelIndices[GetMaxLevel()]; unsigned32 leafsEnd = levelIndices[GetMaxLevel() + 1]; std::vector leafMaterials(levelIndices[GetMaxLevel() + 1] - levelIndices[GetMaxLevel()]); tbb::parallel_for(leafsStart, leafsEnd, [&](const unsigned32 i) { leafMaterials[i - leafsStart] = GetMaterial(GetTypedNode(i)); }); return leafMaterials; } // Replaces the current leaf materials (and thereby also the material library) by the given material. // In this, it assumes the leafMaterials list is ordered in the same way the leafs are ordered in the current tree. // It is advised to follow this command by "ToDAG()" and "PropagateMaterials()" void SetLeafMaterials(const std::vector leafMaterials) { // Create the new material library delete mMaterialLibrary; mMaterialLibrary = new MaterialLibrary(); for (auto material : leafMaterials) mMaterialLibrary->AddMaterial(material); mMaterialLibrary->Finalize(); // Set the leaf node materials size_t i = 0; for (unsigned32 i = 0; i < GetNodeCount(); i++) MaterialLibraryNode* node = GetTypedNode(i); if (node->GetLevel() == GetMaxLevel()) { node->SetMaterial(mMaterialLibrary->GetTextureIndex(leafMaterials[i])); i++; } } T GetMaterial(const MaterialLibraryNode* node) const { assert(mMaterialLibrary->IsFinalized()); return mMaterialLibrary->GetMaterial(node->GetMaterial()); } std::vector GetMaterials() const { // Read all materials from all nodes std::vector materials(GetNodeCount()); //for (unsigned32 i = 0; i < GetNodeCount(); i++) tbb::parallel_for((unsigned32)0, GetNodeCount(), [&](const unsigned32 i) { materials[i] = GetMaterial(GetTypedNode(i)); }); return materials; } void SetMaterials(const std::vector& materials) { // Not a valid material vector if (materials.size() != GetNodeCount()) return; // Create a new material library containing the new materials if (mOwnsLibrary) delete mMaterialLibrary; mMaterialLibrary = new MaterialLibrary(); for (auto material : materials) { mMaterialLibrary->AddMaterial(material); } mMaterialLibrary->Finalize(); mMaterialTexture.clear(); // Update all nodes to point to this new library tbb::parallel_for((unsigned32)0, GetNodeCount(), [&](unsigned32 i) { MaterialLibraryNode* node = GetTypedNode(i); node->SetMaterial(mMaterialLibrary->GetTextureIndex(materials[i])); }); } std::vector GetUniqueMaterials() const { return mMaterialLibrary->GetMaterials(); } // Clears all material pointers from non-leaf nodes void ClearPropagation() { auto levelIndices = SortOnLevel(); tbb::parallel_for((unsigned32)0, levelIndices[GetMaxLevel()], [&](const unsigned32 i) { MaterialLibraryNode* node = GetTypedNode(i); node->SetMaterial(MaterialLibraryPointer(0)); }); } // Replaces the leaf materials by the given leaf materials. It is advised to follow this command by "ToDAG()" and "PropagateMaterials()" void ReplaceLeafMaterials(const std::map& leafMaterialReplacers) { auto newMaterialLibrary = new MaterialLibrary(); for (auto material : leafMaterialReplacers) newMaterialLibrary->AddMaterial(material.second); newMaterialLibrary->Finalize(); std::unordered_map materialLibraryPointerReplacers; for (auto material : leafMaterialReplacers) materialLibraryPointerReplacers.insert(std::make_pair(mMaterialLibrary->GetTextureIndex(material.first), newMaterialLibrary->GetTextureIndex(material.second))); auto levelIndices = SortOnLevel(); tbb::parallel_for(levelIndices[GetMaxLevel()], levelIndices[GetMaxLevel() + 1], [&](const unsigned32 i) { MaterialLibraryNode* node = GetTypedNode(i); node->SetMaterial(materialLibraryPointerReplacers[node->GetMaterial()]); }); delete mMaterialLibrary; mMaterialLibrary = newMaterialLibrary; } unsigned8 GetAdditionalBytesPerNode(unsigned8 level) const override { return 3; // For now, assume that material pointers are always 12+12 bits } std::vector GetAdditionalNodeBytes(const Node* node) const override { MaterialLibraryPointer materialPointer = ((MaterialLibraryNode*)node)->GetMaterial(); std::vector res(3); res[0] = (unsigned8)(materialPointer.x >> 4); res[1] = (unsigned8)((materialPointer.x << 4) | ((materialPointer.y & (0x0FFF)) >> 8)); res[2] = (unsigned8)materialPointer.y; return res; } void AddLeafNode(glm::uvec3 coordinate, T material) { // Get the material pointer assert(mMaterialLibrary->IsFinalized()); // Check if there is already a leaf for this material (for auto reuse) auto existingLeaf = leafMap.find(material); if (existingLeaf != leafMap.end()) { // If the leaf node already exists, reuse it: // Create the parent node of the leaf MaterialLibraryNode* parentOfLeaf = Tree::AddNode(glm::uvec3(coordinate.x >> 1, coordinate.y >> 1, coordinate.z >> 1), GetMaxLevel() - 1); // The last bit of the coordinate can be used to find the childindex: ChildIndex index = (((coordinate.x & 1) == 1) ? 1 : 0) + (((coordinate.y & 1) == 1) ? 2 : 0) + (((coordinate.z & 1) == 1) ? 4 : 0); parentOfLeaf->SetChild(index, existingLeaf->second); } else { MaterialLibraryPointer textureIndex = mMaterialLibrary->GetTextureIndex(material); MaterialLibraryNode* leaf = Tree::AddLeafNode(coordinate); leaf->SetMaterial(textureIndex); leafMap.insert(std::pair(material, leaf)); } } bool HasAdditionalPool() const override { return true; } protected: void WriteProperties(std::ostream& file) override { // Write the material texture WriteMaterialTexture(file); } void ReadProperties(std::istream& file) override { // Reat the material texture ReadMaterialTexture(file); // Restore the material library from the texture delete mMaterialLibrary; mMaterialLibrary = new MaterialLibrary(mMaterialTexture, mMaterialTextureSize, mMaxTextureIndex); mMaterialLibrary->Finalize(); //// Refresh the material texture for debug purposes //mMaterialTexture.clear(); //GetMaterialTexture(); } void WriteAdditionalPoolProperties(std::ostream& file) override { WriteMaterialTexture(file); } void ReadAdditionalPoolProperties(std::istream& file) override { ReadMaterialTexture(file); } };