diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1acad36d3b..a7d5b63d98 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -979,6 +979,10 @@ void Application::doFalseColorizeInView() { _voxels.falseColorizeInView(&_viewFrustum); } +void Application::doFalseColorizeOccluded() { + _voxels.falseColorizeOccluded(); +} + void Application::doTrueVoxelColors() { _voxels.trueColorize(); } @@ -999,6 +1003,10 @@ void Application::setWantsDelta(bool wantsDelta) { _myAvatar.setWantDelta(wantsDelta); } +void Application::setWantsOcclusionCulling(bool wantsOcclusionCulling) { + _myAvatar.setWantOcclusionCulling(wantsOcclusionCulling); +} + void Application::updateVoxelModeActions() { // only the sender can be checked foreach (QAction* action, _voxelModeActions->actions()) { @@ -1318,9 +1326,6 @@ void Application::initMenu() { _frustumOn->setShortcut(Qt::SHIFT | Qt::Key_F); (_viewFrustumFromOffset = frustumMenu->addAction( "Use Offset Camera", this, SLOT(setFrustumOffset(bool)), Qt::SHIFT | Qt::Key_O))->setCheckable(true); - (_cameraFrustum = frustumMenu->addAction("Switch Camera"))->setCheckable(true); - _cameraFrustum->setChecked(true); - _cameraFrustum->setShortcut(Qt::SHIFT | Qt::Key_C); _frustumRenderModeAction = frustumMenu->addAction( "Render Mode", this, SLOT(cycleFrustumRenderMode()), Qt::SHIFT | Qt::Key_R); updateFrustumRenderModeAction(); @@ -1336,11 +1341,13 @@ void Application::initMenu() { renderDebugMenu->addAction("FALSE Color Voxel Every Other Randomly", this, SLOT(doFalseRandomizeEveryOtherVoxelColors())); renderDebugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance())); renderDebugMenu->addAction("FALSE Color Voxel Out of View", this, SLOT(doFalseColorizeInView())); - renderDebugMenu->addAction("Show TRUE Colors", this, SLOT(doTrueVoxelColors())); + renderDebugMenu->addAction("FALSE Color Occluded Voxels", this, SLOT(doFalseColorizeOccluded()), Qt::CTRL | Qt::Key_O); + renderDebugMenu->addAction("Show TRUE Colors", this, SLOT(doTrueVoxelColors()), Qt::CTRL | Qt::Key_T); debugMenu->addAction("Wants Res-In", this, SLOT(setWantsResIn(bool)))->setCheckable(true); debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true); debugMenu->addAction("Wants View Delta Sending", this, SLOT(setWantsDelta(bool)))->setCheckable(true); + debugMenu->addAction("Wants Occlusion Culling", this, SLOT(setWantsOcclusionCulling(bool)))->setCheckable(true); QMenu* settingsMenu = menuBar->addMenu("Settings"); (_settingsAutosave = settingsMenu->addAction("Autosave"))->setCheckable(true); @@ -1750,15 +1757,7 @@ void Application::updateAvatar(float deltaTime) { // void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { // We will use these below, from either the camera or head vectors calculated above - glm::vec3 position; - - // Camera or Head? - if (_cameraFrustum->isChecked()) { - position = camera.getPosition(); - } else { - position = _myAvatar.getHeadJointPosition(); - } - + glm::vec3 position(camera.getPosition()); float fov = camera.getFieldOfView(); float nearClip = camera.getNearClip(); float farClip = camera.getFarClip(); @@ -1925,7 +1924,7 @@ void Application::displayOculus(Camera& whichCamera) { glPopMatrix(); } - + void Application::displaySide(Camera& whichCamera) { // transform by eye offset diff --git a/interface/src/Application.h b/interface/src/Application.h index 1d50b5065f..c84cc025a6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -107,12 +107,14 @@ private slots: void doFalseRandomizeVoxelColors(); void doFalseRandomizeEveryOtherVoxelColors(); void doFalseColorizeByDistance(); + void doFalseColorizeOccluded(); void doFalseColorizeInView(); void doTrueVoxelColors(); void doTreeStats(); void setWantsMonochrome(bool wantsMonochrome); void setWantsResIn(bool wantsResIn); void setWantsDelta(bool wantsDelta); + void setWantsOcclusionCulling(bool wantsOcclusionCulling); void updateVoxelModeActions(); void decreaseVoxelSize(); void increaseVoxelSize(); @@ -144,7 +146,6 @@ private: void displaySide(Camera& whichCamera); void displayOverlay(); void displayStats(); - void renderViewFrustum(ViewFrustum& viewFrustum); void setupPaintingVoxel(); @@ -203,7 +204,6 @@ private: QAction* _destructiveAddVoxel; // when doing voxel editing do we want them to be destructive QAction* _frustumOn; // Whether or not to display the debug view frustum QAction* _viewFrustumFromOffset; // Whether or not to offset the view of the frustum - QAction* _cameraFrustum; // which frustum to look at QAction* _fullScreenMode; // whether we are in full screen mode QAction* _frustumRenderModeAction; QAction* _settingsAutosave; // Whether settings are saved automatically diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 716fe708a3..4a81d3ec2d 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -22,6 +22,7 @@ #include "Application.h" #include "Log.h" #include "VoxelConstants.h" +#include "CoverageMap.h" #include "InterfaceConfig.h" #include "renderer/ProgramObject.h" @@ -1156,3 +1157,139 @@ void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* dest _tree->copyFromTreeIntoSubTree(sourceTree, destinationNode); } +struct FalseColorizeOccludedArgs { + ViewFrustum* viewFrustum; + CoverageMap* map; + VoxelTree* tree; + long totalVoxels; + long coloredVoxels; + long occludedVoxels; + long notOccludedVoxels; + long outOfView; + long subtreeVoxelsSkipped; + long nonLeaves; + long nonLeavesOutOfView; + long nonLeavesOccluded; + long stagedForDeletion; +}; + +struct FalseColorizeSubTreeOperationArgs { + unsigned char color[NUMBER_OF_COLORS]; + long voxelsTouched; +}; + +bool VoxelSystem::falseColorizeSubTreeOperation(VoxelNode* node, void* extraData) { + FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData; + node->setFalseColor(args->color[0], args->color[1], args->color[2]); + args->voxelsTouched++; + return true; +} + +bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraData) { + + FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData; + args->totalVoxels++; + + // if this node is staged for deletion, then just return + if (node->isStagedForDeletion()) { + args->stagedForDeletion++; + return true; + } + + // If we are a parent, let's see if we're completely occluded. + if (!node->isLeaf()) { + args->nonLeaves++; + + AABox voxelBox = node->getAABox(); + voxelBox.scale(TREE_SCALE); + VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(args->viewFrustum->getProjectedShadow(voxelBox)); + + // If we're not all in view, then ignore it, and just return. But keep searching... + if (!voxelShadow->getAllInView()) { + args->nonLeavesOutOfView++; + delete voxelShadow; + return true; + } + + CoverageMap::StorageResult result = args->map->checkMap(voxelShadow, false); + if (result == CoverageMap::OCCLUDED) { + args->nonLeavesOccluded++; + delete voxelShadow; + + FalseColorizeSubTreeOperationArgs subArgs; + subArgs.color[0] = 0; + subArgs.color[1] = 255; + subArgs.color[2] = 0; + subArgs.voxelsTouched = 0; + + args->tree->recurseNodeWithOperation(node, falseColorizeSubTreeOperation, &subArgs ); + + args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); + args->totalVoxels += (subArgs.voxelsTouched - 1); + + return false; + } + + delete voxelShadow; + return true; // keep looking... + } + + if (node->isLeaf() && node->isColored() && node->getShouldRender()) { + args->coloredVoxels++; + + AABox voxelBox = node->getAABox(); + voxelBox.scale(TREE_SCALE); + VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(args->viewFrustum->getProjectedShadow(voxelBox)); + + // If we're not all in view, then ignore it, and just return. But keep searching... + if (!voxelShadow->getAllInView()) { + args->outOfView++; + delete voxelShadow; + return true; + } + + CoverageMap::StorageResult result = args->map->checkMap(voxelShadow, true); + if (result == CoverageMap::OCCLUDED) { + node->setFalseColor(255, 0, 0); + args->occludedVoxels++; + } else if (result == CoverageMap::STORED) { + args->notOccludedVoxels++; + //printLog("***** falseColorizeOccludedOperation() NODE is STORED *****\n"); + } else if (result == CoverageMap::DOESNT_FIT) { + //printLog("***** falseColorizeOccludedOperation() NODE DOESNT_FIT???? *****\n"); + } + } + return true; // keep going! +} +void VoxelSystem::falseColorizeOccluded() { + PerformanceWarning warn(true, "falseColorizeOccluded()",true); + CoverageMap map; + FalseColorizeOccludedArgs args; + args.viewFrustum = Application::getInstance()->getViewFrustum(); + args.map = ↦ + args.totalVoxels = 0; + args.coloredVoxels = 0; + args.occludedVoxels = 0; + args.notOccludedVoxels = 0; + args.outOfView = 0; + args.subtreeVoxelsSkipped = 0; + args.nonLeaves = 0; + args.stagedForDeletion = 0; + args.nonLeavesOutOfView = 0; + args.nonLeavesOccluded = 0; + args.tree = _tree; + + glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE); + + _tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args); + + printLog("falseColorizeOccluded()\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n", + args.totalVoxels, args.coloredVoxels, args.occludedVoxels, + args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped, + args.stagedForDeletion, + args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded); + + + setupNewVoxelsForDrawing(); +} + diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index c4aa33adfc..5087308392 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -56,6 +56,7 @@ public: void falseColorizeInView(ViewFrustum* viewFrustum); void falseColorizeDistanceFromView(ViewFrustum* viewFrustum); void falseColorizeRandomEveryOther(); + void falseColorizeOccluded(); void killLocalVoxels(); void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; }; @@ -120,6 +121,8 @@ private: static bool removeOutOfViewOperation(VoxelNode* node, void* extraData); static bool falseColorizeRandomEveryOtherOperation(VoxelNode* node, void* extraData); static bool collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* extraData); + static bool falseColorizeOccludedOperation(VoxelNode* node, void* extraData); + static bool falseColorizeSubTreeOperation(VoxelNode* node, void* extraData); int updateNodeInArraysAsFullVBO(VoxelNode* node); int updateNodeInArraysAsPartialVBO(VoxelNode* node); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d24754b28f..8fb9fa05d1 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -35,6 +35,7 @@ AvatarData::AvatarData(Agent* owningAgent) : _wantResIn(false), _wantColor(true), _wantDelta(false), + _wantOcclusionCulling(false), _headData(NULL) { @@ -106,9 +107,10 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) { // bitMask of less than byte wide items unsigned char bitItems = 0; - if (_wantResIn) { setAtBit(bitItems,WANT_RESIN_AT_BIT); } - if (_wantColor) { setAtBit(bitItems,WANT_COLOR_AT_BIT); } - if (_wantDelta) { setAtBit(bitItems,WANT_DELTA_AT_BIT); } + if (_wantResIn) { setAtBit(bitItems, WANT_RESIN_AT_BIT); } + if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); } + if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); } + if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); } // key state setSemiNibbleAt(bitItems,KEY_STATE_START_BIT,_keyState); @@ -192,9 +194,10 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { // voxel sending features... unsigned char bitItems = 0; bitItems = (unsigned char)*sourceBuffer++; - _wantResIn = oneAtBit(bitItems,WANT_RESIN_AT_BIT); - _wantColor = oneAtBit(bitItems,WANT_COLOR_AT_BIT); - _wantDelta = oneAtBit(bitItems,WANT_DELTA_AT_BIT); + _wantResIn = oneAtBit(bitItems, WANT_RESIN_AT_BIT); + _wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT); + _wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT); + _wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); // key state, stored as a semi-nibble in the bitItems _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index cf9845ab4c..4a94db133b 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -23,6 +23,7 @@ const int WANT_COLOR_AT_BIT = 1; const int WANT_DELTA_AT_BIT = 2; const int KEY_STATE_START_BIT = 3; // 4th and 5th bits const int HAND_STATE_START_BIT = 5; // 6th and 7th bits +const int WANT_OCCLUSION_CULLING_BIT = 7; // 8th bit const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation @@ -89,9 +90,11 @@ public: bool getWantResIn() const { return _wantResIn; } bool getWantColor() const { return _wantColor; } bool getWantDelta() const { return _wantDelta; } + bool getWantOcclusionCulling() const { return _wantOcclusionCulling; } void setWantResIn(bool wantResIn) { _wantResIn = wantResIn; } void setWantColor(bool wantColor) { _wantColor = wantColor; } void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; } + void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; } void setHeadData(HeadData* headData) { _headData = headData; } @@ -125,6 +128,7 @@ protected: bool _wantResIn; bool _wantColor; bool _wantDelta; + bool _wantOcclusionCulling; HeadData* _headData; private: diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 9c4748f501..449ce36529 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -409,6 +409,7 @@ void printVoxelCode(unsigned char* voxelCode) { // the second array is a sorted key for the value, the third array is the index for the value in it original // non-sorted array // returns -1 if size exceeded +// originalIndexArray is optional int insertIntoSortedArrays(void* value, float key, int originalIndex, void** valueArray, float* keyArray, int* originalIndexArray, int currentCount, int maxCount) { @@ -424,13 +425,17 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex, if (i < currentCount && i+1 < maxCount) { memcpy(&valueArray[i + 1], &valueArray[i], sizeof(void*) * (currentCount - i)); memcpy(&keyArray[i + 1], &keyArray[i], sizeof(float) * (currentCount - i)); - memcpy(&originalIndexArray[i + 1], &originalIndexArray[i], sizeof(int) * (currentCount - i)); + if (originalIndexArray) { + memcpy(&originalIndexArray[i + 1], &originalIndexArray[i], sizeof(int) * (currentCount - i)); + } } } // place new element at i valueArray[i] = value; keyArray[i] = key; - originalIndexArray[i] = originalIndex; + if (originalIndexArray) { + originalIndexArray[i] = originalIndex; + } return currentCount + 1; } return -1; // error case diff --git a/libraries/voxels/src/AABox.cpp b/libraries/voxels/src/AABox.cpp index 1b29c70b59..d4b7e1383c 100644 --- a/libraries/voxels/src/AABox.cpp +++ b/libraries/voxels/src/AABox.cpp @@ -1,332 +1,353 @@ -// -// AABox.h - Axis Aligned Boxes -// hifi -// -// Added by Brad Hefta-Gaub on 04/11/13. -// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards -// -// Simple axis aligned box class. -// - -#include "SharedUtil.h" - -#include "AABox.h" -#include "GeometryUtil.h" - - -void AABox::scale(float scale) { - _corner = _corner * scale; - _size = _size * scale; - _center = _center * scale; -} - - -void AABox::setBox(const glm::vec3& corner, const glm::vec3& size) { - _corner = corner; - _size = size; - - // In the event that the caller gave us negative sizes, fix things up to be reasonable - if (_size.x < 0.0) { - _size.x = -size.x; - _corner.x -= _size.x; - } - if (_size.y < 0.0) { - _size.y = -size.y; - _corner.y -= _size.y; - } - if (_size.z < 0.0) { - _size.z = -size.z; - _corner.z -= _size.z; - } - _center = _corner + (_size * 0.5f); -} - -glm::vec3 AABox::getVertexP(const glm::vec3 &normal) const { - glm::vec3 res = _corner; - if (normal.x > 0) - res.x += _size.x; - - if (normal.y > 0) - res.y += _size.y; - - if (normal.z > 0) - res.z += _size.z; - - return(res); -} - - - -glm::vec3 AABox::getVertexN(const glm::vec3 &normal) const { - glm::vec3 res = _corner; - - if (normal.x < 0) - res.x += _size.x; - - if (normal.y < 0) - res.y += _size.y; - - if (normal.z < 0) - res.z += _size.z; - - return(res); -} - -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - -bool AABox::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _size.x) && - isWithin(point.y, _corner.y, _size.y) && - isWithin(point.z, _corner.z, _size.z); -} - -// determines whether a value is within the expanded extents -static bool isWithinExpanded(float value, float corner, float size, float expansion) { - return value >= corner - expansion && value <= corner + size + expansion; -} - -bool AABox::expandedContains(const glm::vec3& point, float expansion) const { - return isWithinExpanded(point.x, _corner.x, _size.x, expansion) && - isWithinExpanded(point.y, _corner.y, _size.y, expansion) && - isWithinExpanded(point.z, _corner.z, _size.z, expansion); -} - -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { - // handle the trivial cases where the expanded box contains the start or end - if (expandedContains(start, expansion) || expandedContains(end, expansion)) { - return true; - } - // check each axis - glm::vec3 expandedCorner = _corner - glm::vec3(expansion, expansion, expansion); - glm::vec3 expandedSize = _size + glm::vec3(expansion, expansion, expansion) * 2.0f; - glm::vec3 direction = end - start; - float axisDistance; - return (findIntersection(start.x, direction.x, expandedCorner.x, expandedSize.x, axisDistance) && - axisDistance >= 0.0f && axisDistance <= 1.0f && - isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) && - isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) || - (findIntersection(start.y, direction.y, expandedCorner.y, expandedSize.y, axisDistance) && - axisDistance >= 0.0f && axisDistance <= 1.0f && - isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x) && - isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) || - (findIntersection(start.z, direction.z, expandedCorner.z, expandedSize.z, axisDistance) && - axisDistance >= 0.0f && axisDistance <= 1.0f && - isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) && - isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); -} - -bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { - distance = 0; - return true; - } - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _size.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _size.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _size.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - return true; - } - return false; -} - -bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const { - glm::vec4 center4 = glm::vec4(center, 1.0f); - - float minPenetrationLength = FLT_MAX; - for (int i = 0; i < FACE_COUNT; i++) { - glm::vec4 facePlane = getPlane((BoxFace)i); - glm::vec3 vector = getClosestPointOnFace(center, (BoxFace)i) - center; - if (glm::dot(center4, getPlane((BoxFace)i)) >= 0.0f) { - // outside this face, so use vector to closest point to determine penetration - return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration); - } - float vectorLength = glm::length(vector); - if (vectorLength < minPenetrationLength) { - // remember the smallest penetration vector; if we're inside all faces, we'll use that - penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius : - vector * ((vectorLength + radius) / -vectorLength); - minPenetrationLength = vectorLength; - } - } - - return true; -} - -bool AABox::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const { - glm::vec4 start4 = glm::vec4(start, 1.0f); - glm::vec4 end4 = glm::vec4(end, 1.0f); - glm::vec4 startToEnd = glm::vec4(end - start, 0.0f); - - float minPenetrationLength = FLT_MAX; - for (int i = 0; i < FACE_COUNT; i++) { - // find the vector from the segment to the closest point on the face (starting from deeper end) - glm::vec4 facePlane = getPlane((BoxFace)i); - glm::vec3 closest = (glm::dot(start4, facePlane) <= glm::dot(end4, facePlane)) ? - getClosestPointOnFace(start4, startToEnd, (BoxFace)i) : getClosestPointOnFace(end4, -startToEnd, (BoxFace)i); - glm::vec3 vector = -computeVectorFromPointToSegment(closest, start, end); - if (glm::dot(vector, glm::vec3(facePlane)) < 0.0f) { - // outside this face, so use vector to closest point to determine penetration - return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration); - } - float vectorLength = glm::length(vector); - if (vectorLength < minPenetrationLength) { - // remember the smallest penetration vector; if we're inside all faces, we'll use that - penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius : - vector * ((vectorLength + radius) / -vectorLength); - minPenetrationLength = vectorLength; - } - } - - return true; -} - -glm::vec3 AABox::getClosestPointOnFace(const glm::vec3& point, BoxFace face) const { - switch (face) { - case MIN_X_FACE: - return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), - glm::vec3(_corner.x, _corner.y + _size.y, _corner.z + _size.z)); - - case MAX_X_FACE: - return glm::clamp(point, glm::vec3(_corner.x + _size.x, _corner.y, _corner.z), - glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z)); - - case MIN_Y_FACE: - return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), - glm::vec3(_corner.x + _size.x, _corner.y, _corner.z + _size.z)); - - case MAX_Y_FACE: - return glm::clamp(point, glm::vec3(_corner.x, _corner.y + _size.y, _corner.z), - glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z)); - - case MIN_Z_FACE: - return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), - glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z)); - - case MAX_Z_FACE: - return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z + _size.z), - glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z)); - } -} - -glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const { - // check against the four planes that border the face - BoxFace oppositeFace = getOppositeFace(face); - bool anyOutside = false; - for (int i = 0; i < FACE_COUNT; i++) { - if (i == face || i == oppositeFace) { - continue; - } - glm::vec4 iPlane = getPlane((BoxFace)i); - float originDistance = glm::dot(origin, iPlane); - if (originDistance < 0.0f) { - continue; // inside the border - } - anyOutside = true; - float divisor = glm::dot(direction, iPlane); - if (fabs(divisor) < EPSILON) { - continue; // segment is parallel to plane - } - // find intersection and see if it lies within face bounds - float directionalDistance = -originDistance / divisor; - glm::vec4 intersection = origin + direction * directionalDistance; - BoxFace iOppositeFace = getOppositeFace((BoxFace)i); - for (int j = 0; j < FACE_COUNT; j++) { - if (j == face || j == oppositeFace || j == i || j == iOppositeFace) { - continue; - } - if (glm::dot(intersection, getPlane((BoxFace)j)) > 0.0f) { - goto outerContinue; // intersection is out of bounds - } - } - return getClosestPointOnFace(glm::vec3(intersection), face); - - outerContinue: ; - } - - // if we were outside any of the sides, we must check against the diagonals - if (anyOutside) { - int faceAxis = face / 2; - int secondAxis = (faceAxis + 1) % 3; - int thirdAxis = (faceAxis + 2) % 3; - - glm::vec4 secondAxisMinPlane = getPlane((BoxFace)(secondAxis * 2)); - glm::vec4 secondAxisMaxPlane = getPlane((BoxFace)(secondAxis * 2 + 1)); - glm::vec4 thirdAxisMaxPlane = getPlane((BoxFace)(thirdAxis * 2 + 1)); - - glm::vec4 offset = glm::vec4(0.0f, 0.0f, 0.0f, - glm::dot(glm::vec3(secondAxisMaxPlane + thirdAxisMaxPlane), _size) * 0.5f); - glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset, - secondAxisMaxPlane + thirdAxisMaxPlane + offset }; - - float minDistance = FLT_MAX; - for (int i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) { - float divisor = glm::dot(direction, diagonals[i]); - if (fabs(divisor) < EPSILON) { - continue; // segment is parallel to diagonal plane - } - minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance); - } - if (minDistance != FLT_MAX) { - return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face); - } - } - - // last resort or all inside: clamp origin to face - return getClosestPointOnFace(glm::vec3(origin), face); -} - -glm::vec4 AABox::getPlane(BoxFace face) const { - switch (face) { - case MIN_X_FACE: return glm::vec4(-1.0f, 0.0f, 0.0f, _corner.x); - case MAX_X_FACE: return glm::vec4(1.0f, 0.0f, 0.0f, -_corner.x - _size.x); - case MIN_Y_FACE: return glm::vec4(0.0f, -1.0f, 0.0f, _corner.y); - case MAX_Y_FACE: return glm::vec4(0.0f, 1.0f, 0.0f, -_corner.y - _size.y); - case MIN_Z_FACE: return glm::vec4(0.0f, 0.0f, -1.0f, _corner.z); - case MAX_Z_FACE: return glm::vec4(0.0f, 0.0f, 1.0f, -_corner.z - _size.z); - } -} - -BoxFace AABox::getOppositeFace(BoxFace face) { - switch (face) { - case MIN_X_FACE: return MAX_X_FACE; - case MAX_X_FACE: return MIN_X_FACE; - case MIN_Y_FACE: return MAX_Y_FACE; - case MAX_Y_FACE: return MIN_Y_FACE; - case MIN_Z_FACE: return MAX_Z_FACE; - case MAX_Z_FACE: return MIN_Z_FACE; - } -} +// +// AABox.h - Axis Aligned Boxes +// hifi +// +// Added by Brad Hefta-Gaub on 04/11/13. +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// +// Simple axis aligned box class. +// + +#include "SharedUtil.h" + +#include "AABox.h" +#include "GeometryUtil.h" + + +void AABox::scale(float scale) { + _corner = _corner * scale; + _size = _size * scale; + _center = _center * scale; +} + + +glm::vec3 AABox::getVertex(BoxVertex vertex) const { + switch (vertex) { + case BOTTOM_LEFT_NEAR: + return _corner + glm::vec3(_size.x, 0, 0); + case BOTTOM_RIGHT_NEAR: + return _corner; + case TOP_RIGHT_NEAR: + return _corner + glm::vec3(0, _size.y, 0); + case TOP_LEFT_NEAR: + return _corner + glm::vec3(_size.x, _size.y, 0); + case BOTTOM_LEFT_FAR: + return _corner + glm::vec3(_size.x, 0, _size.z); + case BOTTOM_RIGHT_FAR: + return _corner + glm::vec3(0, 0, _size.z); + case TOP_RIGHT_FAR: + return _corner + glm::vec3(0, _size.y, _size.z); + case TOP_LEFT_FAR: + return _corner + _size; + } +} + +void AABox::setBox(const glm::vec3& corner, const glm::vec3& size) { + _corner = corner; + _size = size; + + // In the event that the caller gave us negative sizes, fix things up to be reasonable + if (_size.x < 0.0) { + _size.x = -size.x; + _corner.x -= _size.x; + } + if (_size.y < 0.0) { + _size.y = -size.y; + _corner.y -= _size.y; + } + if (_size.z < 0.0) { + _size.z = -size.z; + _corner.z -= _size.z; + } + _center = _corner + (_size * 0.5f); +} + +glm::vec3 AABox::getVertexP(const glm::vec3& normal) const { + glm::vec3 result = _corner; + if (normal.x > 0) { + result.x += _size.x; + } + if (normal.y > 0) { + result.y += _size.y; + } + if (normal.z > 0) { + result.z += _size.z; + } + return result; +} + +glm::vec3 AABox::getVertexN(const glm::vec3& normal) const { + glm::vec3 result = _corner; + + if (normal.x < 0) { + result.x += _size.x; + } + + if (normal.y < 0) { + result.y += _size.y; + } + + if (normal.z < 0) { + result.z += _size.z; + } + + return result; +} + +// determines whether a value is within the extents +static bool isWithin(float value, float corner, float size) { + return value >= corner && value <= corner + size; +} + +bool AABox::contains(const glm::vec3& point) const { + return isWithin(point.x, _corner.x, _size.x) && + isWithin(point.y, _corner.y, _size.y) && + isWithin(point.z, _corner.z, _size.z); +} + +// determines whether a value is within the expanded extents +static bool isWithinExpanded(float value, float corner, float size, float expansion) { + return value >= corner - expansion && value <= corner + size + expansion; +} + +bool AABox::expandedContains(const glm::vec3& point, float expansion) const { + return isWithinExpanded(point.x, _corner.x, _size.x, expansion) && + isWithinExpanded(point.y, _corner.y, _size.y, expansion) && + isWithinExpanded(point.z, _corner.z, _size.z, expansion); +} + +// finds the intersection between a ray and the facing plane on one axis +static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = (corner - origin) / direction; + return true; + } else if (direction < -EPSILON) { + distance = (corner + size - origin) / direction; + return true; + } + return false; +} + +bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { + // handle the trivial cases where the expanded box contains the start or end + if (expandedContains(start, expansion) || expandedContains(end, expansion)) { + return true; + } + // check each axis + glm::vec3 expandedCorner = _corner - glm::vec3(expansion, expansion, expansion); + glm::vec3 expandedSize = _size + glm::vec3(expansion, expansion, expansion) * 2.0f; + glm::vec3 direction = end - start; + float axisDistance; + return (findIntersection(start.x, direction.x, expandedCorner.x, expandedSize.x, axisDistance) && + axisDistance >= 0.0f && axisDistance <= 1.0f && + isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) && + isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) || + (findIntersection(start.y, direction.y, expandedCorner.y, expandedSize.y, axisDistance) && + axisDistance >= 0.0f && axisDistance <= 1.0f && + isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x) && + isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) || + (findIntersection(start.z, direction.z, expandedCorner.z, expandedSize.z, axisDistance) && + axisDistance >= 0.0f && axisDistance <= 1.0f && + isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) && + isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); +} + +bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const { + // handle the trivial case where the box contains the origin + if (contains(origin)) { + distance = 0; + return true; + } + // check each axis + float axisDistance; + if ((findIntersection(origin.x, direction.x, _corner.x, _size.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) && + isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) { + distance = axisDistance; + face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + return true; + } + if ((findIntersection(origin.y, direction.y, _corner.y, _size.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x) && + isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) { + distance = axisDistance; + face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + return true; + } + if ((findIntersection(origin.z, direction.z, _corner.z, _size.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) && + isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x))) { + distance = axisDistance; + face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + return true; + } + return false; +} + +bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const { + glm::vec4 center4 = glm::vec4(center, 1.0f); + + float minPenetrationLength = FLT_MAX; + for (int i = 0; i < FACE_COUNT; i++) { + glm::vec4 facePlane = getPlane((BoxFace)i); + glm::vec3 vector = getClosestPointOnFace(center, (BoxFace)i) - center; + if (glm::dot(center4, getPlane((BoxFace)i)) >= 0.0f) { + // outside this face, so use vector to closest point to determine penetration + return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration); + } + float vectorLength = glm::length(vector); + if (vectorLength < minPenetrationLength) { + // remember the smallest penetration vector; if we're inside all faces, we'll use that + penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius : + vector * ((vectorLength + radius) / -vectorLength); + minPenetrationLength = vectorLength; + } + } + + return true; +} + +bool AABox::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const { + glm::vec4 start4 = glm::vec4(start, 1.0f); + glm::vec4 end4 = glm::vec4(end, 1.0f); + glm::vec4 startToEnd = glm::vec4(end - start, 0.0f); + + float minPenetrationLength = FLT_MAX; + for (int i = 0; i < FACE_COUNT; i++) { + // find the vector from the segment to the closest point on the face (starting from deeper end) + glm::vec4 facePlane = getPlane((BoxFace)i); + glm::vec3 closest = (glm::dot(start4, facePlane) <= glm::dot(end4, facePlane)) ? + getClosestPointOnFace(start4, startToEnd, (BoxFace)i) : getClosestPointOnFace(end4, -startToEnd, (BoxFace)i); + glm::vec3 vector = -computeVectorFromPointToSegment(closest, start, end); + if (glm::dot(vector, glm::vec3(facePlane)) < 0.0f) { + // outside this face, so use vector to closest point to determine penetration + return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration); + } + float vectorLength = glm::length(vector); + if (vectorLength < minPenetrationLength) { + // remember the smallest penetration vector; if we're inside all faces, we'll use that + penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius : + vector * ((vectorLength + radius) / -vectorLength); + minPenetrationLength = vectorLength; + } + } + + return true; +} + +glm::vec3 AABox::getClosestPointOnFace(const glm::vec3& point, BoxFace face) const { + switch (face) { + case MIN_X_FACE: + return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), + glm::vec3(_corner.x, _corner.y + _size.y, _corner.z + _size.z)); + + case MAX_X_FACE: + return glm::clamp(point, glm::vec3(_corner.x + _size.x, _corner.y, _corner.z), + glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z)); + + case MIN_Y_FACE: + return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), + glm::vec3(_corner.x + _size.x, _corner.y, _corner.z + _size.z)); + + case MAX_Y_FACE: + return glm::clamp(point, glm::vec3(_corner.x, _corner.y + _size.y, _corner.z), + glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z)); + + case MIN_Z_FACE: + return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), + glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z)); + + case MAX_Z_FACE: + return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z + _size.z), + glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z)); + } +} + +glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const { + // check against the four planes that border the face + BoxFace oppositeFace = getOppositeFace(face); + bool anyOutside = false; + for (int i = 0; i < FACE_COUNT; i++) { + if (i == face || i == oppositeFace) { + continue; + } + glm::vec4 iPlane = getPlane((BoxFace)i); + float originDistance = glm::dot(origin, iPlane); + if (originDistance < 0.0f) { + continue; // inside the border + } + anyOutside = true; + float divisor = glm::dot(direction, iPlane); + if (fabs(divisor) < EPSILON) { + continue; // segment is parallel to plane + } + // find intersection and see if it lies within face bounds + float directionalDistance = -originDistance / divisor; + glm::vec4 intersection = origin + direction * directionalDistance; + BoxFace iOppositeFace = getOppositeFace((BoxFace)i); + for (int j = 0; j < FACE_COUNT; j++) { + if (j == face || j == oppositeFace || j == i || j == iOppositeFace) { + continue; + } + if (glm::dot(intersection, getPlane((BoxFace)j)) > 0.0f) { + goto outerContinue; // intersection is out of bounds + } + } + return getClosestPointOnFace(glm::vec3(intersection), face); + + outerContinue: ; + } + + // if we were outside any of the sides, we must check against the diagonals + if (anyOutside) { + int faceAxis = face / 2; + int secondAxis = (faceAxis + 1) % 3; + int thirdAxis = (faceAxis + 2) % 3; + + glm::vec4 secondAxisMinPlane = getPlane((BoxFace)(secondAxis * 2)); + glm::vec4 secondAxisMaxPlane = getPlane((BoxFace)(secondAxis * 2 + 1)); + glm::vec4 thirdAxisMaxPlane = getPlane((BoxFace)(thirdAxis * 2 + 1)); + + glm::vec4 offset = glm::vec4(0.0f, 0.0f, 0.0f, + glm::dot(glm::vec3(secondAxisMaxPlane + thirdAxisMaxPlane), _size) * 0.5f); + glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset, + secondAxisMaxPlane + thirdAxisMaxPlane + offset }; + + float minDistance = FLT_MAX; + for (int i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) { + float divisor = glm::dot(direction, diagonals[i]); + if (fabs(divisor) < EPSILON) { + continue; // segment is parallel to diagonal plane + } + minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance); + } + if (minDistance != FLT_MAX) { + return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face); + } + } + + // last resort or all inside: clamp origin to face + return getClosestPointOnFace(glm::vec3(origin), face); +} + +glm::vec4 AABox::getPlane(BoxFace face) const { + switch (face) { + case MIN_X_FACE: return glm::vec4(-1.0f, 0.0f, 0.0f, _corner.x); + case MAX_X_FACE: return glm::vec4(1.0f, 0.0f, 0.0f, -_corner.x - _size.x); + case MIN_Y_FACE: return glm::vec4(0.0f, -1.0f, 0.0f, _corner.y); + case MAX_Y_FACE: return glm::vec4(0.0f, 1.0f, 0.0f, -_corner.y - _size.y); + case MIN_Z_FACE: return glm::vec4(0.0f, 0.0f, -1.0f, _corner.z); + case MAX_Z_FACE: return glm::vec4(0.0f, 0.0f, 1.0f, -_corner.z - _size.z); + } +} + +BoxFace AABox::getOppositeFace(BoxFace face) { + switch (face) { + case MIN_X_FACE: return MAX_X_FACE; + case MAX_X_FACE: return MIN_X_FACE; + case MIN_Y_FACE: return MAX_Y_FACE; + case MAX_Y_FACE: return MIN_Y_FACE; + case MIN_Z_FACE: return MAX_Z_FACE; + case MAX_Z_FACE: return MIN_Z_FACE; + } +} diff --git a/libraries/voxels/src/AABox.h b/libraries/voxels/src/AABox.h index 7acf2d54f1..d295f24aea 100644 --- a/libraries/voxels/src/AABox.h +++ b/libraries/voxels/src/AABox.h @@ -1,72 +1,86 @@ -// -// AABox.h - Axis Aligned Boxes -// hifi -// -// Added by Brad Hefta-Gaub on 04/11/13. -// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards -// -// Simple axis aligned box class. -// - -#ifndef _AABOX_ -#define _AABOX_ - -#include - -enum BoxFace { - MIN_X_FACE, - MAX_X_FACE, - MIN_Y_FACE, - MAX_Y_FACE, - MIN_Z_FACE, - MAX_Z_FACE -}; - -const int FACE_COUNT = 6; - - class AABox -{ - -public: - - AABox(const glm::vec3& corner, float size) : _corner(corner), _size(size, size, size) { }; - AABox(const glm::vec3& corner, float x, float y, float z) : _corner(corner), _size(x, y, z) { }; - AABox(const glm::vec3& corner, const glm::vec3& size) : _corner(corner), _size(size) { }; - AABox() : _corner(0,0,0), _size(0,0,0) { } - ~AABox() { } - - void setBox(const glm::vec3& corner, float x, float y, float z) { setBox(corner,glm::vec3(x,y,z)); }; - void setBox(const glm::vec3& corner, const glm::vec3& size); - - // for use in frustum computations - glm::vec3 getVertexP(const glm::vec3& normal) const; - glm::vec3 getVertexN(const glm::vec3& normal) const; - - void scale(float scale); - - const glm::vec3& getCorner() const { return _corner; }; - const glm::vec3& getSize() const { return _size; }; - const glm::vec3& getCenter() const { return _center; }; - - bool contains(const glm::vec3& point) const; - bool expandedContains(const glm::vec3& point, float expansion) const; - bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; - bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; - bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; - -private: - - glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; - glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const; - glm::vec4 getPlane(BoxFace face) const; - - static BoxFace getOppositeFace(BoxFace face); - - glm::vec3 _corner; - glm::vec3 _center; - glm::vec3 _size; -}; - - -#endif +// +// AABox.h - Axis Aligned Boxes +// hifi +// +// Added by Brad Hefta-Gaub on 04/11/13. +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// +// Simple axis aligned box class. +// + +#ifndef _AABOX_ +#define _AABOX_ + +#include + +enum BoxFace { + MIN_X_FACE, + MAX_X_FACE, + MIN_Y_FACE, + MAX_Y_FACE, + MIN_Z_FACE, + MAX_Z_FACE +}; + + +enum BoxVertex { + BOTTOM_LEFT_NEAR = 0, + BOTTOM_RIGHT_NEAR = 1, + TOP_RIGHT_NEAR = 2, + TOP_LEFT_NEAR = 3, + BOTTOM_LEFT_FAR = 4, + BOTTOM_RIGHT_FAR = 5, + TOP_RIGHT_FAR = 6, + TOP_LEFT_FAR = 7 +}; + +const int FACE_COUNT = 6; + +class AABox +{ + +public: + + AABox(const glm::vec3& corner, float size) : _corner(corner), _size(size, size, size) { }; + AABox(const glm::vec3& corner, float x, float y, float z) : _corner(corner), _size(x, y, z) { }; + AABox(const glm::vec3& corner, const glm::vec3& size) : _corner(corner), _size(size) { }; + AABox() : _corner(0,0,0), _size(0,0,0) { } + ~AABox() { } + + void setBox(const glm::vec3& corner, float x, float y, float z) { setBox(corner,glm::vec3(x,y,z)); }; + void setBox(const glm::vec3& corner, const glm::vec3& size); + + // for use in frustum computations + glm::vec3 getVertexP(const glm::vec3& normal) const; + glm::vec3 getVertexN(const glm::vec3& normal) const; + + void scale(float scale); + + const glm::vec3& getCorner() const { return _corner; }; + const glm::vec3& getSize() const { return _size; }; + const glm::vec3& getCenter() const { return _center; }; + + glm::vec3 getVertex(BoxVertex vertex) const; + + bool contains(const glm::vec3& point) const; + bool expandedContains(const glm::vec3& point, float expansion) const; + bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; + bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; + +private: + + glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; + glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const; + glm::vec4 getPlane(BoxFace face) const; + + static BoxFace getOppositeFace(BoxFace face); + + glm::vec3 _corner; + glm::vec3 _center; + glm::vec3 _size; +}; + + +#endif diff --git a/libraries/voxels/src/CoverageMap.cpp b/libraries/voxels/src/CoverageMap.cpp new file mode 100644 index 0000000000..e9a1fb6231 --- /dev/null +++ b/libraries/voxels/src/CoverageMap.cpp @@ -0,0 +1,193 @@ +// +// CoverageMap.cpp - +// hifi +// +// Added by Brad Hefta-Gaub on 06/11/13. +// + +#include "CoverageMap.h" +#include +#include +#include "Log.h" + +int CoverageMap::_mapCount = 0; +const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-2.f,-2.f), glm::vec2(4.f,4.f)); + +CoverageMap::CoverageMap(BoundingBox boundingBox, bool isRoot, bool managePolygons) : + _isRoot(isRoot), _myBoundingBox(boundingBox), _managePolygons(managePolygons) { + _mapCount++; + init(); + //printLog("CoverageMap created... _mapCount=%d\n",_mapCount); +}; + +CoverageMap::~CoverageMap() { + erase(); +}; + +void CoverageMap::erase() { + // If we're in charge of managing the polygons, then clean them up first + if (_managePolygons) { + for (int i = 0; i < _polygonCount; i++) { + delete _polygons[i]; + _polygons[i] = NULL; // do we need to do this? + } + } + + // Now, clean up our local storage + _polygonCount = 0; + _polygonArraySize = 0; + if (_polygons) { + delete[] _polygons; + _polygons = NULL; + } + if (_polygonDistances) { + delete[] _polygonDistances; + _polygonDistances = NULL; + } + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + if (_childMaps[i]) { + delete _childMaps[i]; + _childMaps[i] = NULL; + } + } + +/** + if (_isRoot) { + printLog("CoverageMap last to be deleted...\n"); + printLog("_mapCount=%d\n",_mapCount); + printLog("_maxPolygonsUsed=%d\n",_maxPolygonsUsed); + printLog("_totalPolygons=%d\n",_totalPolygons); + + _maxPolygonsUsed = 0; + _totalPolygons = 0; + _mapCount = 0; + } +**/ + +} + +void CoverageMap::init() { + _polygonCount = 0; + _polygonArraySize = 0; + _polygons = NULL; + _polygonDistances = NULL; + memset(_childMaps,0,sizeof(_childMaps)); +} + +// 0 = bottom, right +// 1 = bottom, left +// 2 = top, right +// 3 = top, left +BoundingBox CoverageMap::getChildBoundingBox(int childIndex) { + const int LEFT_BIT = 1; + const int TOP_BIT = 2; + // initialize to our corner, and half our size + BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f); + // if our "left" bit is set, then add size.x to the corner + if ((childIndex & LEFT_BIT) == LEFT_BIT) { + result.corner.x += result.size.x; + } + // if our "top" bit is set, then add size.y to the corner + if ((childIndex & TOP_BIT) == TOP_BIT) { + result.corner.y += result.size.y; + } + return result; +} + + +void CoverageMap::growPolygonArray() { + VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE]; + float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; + + + if (_polygons) { + memcpy(newPolygons, _polygons, sizeof(VoxelProjectedPolygon*) * _polygonCount); + delete[] _polygons; + memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount); + delete[] _polygonDistances; + } + _polygons = newPolygons; + _polygonDistances = newDistances; + _polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE; + //printLog("CoverageMap::growPolygonArray() _polygonArraySize=%d...\n",_polygonArraySize); +} + +int CoverageMap::_maxPolygonsUsed = 0; +int CoverageMap::_totalPolygons = 0; + +// just handles storage in the array, doesn't test for occlusion or +// determining if this is the correct map to store in! +void CoverageMap::storeInArray(VoxelProjectedPolygon* polygon) { + + _totalPolygons++; + + if (_polygonArraySize < _polygonCount + 1) { + growPolygonArray(); + } + + // This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to + // be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the + // insertion point in this array, and shift the array accordingly + const int IGNORED = NULL; + _polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED, + (void**)_polygons, _polygonDistances, IGNORED, + _polygonCount, _polygonArraySize); + + if (_polygonCount > _maxPolygonsUsed) { + _maxPolygonsUsed = _polygonCount; + //printLog("CoverageMap new _maxPolygonsUsed reached=%d\n",_maxPolygonsUsed); + //_myBoundingBox.printDebugDetails("map._myBoundingBox"); + } +} + + +// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT +CoverageMap::StorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) { + if (_isRoot || _myBoundingBox.contains(polygon->getBoundingBox())) { + // check to make sure this polygon isn't occluded by something at this level + for (int i = 0; i < _polygonCount; i++) { + VoxelProjectedPolygon* polygonAtThisLevel = _polygons[i]; + // Check to make sure that the polygon in question is "behind" the polygon in the list + // otherwise, we don't need to test it's occlusion (although, it means we've potentially + // added an item previously that may be occluded??? Is that possible? Maybe not, because two + // voxels can't have the exact same outline. So one occludes the other, they can't both occlude + // each other. + if (polygonAtThisLevel->occludes(*polygon)) { + // if the polygonAtThisLevel is actually behind the one we're inserting, then we don't + // want to report our inserted one as occluded, but we do want to add our inserted one. + if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) { + if (storeIt) { + storeInArray(polygon); + return STORED; + } else { + return NOT_STORED; + } + } + // this polygon is occluded by a closer polygon, so don't store it, and let the caller know + return OCCLUDED; + } + } + // if we made it here, then it means the polygon being stored is not occluded + // at this level of the quad tree, so we can continue to insert it into the map. + // First we check to see if it fits in any of our sub maps + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + BoundingBox childMapBoundingBox = getChildBoundingBox(i); + if (childMapBoundingBox.contains(polygon->getBoundingBox())) { + // if no child map exists yet, then create it + if (!_childMaps[i]) { + _childMaps[i] = new CoverageMap(childMapBoundingBox, NOT_ROOT, _managePolygons); + } + return _childMaps[i]->checkMap(polygon, storeIt); + } + } + // if we got this far, then the polygon is in our bounding box, but doesn't fit in + // any of our child bounding boxes, so we should add it here. + if (storeIt) { + storeInArray(polygon); + return STORED; + } else { + return NOT_STORED; + } + } + return DOESNT_FIT; +} diff --git a/libraries/voxels/src/CoverageMap.h b/libraries/voxels/src/CoverageMap.h new file mode 100644 index 0000000000..38c7dc6145 --- /dev/null +++ b/libraries/voxels/src/CoverageMap.h @@ -0,0 +1,54 @@ +// +// CoverageMap.h - 2D CoverageMap Quad tree for storage of VoxelProjectedPolygons +// hifi +// +// Added by Brad Hefta-Gaub on 06/11/13. +// + +#ifndef _COVERAGE_MAP_ +#define _COVERAGE_MAP_ + +#include +#include "VoxelProjectedPolygon.h" + +class CoverageMap { + +public: + static const int NUMBER_OF_CHILDREN = 4; + static const bool NOT_ROOT=false; + static const bool IS_ROOT=true; + static const BoundingBox ROOT_BOUNDING_BOX; + + CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true); + ~CoverageMap(); + + typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} StorageResult; + StorageResult checkMap(VoxelProjectedPolygon* polygon, bool storeIt = true); + + BoundingBox getChildBoundingBox(int childIndex); + + void erase(); // erase the coverage map + +private: + void init(); + void growPolygonArray(); + void storeInArray(VoxelProjectedPolygon* polygon); + + bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT + BoundingBox _myBoundingBox; + bool _managePolygons; // will the coverage map delete the polygons on destruct + int _polygonCount; // how many polygons at this level + int _polygonArraySize; // how much room is there to store polygons at this level + VoxelProjectedPolygon** _polygons; + float* _polygonDistances; + CoverageMap* _childMaps[NUMBER_OF_CHILDREN]; + + static const int DEFAULT_GROW_SIZE = 100; + static int _mapCount; + static int _maxPolygonsUsed; + static int _totalPolygons; + +}; + + +#endif // _COVERAGE_MAP_ diff --git a/libraries/voxels/src/GeometryUtil.cpp b/libraries/voxels/src/GeometryUtil.cpp index aceaa32b37..1fc4e57013 100644 --- a/libraries/voxels/src/GeometryUtil.cpp +++ b/libraries/voxels/src/GeometryUtil.cpp @@ -115,3 +115,28 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& return currentDirection * glm::max(directionalComponent, currentLength) + newPenetration - (currentDirection * directionalComponent); } + +// Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect? +bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) { + int d1 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y); + int d2 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y); + int d3 = computeDirection(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p1.x, r2p1.y); + int d4 = computeDirection(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y); + return (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && + ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) || + (d1 == 0 && isOnSegment(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y)) || + (d2 == 0 && isOnSegment(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y)) || + (d3 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p1.x, r2p1.y)) || + (d4 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y)); +} + +bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk) { + return (xi <= xk || xj <= xk) && (xk <= xi || xk <= xj) && + (yi <= yk || yj <= yk) && (yk <= yi || yk <= yj); +} + +int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk) { + float a = (xk - xi) * (yj - yi); + float b = (xj - xi) * (yk - yi); + return a < b ? -1 : a > b ? 1 : 0; +} diff --git a/libraries/voxels/src/GeometryUtil.h b/libraries/voxels/src/GeometryUtil.h index 01cd87a4f3..b16ad4e2c0 100644 --- a/libraries/voxels/src/GeometryUtil.h +++ b/libraries/voxels/src/GeometryUtil.h @@ -39,4 +39,8 @@ bool findCapsulePlanePenetration(const glm::vec3& penetratorStart, const glm::ve glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration); +bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2); +bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk); +int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk); + #endif /* defined(__interface__GeometryUtil__) */ diff --git a/libraries/voxels/src/ViewFrustum.cpp b/libraries/voxels/src/ViewFrustum.cpp index b47199310c..786e5a1af0 100644 --- a/libraries/voxels/src/ViewFrustum.cpp +++ b/libraries/voxels/src/ViewFrustum.cpp @@ -425,3 +425,116 @@ void ViewFrustum::printDebugDetails() const { _eyeOffsetOrientation.w ); } + +glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const { + + // Projection matrix : Field of View, ratio, display range : near to far + glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, _farClip); + glm::vec3 lookAt = _position + _direction; + glm::mat4 view = glm::lookAt(_position, lookAt, _up); + // Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it) + glm::mat4 VP = projection * view; // Remember, matrix multiplication is the other way around + + glm::vec4 pointVec4 = glm::vec4(point,1); + glm::vec4 projectedPointVec4 = VP * pointVec4; + pointInView = (projectedPointVec4.w > 0); // math! If the w result is negative then the point is behind the viewer + + // what happens with w is 0??? + float x = projectedPointVec4.x / projectedPointVec4.w; + float y = projectedPointVec4.y / projectedPointVec4.w; + glm::vec2 projectedPoint(x,y); + + // if the point is out of view we also need to flip the signs of x and y + if (!pointInView) { + projectedPoint.x = -x; + projectedPoint.y = -y; + } + + return projectedPoint; +} + + +const int MAX_POSSIBLE_COMBINATIONS = 43; + +const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1] = { + // Number of vertices in shadow polygon for the visible faces, then a list of the index of each vertice from the AABox + {0}, // inside + {4, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR}, // right + {4, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // left + {0}, // n/a + {4, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // bottom + {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR},//bottom, right + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR},//bottom, left + {0}, // n/a + {4, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // top + {6, TOP_RIGHT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // top, right + {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // top, left + {0}, // n/a + {0}, // n/a + {0}, // n/a + {0}, // n/a + {0}, // n/a + {4, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front or near + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, right + {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // front, left + {0}, // n/a + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, // front,bottom + {6, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR}, //front,bottom,right + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, //front,bottom,left + {0}, // n/a + {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, top + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, top, right + {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // front, top, left + {0}, // n/a + {0}, // n/a + {0}, // n/a + {0}, // n/a + {0}, // n/a + {4, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR}, // back + {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, // back, right + {6, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR}, // back, left + {0}, // n/a + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // back, bottom + {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR},//back, bottom, right + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR},//back, bottom, left + {0}, // n/a + {6, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR}, // back, top + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, // back, top, right + {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left +}; + +VoxelProjectedPolygon ViewFrustum::getProjectedShadow(const AABox& box) const { + glm::vec3 bottomNearRight = box.getCorner(); + glm::vec3 topFarLeft = box.getCorner() + box.getSize(); + int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit + + ((_position.x > topFarLeft.x ) << 1) // 2 = left | code to + + ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera + + ((_position.y > topFarLeft.y ) << 3) // 8 = top | with respect to + + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining + + ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes + + int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices + + VoxelProjectedPolygon shadow(vertexCount); + + bool pointInView = true; + bool allPointsInView = false; // assume the best, but wait till we know we have a vertex + bool anyPointsInView = false; // assume the worst! + if (vertexCount) { + allPointsInView = true; // assume the best! + for(int i = 0; i < vertexCount; i++) { + int vertexNum = hullVertexLookup[lookUp][i+1]; + glm::vec3 point = box.getVertex((BoxVertex)vertexNum); + glm::vec2 projectedPoint = projectPoint(point, pointInView); + allPointsInView = allPointsInView && pointInView; + anyPointsInView = anyPointsInView || pointInView; + shadow.setVertex(i, projectedPoint); + } + } + // set the distance from our camera position, to the closest vertex + float distance = glm::distance(getPosition(), box.getCenter()); + shadow.setDistance(distance); + shadow.setAnyInView(anyPointsInView); + shadow.setAllInView(allPointsInView); + return shadow; +} diff --git a/libraries/voxels/src/ViewFrustum.h b/libraries/voxels/src/ViewFrustum.h index d23f2db897..f2b329ee30 100644 --- a/libraries/voxels/src/ViewFrustum.h +++ b/libraries/voxels/src/ViewFrustum.h @@ -15,6 +15,7 @@ #include #include "Plane.h" #include "AABox.h" +#include "VoxelProjectedPolygon.h" const float DEFAULT_KEYHOLE_RADIUS = 2.0f; @@ -87,6 +88,9 @@ public: void printDebugDetails() const; + glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const; + VoxelProjectedPolygon getProjectedShadow(const AABox& box) const; + private: // Used for keyhole calculations diff --git a/libraries/voxels/src/VoxelProjectedPolygon.cpp b/libraries/voxels/src/VoxelProjectedPolygon.cpp new file mode 100644 index 0000000000..baef436d08 --- /dev/null +++ b/libraries/voxels/src/VoxelProjectedPolygon.cpp @@ -0,0 +1,126 @@ +// +// VoxelProjectedPolygon.cpp - The projected shadow (on the 2D view plane) for a voxel +// hifi +// +// Added by Brad Hefta-Gaub on 06/11/13. +// + +#include "VoxelProjectedPolygon.h" +#include "GeometryUtil.h" +#include "Log.h" + + +bool BoundingBox::contains(const BoundingBox& box) const { + return ( + (box.corner.x >= corner.x) && + (box.corner.y >= corner.y) && + (box.corner.x + box.size.x <= corner.x + size.x) && + (box.corner.y + box.size.y <= corner.y + size.y) + ); +}; + +void BoundingBox::printDebugDetails(const char* label) const { + if (label) { + printLog(label); + } else { + printLog("BoundingBox"); + } + printLog("\n corner=%f,%f size=%f,%f\n", corner.x, corner.y, size.x, size.y); +} + + +void VoxelProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { + _vertices[vertex] = point; + + // keep track of our bounding box + if (point.x > _maxX) { + _maxX = point.x; + } + if (point.y > _maxY) { + _maxY = point.y; + } + if (point.x < _minX) { + _minX = point.x; + } + if (point.y < _minY) { + _minY = point.y; + } + +}; + +bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee) const { + + // if we are completely out of view, then we definitely don't occlude! + // if the occludee is completely out of view, then we also don't occlude it + // + // this is true, but unfortunately, we're not quite handling projects in the + // case when SOME points are in view and others are not. So, we will not consider + // occlusion for any shadows that are partially in view. + if (!getAllInView() || !occludee.getAllInView() ) { + return false; + } + + // first check the bounding boxes, the occludee must be fully within the boounding box of this shadow + if ((occludee.getMaxX() > getMaxX()) || + (occludee.getMaxY() > getMaxY()) || + (occludee.getMinX() < getMinX()) || + (occludee.getMinY() < getMinY())) { + return false; + } + + // if we got this far, then check each vertex of the occludee, if all those points + // are inside our polygon, then the tested occludee is fully occluded + for(int i = 0; i < occludee.getVertexCount(); i++) { + if (!pointInside(occludee.getVertex(i))) { + return false; + } + } + + // if we got this far, then indeed the occludee is fully occluded by us + return true; +} + +bool VoxelProjectedPolygon::pointInside(const glm::vec2& point) const { + // first check the bounding boxes, the point must be fully within the boounding box of this shadow + if ((point.x > getMaxX()) || + (point.y > getMaxY()) || + (point.x < getMinX()) || + (point.y < getMinY())) { + return false; + } + + float e = (getMaxX() - getMinX()) / 100.0f; // some epsilon + + // We need to have one ray that goes from a known outside position to the point in question. We'll pick a + // start point just outside of our min X + glm::vec2 r1p1(getMinX() - e, point.y); + glm::vec2 r1p2(point); + + glm::vec2 r2p1(getVertex(getVertexCount()-1)); // start with last vertex to first vertex + glm::vec2 r2p2; + + // Test the ray against all sides + int intersections = 0; + for (int i = 0; i < getVertexCount(); i++) { + r2p2 = getVertex(i); + if (doLineSegmentsIntersect(r1p1, r1p2, r2p1, r2p2)) { + intersections++; + } + r2p1 = r2p2; // set up for next side + } + + // If odd number of intersections, we're inside + return ((intersections & 1) == 1); +} + +void VoxelProjectedPolygon::printDebugDetails() const { + printf("VoxelProjectedPolygon..."); + printf(" minX=%f maxX=%f minY=%f maxY=%f\n", getMinX(), getMaxX(), getMinY(), getMaxY()); + printf(" vertex count=%d distance=%f\n", getVertexCount(), getDistance()); + for (int i = 0; i < getVertexCount(); i++) { + glm::vec2 point = getVertex(i); + printf(" vertex[%d] = %f, %f \n", i, point.x, point.y); + } +} + + diff --git a/libraries/voxels/src/VoxelProjectedPolygon.h b/libraries/voxels/src/VoxelProjectedPolygon.h new file mode 100644 index 0000000000..c6e4b665fa --- /dev/null +++ b/libraries/voxels/src/VoxelProjectedPolygon.h @@ -0,0 +1,78 @@ +// +// VoxelProjectedPolygon.h - The projected shadow (on the 2D view plane) for a voxel +// hifi +// +// Added by Brad Hefta-Gaub on 06/11/13. +// + +#ifndef _VOXEL_PROJECTED_SHADOW_ +#define _VOXEL_PROJECTED_SHADOW_ + +#include + +const int MAX_SHADOW_VERTEX_COUNT = 6; + +typedef glm::vec2 ShadowVertices[MAX_SHADOW_VERTEX_COUNT]; + +class BoundingBox { +public: + BoundingBox(glm::vec2 corner, glm::vec2 size) : corner(corner), size(size) {}; + glm::vec2 corner; + glm::vec2 size; + bool contains(const BoundingBox& box) const; + + void printDebugDetails(const char* label=NULL) const; +}; + +class VoxelProjectedPolygon { + +public: + VoxelProjectedPolygon(int vertexCount = 0) : + _vertexCount(vertexCount), + _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), + _distance(0) + { }; + + ~VoxelProjectedPolygon() { }; + const ShadowVertices& getVerices() const { return _vertices; }; + const glm::vec2& getVertex(int i) const { return _vertices[i]; }; + void setVertex(int vertex, const glm::vec2& point); + int getVertexCount() const { return _vertexCount; }; + void setVertexCount(int vertexCount) { _vertexCount = vertexCount; }; + + float getDistance() const { return _distance; } + void setDistance(float distance) { _distance = distance; } + + bool getAnyInView() const { return _anyInView; }; + void setAnyInView(bool anyInView) { _anyInView = anyInView; }; + bool getAllInView() const { return _allInView; }; + void setAllInView(bool allInView) { _allInView = allInView; }; + + bool occludes(const VoxelProjectedPolygon& occludee) const; + bool pointInside(const glm::vec2& point) const; + + float getMaxX() const { return _maxX; } + float getMaxY() const { return _maxY; } + float getMinX() const { return _minX; } + float getMinY() const { return _minY; } + + BoundingBox getBoundingBox() const { + return BoundingBox(glm::vec2(_minX,_minY), glm::vec2(_maxX - _minX, _maxY - _minY)); + }; + + void printDebugDetails() const; + +private: + int _vertexCount; + ShadowVertices _vertices; + float _maxX; + float _maxY; + float _minX; + float _minY; + float _distance; + bool _anyInView; // if any points are in view + bool _allInView; // if all points are in view +}; + + +#endif // _VOXEL_PROJECTED_SHADOW_ diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 100f343b6d..4329b94177 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 @@ -68,7 +69,51 @@ void VoxelTree::recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperati } } -VoxelNode * VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const { +// Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node. +// stops recursion if operation function returns false. +void VoxelTree::recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, + const glm::vec3& point, void* extraData) { + recurseNodeWithOperationDistanceSorted(rootNode, operation, point, extraData); +} + +// Recurses voxel node with an operation function +void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, + const glm::vec3& point, void* extraData) { + if (operation(node, extraData)) { + // determine the distance sorted order of our 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 (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(""); + 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(""); + recurseNodeWithOperationDistanceSorted(childNode, operation, point, extraData); + } + } + } +} + + +VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, + unsigned char* needleCode, VoxelNode** parentOfFoundNode) const { // find the appropriate branch index based on this ancestorNode if (*needleCode > 0) { int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode); @@ -1072,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); + VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(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! @@ -1080,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) { @@ -1120,16 +1229,48 @@ 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()) { + // Don't check occlusion here, just add them to our distance ordered array... + + AABox voxelBox = childNode->getAABox(); + voxelBox.scale(TREE_SCALE); + VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(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()) { + 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 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) { + childIsOccluded = true; + } + } else { + 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++; } } @@ -1189,15 +1330,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. @@ -1227,15 +1388,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 9550c6c6b3..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) {} }; @@ -96,6 +107,8 @@ public: creationMode mode, bool destructive = false, bool debug = false); void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); + void recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, + const glm::vec3& point, void* extraData=NULL); int encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, EncodeBitstreamParams& params) const; @@ -127,6 +140,10 @@ public: void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode); bool getShouldReaverage() const { return _shouldReaverage; } + + void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); + void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, + const glm::vec3& point, void* extraData); private: void deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData); @@ -141,7 +158,6 @@ private: static bool countVoxelsOperation(VoxelNode* node, void* extraData); - void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const; VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate); int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes, 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(); }