#pragma once #include "Tree.h" #include "MaterialNode.h" #include "../../inc/tbb/parallel_for_each.h" #include "../../core/BitHelper.h" #include "../../core/CollectionHelper.h" #include "../../core/Serializer.h" #include //#include #include template> class MaterialTree : public Tree> { private: std::unordered_map mLeafMap; bool mUseLeafMap; public: // Creates a Material Tree with "maxLevel" levels. MaterialTree(unsigned8 maxLevel) : Tree>(maxLevel), mUseLeafMap(false) { mLeafsAreEqual = false; } ~MaterialTree() override { } // When using the leaf map, leaf nodes with the same material will only be created once (e.g. the leaf level is already a DAG) void UseLeafMap(bool value) { mUseLeafMap = value; // Build the leaf map for the nodes that already exist if (mUseLeafMap) { for (unsigned32 i = 0; i < GetNodeCount(); i++) { MaterialNode* node = GetTypedNode(i); if (node->GetLevel() == GetMaxLevel()) mLeafMap.insert(std::pair(node->GetMaterial(), node)); } // Convert the leaf nodes to a DAG ToDAG(GetMaxLevel() - 1); } } // 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(T(*Average)(const std::vector& materials, const std::vector& weights)) { // Bottom up go through the nodes to propagate the materials auto levelIndices = SortOnLevel(); // Calculate how many levels actually contain data unsigned8 filledLevels = 0; for (unsigned8 level = 0; level <= GetMaxLevel(); level++) { if (levelIndices[level] != levelIndices[level + 1]) filledLevels++; else break; } // If the levelIndices.size() <= 2, that means there's only the root level, thus no propagation is needed if (filledLevels < 2) return; // Set node weights for weighted average calculation std::vector lastLevelNodeWeights; std::vector levelNodeWeights; // Bottom-up calculate the weighted average material for each node in the tree, and store them in perfectMaterialPerNode for (unsigned8 level = filledLevels - 1; level-- > 0;) { auto levelStart = levelIndices[level]; auto levelEnd = levelIndices[level + 1]; levelNodeWeights = std::vector(levelEnd - levelStart); tbb::parallel_for(levelStart, levelEnd, [&](const unsigned32 i) { // Get the current node MaterialNode* 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++) { const unsigned32& i = children[c]; MaterialNode* child = GetTypedNode(i); float childWeight = (i - levelEnd < lastLevelNodeWeights.size()) ? lastLevelNodeWeights[i - levelEnd] : 1.f; childMaterials.push_back(child->GetMaterial()); childWeights.push_back(childWeight); nodeWeight += childWeight; } // Calculate the average material and retrieve the closest material to that from the library node->SetMaterial(Average(childMaterials, childWeights)); // Store the weighted average in perfectMaterialPerNode levelNodeWeights[i - levelStart] = nodeWeight; }); // Update the last level node weights to the node weights of the current level lastLevelNodeWeights = std::move(levelNodeWeights); } } void ReplaceMaterials(const std::unordered_map& replacementMap) { tbb::parallel_for((unsigned32)0, GetNodeCount(), [&](unsigned32 i) { auto node = GetTypedNode(i); node->SetMaterial(replacementMap.at(node->GetMaterial())); }); } void SetMaterials(std::vector materials) { assert(materials.size() == GetNodeCount()); tbb::parallel_for((unsigned32)0, GetNodeCount(), [&](const unsigned32 i) { MaterialNode* node = GetTypedNode(i); node->SetMaterial(materials[i]); }); } T GetMaterial(MaterialNode* node) { return node->GetMaterial(); } std::vector GetMaterials() const { std::vector nodeMaterials(GetNodeCount()); tbb::parallel_for((unsigned32)0, GetNodeCount(), [&](const unsigned32 i) { nodeMaterials[i] = GetTypedNode(i)->GetMaterial(); }); return nodeMaterials; } // Returns a list with all unique materials in the tree std::vector GetUniqueMaterials() const { std::vector nodeMaterials = GetMaterials(); CollectionHelper::Unique(nodeMaterials, Comparer()); return nodeMaterials; } // Sets the material of a node. void SetMaterial(glm::uvec3 coordinate, unsigned level, T material) { MaterialNode* node = AddNode(coordinate, level); node->SetMaterial(material); } // Creates a leaf node and sets its material void AddLeafNode(glm::uvec3 coordinate, T material) { if (mUseLeafMap) { // Check if there is already a leaf for this material (for auto reuse) auto existingLeaf = mLeafMap.find(material); Node* leaf = NULL; if (existingLeaf != mLeafMap.end()) { // If there is a leaf, use it assert(material == ((MaterialNode*)(existingLeaf->second))->GetMaterial()); leaf = existingLeaf->second; } else { // If no leaf with this material exists yet, create it leaf = Create(GetMaxLevel()); ((MaterialNode*)leaf)->SetMaterial(material); mLeafMap.insert(std::pair(material, leaf)); } // Create the parent node of the leaf MaterialNode* 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 in this parent: ChildIndex index = (((coordinate.x & 1) == 1) ? 1 : 0) + (((coordinate.y & 1) == 1) ? 2 : 0) + (((coordinate.z & 1) == 1) ? 4 : 0); // Make sure the leaf points to this child parentOfLeaf->SetChild(index, leaf); } else { MaterialNode* leaf = Tree>::AddLeafNode(coordinate); leaf->SetMaterial(material); } } bool HasAdditionalPool() const override { return false; } protected: unsigned8 GetAdditionalBytesPerNode(unsigned8 level) const override { return sizeof(T); } std::vector GetAdditionalNodeBytes(const Node* node) const override { return ((MaterialNode*)node)->GetMaterial().Serialize(); } };