diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 615f63df47..d6934c4042 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -748,22 +748,45 @@ void VoxelTree::createSphere(float r,float xc, float yc, float zc, float s, bool // This will encode a larger tree into multiple subtree bitstreams. Given a node it will search for deeper subtrees that // have color. It will search for sub trees, and upon finding a subTree, it will stick the node in the bag to for later -// endcoding. -void VoxelTree::searchForColoredNodes(VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag) { +// endcoding. It returns the maximum level that we reached during our search. That might be the maximum level we were asked +// to search (if the tree is deeper than that max level), or it will be the maximum level of the tree (if we asked to search +// deeper than the tree exists). +int VoxelTree::searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag) { // call the recursive version, this will add all found colored node roots to the bag - searchForColoredNodesRecursion(rootNode, viewFrustum, bag); + int currentSearchLevel = 0; + + // levelReached will be the maximum level reached. If we made it to the maxSearchLevel, then it will be that. + // but if the tree is shallower than the maxSearchLevel, then we will return the deepest level of the tree that + // exists. + int levelReached = searchForColoredNodesRecursion(maxSearchLevel, currentSearchLevel, rootNode, viewFrustum, bag); + return levelReached; } -void VoxelTree::searchForColoredNodesRecursion(VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag) { +int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel, + VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag) { + + // Keep track of how deep we've searched. + currentSearchLevel++; + + // If we've reached our max Search Level, then stop searching. + if (currentSearchLevel >= maxSearchLevel) { + return currentSearchLevel; + } // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! if (!node->isInView(viewFrustum)) { - return; + return currentSearchLevel; } + // Ok, this is a little tricky, each child may have been deeper than the others, so we need to track + // how deep each child went. And we actually return the maximum of each child. We use these variables below + // when we recurse the children. + int thisLevel = currentSearchLevel; + int maxChildLevel = thisLevel; + const int MAX_CHILDREN = 8; VoxelNode* inViewChildren[MAX_CHILDREN]; float distancesToChildren[MAX_CHILDREN]; @@ -812,12 +835,14 @@ void VoxelTree::searchForColoredNodesRecursion(VoxelNode* node, const ViewFrustu // at this point, we need to iterate the children who are in view, even if not colored // and we need to determine if there's a deeper tree below them that we care about. We will iterate // these based on which tree is closer. - // for (int i = 0; i < inViewCount; i++) { VoxelNode* childNode = inViewChildren[i]; - searchForColoredNodesRecursion(childNode, viewFrustum, bag); + thisLevel = currentSearchLevel; // reset this, since the children will munge it up + int childLevelReached = searchForColoredNodesRecursion(maxSearchLevel, thisLevel, childNode, viewFrustum, bag); + maxChildLevel = std::max(maxChildLevel, childLevelReached); } } + return maxChildLevel; } // This will encode a tree bitstream, given a node it will encode the full tree from that point onward. @@ -830,7 +855,7 @@ void VoxelTree::searchForColoredNodesRecursion(VoxelNode* node, const ViewFrustu // extraTrees is assumed to me an allocated array of VoxelNode*, if we're unable to fully encode the tree // because we run out of room on the outputBuffer, then we will add VoxelNode*'s of the trees that need // to be encoded to that array. If the array -int VoxelTree::encodeTreeBitstream(VoxelNode* node, const ViewFrustum& viewFrustum, +int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, const ViewFrustum& viewFrustum, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag) { @@ -850,7 +875,9 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, const ViewFrustum& viewFrust bytesWritten += codeLength; // keep track of byte count availableBytes -= codeLength; // keep track or remaining space - int childBytesWritten = encodeTreeBitstreamRecursion(node, viewFrustum, + int currentEncodeLevel = 0; + int childBytesWritten = encodeTreeBitstreamRecursion(maxEncodeLevel, currentEncodeLevel, + node, viewFrustum, outputBuffer, availableBytes, bag); // if childBytesWritten == 1 then something went wrong... that's not possible @@ -878,12 +905,21 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, const ViewFrustum& viewFrust // // NOTE: This recursive function DOES NOT add the octcode to the buffer. It's assumed that the caller has // already done that. -int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, const ViewFrustum& viewFrustum, +int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, + VoxelNode* node, const ViewFrustum& viewFrustum, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag) const { // How many bytes have we written so far at this level; int bytesAtThisLevel = 0; + // Keep track of how deep we've encoded. + currentEncodeLevel++; + + // If we've reached our max Search Level, then stop searching. + if (currentEncodeLevel >= maxEncodeLevel) { + return bytesAtThisLevel; + } + // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if // we're out of view @@ -1002,7 +1038,10 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, const ViewFrustum& if (oneAtBit(childrenExistBits, i)) { VoxelNode* childNode = node->children[i]; - int childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, viewFrustum, outputBuffer, availableBytes, bag); + + int thisLevel = currentEncodeLevel; + int childTreeBytesOut = encodeTreeBitstreamRecursion(maxEncodeLevel, thisLevel, + childNode, viewFrustum, outputBuffer, availableBytes, bag); // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, // basically, the children below don't contain any info. diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 05d6d39062..c94d4ee2a7 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -59,18 +59,20 @@ public: void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); - int encodeTreeBitstream(VoxelNode* node, const ViewFrustum& viewFrustum, + int encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, const ViewFrustum& viewFrustum, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag); - void searchForColoredNodes(VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag); + int searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag); private: - int encodeTreeBitstreamRecursion(VoxelNode* node, const ViewFrustum& viewFrustum, + int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, + VoxelNode* node, const ViewFrustum& viewFrustum, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag) const; - void searchForColoredNodesRecursion(VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag); + int searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel, + VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag); void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode); diff --git a/voxel-server/src/VoxelAgentData.cpp b/voxel-server/src/VoxelAgentData.cpp index a86b25b78d..53e07dcc7e 100644 --- a/voxel-server/src/VoxelAgentData.cpp +++ b/voxel-server/src/VoxelAgentData.cpp @@ -20,6 +20,8 @@ void VoxelAgentData::init() { _voxelPacket = new unsigned char[MAX_VOXEL_PACKET_SIZE]; _voxelPacketAvailableBytes = MAX_VOXEL_PACKET_SIZE; _voxelPacketAt = _voxelPacket; + _maxSearchLevel = 1; + _maxLevelReachedInLastSearch = 1; resetVoxelPacket(); } diff --git a/voxel-server/src/VoxelAgentData.h b/voxel-server/src/VoxelAgentData.h index 56d9ebf03e..1955c6130a 100644 --- a/voxel-server/src/VoxelAgentData.h +++ b/voxel-server/src/VoxelAgentData.h @@ -35,6 +35,12 @@ public: int getPacketLength() const { return (MAX_VOXEL_PACKET_SIZE-_voxelPacketAvailableBytes); } bool isPacketWaiting() const { return _voxelPacketWaiting; } int getAvailable() const { return _voxelPacketAvailableBytes; } + int getMaxSearchLevel() const { return _maxSearchLevel; }; + void resetMaxSearchLevel() { _maxSearchLevel = 1; }; + void incrementMaxSearchLevel() { _maxSearchLevel++; }; + + int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; }; + void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } VoxelNodeBag nodeBag; private: @@ -42,8 +48,8 @@ private: unsigned char* _voxelPacketAt; int _voxelPacketAvailableBytes; bool _voxelPacketWaiting; - - + int _maxSearchLevel; + int _maxLevelReachedInLastSearch; }; #endif /* defined(__hifi__VoxelAgentData__) */ diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index 15d71aa504..319723e7e3 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -152,7 +152,6 @@ void eraseVoxelTreeAndCleanupAgentVisitData() { void newDistributeHelper(AgentList* agentList, AgentList::iterator& agent, VoxelAgentData* agentData, ViewFrustum& viewFrustum) { - // A quick explanation of the strategy here. First, each time through, we ask ourselves, do we have voxels // that need to be sent? If not, we search for them, if we do, then we send them. We store our to be sent voxel sub trees // in a VoxelNodeBag on a per agent basis. The bag stores just pointers to the root node of the sub tree to be sent, so @@ -185,12 +184,28 @@ void newDistributeHelper(AgentList* agentList, AgentList::iterator& agent, Voxel // can include the siblings. Since dense trees take more space per ranch, we often end up only being able to encode a // single branch. This means on a per packet basis, the trees actually _are not_ dense. And sparse trees are shorter to // encode when we only include the child tree. - randomTree.searchForColoredNodes(randomTree.rootNode, viewFrustum, agentData->nodeBag); + // + // Now, a quick explanation of maxSearchLevel: We will actually send the entire scene, multiple times for each search + // level. We start at level 1, and we scan the scene for this level, then we increment to the next level until we've + // sent the entire scene at it's deepest possible level. This means that clients will get an initial view of the scene + // with chunky granularity and then finer and finer granularity until they've gotten the whole scene. Then we start + // over to handle packet loss and changes in the scene. + int maxLevelReached = randomTree.searchForColoredNodes(agentData->getMaxSearchLevel(), randomTree.rootNode, + viewFrustum, agentData->nodeBag); + agentData->setMaxLevelReached(maxLevelReached); + + // If nothing got added, then we bump our levels. + if (agentData->nodeBag.isEmpty()) { + if (agentData->getMaxLevelReached() < agentData->getMaxSearchLevel()) { + agentData->resetMaxSearchLevel(); + } else { + agentData->incrementMaxSearchLevel(); + } + } } // If we have something in our nodeBag, then turn them into packets and send them out... if (!agentData->nodeBag.isEmpty()) { - static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE-1]; // save on allocs by making this static int bytesWritten = 0; @@ -203,7 +218,7 @@ void newDistributeHelper(AgentList* agentList, AgentList::iterator& agent, Voxel // Only let this guy create at largest packets equal to the amount of space we have left in our final??? // Or let it create the largest possible size (minus 1 for the "V") - bytesWritten = randomTree.encodeTreeBitstream(subTree, viewFrustum, + bytesWritten = randomTree.encodeTreeBitstream(agentData->getMaxSearchLevel(), subTree, viewFrustum, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE-1, agentData->nodeBag); // if we have room in our final packet, add this buffer to the final packet @@ -232,6 +247,7 @@ void newDistributeHelper(AgentList* agentList, AgentList::iterator& agent, Voxel // reset our finalOutputBuffer (keep the 'V') agentData->resetVoxelPacket(); + } // and we're done now for this interval, because we know we have not nodes in our @@ -240,6 +256,16 @@ void newDistributeHelper(AgentList* agentList, AgentList::iterator& agent, Voxel packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; } } + + // Ok, so we're in the "send from our bag mode"... if during this last pass, we emptied our bag, then + // we want to move to the next level. + if (agentData->nodeBag.isEmpty()) { + if (agentData->getMaxLevelReached() < agentData->getMaxSearchLevel()) { + agentData->resetMaxSearchLevel(); + } else { + agentData->incrementMaxSearchLevel(); + } + } } }