Merge pull request #543 from ZappoMan/occlusion_culling

Occlusion culling
This commit is contained in:
Philip Rosedale 2013-06-18 12:07:33 -07:00
commit 726872d955
21 changed files with 1455 additions and 463 deletions

View file

@ -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

View file

@ -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

View file

@ -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();
}

View file

@ -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);

View file

@ -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);

View file

@ -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:

View file

@ -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

View file

@ -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;
}
}

View file

@ -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 <glm/glm.hpp>
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 <glm/glm.hpp>
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

View file

@ -0,0 +1,193 @@
//
// CoverageMap.cpp -
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
//
#include "CoverageMap.h"
#include <SharedUtil.h>
#include <string>
#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;
}

View file

@ -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 <glm/glm.hpp>
#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_

View file

@ -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;
}

View file

@ -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__) */

View file

@ -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;
}

View file

@ -15,6 +15,7 @@
#include <glm/gtc/quaternion.hpp>
#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

View file

@ -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);
}
}

View file

@ -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 <glm/glm.hpp>
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_

View file

@ -22,6 +22,7 @@
#include "ViewFrustum.h"
#include <fstream> // to load voxels from file
#include "VoxelConstants.h"
#include "CoverageMap.h"
#include <glm/gtc/noise.hpp>
@ -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;
}

View file

@ -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,

View file

@ -14,6 +14,7 @@
#include <AvatarData.h>
#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; };

View file

@ -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();
}