From 58c4010185f4ad0e557e029718487b9f92f74b92 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 18 Jun 2013 09:36:43 -0700 Subject: [PATCH] added CoverageMap and occlusion culling to server --- libraries/voxels/src/VoxelTree.cpp | 208 +++++++++++++++++++++++++---- libraries/voxels/src/VoxelTree.h | 33 +++-- voxel-server/src/VoxelAgentData.h | 2 + voxel-server/src/main.cpp | 7 +- 4 files changed, 211 insertions(+), 39 deletions(-) diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 6853df3e9b..6e90b06b9c 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -22,6 +22,7 @@ #include "ViewFrustum.h" #include // to load voxels from file #include "VoxelConstants.h" +#include "CoverageMap.h" #include @@ -91,21 +92,19 @@ void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseV if (childNode) { // chance to optimize, doesn't need to be actual distance!! Could be distance squared float distanceSquared = childNode->distanceSquareToPoint(point); -//printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); -//childNode->printDebugDetails(""); + //printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); + //childNode->printDebugDetails(""); currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i, (void**)&sortedChildren, (float*)&distancesToChildren, (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); } - - } for (int i = 0; i < currentCount; i++) { VoxelNode* childNode = sortedChildren[i]; if (childNode) { -//printLog("recurseNodeWithOperationDistanceSorted() PROCESSING child[%d] distance=%f...\n", i, distancesToChildren[i]); -//childNode->printDebugDetails(""); + //printLog("recurseNodeWithOperationDistanceSorted() PROCESSING child[%d] distance=%f...\n", i, distancesToChildren[i]); + //childNode->printDebugDetails(""); recurseNodeWithOperationDistanceSorted(childNode, operation, point, extraData); } } @@ -1118,6 +1117,39 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp if (!node->isInView(*params.viewFrustum)) { return bytesAtThisLevel; } + + + // If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf. + // leaf occlusion is handled down below when we check child nodes + if (params.wantOcclusionCulling && !node->isLeaf()) { + //node->printDebugDetails("upper section, params.wantOcclusionCulling... node="); + AABox voxelBox = node->getAABox(); + voxelBox.scale(TREE_SCALE); + VoxelProjectedShadow* voxelShadow = new VoxelProjectedShadow(params.viewFrustum->getProjectedShadow(voxelBox)); + + // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion + // culling and proceed as normal + if (voxelShadow->getAllInView()) { + //node->printDebugDetails("upper section, voxelShadow->getAllInView() node="); + + CoverageMap::StorageResult result = params.map->checkMap(voxelShadow, false); + delete voxelShadow; // cleanup + if (result == CoverageMap::OCCLUDED) { + //node->printDebugDetails("upper section, non-Leaf is occluded!! node="); + //args->nonLeavesOccluded++; + + //args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); + //args->totalVoxels += (subArgs.voxelsTouched - 1); + + return bytesAtThisLevel; + } + } else { + //node->printDebugDetails("upper section, shadow Not in view node="); + // If this shadow wasn't "all in view" then we ignored it for occlusion culling, but + // we do need to clean up memory and proceed as normal... + delete voxelShadow; + } + } } bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! @@ -1126,37 +1158,68 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. There could be sub trees // below this point, which might take many more bytes, but that's ok, because we can always mark our subtrees as // not existing and stop the packet at this point, then start up with a new packet for the remaining sub trees. - const int CHILD_COLOR_MASK_BYTES = 1; + unsigned char childrenExistInTreeBits = 0; + unsigned char childrenExistInPacketBits = 0; + unsigned char childrenColoredBits = 0; + + const int CHILD_COLOR_MASK_BYTES = sizeof(childrenColoredBits); const int BYTES_PER_COLOR = 3; - const int CHILD_TREE_EXISTS_BYTES = 1; + const int CHILD_TREE_EXISTS_BYTES = sizeof(childrenExistInTreeBits) + sizeof(childrenExistInPacketBits); const int MAX_LEVEL_BYTES = CHILD_COLOR_MASK_BYTES + NUMBER_OF_CHILDREN * BYTES_PER_COLOR + CHILD_TREE_EXISTS_BYTES; // Make our local buffer large enough to handle writing at this level in case we need to. unsigned char thisLevelBuffer[MAX_LEVEL_BYTES]; unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0]; - unsigned char childrenExistInTreeBits = 0; - unsigned char childrenExistInPacketBits = 0; - unsigned char childrenColoredBits = 0; int inViewCount = 0; int inViewNotLeafCount = 0; int inViewWithColorCount = 0; - // for each child node, check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children + VoxelNode* sortedChildren[NUMBER_OF_CHILDREN]; + float distancesToChildren[NUMBER_OF_CHILDREN]; + int indexOfChildren[NUMBER_OF_CHILDREN]; // not really needed + int currentCount = 0; + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelNode* childNode = node->getChildAtIndex(i); - + // if the caller wants to include childExistsBits, then include them even if not in view if (params.includeExistsBits && childNode) { childrenExistInTreeBits += (1 << (7 - i)); } - + + if (params.wantOcclusionCulling) { + if (childNode) { + // chance to optimize, doesn't need to be actual distance!! Could be distance squared + //float distanceSquared = childNode->distanceSquareToPoint(point); + //printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); + //childNode->printDebugDetails(""); + + float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; + + currentCount = insertIntoSortedArrays((void*)childNode, distance, i, + (void**)&sortedChildren, (float*)&distancesToChildren, + (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); + } + } else { + sortedChildren[i] = childNode; + indexOfChildren[i] = i; + distancesToChildren[i] = 0.0f; + currentCount++; + } + } + + // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so + // add them to our distance ordered array of children + for (int i = 0; i < currentCount; i++) { + VoxelNode* childNode = sortedChildren[i]; + int originalIndex = indexOfChildren[i]; + bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum))); if (childIsInView) { // Before we determine consider this further, let's see if it's in our LOD scope... - float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; + float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; float boundaryDistance = params.viewFrustum ? boundaryDistanceForRenderLevel(*childNode->getOctalCode() + 1) : 1; if (distance < boundaryDistance) { @@ -1166,16 +1229,62 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // we don't care about recursing deeper on them, and we don't consider their // subtree to exist if (!(childNode && childNode->isLeaf())) { - childrenExistInPacketBits += (1 << (7 - i)); + childrenExistInPacketBits += (1 << (7 - originalIndex)); inViewNotLeafCount++; } + bool childIsOccluded = false; // assume it's not occluded + + // If the user also asked for occlusion culling, check if this node is occluded + if (params.wantOcclusionCulling && childNode->isLeaf()) { +//printLog("params.wantOcclusionCulling... in childNode section...\n"); + // Don't check occlusion here, just add them to our distance ordered array... + + AABox voxelBox = childNode->getAABox(); + voxelBox.scale(TREE_SCALE); + VoxelProjectedShadow* voxelShadow = new VoxelProjectedShadow(params.viewFrustum->getProjectedShadow(voxelBox)); + + // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion + // culling and proceed as normal + if (voxelShadow->getAllInView()) { +//childNode->printDebugDetails("voxelShadow->getAllInView() childNode="); + + CoverageMap::StorageResult result = params.map->checkMap(voxelShadow, true); + + // In all cases where the shadow wasn't stored, we need to free our own memory. + // In the case where it is stored, the CoverageMap will free memory for us later. + if (result != CoverageMap::STORED) { + delete voxelShadow; + } + + if (result == CoverageMap::STORED) { +//childNode->printDebugDetails("Leaf is stored!! childNode="); + } + + if (result == CoverageMap::DOESNT_FIT) { +//printLog(">>>> Leaf DOESNT_FIT!! That's not expected!!! \n"); + } + + + // If while attempting to add this voxel's shadow, we determined it was occluded, then + // we don't need to process it further and we can exit early. + if (result == CoverageMap::OCCLUDED) { +//childNode->printDebugDetails("Leaf is occluded!! childNode="); + childIsOccluded = true; + } + } else { +//childNode->printDebugDetails("Not all in view, cleanup... childNode="); + delete voxelShadow; + } + } // wants occlusion culling & isLeaf() + + bool childWasInView = (childNode && params.deltaViewFrustum && (params.lastViewFrustum && ViewFrustum::INSIDE == childNode->inFrustum(*params.lastViewFrustum))); // track children with actual color, only if the child wasn't previously in view! - if (childNode && childNode->isColored() && !childWasInView) { - childrenColoredBits += (1 << (7 - i)); + if (childNode && childNode->isColored() && !childWasInView && !childIsOccluded) { + childrenColoredBits += (1 << (7 - originalIndex)); inViewWithColorCount++; } } @@ -1235,15 +1344,35 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // // we know the last thing we wrote to the outputBuffer was our childrenExistInPacketBits. Let's remember where that was! unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistInPacketBits); - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenExistInPacketBits, i)) { - VoxelNode* childNode = node->getChildAtIndex(i); - + // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the + // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, + // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make + // a single recursive pass in distance sorted order, but retain standard order in our encoded packet + int recursiveSliceSizes[NUMBER_OF_CHILDREN]; + unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN]; + unsigned char* firstRecursiveSlice = outputBuffer; + int allSlicesSize = 0; + + // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so + // add them to our distance ordered array of children + for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) { + VoxelNode* childNode = sortedChildren[indexByDistance]; + int originalIndex = indexOfChildren[indexByDistance]; + + if (oneAtBit(childrenExistInPacketBits, originalIndex)) { + int thisLevel = currentEncodeLevel; + + // remember this for reshuffling + recursiveSliceStarts[originalIndex] = outputBuffer; + int childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, outputBuffer, availableBytes, bag, params, thisLevel); + + // remember this for reshuffling + recursiveSliceSizes[originalIndex] = childTreeBytesOut; + allSlicesSize += childTreeBytesOut; // 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. @@ -1273,15 +1402,40 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // then we want to remove their bit from the childExistsPlaceHolder bitmask if (childTreeBytesOut == 0) { // remove this child's bit... - childrenExistInPacketBits -= (1 << (7 - i)); + childrenExistInPacketBits -= (1 << (7 - originalIndex)); // repair the child exists mask *childExistsPlaceHolder = childrenExistInPacketBits; // Note: no need to move the pointer, cause we already stored this } // end if (childTreeBytesOut == 0) - } // end if (oneAtBit(childrenExistInPacketBits, i)) + } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) } // end for - } // end keepDiggingDeeper + // reshuffle here... + if (params.wantOcclusionCulling) { + unsigned char tempReshuffleBuffer[MAX_VOXEL_PACKET_SIZE]; + + unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination + + // iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree + // details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them + // back into original distance order + for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) { + if (oneAtBit(childrenExistInPacketBits, originalIndex)) { + int thisSliceSize = recursiveSliceSizes[originalIndex]; + unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex]; + + memcpy(tempBufferTo, thisSliceStarts, thisSliceSize); + tempBufferTo += thisSliceSize; + } + } + + // now that all slices are back in the correct order, copy them to the correct output buffer + memcpy(firstRecursiveSlice, &tempReshuffleBuffer[0], allSlicesSize); + } + + + } // end keepDiggingDeeper + return bytesAtThisLevel; } diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 650e662a3b..df721d0ba5 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -13,20 +13,25 @@ #include "ViewFrustum.h" #include "VoxelNode.h" #include "VoxelNodeBag.h" +#include "CoverageMap.h" // Callback function, for recuseTreeWithOperation typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData); typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; -#define NO_EXISTS_BITS false -#define WANT_EXISTS_BITS true -#define NO_COLOR false -#define WANT_COLOR true -#define IGNORE_VIEW_FRUSTUM NULL -#define JUST_STAGE_DELETION true -#define ACTUALLY_DELETE false -#define COLLAPSE_EMPTY_TREE true -#define DONT_COLLAPSE false +#define NO_EXISTS_BITS false +#define WANT_EXISTS_BITS true +#define NO_COLOR false +#define WANT_COLOR true +#define IGNORE_VIEW_FRUSTUM NULL +#define JUST_STAGE_DELETION true +#define ACTUALLY_DELETE false +#define COLLAPSE_EMPTY_TREE true +#define DONT_COLLAPSE false +#define NO_OCCLUSION_CULLING false +#define WANT_OCCLUSION_CULLING true +#define IGNORE_COVERAGE_MAP NULL +#define DONT_CHOP 0 class EncodeBitstreamParams { public: @@ -37,6 +42,8 @@ public: int chopLevels; bool deltaViewFrustum; const ViewFrustum* lastViewFrustum; + bool wantOcclusionCulling; + CoverageMap* map; EncodeBitstreamParams( int maxEncodeLevel = INT_MAX, @@ -45,7 +52,9 @@ public: bool includeExistsBits = WANT_EXISTS_BITS, int chopLevels = 0, bool deltaViewFrustum = false, - const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM) : + const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM, + bool wantOcclusionCulling= NO_OCCLUSION_CULLING, + CoverageMap* map = IGNORE_COVERAGE_MAP) : maxEncodeLevel (maxEncodeLevel), viewFrustum (viewFrustum), @@ -53,7 +62,9 @@ public: includeExistsBits (includeExistsBits), chopLevels (chopLevels), deltaViewFrustum (deltaViewFrustum), - lastViewFrustum (lastViewFrustum) + lastViewFrustum (lastViewFrustum), + wantOcclusionCulling(wantOcclusionCulling), + map (map) {} }; diff --git a/voxel-server/src/VoxelAgentData.h b/voxel-server/src/VoxelAgentData.h index 2afc64a6c8..6606530b94 100644 --- a/voxel-server/src/VoxelAgentData.h +++ b/voxel-server/src/VoxelAgentData.h @@ -14,6 +14,7 @@ #include #include "VoxelNodeBag.h" #include "VoxelConstants.h" +#include "CoverageMap.h" class VoxelAgentData : public AvatarData { public: @@ -36,6 +37,7 @@ public: void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } VoxelNodeBag nodeBag; + CoverageMap map; ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }; ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; }; diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index 5853e3f245..f8b8c2f36e 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -312,9 +312,13 @@ void deepestLevelVoxelDistributor(AgentList* agentList, while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) { if (!agentData->nodeBag.isEmpty()) { VoxelNode* subTree = agentData->nodeBag.extract(); + + bool wantOcclusionCulling = agentData->getWantOcclusionCulling(); + CoverageMap* coverageMap = wantOcclusionCulling ? &agentData->map : IGNORE_COVERAGE_MAP; EncodeBitstreamParams params(INT_MAX, &agentData->getCurrentViewFrustum(), agentData->getWantColor(), - WANT_EXISTS_BITS, wantDelta, lastViewFrustum); + WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, + wantOcclusionCulling, coverageMap); bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, agentData->nodeBag, params); @@ -375,6 +379,7 @@ void deepestLevelVoxelDistributor(AgentList* agentList, if (agentData->nodeBag.isEmpty()) { agentData->updateLastKnownViewFrustum(); agentData->setViewSent(true); + agentData->map.erase(); }