#pragma once #include "HierarchicalMaterialMultiRoot.h" #include "../Material/BitsMaterial.h" #include "IMaterialTexture.h" #include "../../inc/tbb/parallel_for_each.h" #include "../Material/BlockBasedMaterialLibrary.h" #include "../../core/BitHelper.h" #include "../../core/CollectionHelper.h" #include "../../core/Util/BoolArray.h" #include "NodeReplacementFinder.h" #include "Tree.h" #include #include // Usage: // This tree can only be built correctly if it is based on some material tree that has materials throughout // (Such as HierarchicalRoot or MaterialRoot). To build this tree, create this root object and then call // the "BaseOn(tree)" method with the material tree. template, unsigned8 channelsPerPixel = 3> class MaterialLibraryMultiRoot : public HierarchicalMaterialMultiRoot>, public IMaterialTexture { private: // After the tree is finalized, the material library will be used to contain the actual materials MaterialLibrary* mMaterialLibrary; std::vector mBitMap; std::vector mMaterialTexture; unsigned short mMaterialTextureSize; MaterialLibraryPointer mMaxTextureIndex; inline void WriteMaterialTexture(std::ostream& file) { if (mMaterialTexture.empty()) GetMaterialTexture(); // Pack the texture size and the biggest texture index in one 32 bit unsigned int (for historic reasons...) unsigned materialTextureSizeSummary = (mMaxTextureIndex.x << 20) | (mMaxTextureIndex.y << 10) | (mMaterialTextureSize - 1); Serializer::Serialize(materialTextureSizeSummary, file); Serializer::Serialize(&mMaterialTexture[0], (size_t)mMaterialTextureSize * (size_t)mMaterialTextureSize * (size_t)channelsPerPixel, file); } inline void ReadMaterialTexture(std::istream& file) { unsigned materialTextureSizeSummary; Serializer::Deserialize(materialTextureSizeSummary, file); unsigned mask1 = BitHelper::GetLSMask(20, 30); unsigned mask2 = BitHelper::GetLSMask(10, 20); unsigned mask3 = BitHelper::GetLSMask(0, 10); unsigned short maxTextureIndexX = (mask1 & materialTextureSizeSummary) >> 20; unsigned short maxTextureIndexY = (mask2 & materialTextureSizeSummary) >> 10; unsigned materialTextureSize = mask3 & materialTextureSizeSummary; mMaterialTextureSize = materialTextureSize + 1; mMaxTextureIndex = MaterialLibraryPointer(maxTextureIndexX, maxTextureIndexY); size_t textureArraySize = (size_t)mMaterialTextureSize * (size_t)mMaterialTextureSize * (size_t)channelsPerPixel; mMaterialTexture.resize(textureArraySize); Serializer::Deserialize(&mMaterialTexture[0], textureArraySize, file); } void AddMaterial(const T& material) { assert(!mMaterialLibrary->IsFinalized()); mMaterialLibrary->AddMaterial(material); } void FinalizeMaterials() { assert(!mMaterialLibrary->IsFinalized()); mMaterialLibrary->Finalize(); unsigned requiredXBits = BitHelper::Log2Ceil(mMaterialLibrary->GetTextureSize()); unsigned requiredYBits = BitHelper::Log2Ceil(mMaterialLibrary->GetMaxTextureIndex().y); unsigned requiredBits = requiredXBits + requiredYBits; unsigned32 mask = BitHelper::GetLSMask(16, 16 + requiredXBits) | BitHelper::GetLSMask(0, requiredYBits); mBitMap = BitHelper::GetBitMapHS(mask); AddSlaveRoots(requiredBits); // The main root can be used for the first bit, the rest of the bits require slave roots } bool CheckNodesToRemove(MultiRootMaterialNode>* node, bool curValue, BoolArray& nodesCanBeShaved) { if (!node->HasChildren()) return true; auto mat = node->GetMaterial(); bool childrenEqual = true; for (ChildIndex c = 0; c < 8; c++) { if (node->HasChild(c)) { bool nodeMat = mat.GetLS(c); MultiRootMaterialNode>* child = (MultiRootMaterialNode>*)GetNode(node->GetChildIndex(c)); bool nodeHasSameMat = CheckNodesToRemove(child, nodeMat, nodesCanBeShaved); if (nodeMat != curValue || !nodeHasSameMat) childrenEqual = false; } } if (!childrenEqual) nodesCanBeShaved.Set(node->GetIndex(), false); return childrenEqual; } static std::vector HashChildren(const MultiRootMaterialNode>* node) { assert(!node->GetIsGeometry()); std::vector hash(8, 0); auto mat = node->GetMaterial(); for (ChildIndex c = 0; c < 8; c++) { if (node->HasChild(c)) hash[c] = (node->GetChildIndex(c) << 1) | (mat.GetLS(c) ? 1 : 0); } return hash; } public: MaterialLibraryMultiRoot(unsigned8 maxLevel) : HierarchicalMaterialMultiRoot>(maxLevel, 0) { mMaterialLibrary = new BlockBasedMaterialLibrary(); } ~MaterialLibraryMultiRoot() override { if (mMaterialLibrary != NULL) delete mMaterialLibrary; } void CreateMaterialLibrary(const std::vector& materials) { for (const T& material : materials) AddMaterial(material); FinalizeMaterials(); } // Copies the geometry and material information of the given tree to this tree template void BaseOn(MaterialTree* tree, bool autoDAG) { // Make sure the leaf nodes are the last nodes in the tree, so that we can skip them and only construct one. std::vector levelIndices = tree->SortOnLevel(); // Clear the existing tree Clear(); auto root = Create(0); root->SetIsGeometry(true); // Create the single leaf node that can exist for the geometry tree (for memory efficiency) auto geometryLeaf = Create(GetMaxLevel()); geometryLeaf->SetIsGeometry(true); // Copy the geometry information to a geometry tree unsigned32 destChildrenCache[8]; unsigned32 offset = GetNodeCount(); for (unsigned32 i = 0; i < levelIndices[GetMaxLevel()]; i++) { Node* source = tree->GetNode(i); unsigned32* sourceChildren = source->GetChildren(); if (source->GetLevel() == GetMaxLevel() - 1) { for (ChildIndex child = 0; child < source->GetChildCount(); child++) destChildrenCache[child] = geometryLeaf->GetIndex(); } else { for (ChildIndex child = 0; child < source->GetChildCount(); child++) destChildrenCache[child] = offset + sourceChildren[child] - 1; // The root is reused, so that index shouldn't be counted towards the offset } auto dest = source->GetLevel() == 0 ? root : Create(source->GetLevel()); dest->SetIsGeometry(true); dest->SetChildren(source->GetChildmask(), destChildrenCache); } // Create and fill the material library (if this hasn't been done yet) if (!mMaterialLibrary->IsFinalized()) { auto materials = tree->GetUniqueMaterials(); CreateMaterialLibrary(materials); } else { // Re-add the slaveroots AddSlaveRoots(mBitMap.size()); } unsigned32 bitCount = (unsigned32)mBitMap.size(); // Go through all nodes that aren't leaf nodes // And construct their (bit-based) material tree (e.g. the not-geometry part of the tree) auto leaf = Create(GetMaxLevel()); leaf->SetIsGeometry(false); for (unsigned32 bit = 0; bit < bitCount; bit++) { offset = GetNodeCount(); for (unsigned32 i = 0; i < levelIndices[GetMaxLevel()]; i++) { auto source = tree->GetTypedNode(i); // Create the material the new node has to have BitsMaterial<8> destMaterial; for (ChildIndex childIdx = 0; childIdx < 8; childIdx++) { if (source->HasChild(childIdx)) { auto child = tree->GetTypedNode(source->GetChildIndex(childIdx)); T sourceMaterial = tree->GetMaterial(child); unsigned32 sourceMaterialPointer = (unsigned32)mMaterialLibrary->GetTextureIndex(sourceMaterial); destMaterial.SetLS(childIdx, BitHelper::GetHS(sourceMaterialPointer, mBitMap[bit])); } } // Create the children pointers the new node has to have unsigned32* sourceChildren = source->GetChildren(); if (source->GetLevel() == GetMaxLevel() - 1) { for (ChildIndex child = 0; child < source->GetChildCount(); child++) destChildrenCache[child] = leaf->GetIndex(); } else { for (ChildIndex child = 0; child < source->GetChildCount(); child++) destChildrenCache[child] = offset + sourceChildren[child] - 1; // The root gets reused so it shouldn't be counted towards the offset } // Create the new node auto dest = source->GetLevel() == 0 ? GetSlaveRoot(bit) : Create(source->GetLevel()); dest->SetMaterial(destMaterial); dest->SetIsGeometry(false); dest->SetChildren(source->GetChildmask(), destChildrenCache); } if (autoDAG) { ToDAG(1, false); printf("."); } } } // Bottom-up remove node that only have the same bit value (e.g. all 1 or all 0 for the non-geometry nodes). void ShaveEquals() { // For all the nodes on level 1, RemoveChildrenWithSameBit std::vector levelIndices = SortOnLevel(); BoolArray nodesToShave(GetNodeCount()); // Assume that all nodes that arent geometry (or roots) can be shaved until the opposite has been proven for (unsigned32 i = 0; i < GetNodeCount(); i++) { Node* node = GetNode((unsigned32)i); if (node->GetLevel() > 0) { MultiRootMaterialNode>* matNode = (MultiRootMaterialNode>*)node; nodesToShave.Set(matNode->GetIndex(), !matNode->GetIsGeometry()); } } // Now try to find proof not to shave certain nodes for (unsigned32 i = levelIndices[1]; i < levelIndices[2]; i++) { MultiRootMaterialNode>* node =GetTypedNode(i); if (!node->GetIsGeometry()) CheckNodesToRemove(node, false, nodesToShave); // Since nodes on the second level don't have material properties, assume the bit value is false } // Shave all nodes of which no evidence is found not to shave them size_t nodesRemoved = 0; for (unsigned32 i = 0; i < GetNodeCount(); i++) { if (nodesToShave.Get(i)) { nodesRemoved++; MultiRootMaterialNode>* node = (MultiRootMaterialNode>*)GetNode((unsigned32)i); node->SetMaterial(BitsMaterial<8>()); node->SetChildren(0, NULL); } } printf("Connections removed from %llu nodes.\n", (unsigned64)nodesRemoved); ClearOrphans(); } // Since the bit values only need to be correct for parts of the scene that are defined, we can randomly fill in the rest to be correct void FillEmptySpace(bool full = true) { // TODO: For the layer above the leaf node, just add all 8 children for each node (all pointing to the leaf node), and make // the only difference the bit node. Then connect all nodes in the layer above it correctly. std::vector levelOffsets = SortOnLevel(); // Find the leaf node that is not geometry MultiRootMaterialNode>* leaf = NULL; for (unsigned32 i = levelOffsets[GetMaxLevel()]; i < levelOffsets[GetMaxLevel() + 1]; i++) { auto node = GetTypedNode(i); if (!node->GetIsGeometry()) { leaf = node; break; } } assert(leaf != NULL); // Create properties for pointers to this leaf node unsigned32 leafPointer = leaf->GetIndex(); unsigned32 leafChildren[8]; for (size_t i = 0; i < 8; i++) leafChildren[i] = leafPointer; ChildMask leafMask(255); // Make sure all nodes above the leaf level have full geometry, allowing more merging for (unsigned32 i = levelOffsets[GetMaxLevel() - 1]; i < levelOffsets[GetMaxLevel()]; i++) { auto node = GetTypedNode(i); if (!node->GetIsGeometry() && node->HasChildren()) node->SetChildren(leafMask, leafChildren); } if (!full) return; // TODO: After every layer is made smaller, call "ToDAG" up to the next level to process :P // Now use a lookup table to quickly find all feasible nodes for a merge. std::function(const MultiRootMaterialNode>*)> childrenHasher = &MaterialLibraryMultiRoot::HashChildren; for (auto level = GetMaxLevel() - 1; level-- > 1;) { ToDAG(level - 1); levelOffsets = SortOnLevel(); std::vector parentsPerNode = GetParentCounts(); NodeReplacementFinder>*> finder(childrenHasher); auto levelStart = levelOffsets[level]; auto levelEnd = levelOffsets[level + 1]; std::vector>*> nodesToTryVec; // Add all nodes to the finder: for (size_t i = levelStart; i < levelEnd; i++) { MultiRootMaterialNode>* node = (MultiRootMaterialNode>*)GetNode((unsigned32)i); if (!node->GetIsGeometry() && node->HasChildren()) { finder.Add(node); nodesToTryVec.push_back(node); } } // Sort nodes on number of parents. Merging nodes with many parents is preferred as it has a high probability of leading to more merges higher up in the tree std::sort(nodesToTryVec.begin(), nodesToTryVec.end(), [&](MultiRootMaterialNode>* a, MultiRootMaterialNode>* b) { return parentsPerNode[a->GetIndex()] > parentsPerNode[b->GetIndex()]; }); std::queue>*> nodesToTry; for (auto nodeToTry : nodesToTryVec) nodesToTry.push(nodeToTry); std::unordered_set>*> allMergedNodes; // Now replace as much as possible while (!nodesToTry.empty()) { MultiRootMaterialNode>* node = nodesToTry.front(); if (allMergedNodes.find(node) == allMergedNodes.end()) { std::vector>*> mergeOptions = finder.Find(node); // Prefer the merge options with most children :) std::sort(mergeOptions.begin(), mergeOptions.end(), [&](Node* a, Node* b) { if (parentsPerNode[a->GetIndex()] != parentsPerNode[b->GetIndex()]) return parentsPerNode[a->GetIndex()] > parentsPerNode[b->GetIndex()]; return a->GetChildCount() > b->GetChildCount(); }); // All merge options for this node will be explored, so remove it if (mergeOptions.size() > 1) { // Keep track of which nodes have been merged, so that we can set them to be equal to this node in the end ChildMask combinedMask = node->GetChildmask(); unsigned32* combinedChildIndices = new unsigned32[8]; for (ChildIndex c = 0; c < 8; c++) if (node->HasChild(c)) combinedChildIndices[c] = node->GetChildIndex(c); unsigned8 combinedMaterial = (unsigned8)node->GetMaterial().GetValue(); std::vector>*> mergedNodes(1, node); for (auto option : mergeOptions) { if (option != node) { // Check if the merge is still valid unsigned8 optionMaterial = (unsigned8)option->GetMaterial().GetValue(); unsigned8 optionMask = (unsigned8)option->GetChildmask().mask; // The material mask should be the same for children that both nodes have: bool valid = (optionMaterial & (optionMask & combinedMask.mask)) == (combinedMaterial & (optionMask & combinedMask.mask)); if (valid) for (ChildIndex c = 0; c < 8; c++) if (combinedMask.Get(c) && option->HasChild(c) && combinedChildIndices[c] != node->GetChildIndex(c)) { valid = false; break; } // If the merge is still valid, updated the combined Mask, combined material and combinedChildIndices if (valid) { for (ChildIndex c = 0; c < 8; c++) { if (!combinedMask.Get(c) && option->HasChild(c)) { combinedChildIndices[c] = option->GetChildIndex(c); combinedMask.Set(c, true); } } combinedMaterial |= optionMaterial; mergedNodes.push_back(option); } } } // If more nodes then the current node are merged, // Update all merged nodes to be equal. Also remove the (old) merged nodes from the finder and add the merged version if (mergedNodes.size() > 1) { unsigned8 i = 0; unsigned32* combinedChildren = new unsigned32[combinedMask.GetSet()]; for (ChildIndex c = 0; c < 8; c++) if (combinedMask.Get(c)) combinedChildren[i++] = combinedChildIndices[c]; BitsMaterial<8> combinedMaterialProp((size_t)combinedMaterial); for (auto mergedNode : mergedNodes) { finder.Remove(mergedNode); mergedNode->SetChildren(combinedMask, combinedChildren); mergedNode->SetMaterial(combinedMaterialProp); allMergedNodes.insert(mergedNode); } delete combinedChildren; finder.Add(node); } else { finder.Remove(node); } } else { finder.Remove(node); } } nodesToTry.pop(); } } } 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; } bool HasAdditionalPool() const override { return true; } protected: void AppendPostProcess(glm::uvec3 coordinates, unsigned8 level, Tree>>* tree) override { MaterialLibraryMultiRoot* other = (MaterialLibraryMultiRoot*)tree; if (other->mMaterialLibrary != NULL) { // Copy the material library of the appended tree if ((!this->mMaterialLibrary->IsFinalized()) && (*(other->mMaterialLibrary)) != (*(this->mMaterialLibrary))) { // Use copy constructor to copy the library of the other tree delete mMaterialLibrary; this->mMaterialLibrary = new MaterialLibrary(*(other->mMaterialLibrary)); } assert((*(this->mMaterialLibrary)) == (*(other->mMaterialLibrary))); } } void WriteProperties(std::ostream& file) override { WriteMaterialTexture(file); HierarchicalMaterialMultiRoot>::WriteProperties(file); } void ReadProperties(std::istream& file) override { // Reat the material texture ReadMaterialTexture(file); // Restore the material library from the texture if (mMaterialLibrary != NULL) delete mMaterialLibrary; mMaterialLibrary = new MaterialLibrary(mMaterialTexture, mMaterialTextureSize, mMaxTextureIndex); mMaterialLibrary->Finalize(); HierarchicalMaterialMultiRoot>::ReadProperties(file); } void WriteAdditionalPoolProperties(std::ostream& file) override { WriteMaterialTexture(file); HierarchicalMaterialMultiRoot>::WriteAdditionalPoolProperties(file); } void ReadAdditionalPoolProperties(std::istream& file) override { ReadMaterialTexture(file); HierarchicalMaterialMultiRoot>::ReadAdditionalPoolProperties(file); } };