Merge pull request #644 from ZappoMan/occlusion_culling

Latest Revision of Occlusion Culling & Delta Sending Work - now it's always better and never worse
This commit is contained in:
Philip Rosedale 2013-07-11 09:05:03 -07:00
commit cf665e10b3
25 changed files with 2544 additions and 340 deletions

View file

@ -1219,6 +1219,10 @@ void Application::doFalseColorizeOccluded() {
_voxels.falseColorizeOccluded();
}
void Application::doFalseColorizeOccludedV2() {
_voxels.falseColorizeOccludedV2();
}
void Application::doTrueVoxelColors() {
_voxels.trueColorize();
}
@ -1231,16 +1235,12 @@ void Application::setWantsMonochrome(bool wantsMonochrome) {
_myAvatar.setWantColor(!wantsMonochrome);
}
void Application::setWantsResIn(bool wantsResIn) {
_myAvatar.setWantResIn(wantsResIn);
}
void Application::setWantsDelta(bool wantsDelta) {
_myAvatar.setWantDelta(wantsDelta);
}
void Application::setWantsOcclusionCulling(bool wantsOcclusionCulling) {
_myAvatar.setWantOcclusionCulling(wantsOcclusionCulling);
void Application::disableOcclusionCulling(bool disableOcclusionCulling) {
_myAvatar.setWantOcclusionCulling(!disableOcclusionCulling);
}
void Application::updateVoxelModeActions() {
@ -1622,13 +1622,20 @@ void Application::initMenu() {
renderDebugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance()));
renderDebugMenu->addAction("FALSE Color Voxel Out of View", this, SLOT(doFalseColorizeInView()));
renderDebugMenu->addAction("FALSE Color Occluded Voxels", this, SLOT(doFalseColorizeOccluded()), Qt::CTRL | Qt::Key_O);
renderDebugMenu->addAction("FALSE Color Occluded V2 Voxels", this, SLOT(doFalseColorizeOccludedV2()), Qt::CTRL | Qt::Key_P);
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);
(_shouldLowPassFilter = debugMenu->addAction("Test: LowPass filter"))->setCheckable(true);
debugMenu->addAction("Wants Occlusion Culling", this, SLOT(setWantsOcclusionCulling(bool)))->setCheckable(true);
debugMenu->addAction("Disable Occlusion Culling", this, SLOT(disableOcclusionCulling(bool)),
Qt::SHIFT | Qt::Key_C)->setCheckable(true);
(_renderCoverageMap = debugMenu->addAction("Render Coverage Map"))->setCheckable(true);
_renderCoverageMap->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_O);
(_renderCoverageMapV2 = debugMenu->addAction("Render Coverage Map V2"))->setCheckable(true);
_renderCoverageMapV2->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_P);
QMenu* settingsMenu = menuBar->addMenu("Settings");
(_settingsAutosave = settingsMenu->addAction("Autosave"))->setCheckable(true);
@ -2388,6 +2395,7 @@ void Application::displaySide(Camera& whichCamera) {
// brad's frustum for debugging
if (_frustumOn->isChecked()) renderViewFrustum(_viewFrustum);
}
void Application::displayOverlay() {
@ -2436,6 +2444,9 @@ void Application::displayOverlay() {
if (_renderStatsOn->isChecked()) { displayStats(); }
// testing rendering coverage map
if (_renderCoverageMapV2->isChecked()) { renderCoverageMapV2(); }
if (_renderCoverageMap->isChecked()) { renderCoverageMap(); }
if (_bandwidthDisplayOn->isChecked()) { _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); }
if (_logOn->isChecked()) { LogDisplay::instance.render(_glWidget->width(), _glWidget->height()); }
@ -2621,8 +2632,8 @@ void Application::renderThrustAtVoxel(const glm::vec3& thrust) {
glVertex3f(voxelTouched.x + thrust.x, voxelTouched.y + thrust.y, voxelTouched.z + thrust.z);
glEnd();
}
}
void Application::renderLineToTouchedVoxel() {
// Draw a teal line to the voxel I am currently dragging on
if (_mousePressed) {
@ -2637,6 +2648,149 @@ void Application::renderLineToTouchedVoxel() {
}
}
glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) {
float horizontalScale = _glWidget->width() / 2.0f;
float verticalScale = _glWidget->height() / 2.0f;
// -1,-1 is 0,windowHeight
// 1,1 is windowWidth,0
// -1,1 1,1
// +-----------------------+
// | | |
// | | |
// | -1,0 | |
// |-----------+-----------|
// | 0,0 |
// | | |
// | | |
// | | |
// +-----------------------+
// -1,-1 1,-1
glm::vec2 screenPoint((projectedPoint.x + 1.0) * horizontalScale,
((projectedPoint.y + 1.0) * -verticalScale) + _glWidget->height());
return screenPoint;
}
// render the coverage map on screen
void Application::renderCoverageMapV2() {
//printLog("renderCoverageMap()\n");
glDisable(GL_LIGHTING);
glLineWidth(2.0);
glBegin(GL_LINES);
glColor3f(0,1,1);
renderCoverageMapsV2Recursively(&_voxels.myCoverageMapV2);
glEnd();
glEnable(GL_LIGHTING);
}
void Application::renderCoverageMapsV2Recursively(CoverageMapV2* map) {
// render ourselves...
if (map->isCovered()) {
BoundingBox box = map->getBoundingBox();
glm::vec2 firstPoint = getScaledScreenPoint(box.getVertex(0));
glm::vec2 lastPoint(firstPoint);
for (int i = 1; i < box.getVertexCount(); i++) {
glm::vec2 thisPoint = getScaledScreenPoint(box.getVertex(i));
glVertex2f(lastPoint.x, lastPoint.y);
glVertex2f(thisPoint.x, thisPoint.y);
lastPoint = thisPoint;
}
glVertex2f(lastPoint.x, lastPoint.y);
glVertex2f(firstPoint.x, firstPoint.y);
} else {
// iterate our children and call render on them.
for (int i = 0; i < CoverageMapV2::NUMBER_OF_CHILDREN; i++) {
CoverageMapV2* childMap = map->getChild(i);
if (childMap) {
renderCoverageMapsV2Recursively(childMap);
}
}
}
}
// render the coverage map on screen
void Application::renderCoverageMap() {
//printLog("renderCoverageMap()\n");
glDisable(GL_LIGHTING);
glLineWidth(2.0);
glBegin(GL_LINES);
glColor3f(0,0,1);
renderCoverageMapsRecursively(&_voxels.myCoverageMap);
glEnd();
glEnable(GL_LIGHTING);
}
void Application::renderCoverageMapsRecursively(CoverageMap* map) {
for (int i = 0; i < map->getPolygonCount(); i++) {
VoxelProjectedPolygon* polygon = map->getPolygon(i);
if (polygon->getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) {
glColor3f(.5,0,0); // dark red
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT)) {
glColor3f(.5,.5,0); // dark yellow
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT)) {
glColor3f(.5,.5,.5); // gray
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT | PROJECTION_BOTTOM)) {
glColor3f(.5,0,.5); // dark magenta
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM)) {
glColor3f(.75,0,0); // red
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP)) {
glColor3f(1,0,1); // magenta
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT | PROJECTION_TOP)) {
glColor3f(0,0,1); // Blue
} else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT | PROJECTION_TOP)) {
glColor3f(0,1,0); // green
} else if (polygon->getProjectionType() == (PROJECTION_NEAR)) {
glColor3f(1,1,0); // yellow
} else if (polygon->getProjectionType() == (PROJECTION_FAR | PROJECTION_RIGHT | PROJECTION_BOTTOM)) {
glColor3f(0,.5,.5); // dark cyan
} else {
glColor3f(1,0,0);
}
glm::vec2 firstPoint = getScaledScreenPoint(polygon->getVertex(0));
glm::vec2 lastPoint(firstPoint);
for (int i = 1; i < polygon->getVertexCount(); i++) {
glm::vec2 thisPoint = getScaledScreenPoint(polygon->getVertex(i));
glVertex2f(lastPoint.x, lastPoint.y);
glVertex2f(thisPoint.x, thisPoint.y);
lastPoint = thisPoint;
}
glVertex2f(lastPoint.x, lastPoint.y);
glVertex2f(firstPoint.x, firstPoint.y);
}
// iterate our children and call render on them.
for (int i = 0; i < CoverageMapV2::NUMBER_OF_CHILDREN; i++) {
CoverageMap* childMap = map->getChild(i);
if (childMap) {
renderCoverageMapsRecursively(childMap);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// renderViewFrustum()
//

View file

@ -131,13 +131,13 @@ private slots:
void doFalseRandomizeEveryOtherVoxelColors();
void doFalseColorizeByDistance();
void doFalseColorizeOccluded();
void doFalseColorizeOccludedV2();
void doFalseColorizeInView();
void doTrueVoxelColors();
void doTreeStats();
void setWantsMonochrome(bool wantsMonochrome);
void setWantsResIn(bool wantsResIn);
void setWantsDelta(bool wantsDelta);
void setWantsOcclusionCulling(bool wantsOcclusionCulling);
void disableOcclusionCulling(bool disableOcclusionCulling);
void updateVoxelModeActions();
void decreaseVoxelSize();
void increaseVoxelSize();
@ -153,6 +153,14 @@ private slots:
void copyVoxels();
void pasteVoxels();
void runTests();
void renderCoverageMap();
void renderCoverageMapsRecursively(CoverageMap* map);
void renderCoverageMapV2();
void renderCoverageMapsV2Recursively(CoverageMapV2* map);
glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint);
void goHome();
private:
@ -246,6 +254,9 @@ private:
QAction* _fullScreenMode; // whether we are in full screen mode
QAction* _frustumRenderModeAction;
QAction* _settingsAutosave; // Whether settings are saved automatically
QAction* _renderCoverageMapV2;
QAction* _renderCoverageMap;
BandwidthMeter _bandwidthMeter;
BandwidthDialog* _bandwidthDialog;

View file

@ -499,6 +499,20 @@ void runTimingTests() {
gettimeofday(&endTime, NULL);
elapsedMsecs = diffclock(&startTime, &endTime);
printLog("powf(f, 0.5) usecs: %f\n", 1000.0f * elapsedMsecs / (float) numTests);
// Vector Math
float distance;
glm::vec3 pointA(randVector()), pointB(randVector());
gettimeofday(&startTime, NULL);
for (int i = 1; i < numTests; i++) {
//glm::vec3 temp = pointA - pointB;
//float distanceSquared = glm::dot(temp, temp);
distance = glm::distance(pointA, pointB);
}
gettimeofday(&endTime, NULL);
elapsedMsecs = diffclock(&startTime, &endTime);
printLog("vector math usecs: %f [%f msecs total for %d tests]\n",
1000.0f * elapsedMsecs / (float) numTests, elapsedMsecs, numTests);
// Vec3 test
glm::vec3 vecA(randVector()), vecB(randVector());

View file

@ -23,6 +23,7 @@
#include "Log.h"
#include "VoxelConstants.h"
#include "CoverageMap.h"
#include "CoverageMapV2.h"
#include "InterfaceConfig.h"
#include "renderer/ProgramObject.h"
@ -1089,13 +1090,13 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
args.expectedMax = _voxelsInWriteArrays;
_tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);
printLog("_voxelsDirty=%s _voxelsInWriteArrays=%ld minDirty=%ld maxDirty=%ld \n", debug::valueOf(_voxelsDirty),
_voxelsInWriteArrays, minDirty, maxDirty);
printLog("stats: total %ld, leaves %ld, dirty %ld, colored %ld, shouldRender %ld, inVBO %ld\n",
printLog("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld \n",
args.totalNodes, args.leafNodes, args.dirtyNodes, args.coloredNodes, args.shouldRenderNodes);
printLog("inVBO %ld, nodesInVBOOverExpectedMax %ld, duplicateVBOIndex %ld, nodesInVBONotShouldRender %ld\n",
printLog(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld \n", debug::valueOf(_voxelsDirty),
_voxelsInWriteArrays, minDirty, maxDirty);
printLog(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld \n",
args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender);
glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN;
@ -1108,7 +1109,7 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
}
}
printLog("minInVBO=%ld maxInVBO=%ld _voxelsInWriteArrays=%ld _voxelsInReadArrays=%ld\n",
printLog(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld \n",
minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays);
}
@ -1162,6 +1163,7 @@ void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* dest
struct FalseColorizeOccludedArgs {
ViewFrustum* viewFrustum;
CoverageMap* map;
CoverageMapV2* mapV2;
VoxelTree* tree;
long totalVoxels;
long coloredVoxels;
@ -1181,9 +1183,11 @@ struct FalseColorizeSubTreeOperationArgs {
};
bool VoxelSystem::falseColorizeSubTreeOperation(VoxelNode* node, void* extraData) {
FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData;
node->setFalseColor(args->color[0], args->color[1], args->color[2]);
args->voxelsTouched++;
if (node->getShouldRender()) {
FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData;
node->setFalseColor(args->color[0], args->color[1], args->color[2]);
args->voxelsTouched++;
}
return true;
}
@ -1263,12 +1267,143 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat
}
return true; // keep going!
}
void VoxelSystem::falseColorizeOccluded() {
PerformanceWarning warn(true, "falseColorizeOccluded()",true);
CoverageMap map;
myCoverageMap.erase();
FalseColorizeOccludedArgs args;
args.viewFrustum = Application::getInstance()->getViewFrustum();
args.map = &map;
args.map = &myCoverageMap;
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;
VoxelProjectedPolygon::pointInside_calls = 0;
VoxelProjectedPolygon::occludes_calls = 0;
VoxelProjectedPolygon::intersects_calls = 0;
glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE);
_tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args);
printLog("falseColorizeOccluded()\n position=(%f,%f)\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 pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n",
position.x, position.y,
args.totalVoxels, args.coloredVoxels, args.occludedVoxels,
args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped,
args.stagedForDeletion,
args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded,
VoxelProjectedPolygon::pointInside_calls,
VoxelProjectedPolygon::occludes_calls,
VoxelProjectedPolygon::intersects_calls
);
//myCoverageMap.erase();
setupNewVoxelsForDrawing();
}
bool VoxelSystem::falseColorizeOccludedV2Operation(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* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox));
// If we're not all in view, then ignore it, and just return. But keep searching...
if (!voxelPolygon->getAllInView()) {
args->nonLeavesOutOfView++;
delete voxelPolygon;
return true;
}
CoverageMapV2StorageResult result = args->mapV2->checkMap(voxelPolygon, false);
if (result == V2_OCCLUDED) {
args->nonLeavesOccluded++;
delete voxelPolygon;
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 voxelPolygon;
return true; // keep looking...
}
if (node->isLeaf() && node->isColored() && node->getShouldRender()) {
args->coloredVoxels++;
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox));
// If we're not all in view, then ignore it, and just return. But keep searching...
if (!voxelPolygon->getAllInView()) {
args->outOfView++;
delete voxelPolygon;
return true;
}
CoverageMapV2StorageResult result = args->mapV2->checkMap(voxelPolygon, true);
if (result == V2_OCCLUDED) {
node->setFalseColor(255, 0, 0);
args->occludedVoxels++;
} else if (result == V2_STORED) {
args->notOccludedVoxels++;
//printLog("***** falseColorizeOccludedOperation() NODE is STORED *****\n");
} else if (result == V2_DOESNT_FIT) {
//printLog("***** falseColorizeOccludedOperation() NODE DOESNT_FIT???? *****\n");
}
delete voxelPolygon; // V2 maps don't store polygons, so we're always in charge of freeing
}
return true; // keep going!
}
void VoxelSystem::falseColorizeOccludedV2() {
PerformanceWarning warn(true, "falseColorizeOccludedV2()",true);
myCoverageMapV2.erase();
CoverageMapV2::wantDebugging = true;
VoxelProjectedPolygon::pointInside_calls = 0;
VoxelProjectedPolygon::occludes_calls = 0;
VoxelProjectedPolygon::intersects_calls = 0;
FalseColorizeOccludedArgs args;
args.viewFrustum = Application::getInstance()->getViewFrustum();
args.mapV2 = &myCoverageMapV2;
args.totalVoxels = 0;
args.coloredVoxels = 0;
args.occludedVoxels = 0;
@ -1283,15 +1418,22 @@ void VoxelSystem::falseColorizeOccluded() {
glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE);
_tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args);
_tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedV2Operation, 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",
printLog("falseColorizeOccludedV2()\n position=(%f,%f)\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 pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n",
position.x, position.y,
args.totalVoxels, args.coloredVoxels, args.occludedVoxels,
args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped,
args.stagedForDeletion,
args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded);
args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded,
VoxelProjectedPolygon::pointInside_calls,
VoxelProjectedPolygon::occludes_calls,
VoxelProjectedPolygon::intersects_calls
);
//myCoverageMapV2.erase();
setupNewVoxelsForDrawing();
}

View file

@ -11,11 +11,15 @@
#include "InterfaceConfig.h"
#include <glm/glm.hpp>
#include <SharedUtil.h>
#include <UDPSocket.h>
#include <CoverageMapV2.h>
#include <NodeData.h>
#include <VoxelTree.h>
#include <ViewFrustum.h>
#include <VoxelTree.h>
#include "Camera.h"
#include "Util.h"
#include "world.h"
@ -57,6 +61,7 @@ public:
void falseColorizeDistanceFromView(ViewFrustum* viewFrustum);
void falseColorizeRandomEveryOther();
void falseColorizeOccluded();
void falseColorizeOccludedV2();
void killLocalVoxels();
void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; };
@ -84,6 +89,9 @@ public:
void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot);
void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode);
CoverageMapV2 myCoverageMapV2;
CoverageMap myCoverageMap;
protected:
float _treeScale;
@ -123,6 +131,8 @@ private:
static bool collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* extraData);
static bool falseColorizeOccludedOperation(VoxelNode* node, void* extraData);
static bool falseColorizeSubTreeOperation(VoxelNode* node, void* extraData);
static bool falseColorizeOccludedV2Operation(VoxelNode* node, void* extraData);
int updateNodeInArraysAsFullVBO(VoxelNode* node);
int updateNodeInArraysAsPartialVBO(VoxelNode* node);

View file

@ -32,10 +32,9 @@ AvatarData::AvatarData(Node* owningNode) :
_cameraNearClip(0.0f),
_cameraFarClip(0.0f),
_keyState(NO_KEY_DOWN),
_wantResIn(false),
_wantColor(true),
_wantDelta(false),
_wantOcclusionCulling(false),
_wantOcclusionCulling(true),
_headData(NULL),
_handData(NULL)
{
@ -113,7 +112,6 @@ 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 (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); }
@ -240,7 +238,6 @@ 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);
_wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT);

View file

@ -20,7 +20,7 @@
#include "HeadData.h"
#include "HandData.h"
const int WANT_RESIN_AT_BIT = 0;
const int UNUSED_BIT = 0; // this bit is available to use
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
@ -91,11 +91,9 @@ public:
const std::string& chatMessage () const { return _chatMessage; }
// related to Voxel Sending strategies
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; }
@ -130,7 +128,6 @@ protected:
std::string _chatMessage;
// voxel server sending items
bool _wantResIn;
bool _wantColor;
bool _wantDelta;
bool _wantOcclusionCulling;

View file

@ -110,12 +110,12 @@ PerformanceWarning::~PerformanceWarning() {
if ((_alwaysDisplay || _renderWarningsOn) && elapsedmsec > 1) {
if (elapsedmsec > 1000) {
double elapsedsec = (end - _start) / 1000000.0;
printLog("WARNING! %s took %lf seconds\n", _message, elapsedsec);
printLog("%s%s took %lf seconds\n", (_alwaysDisplay ? "" : "WARNING!"), _message, elapsedsec);
} else {
printLog("WARNING! %s took %lf milliseconds\n", _message, elapsedmsec);
printLog("%s%s took %lf milliseconds\n", (_alwaysDisplay ? "" : "WARNING!"), _message, elapsedmsec);
}
} else if (_alwaysDisplay) {
printLog("WARNING! %s took %lf milliseconds\n", _message, elapsedmsec);
printLog("%s took %lf milliseconds\n", _message, elapsedmsec);
}
};

View file

@ -440,3 +440,25 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex,
return -1; // error case
}
int removeFromSortedArrays(void* value, void** valueArray, float* keyArray, int* originalIndexArray,
int currentCount, int maxCount) {
int i = 0;
if (currentCount > 0) {
while (i < currentCount && value != valueArray[i]) {
i++;
}
if (value == valueArray[i] && i < currentCount) {
// i is the location of the item we were looking for
// shift array elements to the left
memmove(&valueArray[i], &valueArray[i + 1], sizeof(void*) * ((currentCount-1) - i));
memmove(&keyArray[i], &keyArray[i + 1], sizeof(float) * ((currentCount-1) - i));
if (originalIndexArray) {
memmove(&originalIndexArray[i], &originalIndexArray[i + 1], sizeof(int) * ((currentCount-1) - i));
}
return currentCount-1;
}
}
return -1; // error case
}

View file

@ -88,6 +88,11 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex,
void** valueArray, float* keyArray, int* originalIndexArray,
int currentCount, int maxCount);
int removeFromSortedArrays(void* value, void** valueArray, float* keyArray, int* originalIndexArray,
int currentCount, int maxCount);
// Helper Class for debugging
class debug {
public:

View file

@ -13,9 +13,12 @@
int CoverageMap::_mapCount = 0;
int CoverageMap::_checkMapRootCalls = 0;
int CoverageMap::_notAllInView = 0;
bool CoverageMap::wantDebugging = false;
const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-2.f,-2.f), glm::vec2(4.f,4.f));
const int MAX_POLYGONS_PER_REGION = 50;
const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.f,-1.f), glm::vec2(2.f,2.f));
// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space.
//
@ -64,6 +67,22 @@ CoverageMap::~CoverageMap() {
erase();
};
void CoverageMap::printStats() {
printLog("CoverageMap::printStats()...\n");
printLog("MINIMUM_POLYGON_AREA_TO_STORE=%f\n",MINIMUM_POLYGON_AREA_TO_STORE);
printLog("_mapCount=%d\n",_mapCount);
printLog("_checkMapRootCalls=%d\n",_checkMapRootCalls);
printLog("_notAllInView=%d\n",_notAllInView);
printLog("_maxPolygonsUsed=%d\n",CoverageRegion::_maxPolygonsUsed);
printLog("_totalPolygons=%d\n",CoverageRegion::_totalPolygons);
printLog("_occlusionTests=%d\n",CoverageRegion::_occlusionTests);
printLog("_regionSkips=%d\n",CoverageRegion::_regionSkips);
printLog("_tooSmallSkips=%d\n",CoverageRegion::_tooSmallSkips);
printLog("_regionFullSkips=%d\n",CoverageRegion::_regionFullSkips);
printLog("_outOfOrderPolygon=%d\n",CoverageRegion::_outOfOrderPolygon);
printLog("_clippedPolygons=%d\n",CoverageRegion::_clippedPolygons);
}
void CoverageMap::erase() {
// tell our regions to erase()
_topHalf.erase();
@ -81,19 +100,19 @@ void CoverageMap::erase() {
if (_isRoot && wantDebugging) {
printLog("CoverageMap last to be deleted...\n");
printLog("MINIMUM_POLYGON_AREA_TO_STORE=%f\n",MINIMUM_POLYGON_AREA_TO_STORE);
printLog("_mapCount=%d\n",_mapCount);
printLog("_checkMapRootCalls=%d\n",_checkMapRootCalls);
printLog("_maxPolygonsUsed=%d\n",CoverageRegion::_maxPolygonsUsed);
printLog("_totalPolygons=%d\n",CoverageRegion::_totalPolygons);
printLog("_occlusionTests=%d\n",CoverageRegion::_occlusionTests);
printLog("_outOfOrderPolygon=%d\n",CoverageRegion::_outOfOrderPolygon);
printStats();
CoverageRegion::_maxPolygonsUsed = 0;
CoverageRegion::_totalPolygons = 0;
CoverageRegion::_occlusionTests = 0;
CoverageRegion::_regionSkips = 0;
CoverageRegion::_tooSmallSkips = 0;
CoverageRegion::_regionFullSkips = 0;
CoverageRegion::_outOfOrderPolygon = 0;
CoverageRegion::_clippedPolygons = 0;
_mapCount = 0;
_checkMapRootCalls = 0;
_notAllInView = 0;
}
}
@ -121,16 +140,60 @@ BoundingBox CoverageMap::getChildBoundingBox(int childIndex) {
return result;
}
int CoverageMap::getPolygonCount() const {
return (_topHalf.getPolygonCount() +
_bottomHalf.getPolygonCount() +
_leftHalf.getPolygonCount() +
_rightHalf.getPolygonCount() +
_remainder.getPolygonCount());
}
VoxelProjectedPolygon* CoverageMap::getPolygon(int index) const {
int base = 0;
if ((index - base) < _topHalf.getPolygonCount()) {
return _topHalf.getPolygon((index - base));
}
base += _topHalf.getPolygonCount();
if ((index - base) < _bottomHalf.getPolygonCount()) {
return _bottomHalf.getPolygon((index - base));
}
base += _bottomHalf.getPolygonCount();
if ((index - base) < _leftHalf.getPolygonCount()) {
return _leftHalf.getPolygon((index - base));
}
base += _leftHalf.getPolygonCount();
if ((index - base) < _rightHalf.getPolygonCount()) {
return _rightHalf.getPolygon((index - base));
}
base += _rightHalf.getPolygonCount();
if ((index - base) < _remainder.getPolygonCount()) {
return _remainder.getPolygon((index - base));
}
return NULL;
}
// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT
CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) {
if (_isRoot) {
_checkMapRootCalls++;
//printLog("CoverageMap::checkMap()... storeIt=%s\n", debug::valueOf(storeIt));
//polygon->printDebugDetails();
}
// short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is
// not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later.
if (!polygon->getAllInView()) {
_notAllInView++;
//printLog("CoverageMap2::checkMap()... V2_OCCLUDED\n");
return DOESNT_FIT;
}
@ -142,22 +205,25 @@ CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, b
bool fitsInAHalf = false;
// Check each half of the box independently
if (_topHalf.contains(polygonBox)) {
result = _topHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_topHalf;
fitsInAHalf = true;
} else if (_bottomHalf.contains(polygonBox)) {
result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_bottomHalf;
fitsInAHalf = true;
} else if (_leftHalf.contains(polygonBox)) {
result = _leftHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_leftHalf;
fitsInAHalf = true;
} else if (_rightHalf.contains(polygonBox)) {
result = _rightHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_rightHalf;
fitsInAHalf = true;
const bool useRegions = true; // for now we will continue to use regions
if (useRegions) {
if (_topHalf.contains(polygonBox)) {
result = _topHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_topHalf;
fitsInAHalf = true;
} else if (_bottomHalf.contains(polygonBox)) {
result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_bottomHalf;
fitsInAHalf = true;
} else if (_leftHalf.contains(polygonBox)) {
result = _leftHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_leftHalf;
fitsInAHalf = true;
} else if (_rightHalf.contains(polygonBox)) {
result = _rightHalf.checkRegion(polygon, polygonBox, storeIt);
storeIn = &_rightHalf;
fitsInAHalf = true;
}
}
// if we got this far, there are one of two possibilities, either a polygon doesn't fit
@ -171,36 +237,77 @@ CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, b
// It's possible that this first set of checks might have resulted in an out of order polygon
// in which case we just return..
if (result == STORED || result == OCCLUDED) {
/*
if (result == STORED)
printLog("CoverageMap2::checkMap()... STORED\n");
else
printLog("CoverageMap2::checkMap()... OCCLUDED\n");
*/
return result;
}
// 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);
const bool useChildMaps = true; // for now we will continue to use child maps
if (useChildMaps) {
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);
}
result = _childMaps[i]->checkMap(polygon, storeIt);
/*
switch (result) {
case STORED:
printLog("checkMap() = STORED\n");
break;
case NOT_STORED:
printLog("checkMap() = NOT_STORED\n");
break;
case OCCLUDED:
printLog("checkMap() = OCCLUDED\n");
break;
default:
printLog("checkMap() = ????? \n");
break;
}
*/
return result;
}
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) {
if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) {
//printLog("storing polygon of area: %f\n",polygon->getBoundingBox().area());
storeIn->storeInArray(polygon);
return STORED;
//printLog("storing polygon of area: %f\n",polygon->getBoundingBox().area());
if (storeIn->getPolygonCount() < MAX_POLYGONS_PER_REGION) {
storeIn->storeInArray(polygon);
//printLog("CoverageMap2::checkMap()... STORED\n");
return STORED;
} else {
CoverageRegion::_regionFullSkips++;
//printLog("CoverageMap2::checkMap()... NOT_STORED\n");
return NOT_STORED;
}
} else {
CoverageRegion::_tooSmallSkips++;
//printLog("CoverageMap2::checkMap()... NOT_STORED\n");
return NOT_STORED;
}
} else {
//printLog("CoverageMap2::checkMap()... NOT_STORED\n");
return NOT_STORED;
}
}
//printLog("CoverageMap2::checkMap()... DOESNT_FIT\n");
return DOESNT_FIT;
}
@ -223,12 +330,13 @@ void CoverageRegion::init() {
_polygonArraySize = 0;
_polygons = NULL;
_polygonDistances = NULL;
_polygonSizes = NULL;
}
void CoverageRegion::erase() {
/*
/**
if (_polygonCount) {
printLog("CoverageRegion::erase()...\n");
printLog("_polygonCount=%d\n",_polygonCount);
@ -238,7 +346,7 @@ void CoverageRegion::erase() {
// _polygons[i]->getBoundingBox().printDebugDetails();
//}
}
*/
**/
// If we're in charge of managing the polygons, then clean them up first
if (_managePolygons) {
for (int i = 0; i < _polygonCount; i++) {
@ -258,11 +366,16 @@ void CoverageRegion::erase() {
delete[] _polygonDistances;
_polygonDistances = NULL;
}
if (_polygonSizes) {
delete[] _polygonSizes;
_polygonSizes = NULL;
}
}
void CoverageRegion::growPolygonArray() {
VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE];
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
float* newSizes = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
if (_polygons) {
@ -270,9 +383,12 @@ void CoverageRegion::growPolygonArray() {
delete[] _polygons;
memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount);
delete[] _polygonDistances;
memcpy(newSizes, _polygonSizes, sizeof(float) * _polygonCount);
delete[] _polygonSizes;
}
_polygons = newPolygons;
_polygons = newPolygons;
_polygonDistances = newDistances;
_polygonSizes = newSizes;
_polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE;
//printLog("CoverageMap::growPolygonArray() _polygonArraySize=%d...\n",_polygonArraySize);
}
@ -297,26 +413,87 @@ const char* CoverageRegion::getRegionName() const {
int CoverageRegion::_maxPolygonsUsed = 0;
int CoverageRegion::_totalPolygons = 0;
int CoverageRegion::_occlusionTests = 0;
int CoverageRegion::_regionSkips = 0;
int CoverageRegion::_tooSmallSkips = 0;
int CoverageRegion::_regionFullSkips = 0;
int CoverageRegion::_outOfOrderPolygon = 0;
int CoverageRegion::_clippedPolygons = 0;
bool CoverageRegion::mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInArray) {
for (int i = 0; i < _polygonCount; i++) {
VoxelProjectedPolygon* otherPolygon = _polygons[i];
if (otherPolygon->canMerge(*seed)) {
otherPolygon->merge(*seed);
if (seedInArray) {
const int IGNORED = NULL;
// remove this otherOtherPolygon for our polygon array
_polygonCount = removeFromSortedArrays((void*)seed,
(void**)_polygons, _polygonDistances, IGNORED,
_polygonCount, _polygonArraySize);
_totalPolygons--;
}
//printLog("_polygonCount=%d\n",_polygonCount);
// clean up
if (_managePolygons) {
delete seed;
}
// Now run again using our newly merged polygon as the seed
mergeItemsInArray(otherPolygon, true);
return true;
}
}
return false;
}
// just handles storage in the array, doesn't test for occlusion or
// determining if this is the correct map to store in!
void CoverageRegion::storeInArray(VoxelProjectedPolygon* polygon) {
_currentCoveredBounds.explandToInclude(polygon->getBoundingBox());
// Before we actually store this polygon in the array, check to see if this polygon can be merged to any of the existing
// polygons already in our array.
if (mergeItemsInArray(polygon, false)) {
return; // exit early
}
// only after we attempt to merge!
_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
// As an experiment we're going to see if we get an improvement by storing the polygons in coverage area sorted order
// this means the bigger polygons are earlier in the array. We should have a higher probability of being occluded earlier
// in the list. We still check to see if the polygon is "in front" of the target polygon before we test occlusion. Since
// sometimes things come out of order.
const bool SORT_BY_SIZE = false;
const int IGNORED = NULL;
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
(void**)_polygons, _polygonDistances, IGNORED,
_polygonCount, _polygonArraySize);
if (SORT_BY_SIZE) {
// 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
float area = polygon->getBoundingBox().area();
float reverseArea = 4.0f - area;
//printLog("store by size area=%f reverse area=%f\n", area, reverseArea);
_polygonCount = insertIntoSortedArrays((void*)polygon, reverseArea, IGNORED,
(void**)_polygons, _polygonSizes, IGNORED,
_polygonCount, _polygonArraySize);
} else {
const int IGNORED = NULL;
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
(void**)_polygons, _polygonDistances, IGNORED,
_polygonCount, _polygonArraySize);
}
// Debugging and Optimization Tuning code.
if (_polygonCount > _maxPolygonsUsed) {
_maxPolygonsUsed = _polygonCount;
@ -335,37 +512,47 @@ CoverageMapStorageResult CoverageRegion::checkRegion(VoxelProjectedPolygon* poly
if (_isRoot || _myBoundingBox.contains(polygonBox)) {
result = NOT_STORED; // if we got here, then we DO fit...
// only actually check the polygons if this polygon is in the covered bounds for this region
if (!_currentCoveredBounds.contains(polygonBox)) {
_regionSkips += _polygonCount;
} else {
// 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 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.
_occlusionTests++;
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()) {
_outOfOrderPolygon++;
if (storeIt) {
if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) {
//printLog("storing polygon of area: %f\n",polygon->getBoundingBox().area());
storeInArray(polygon);
return STORED;
// 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.
_occlusionTests++;
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()) {
_outOfOrderPolygon++;
if (storeIt) {
if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) {
if (getPolygonCount() < MAX_POLYGONS_PER_REGION) {
storeInArray(polygon);
return STORED;
} else {
CoverageRegion::_regionFullSkips++;
return NOT_STORED;
}
} else {
_tooSmallSkips++;
return NOT_STORED;
}
} else {
return NOT_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;
}
// this polygon is occluded by a closer polygon, so don't store it, and let the caller know
return OCCLUDED;
}
}
}

View file

@ -31,24 +31,38 @@ public:
static int _maxPolygonsUsed;
static int _totalPolygons;
static int _occlusionTests;
static int _regionSkips;
static int _tooSmallSkips;
static int _regionFullSkips;
static int _outOfOrderPolygon;
static int _clippedPolygons;
const char* getRegionName() const;
int getPolygonCount() const { return _polygonCount; };
VoxelProjectedPolygon* getPolygon(int index) const { return _polygons[index]; };
private:
void init();
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
BoundingBox _myBoundingBox;
BoundingBox _currentCoveredBounds; // area in this region currently covered by some polygon
bool _managePolygons; // will the coverage map delete the polygons on destruct
RegionName _regionName;
int _polygonCount; // how many polygons at this level
int _polygonArraySize; // how much room is there to store polygons at this level
VoxelProjectedPolygon** _polygons;
// we will use one or the other of these depending on settings in the code.
float* _polygonDistances;
float* _polygonSizes;
void growPolygonArray();
static const int DEFAULT_GROW_SIZE = 100;
bool mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInArray);
};
class CoverageMap {
@ -68,9 +82,14 @@ public:
BoundingBox getChildBoundingBox(int childIndex);
void erase(); // erase the coverage map
void printStats();
static bool wantDebugging;
int getPolygonCount() const;
VoxelProjectedPolygon* getPolygon(int index) const;
CoverageMap* getChild(int childIndex) const { return _childMaps[childIndex]; };
private:
void init();
@ -89,6 +108,7 @@ private:
static int _mapCount;
static int _checkMapRootCalls;
static int _notAllInView;
};

View file

@ -0,0 +1,246 @@
//
// CoverageMapV2.cpp -
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <algorithm>
#include "CoverageMapV2.h"
#include <SharedUtil.h>
#include <cstring>
#include "Log.h"
int CoverageMapV2::_mapCount = 0;
int CoverageMapV2::_checkMapRootCalls = 0;
int CoverageMapV2::_notAllInView = 0;
bool CoverageMapV2::wantDebugging = false;
const BoundingBox CoverageMapV2::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.f,-1.f), glm::vec2(2.f,2.f));
// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space.
//
// (0,0) (windowWidth, 0)
// -1,1 1,1
// +-----------------------+
// | | |
// | | |
// | -1,0 | |
// |-----------+-----------|
// | 0,0 |
// | | |
// | | |
// | | |
// +-----------------------+
// -1,-1 1,-1
// (0,windowHeight) (windowWidth,windowHeight)
//
// Choosing a minimum sized polygon. Since we know a typical window is approximately 1500 pixels wide
// then a pixel on our screen will be ~ 2.0/1500 or 0.0013 "units" wide, similarly pixels are typically
// about that tall as well. If we say that polygons should be at least 10x10 pixels to be considered "big enough"
// then we can calculate a reasonable polygon area
const int TYPICAL_SCREEN_WIDTH_IN_PIXELS = 1500;
const int MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS = 10;
const float TYPICAL_SCREEN_PIXEL_WIDTH = (2.0f / TYPICAL_SCREEN_WIDTH_IN_PIXELS);
const float CoverageMapV2::MINIMUM_POLYGON_AREA_TO_STORE = (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS) *
(TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS);
const float CoverageMapV2::NOT_COVERED = FLT_MAX;
const float CoverageMapV2::MINIMUM_OCCLUSION_CHECK_AREA = MINIMUM_POLYGON_AREA_TO_STORE/10.0f; // one quarter the size of poly
CoverageMapV2::CoverageMapV2(BoundingBox boundingBox, bool isRoot, bool isCovered, float coverageDistance) :
_isRoot(isRoot),
_myBoundingBox(boundingBox),
_isCovered(isCovered),
_coveredDistance(coverageDistance)
{
_mapCount++;
init();
//printLog("CoverageMapV2 created... _mapCount=%d\n",_mapCount);
};
CoverageMapV2::~CoverageMapV2() {
erase();
};
void CoverageMapV2::erase() {
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
if (_childMaps[i]) {
delete _childMaps[i];
_childMaps[i] = NULL;
}
}
if (_isRoot && wantDebugging) {
printLog("CoverageMapV2 last to be deleted...\n");
printLog("MINIMUM_POLYGON_AREA_TO_STORE=%f\n",MINIMUM_POLYGON_AREA_TO_STORE);
printLog("_mapCount=%d\n",_mapCount);
printLog("_checkMapRootCalls=%d\n",_checkMapRootCalls);
printLog("_notAllInView=%d\n",_notAllInView);
_mapCount = 0;
_checkMapRootCalls = 0;
_notAllInView = 0;
}
}
void CoverageMapV2::init() {
memset(_childMaps,0,sizeof(_childMaps));
}
// 0 = bottom, left
// 1 = bottom, right
// 2 = top, left
// 3 = top, right
BoundingBox CoverageMapV2::getChildBoundingBox(int childIndex) {
const int RIGHT_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 "right" bit is set, then add size.x to the corner
if ((childIndex & RIGHT_BIT) == RIGHT_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;
}
// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT
CoverageMapV2StorageResult CoverageMapV2::checkMap(const VoxelProjectedPolygon* polygon, bool storeIt) {
assert(_isRoot); // you can only call this on the root map!!!
_checkMapRootCalls++;
// short circuit: if we're the root node (only case we're here), and we're covered, and this polygon is deeper than our
// covered depth, then this polygon is occluded!
if (_isCovered && _coveredDistance < polygon->getDistance()) {
return V2_OCCLUDED;
}
// short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is
// not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later.
if (!polygon->getAllInView()) {
_notAllInView++;
return V2_DOESNT_FIT;
}
// Here's where we recursively check the polygon against the coverage map. We need to maintain two pieces of state.
// The first state is: have we seen at least one "fully occluded" map items. If we haven't then we don't track the covered
// state of the polygon.
// The second piece of state is: Are all of our "fully occluded" map items "covered". If even one of these occluded map
// items is not covered, then our polygon is not covered.
bool seenOccludedMapNodes = false;
bool allOccludedMapNodesCovered = false;
recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered);
// Ok, no matter how we were called, if all our occluded map nodes are covered, then we know this polygon
// is occluded, otherwise, we will report back to the caller about whether or not we stored the polygon
if (allOccludedMapNodesCovered) {
return V2_OCCLUDED;
}
if (storeIt) {
return V2_STORED; // otherwise report that we STORED it
}
return V2_NOT_STORED; // unless we weren't asked to store it, then we didn't
}
void CoverageMapV2::recurseMap(const VoxelProjectedPolygon* polygon, bool storeIt,
bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered) {
// if we are really small, then we act like we don't intersect, this allows us to stop
// recusing as we get to the smalles edge of the polygon
if (_myBoundingBox.area() < MINIMUM_OCCLUSION_CHECK_AREA) {
return; // stop recursion, we're done!
}
// Determine if this map node intersects the polygon and/or is fully covered by the polygon
// There are a couple special cases: If we're the root, we are assumed to intersect with all
// polygons. Also, any map node that is fully occluded also intersects.
bool nodeIsCoveredByPolygon = polygon->occludes(_myBoundingBox);
bool nodeIsIntersectedByPolygon = nodeIsCoveredByPolygon || _isRoot || polygon->intersects(_myBoundingBox);
// If we don't intersect, then we can just return, we're done recursing
if (!nodeIsIntersectedByPolygon) {
return; // stop recursion, we're done!
}
// At this point, we know our node intersects with the polygon. If this node is covered, then we want to treat it
// as if the node was fully covered, because this allows us to short circuit further recursion...
if (_isCovered && _coveredDistance < polygon->getDistance()) {
nodeIsCoveredByPolygon = true; // fake it till you make it
}
// If this node in the map is fully covered by our polygon, then we don't need to recurse any further, but
// we do need to do some bookkeeping.
if (nodeIsCoveredByPolygon) {
// If this is the very first fully covered node we've seen, then we're initialize our allOccludedMapNodesCovered
// to be our current covered state. This has the following effect: if this node isn't already covered, then by
// definition, we know that at least one node for this polygon isn't covered, and therefore we aren't fully covered.
if (!seenOccludedMapNodes) {
allOccludedMapNodesCovered = (_isCovered && _coveredDistance < polygon->getDistance());
// We need to mark that we've seen at least one node of our polygon! ;)
seenOccludedMapNodes = true;
} else {
// If this is our second or later node of our polygon, then we need to track our allOccludedMapNodesCovered state
allOccludedMapNodesCovered = allOccludedMapNodesCovered &&
(_isCovered && _coveredDistance < polygon->getDistance());
}
// if we're in store mode then we want to record that this node is covered.
if (storeIt) {
_isCovered = true;
// store the minimum distance of our previous known distance, or our current polygon's distance. This is because
// we know that we're at least covered at this distance, but if we had previously identified that we're covered
// at a shallower distance, then we want to maintain that distance
_coveredDistance = std::min(polygon->getDistance(), _coveredDistance);
// Note: this might be a good chance to delete child maps, but we're not going to do that at this point because
// we're trying to maintain the known distances in the lower portion of the tree.
}
// and since this node of the quad map is covered, we can safely stop recursion. because we know all smaller map
// nodes will also be covered.
return;
}
// If we got here, then it means we know that this node is not fully covered by the polygon, but it does intersect
// with the polygon.
// Another case is that we aren't yet marked as covered, and so we should recurse and process smaller quad tree nodes.
// Note: we use this to determine if we can collapse the child quad trees and mark this node as covered
bool allChildrenOccluded = true;
float maxChildCoveredDepth = NOT_COVERED;
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
BoundingBox childMapBoundingBox = getChildBoundingBox(i);
// if no child map exists yet, then create it
if (!_childMaps[i]) {
// children get created with the coverage state of their parent.
_childMaps[i] = new CoverageMapV2(childMapBoundingBox, NOT_ROOT, _isCovered, _coveredDistance);
}
_childMaps[i]->recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered);
// if so far, all of our children are covered, then record our furthest coverage distance
if (allChildrenOccluded && _childMaps[i]->_isCovered) {
maxChildCoveredDepth = std::max(maxChildCoveredDepth, _childMaps[i]->_coveredDistance);
} else {
// otherwise, at least one of our children is not covered, so not all are covered
allChildrenOccluded = false;
}
}
// if all the children are covered, this makes our quad tree "shallower" because it records that
// entire quad is covered, it uses the "furthest" z-order so that if a shalower polygon comes through
// we won't assume its occluded
if (allChildrenOccluded && storeIt) {
_isCovered = true;
_coveredDistance = maxChildCoveredDepth;
}
// normal exit case... return...
}

View file

@ -0,0 +1,69 @@
//
// CoverageMapV2.h - 2D CoverageMapV2 Quad tree for storage of VoxelProjectedPolygons
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef _COVERAGE_MAP_V2_
#define _COVERAGE_MAP_V2_
#include <glm/glm.hpp>
#include "VoxelProjectedPolygon.h"
typedef enum {
V2_DOESNT_FIT, V2_STORED, V2_NOT_STORED,
V2_INTERSECT, V2_NO_INTERSECT,
V2_OCCLUDED, V2_NOT_OCCLUDED
} CoverageMapV2StorageResult;
class CoverageMapV2 {
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;
static const float MINIMUM_POLYGON_AREA_TO_STORE;
static const float NOT_COVERED;
static const float MINIMUM_OCCLUSION_CHECK_AREA;
static bool wantDebugging;
CoverageMapV2(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT,
bool isCovered = false, float coverageDistance = NOT_COVERED);
~CoverageMapV2();
CoverageMapV2StorageResult checkMap(const VoxelProjectedPolygon* polygon, bool storeIt = true);
BoundingBox getChildBoundingBox(int childIndex);
const BoundingBox& getBoundingBox() const { return _myBoundingBox; };
CoverageMapV2* getChild(int childIndex) const { return _childMaps[childIndex]; };
bool isCovered() const { return _isCovered; };
void erase(); // erase the coverage map
void render();
private:
void recurseMap(const VoxelProjectedPolygon* polygon, bool storeIt,
bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered);
void init();
bool _isRoot;
BoundingBox _myBoundingBox;
CoverageMapV2* _childMaps[NUMBER_OF_CHILDREN];
bool _isCovered;
float _coveredDistance;
static int _mapCount;
static int _checkMapRootCalls;
static int _notAllInView;
};
#endif // _COVERAGE_MAP_V2_

View file

@ -5,6 +5,9 @@
// Created by Andrzej Kapolka on 5/21/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
#include <cstring>
#include <Log.h>
#include <SharedUtil.h>
#include "GeometryUtil.h"
@ -117,6 +120,7 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
}
// Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect?
// from: http://ptspts.blogspot.com/2010/06/how-to-determine-if-two-line-segments.html
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);
@ -140,3 +144,193 @@ int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk)
float b = (xj - xi) * (yk - yi);
return a < b ? -1 : a > b ? 1 : 0;
}
//
// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html
//
// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space.
//
// (0,0) (windowWidth, 0)
// -1,1 1,1
// +-----------------------+
// | | |
// | | |
// | -1,0 | |
// |-----------+-----------|
// | 0,0 |
// | | |
// | | |
// | | |
// +-----------------------+
// -1,-1 1,-1
// (0,windowHeight) (windowWidth,windowHeight)
//
const float PolygonClip::TOP_OF_CLIPPING_WINDOW = 1.0f;
const float PolygonClip::BOTTOM_OF_CLIPPING_WINDOW = -1.0f;
const float PolygonClip::LEFT_OF_CLIPPING_WINDOW = -1.0f;
const float PolygonClip::RIGHT_OF_CLIPPING_WINDOW = 1.0f;
const glm::vec2 PolygonClip::TOP_LEFT_CLIPPING_WINDOW ( LEFT_OF_CLIPPING_WINDOW , TOP_OF_CLIPPING_WINDOW );
const glm::vec2 PolygonClip::TOP_RIGHT_CLIPPING_WINDOW ( RIGHT_OF_CLIPPING_WINDOW, TOP_OF_CLIPPING_WINDOW );
const glm::vec2 PolygonClip::BOTTOM_LEFT_CLIPPING_WINDOW ( LEFT_OF_CLIPPING_WINDOW , BOTTOM_OF_CLIPPING_WINDOW );
const glm::vec2 PolygonClip::BOTTOM_RIGHT_CLIPPING_WINDOW ( RIGHT_OF_CLIPPING_WINDOW, BOTTOM_OF_CLIPPING_WINDOW );
void PolygonClip::clipToScreen(const glm::vec2* inputVertexArray, int inLength, glm::vec2*& outputVertexArray, int& outLength) {
int tempLengthA = inLength;
int tempLengthB;
int maxLength = inLength * 2;
glm::vec2* tempVertexArrayA = new glm::vec2[maxLength];
glm::vec2* tempVertexArrayB = new glm::vec2[maxLength];
// set up our temporary arrays
memcpy(tempVertexArrayA, inputVertexArray, sizeof(glm::vec2) * inLength);
// Left edge
LineSegment2 edge;
edge[0] = TOP_LEFT_CLIPPING_WINDOW;
edge[1] = BOTTOM_LEFT_CLIPPING_WINDOW;
// clip the array from tempVertexArrayA and copy end result to tempVertexArrayB
sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge);
// clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA
copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB);
// Bottom Edge
edge[0] = BOTTOM_LEFT_CLIPPING_WINDOW;
edge[1] = BOTTOM_RIGHT_CLIPPING_WINDOW;
// clip the array from tempVertexArrayA and copy end result to tempVertexArrayB
sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge);
// clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA
copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB);
// Right Edge
edge[0] = BOTTOM_RIGHT_CLIPPING_WINDOW;
edge[1] = TOP_RIGHT_CLIPPING_WINDOW;
// clip the array from tempVertexArrayA and copy end result to tempVertexArrayB
sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge);
// clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA
copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB);
// Top Edge
edge[0] = TOP_RIGHT_CLIPPING_WINDOW;
edge[1] = TOP_LEFT_CLIPPING_WINDOW;
// clip the array from tempVertexArrayA and copy end result to tempVertexArrayB
sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge);
// clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA
copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB);
// copy final output to outputVertexArray
outputVertexArray = tempVertexArrayA;
outLength = tempLengthA;
// cleanup our unused temporary buffer...
delete[] tempVertexArrayB;
// Note: we don't delete tempVertexArrayA, because that's the caller's responsibility
}
void PolygonClip::sutherlandHodgmanPolygonClip(glm::vec2* inVertexArray, glm::vec2* outVertexArray,
int inLength, int& outLength, const LineSegment2& clipBoundary) {
glm::vec2 start, end; // Start, end point of current polygon edge
glm::vec2 intersection; // Intersection point with a clip boundary
outLength = 0;
start = inVertexArray[inLength - 1]; // Start with the last vertex in inVertexArray
for (int j = 0; j < inLength; j++) {
end = inVertexArray[j]; // Now start and end correspond to the vertices
// Cases 1 and 4 - the endpoint is inside the boundary
if (pointInsideBoundary(end,clipBoundary)) {
// Case 1 - Both inside
if (pointInsideBoundary(start, clipBoundary)) {
appendPoint(end, outLength, outVertexArray);
} else { // Case 4 - end is inside, but start is outside
segmentIntersectsBoundary(start, end, clipBoundary, intersection);
appendPoint(intersection, outLength, outVertexArray);
appendPoint(end, outLength, outVertexArray);
}
} else { // Cases 2 and 3 - end is outside
if (pointInsideBoundary(start, clipBoundary)) {
// Cases 2 - start is inside, end is outside
segmentIntersectsBoundary(start, end, clipBoundary, intersection);
appendPoint(intersection, outLength, outVertexArray);
} else {
// Case 3 - both are outside, No action
}
}
start = end; // Advance to next pair of vertices
}
}
bool PolygonClip::pointInsideBoundary(const glm::vec2& testVertex, const LineSegment2& clipBoundary) {
// bottom edge
if (clipBoundary[1].x > clipBoundary[0].x) {
if (testVertex.y >= clipBoundary[0].y) {
return true;
}
}
// top edge
if (clipBoundary[1].x < clipBoundary[0].x) {
if (testVertex.y <= clipBoundary[0].y) {
return true;
}
}
// right edge
if (clipBoundary[1].y > clipBoundary[0].y) {
if (testVertex.x <= clipBoundary[1].x) {
return true;
}
}
// left edge
if (clipBoundary[1].y < clipBoundary[0].y) {
if (testVertex.x >= clipBoundary[1].x) {
return true;
}
}
return false;
}
void PolygonClip::segmentIntersectsBoundary(const glm::vec2& first, const glm::vec2& second,
const LineSegment2& clipBoundary, glm::vec2& intersection) {
// horizontal
if (clipBoundary[0].y==clipBoundary[1].y) {
intersection.y = clipBoundary[0].y;
intersection.x = first.x + (clipBoundary[0].y - first.y) * (second.x - first.x) / (second.y - first.y);
} else { // Vertical
intersection.x = clipBoundary[0].x;
intersection.y = first.y + (clipBoundary[0].x - first.x) * (second.y - first.y) / (second.x - first.x);
}
}
void PolygonClip::appendPoint(glm::vec2 newVertex, int& outLength, glm::vec2* outVertexArray) {
outVertexArray[outLength].x = newVertex.x;
outVertexArray[outLength].y = newVertex.y;
outLength++;
}
// The copyCleanArray() function sets the resulting polygon of the previous step up to be the input polygon for next step of the
// clipping algorithm. As the Sutherland-Hodgman algorithm is a polygon clipping algorithm, it does not handle line
// clipping very well. The modification so that lines may be clipped as well as polygons is included in this function.
// when completed vertexArrayA will be ready for output and/or next step of clipping
void PolygonClip::copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& lengthB, glm::vec2* vertexArrayB) {
// Fix lines: they will come back with a length of 3, from an original of length of 2
if ((lengthA == 2) && (lengthB == 3)) {
// The first vertex should be copied as is.
vertexArrayA[0] = vertexArrayB[0];
// If the first two vertices of the "B" array are same, then collapse them down to be the 2nd vertex
if (vertexArrayB[0].x == vertexArrayB[1].x) {
vertexArrayA[1] = vertexArrayB[2];
} else {
// Otherwise the first vertex should be the same as third vertex
vertexArrayA[1] = vertexArrayB[1];
}
lengthA=2;
} else {
// for all other polygons, then just copy the vertexArrayB to vertextArrayA for next step
lengthA = lengthB;
for (int i = 0; i < lengthB; i++) {
vertexArrayA[i] = vertexArrayB[i];
}
}
}

View file

@ -43,4 +43,39 @@ bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm
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);
typedef glm::vec2 LineSegment2[2];
// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html
class PolygonClip {
public:
static void clipToScreen(const glm::vec2* inputVertexArray, int length, glm::vec2*& outputVertexArray, int& outLength);
static const float TOP_OF_CLIPPING_WINDOW;
static const float BOTTOM_OF_CLIPPING_WINDOW;
static const float LEFT_OF_CLIPPING_WINDOW;
static const float RIGHT_OF_CLIPPING_WINDOW;
static const glm::vec2 TOP_LEFT_CLIPPING_WINDOW;
static const glm::vec2 TOP_RIGHT_CLIPPING_WINDOW;
static const glm::vec2 BOTTOM_LEFT_CLIPPING_WINDOW;
static const glm::vec2 BOTTOM_RIGHT_CLIPPING_WINDOW;
private:
static void sutherlandHodgmanPolygonClip(glm::vec2* inVertexArray, glm::vec2* outVertexArray,
int inLength, int& outLength, const LineSegment2& clipBoundary);
static bool pointInsideBoundary(const glm::vec2& testVertex, const LineSegment2& clipBoundary);
static void segmentIntersectsBoundary(const glm::vec2& first, const glm::vec2& second,
const LineSegment2& clipBoundary, glm::vec2& intersection);
static void appendPoint(glm::vec2 newVertex, int& outLength, glm::vec2* outVertexArray);
static void copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& lengthB, glm::vec2* vertexArrayB);
};
#endif /* defined(__interface__GeometryUtil__) */

View file

@ -115,8 +115,8 @@ TagCompound::TagCompound(std::stringstream &ss) :
_width(0),
_length(0),
_height(0),
_blocksId(NULL),
_blocksData(NULL)
_blocksData(NULL),
_blocksId(NULL)
{
int tagId;

View file

@ -12,11 +12,15 @@
#include <glm/gtx/transform.hpp>
#include "ViewFrustum.h"
#include "VoxelConstants.h"
#include "SharedUtil.h"
#include "Log.h"
#include "CoverageMap.h"
#include "GeometryUtil.h"
#include "ViewFrustum.h"
#include "VoxelConstants.h"
using namespace std;
ViewFrustum::ViewFrustum() :
@ -262,6 +266,7 @@ ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, floa
ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
ViewFrustum::location regularResult = INSIDE;
ViewFrustum::location keyholeResult = OUTSIDE;
@ -274,11 +279,11 @@ ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
}
for(int i=0; i < 6; i++) {
glm::vec3 normal = _planes[i].getNormal();
glm::vec3 boxVertexP = box.getVertexP(normal);
const glm::vec3& normal = _planes[i].getNormal();
const glm::vec3& boxVertexP = box.getVertexP(normal);
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
glm::vec3 boxVertexN = box.getVertexN(normal);
const glm::vec3& boxVertexN = box.getVertexN(normal);
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
if (planeToBoxVertexPDistance < 0) {
@ -451,16 +456,22 @@ glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const {
const int MAX_POSSIBLE_COMBINATIONS = 43;
const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1] = {
const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_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
{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
{4, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR }, // 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
//4
{4, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, // bottom
//5
{6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR },//bottom, right
{6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, },//bottom, left
{0}, // n/a
//8
{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
@ -469,32 +480,52 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1]
{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
//16
{4, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, // front or near
{6, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, // front, right
{6, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, }, // 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
//20
{6, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, // front,bottom
//21
{6, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, //front,bottom,right
//22
{6, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR }, //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
{6, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // front, top
{6, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR }, // front, top, right
{6, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR }, // 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
//32
{4, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR }, // back
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR}, // back, right
//34
{6, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, TOP_RIGHT_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
//36
{6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR}, // back, bottom
{6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR},//back, bottom, right
// 38
{6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR },//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
// 40
{6, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_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
//42
{6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left
};
@ -508,9 +539,11 @@ VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const {
+ ((_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
//printLog(">>>>>>>>> ViewFrustum::getProjectedPolygon() lookup=%d\n",lookUp);
VoxelProjectedPolygon shadow(vertexCount);
int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices
VoxelProjectedPolygon projectedPolygon(vertexCount);
bool pointInView = true;
bool allPointsInView = false; // assume the best, but wait till we know we have a vertex
@ -523,13 +556,40 @@ VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const {
glm::vec2 projectedPoint = projectPoint(point, pointInView);
allPointsInView = allPointsInView && pointInView;
anyPointsInView = anyPointsInView || pointInView;
shadow.setVertex(i, projectedPoint);
projectedPolygon.setVertex(i, projectedPoint);
}
/***
// Now that we've got the polygon, if it extends beyond the clipping window, then let's clip it
// NOTE: This clipping does not improve our overall performance. It basically causes more polygons to
// end up in the same quad/half and so the polygon lists get longer, and that's more calls to polygon.occludes()
if ( (projectedPolygon.getMaxX() > PolygonClip::RIGHT_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) {
CoverageRegion::_clippedPolygons++;
glm::vec2* clippedVertices;
int clippedVertexCount;
PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount);
// Now reset the vertices of our projectedPolygon object
projectedPolygon.setVertexCount(clippedVertexCount);
for(int i = 0; i < clippedVertexCount; i++) {
projectedPolygon.setVertex(i, clippedVertices[i]);
}
delete[] clippedVertices;
lookUp += PROJECTION_CLIPPED;
}
***/
}
// 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;
projectedPolygon.setDistance(distance);
projectedPolygon.setAnyInView(anyPointsInView);
projectedPolygon.setAllInView(allPointsInView);
projectedPolygon.setProjectionType(lookUp); // remember the projection type
return projectedPolygon;
}

View file

@ -62,7 +62,7 @@ public:
const glm::vec3& getCenter() const { return _box.getCenter(); };
const glm::vec3& getCorner() const { return _box.getCorner(); };
float getScale() const { return _box.getSize().x; /* voxelScale = (1 / powf(2, *node->getOctalCode())); */ };
int getLevel() const { return *_octalCode + 1; /* one based or zero based? */ };
int getLevel() const { return *_octalCode + 1; /* one based or zero based? this doesn't correctly handle 2 byte case */ };
float getEnclosingRadius() const;

File diff suppressed because it is too large Load diff

View file

@ -10,29 +10,58 @@
#include <glm/glm.hpp>
const int MAX_SHADOW_VERTEX_COUNT = 6;
typedef glm::vec2 ShadowVertices[MAX_SHADOW_VERTEX_COUNT];
// there's a max of 6 vertices of a project polygon, and a max of twice that when clipped to the screen
const int MAX_PROJECTED_POLYGON_VERTEX_COUNT = 6;
const int MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT = MAX_PROJECTED_POLYGON_VERTEX_COUNT * 2;
typedef glm::vec2 ProjectedVertices[MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT];
class BoundingBox {
public:
BoundingBox(glm::vec2 corner, glm::vec2 size) : corner(corner), size(size) {};
enum { BOTTOM_LEFT, BOTTOM_RIGHT, TOP_RIGHT, TOP_LEFT, VERTEX_COUNT };
BoundingBox(glm::vec2 corner, glm::vec2 size) : corner(corner), size(size), _set(true) {};
BoundingBox() : _set(false) {};
glm::vec2 corner;
glm::vec2 size;
bool contains(const BoundingBox& box) const;
bool contains(const glm::vec2& point) const;
bool pointInside(const glm::vec2& point) const { return contains(point); };
void explandToInclude(const BoundingBox& box);
float area() const { return size.x * size.y; };
int getVertexCount() const { return VERTEX_COUNT; };
glm::vec2 getVertex(int vertexNumber) const;
BoundingBox topHalf() const;
BoundingBox bottomHalf() const;
BoundingBox leftHalf() const;
BoundingBox rightHalf() const;
float getMaxX() const { return corner.x + size.x; }
float getMaxY() const { return corner.y + size.y; }
float getMinX() const { return corner.x; }
float getMinY() const { return corner.y; }
void printDebugDetails(const char* label=NULL) const;
private:
bool _set;
};
const int PROJECTION_RIGHT = 1;
const int PROJECTION_LEFT = 2;
const int PROJECTION_BOTTOM = 4;
const int PROJECTION_TOP = 8;
const int PROJECTION_NEAR = 16;
const int PROJECTION_FAR = 32;
const int PROJECTION_CLIPPED = 64;
class VoxelProjectedPolygon {
public:
VoxelProjectedPolygon(const BoundingBox& box);
VoxelProjectedPolygon(int vertexCount = 0) :
_vertexCount(vertexCount),
_maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX),
@ -40,22 +69,33 @@ public:
{ };
~VoxelProjectedPolygon() { };
const ShadowVertices& getVerices() const { return _vertices; };
const ProjectedVertices& getVertices() 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; }
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; };
void setProjectionType(unsigned char type) { _projectionType = type; };
unsigned char getProjectionType() const { return _projectionType; };
bool getAnyInView() const { return _anyInView; };
void setAnyInView(bool anyInView) { _anyInView = anyInView; };
bool getAllInView() const { return _allInView; };
void setAllInView(bool allInView) { _allInView = allInView; };
bool pointInside(const glm::vec2& point, bool* matchesVertex = NULL) const;
bool occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView = false) const;
bool pointInside(const glm::vec2& point) const;
bool occludes(const BoundingBox& occludee) const;
bool intersects(const VoxelProjectedPolygon& testee) const;
bool intersects(const BoundingBox& box) const;
bool matches(const VoxelProjectedPolygon& testee) const;
bool matches(const BoundingBox& testee) const;
bool intersectsOnAxes(const VoxelProjectedPolygon& testee) const;
bool canMerge(const VoxelProjectedPolygon& that) const;
void merge(const VoxelProjectedPolygon& that); // replaces vertices of this with new merged version
float getMaxX() const { return _maxX; }
float getMaxY() const { return _maxY; }
@ -67,10 +107,14 @@ public:
};
void printDebugDetails() const;
static long pointInside_calls;
static long occludes_calls;
static long intersects_calls;
private:
int _vertexCount;
ShadowVertices _vertices;
ProjectedVertices _vertices;
float _maxX;
float _maxY;
float _minX;
@ -78,6 +122,7 @@ private:
float _distance;
bool _anyInView; // if any points are in view
bool _allInView; // if all points are in view
unsigned char _projectionType;
};

View file

@ -29,12 +29,16 @@
#include <glm/gtc/noise.hpp>
int boundaryDistanceForRenderLevel(unsigned int renderLevel) {
float voxelSizeScale = 50000.0f;
float boundaryDistanceForRenderLevel(unsigned int renderLevel) {
const float voxelSizeScale = 50000.0f;
return voxelSizeScale / powf(2, renderLevel);
}
float boundaryDistanceSquaredForRenderLevel(unsigned int renderLevel) {
const float voxelSizeScale = (50000.0f/TREE_SCALE) * (50000.0f/TREE_SCALE);
return voxelSizeScale / powf(2, (2 * renderLevel));
}
VoxelTree::VoxelTree(bool shouldReaverage) :
voxelsCreated(0),
voxelsColored(0),
@ -55,6 +59,61 @@ VoxelTree::~VoxelTree() {
}
}
void VoxelTree::recurseTreeWithOperationDistanceSortedTimed(PointerStack* stackOfNodes, long allowedTime,
RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData) {
long long start = usecTimestampNow();
// start case, stack empty, so start with root...
if (stackOfNodes->empty()) {
stackOfNodes->push(rootNode);
}
while (!stackOfNodes->empty()) {
VoxelNode* node = (VoxelNode*)stackOfNodes->top();
stackOfNodes->pop();
if (operation(node, extraData)) {
//sortChildren... CLOSEST to FURTHEST
// 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);
currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i,
(void**)&sortedChildren, (float*)&distancesToChildren,
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
}
}
//iterate sorted children FURTHEST to CLOSEST
for (int i = currentCount-1; i >= 0; i--) {
VoxelNode* child = sortedChildren[i];
stackOfNodes->push(child);
}
}
// at this point, we can check to see if we should bail for timing reasons
// because if we bail at this point, then reenter the while, we will basically
// be back to processing the stack from same place we left off, and all can proceed normally
long long now = usecTimestampNow();
long elapsedTime = now - start;
if (elapsedTime > allowedTime) {
return; // caller responsible for calling us again to finish the job!
}
}
}
// Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node.
// stops recursion if operation function returns false.
void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData) {
@ -62,7 +121,7 @@ void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, vo
}
// Recurses voxel node with an operation function
void VoxelTree::recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperation operation, void* extraData) {
void VoxelTree::recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData) {
if (operation(node, extraData)) {
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
VoxelNode* child = node->getChildAtIndex(i);
@ -77,15 +136,15 @@ void VoxelTree::recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperati
// 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,
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
@ -1099,6 +1158,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
// Keep track of how deep we've encoded.
currentEncodeLevel++;
params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached);
// If we've reached our max Search Level, then stop searching.
if (currentEncodeLevel >= params.maxEncodeLevel) {
@ -1270,10 +1331,6 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
} // wants occlusion culling & isLeaf()
bool childWasInView = (childNode && params.deltaViewFrustum &&
(params.lastViewFrustum && ViewFrustum::INSIDE == childNode->inFrustum(*params.lastViewFrustum)));
// There are two types of nodes for which we want to send colors:
// 1) Leaves - obviously
// 2) Non-leaves who's children would be visible and beyond our LOD.
@ -1299,15 +1356,34 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
}
// if any of our grandchildren ARE in view, then we don't want to include our color. If none are, then
// we do want to include our color
if (grandChildrenInView > 0 && grandChildrenInLOD==0) {
if (grandChildrenInView > 0 && grandChildrenInLOD == 0) {
isLeafOrLOD = true;
}
}
// track children with actual color, only if the child wasn't previously in view!
if (childNode && isLeafOrLOD && childNode->isColored() && !childWasInView && !childIsOccluded) {
childrenColoredBits += (1 << (7 - originalIndex));
inViewWithColorCount++;
if (childNode && isLeafOrLOD && childNode->isColored() && !childIsOccluded) {
bool childWasInView = false;
if (childNode && params.deltaViewFrustum && params.lastViewFrustum) {
ViewFrustum::location location = childNode->inFrustum(*params.lastViewFrustum);
// If we're a leaf, then either intersect or inside is considered "formerly in view"
if (childNode->isLeaf()) {
childWasInView = location != ViewFrustum::OUTSIDE;
} else {
childWasInView = location == ViewFrustum::INSIDE;
}
}
// If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items
if (!childWasInView) {
childrenColoredBits += (1 << (7 - originalIndex));
inViewWithColorCount++;
} else {
// otherwise just track stats of the items we discarded
params.childWasInViewDiscarded++;
}
}
}
}

View file

@ -14,6 +14,7 @@
#include "VoxelNode.h"
#include "VoxelNodeBag.h"
#include "CoverageMap.h"
#include "PointerStack.h"
// Callback function, for recuseTreeWithOperation
typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData);
@ -36,6 +37,7 @@ typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
class EncodeBitstreamParams {
public:
int maxEncodeLevel;
int maxLevelReached;
const ViewFrustum* viewFrustum;
bool includeColor;
bool includeExistsBits;
@ -43,11 +45,13 @@ public:
bool deltaViewFrustum;
const ViewFrustum* lastViewFrustum;
bool wantOcclusionCulling;
long childWasInViewDiscarded;
CoverageMap* map;
EncodeBitstreamParams(
int maxEncodeLevel = INT_MAX,
const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM,
const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM,
bool includeColor = WANT_COLOR,
bool includeExistsBits = WANT_EXISTS_BITS,
int chopLevels = 0,
@ -55,8 +59,8 @@ public:
const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM,
bool wantOcclusionCulling= NO_OCCLUSION_CULLING,
CoverageMap* map = IGNORE_COVERAGE_MAP) :
maxEncodeLevel (maxEncodeLevel),
maxLevelReached (0),
viewFrustum (viewFrustum),
includeColor (includeColor),
includeExistsBits (includeExistsBits),
@ -64,6 +68,7 @@ public:
deltaViewFrustum (deltaViewFrustum),
lastViewFrustum (lastViewFrustum),
wantOcclusionCulling(wantOcclusionCulling),
childWasInViewDiscarded(0),
map (map)
{}
};
@ -148,6 +153,11 @@ public:
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData);
void recurseTreeWithOperationDistanceSortedTimed(PointerStack* stackOfNodes, long allowedTime,
RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData);
private:
void deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData);
@ -172,6 +182,7 @@ private:
bool _shouldReaverage;
};
int boundaryDistanceForRenderLevel(unsigned int renderLevel);
float boundaryDistanceForRenderLevel(unsigned int renderLevel);
float boundaryDistanceSquaredForRenderLevel(unsigned int renderLevel);
#endif /* defined(__hifi__VoxelTree__) */

View file

@ -53,7 +53,6 @@ public:
long long getLastTimeBagEmpty() const { return _lastTimeBagEmpty; };
void setLastTimeBagEmpty(long long lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; };
private:
VoxelNodeData(const VoxelNodeData &);
VoxelNodeData& operator= (const VoxelNodeData&);

View file

@ -111,130 +111,6 @@ void eraseVoxelTreeAndCleanupNodeVisitData() {
}
// Version of voxel distributor that sends each LOD level at a time
void resInVoxelDistributor(NodeList* nodeList,
NodeList::iterator& node,
VoxelNodeData* nodeData) {
ViewFrustum viewFrustum = nodeData->getCurrentViewFrustum();
bool searchReset = false;
int searchLoops = 0;
int searchLevelWas = nodeData->getMaxSearchLevel();
long long start = usecTimestampNow();
while (!searchReset && nodeData->nodeBag.isEmpty()) {
searchLoops++;
searchLevelWas = nodeData->getMaxSearchLevel();
int maxLevelReached = serverTree.searchForColoredNodes(nodeData->getMaxSearchLevel(), serverTree.rootNode,
viewFrustum, nodeData->nodeBag);
nodeData->setMaxLevelReached(maxLevelReached);
// If nothing got added, then we bump our levels.
if (nodeData->nodeBag.isEmpty()) {
if (nodeData->getMaxLevelReached() < nodeData->getMaxSearchLevel()) {
nodeData->resetMaxSearchLevel();
searchReset = true;
} else {
nodeData->incrementMaxSearchLevel();
}
}
}
long long end = usecTimestampNow();
int elapsedmsec = (end - start)/1000;
if (elapsedmsec > 100) {
if (elapsedmsec > 1000) {
int elapsedsec = (end - start)/1000000;
printf("WARNING! searchForColoredNodes() took %d seconds to identify %d nodes at level %d in %d loops\n",
elapsedsec, nodeData->nodeBag.count(), searchLevelWas, searchLoops);
} else {
printf("WARNING! searchForColoredNodes() took %d milliseconds to identify %d nodes at level %d in %d loops\n",
elapsedmsec, nodeData->nodeBag.count(), searchLevelWas, searchLoops);
}
} else if (::debugVoxelSending) {
printf("searchForColoredNodes() took %d milliseconds to identify %d nodes at level %d in %d loops\n",
elapsedmsec, nodeData->nodeBag.count(), searchLevelWas, searchLoops);
}
// If we have something in our nodeBag, then turn them into packets and send them out...
if (!nodeData->nodeBag.isEmpty()) {
static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static
int bytesWritten = 0;
int packetsSentThisInterval = 0;
int truePacketsSent = 0;
int trueBytesSent = 0;
long long start = usecTimestampNow();
bool shouldSendEnvironments = shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS);
while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) {
if (!nodeData->nodeBag.isEmpty()) {
VoxelNode* subTree = nodeData->nodeBag.extract();
EncodeBitstreamParams params(nodeData->getMaxSearchLevel(), &viewFrustum,
nodeData->getWantColor(), WANT_EXISTS_BITS);
bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
nodeData->nodeBag, params);
if (nodeData->getAvailable() >= bytesWritten) {
nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
} else {
nodeList->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
trueBytesSent += nodeData->getPacketLength();
truePacketsSent++;
packetsSentThisInterval++;
nodeData->resetVoxelPacket();
nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
}
} else {
if (nodeData->isPacketWaiting()) {
nodeList->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
trueBytesSent += nodeData->getPacketLength();
truePacketsSent++;
nodeData->resetVoxelPacket();
}
packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left
}
}
// send the environment packets
if (shouldSendEnvironments) {
int envPacketLength = 1;
*tempOutputBuffer = PACKET_HEADER_ENVIRONMENT_DATA;
for (int i = 0; i < sizeof(environmentData) / sizeof(environmentData[0]); i++) {
envPacketLength += environmentData[i].getBroadcastData(tempOutputBuffer + envPacketLength);
}
nodeList->getNodeSocket()->send(node->getActiveSocket(), tempOutputBuffer, envPacketLength);
trueBytesSent += envPacketLength;
truePacketsSent++;
}
long long end = usecTimestampNow();
int elapsedmsec = (end - start)/1000;
if (elapsedmsec > 100) {
if (elapsedmsec > 1000) {
int elapsedsec = (end - start)/1000000;
printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n",
elapsedsec, trueBytesSent, truePacketsSent, searchLevelWas, nodeData->nodeBag.count());
} else {
printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n",
elapsedmsec, trueBytesSent, truePacketsSent, searchLevelWas, nodeData->nodeBag.count());
}
} else if (::debugVoxelSending) {
printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n",
elapsedmsec, trueBytesSent, truePacketsSent, searchLevelWas, nodeData->nodeBag.count());
}
// if during this last pass, we emptied our bag, then we want to move to the next level.
if (nodeData->nodeBag.isEmpty()) {
if (nodeData->getMaxLevelReached() < nodeData->getMaxSearchLevel()) {
nodeData->resetMaxSearchLevel();
} else {
nodeData->incrementMaxSearchLevel();
}
}
}
}
pthread_mutex_t treeLock;
@ -251,7 +127,7 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
long long start = usecTimestampNow();
// FOR NOW... node tells us if it wants to receive only view frustum deltas
bool wantDelta = nodeData->getWantDelta();
bool wantDelta = viewFrustumChanged && nodeData->getWantDelta();
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
if (::debugVoxelSending) {
@ -270,7 +146,6 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
long long now = usecTimestampNow();
if (nodeData->getLastTimeBagEmpty() > 0) {
float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f;
if (viewFrustumChanged) {
printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend);
} else {
@ -304,7 +179,6 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
} else {
nodeData->nodeBag.insert(serverTree.rootNode);
}
}
long long end = usecTimestampNow();
int elapsedmsec = (end - start)/1000;
@ -350,7 +224,6 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
if (!nodeData->nodeBag.isEmpty()) {
VoxelNode* subTree = nodeData->nodeBag.extract();
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
@ -360,12 +233,16 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
nodeData->nodeBag, params);
if (::debugVoxelSending && wantDelta) {
printf("encodeTreeBitstream() childWasInViewDiscarded=%ld\n", params.childWasInViewDiscarded);
}
if (nodeData->getAvailable() >= bytesWritten) {
nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
} else {
nodeList->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
nodeData->getPacket(), nodeData->getPacketLength());
trueBytesSent += nodeData->getPacketLength();
truePacketsSent++;
packetsSentThisInterval++;
@ -375,7 +252,7 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
} else {
if (nodeData->isPacketWaiting()) {
nodeList->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
nodeData->getPacket(), nodeData->getPacketLength());
trueBytesSent += nodeData->getPacketLength();
truePacketsSent++;
nodeData->resetVoxelPacket();
@ -417,6 +294,9 @@ void deepestLevelVoxelDistributor(NodeList* nodeList,
if (nodeData->nodeBag.isEmpty()) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
if (::debugVoxelSending) {
nodeData->map.printStats();
}
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
}
@ -463,12 +343,7 @@ void *distributeVoxelsToListeners(void *args) {
if (::debugVoxelSending) {
printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
}
if (nodeData->getWantResIn()) {
resInVoxelDistributor(nodeList, node, nodeData);
} else {
deepestLevelVoxelDistributor(nodeList, node, nodeData, viewFrustumChanged);
}
deepestLevelVoxelDistributor(nodeList, node, nodeData, viewFrustumChanged);
}
}
@ -755,3 +630,5 @@ int main(int argc, const char * argv[]) {
return 0;
}