Merge pull request #560 from ZappoMan/occlusion_culling

Occlusion culling optimizations
This commit is contained in:
Philip Rosedale 2013-06-19 19:26:56 -07:00
commit 5dae92d610
11 changed files with 468 additions and 205 deletions

View file

@ -980,6 +980,7 @@ void Application::doFalseColorizeInView() {
}
void Application::doFalseColorizeOccluded() {
CoverageMap::wantDebugging = true;
_voxels.falseColorizeOccluded();
}

View file

@ -1202,19 +1202,19 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(args->viewFrustum->getProjectedShadow(voxelBox));
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 (!voxelShadow->getAllInView()) {
if (!voxelPolygon->getAllInView()) {
args->nonLeavesOutOfView++;
delete voxelShadow;
delete voxelPolygon;
return true;
}
CoverageMap::StorageResult result = args->map->checkMap(voxelShadow, false);
if (result == CoverageMap::OCCLUDED) {
CoverageMapStorageResult result = args->map->checkMap(voxelPolygon, false);
if (result == OCCLUDED) {
args->nonLeavesOccluded++;
delete voxelShadow;
delete voxelPolygon;
FalseColorizeSubTreeOperationArgs subArgs;
subArgs.color[0] = 0;
@ -1230,7 +1230,7 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat
return false;
}
delete voxelShadow;
delete voxelPolygon;
return true; // keep looking...
}
@ -1239,23 +1239,23 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(args->viewFrustum->getProjectedShadow(voxelBox));
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 (!voxelShadow->getAllInView()) {
if (!voxelPolygon->getAllInView()) {
args->outOfView++;
delete voxelShadow;
delete voxelPolygon;
return true;
}
CoverageMap::StorageResult result = args->map->checkMap(voxelShadow, true);
if (result == CoverageMap::OCCLUDED) {
CoverageMapStorageResult result = args->map->checkMap(voxelPolygon, true);
if (result == OCCLUDED) {
node->setFalseColor(255, 0, 0);
args->occludedVoxels++;
} else if (result == CoverageMap::STORED) {
} else if (result == STORED) {
args->notOccludedVoxels++;
//printLog("***** falseColorizeOccludedOperation() NODE is STORED *****\n");
} else if (result == CoverageMap::DOESNT_FIT) {
} else if (result == DOESNT_FIT) {
//printLog("***** falseColorizeOccludedOperation() NODE DOESNT_FIT???? *****\n");
}
}

View file

@ -105,6 +105,17 @@ bool AABox::contains(const glm::vec3& point) const {
isWithin(point.z, _corner.z, _size.z);
}
bool AABox::contains(const AABox& otherBox) const {
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
glm::vec3 vertex = otherBox.getVertex((BoxVertex)v);
if (!contains(vertex)) {
return false;
}
}
return true;
}
// determines whether a value is within the expanded extents
static bool isWithinExpanded(float value, float corner, float size, float expansion) {
return value >= corner - expansion && value <= corner + size + expansion;

View file

@ -63,6 +63,7 @@ public:
glm::vec3 getVertex(BoxVertex vertex) const;
bool contains(const glm::vec3& point) const;
bool contains(const AABox& otherBox) const;
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;

View file

@ -3,6 +3,7 @@
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "CoverageMap.h"
@ -11,10 +12,49 @@
#include "Log.h"
int CoverageMap::_mapCount = 0;
int CoverageMap::_checkMapRootCalls = 0;
bool CoverageMap::wantDebugging = false;
const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-2.f,-2.f), glm::vec2(4.f,4.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 CoverageMap::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);
CoverageMap::CoverageMap(BoundingBox boundingBox, bool isRoot, bool managePolygons) :
_isRoot(isRoot), _myBoundingBox(boundingBox), _managePolygons(managePolygons) {
_isRoot(isRoot),
_myBoundingBox(boundingBox),
_managePolygons(managePolygons),
_topHalf (boundingBox.topHalf() , false, managePolygons, TOP_HALF ),
_bottomHalf (boundingBox.bottomHalf(), false, managePolygons, BOTTOM_HALF ),
_leftHalf (boundingBox.leftHalf() , false, managePolygons, LEFT_HALF ),
_rightHalf (boundingBox.rightHalf() , false, managePolygons, RIGHT_HALF ),
_remainder (boundingBox, isRoot, managePolygons, REMAINDER )
{
_mapCount++;
init();
//printLog("CoverageMap created... _mapCount=%d\n",_mapCount);
@ -25,25 +65,13 @@ CoverageMap::~CoverageMap() {
};
void CoverageMap::erase() {
// If we're in charge of managing the polygons, then clean them up first
if (_managePolygons) {
for (int i = 0; i < _polygonCount; i++) {
delete _polygons[i];
_polygons[i] = NULL; // do we need to do this?
}
}
// Now, clean up our local storage
_polygonCount = 0;
_polygonArraySize = 0;
if (_polygons) {
delete[] _polygons;
_polygons = NULL;
}
if (_polygonDistances) {
delete[] _polygonDistances;
_polygonDistances = NULL;
}
// tell our regions to erase()
_topHalf.erase();
_bottomHalf.erase();
_leftHalf.erase();
_rightHalf.erase();
_remainder.erase();
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
if (_childMaps[i]) {
delete _childMaps[i];
@ -51,26 +79,25 @@ void CoverageMap::erase() {
}
}
/**
if (_isRoot) {
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("_maxPolygonsUsed=%d\n",_maxPolygonsUsed);
printLog("_totalPolygons=%d\n",_totalPolygons);
_maxPolygonsUsed = 0;
_totalPolygons = 0;
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);
CoverageRegion::_maxPolygonsUsed = 0;
CoverageRegion::_totalPolygons = 0;
CoverageRegion::_occlusionTests = 0;
CoverageRegion::_outOfOrderPolygon = 0;
_mapCount = 0;
_checkMapRootCalls = 0;
}
**/
}
void CoverageMap::init() {
_polygonCount = 0;
_polygonArraySize = 0;
_polygons = NULL;
_polygonDistances = NULL;
memset(_childMaps,0,sizeof(_childMaps));
}
@ -94,79 +121,59 @@ BoundingBox CoverageMap::getChildBoundingBox(int childIndex) {
return result;
}
void CoverageMap::growPolygonArray() {
VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE];
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
if (_polygons) {
memcpy(newPolygons, _polygons, sizeof(VoxelProjectedPolygon*) * _polygonCount);
delete[] _polygons;
memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount);
delete[] _polygonDistances;
}
_polygons = newPolygons;
_polygonDistances = newDistances;
_polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE;
//printLog("CoverageMap::growPolygonArray() _polygonArraySize=%d...\n",_polygonArraySize);
}
int CoverageMap::_maxPolygonsUsed = 0;
int CoverageMap::_totalPolygons = 0;
// just handles storage in the array, doesn't test for occlusion or
// determining if this is the correct map to store in!
void CoverageMap::storeInArray(VoxelProjectedPolygon* polygon) {
_totalPolygons++;
if (_polygonArraySize < _polygonCount + 1) {
growPolygonArray();
}
// This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to
// be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the
// insertion point in this array, and shift the array accordingly
const int IGNORED = NULL;
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
(void**)_polygons, _polygonDistances, IGNORED,
_polygonCount, _polygonArraySize);
if (_polygonCount > _maxPolygonsUsed) {
_maxPolygonsUsed = _polygonCount;
//printLog("CoverageMap new _maxPolygonsUsed reached=%d\n",_maxPolygonsUsed);
//_myBoundingBox.printDebugDetails("map._myBoundingBox");
}
}
// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT
CoverageMap::StorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) {
if (_isRoot || _myBoundingBox.contains(polygon->getBoundingBox())) {
// check to make sure this polygon isn't occluded by something at this level
for (int i = 0; i < _polygonCount; i++) {
VoxelProjectedPolygon* polygonAtThisLevel = _polygons[i];
// Check to make sure that the polygon in question is "behind" the polygon in the list
// otherwise, we don't need to test it's occlusion (although, it means we've potentially
// added an item previously that may be occluded??? Is that possible? Maybe not, because two
// voxels can't have the exact same outline. So one occludes the other, they can't both occlude
// each other.
if (polygonAtThisLevel->occludes(*polygon)) {
// if the polygonAtThisLevel is actually behind the one we're inserting, then we don't
// want to report our inserted one as occluded, but we do want to add our inserted one.
if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) {
if (storeIt) {
storeInArray(polygon);
return STORED;
} else {
return NOT_STORED;
}
}
// this polygon is occluded by a closer polygon, so don't store it, and let the caller know
return OCCLUDED;
}
CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) {
if (_isRoot) {
_checkMapRootCalls++;
}
// 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()) {
return DOESNT_FIT;
}
BoundingBox polygonBox(polygon->getBoundingBox());
if (_isRoot || _myBoundingBox.contains(polygonBox)) {
CoverageMapStorageResult result = NOT_STORED;
CoverageRegion* storeIn = &_remainder;
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;
}
// if we got this far, there are one of two possibilities, either a polygon doesn't fit
// in one of the halves, or it did fit, but it wasn't occluded by anything only in that
// half. In either of these cases, we want to check our remainder region to see if its
// occluded by anything there
if (!(result == STORED || result == OCCLUDED)) {
result = _remainder.checkRegion(polygon, polygonBox, storeIt);
}
// 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) {
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
@ -183,11 +190,184 @@ CoverageMap::StorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon,
// if we got this far, then the polygon is in our bounding box, but doesn't fit in
// any of our child bounding boxes, so we should add it here.
if (storeIt) {
storeInArray(polygon);
return STORED;
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;
} else {
return NOT_STORED;
}
} else {
return NOT_STORED;
}
}
return DOESNT_FIT;
}
CoverageRegion::CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons, RegionName regionName) :
_isRoot(isRoot),
_myBoundingBox(boundingBox),
_managePolygons(managePolygons),
_regionName(regionName)
{
init();
};
CoverageRegion::~CoverageRegion() {
erase();
};
void CoverageRegion::init() {
_polygonCount = 0;
_polygonArraySize = 0;
_polygons = NULL;
_polygonDistances = NULL;
}
void CoverageRegion::erase() {
/*
if (_polygonCount) {
printLog("CoverageRegion::erase()...\n");
printLog("_polygonCount=%d\n",_polygonCount);
_myBoundingBox.printDebugDetails(getRegionName());
//for (int i = 0; i < _polygonCount; i++) {
// printLog("_polygons[%d]=",i);
// _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++) {
delete _polygons[i];
_polygons[i] = NULL; // do we need to do this?
}
}
// Now, clean up our local storage
_polygonCount = 0;
_polygonArraySize = 0;
if (_polygons) {
delete[] _polygons;
_polygons = NULL;
}
if (_polygonDistances) {
delete[] _polygonDistances;
_polygonDistances = NULL;
}
}
void CoverageRegion::growPolygonArray() {
VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE];
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
if (_polygons) {
memcpy(newPolygons, _polygons, sizeof(VoxelProjectedPolygon*) * _polygonCount);
delete[] _polygons;
memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount);
delete[] _polygonDistances;
}
_polygons = newPolygons;
_polygonDistances = newDistances;
_polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE;
//printLog("CoverageMap::growPolygonArray() _polygonArraySize=%d...\n",_polygonArraySize);
}
const char* CoverageRegion::getRegionName() const {
switch (_regionName) {
case TOP_HALF:
return "TOP_HALF";
case BOTTOM_HALF:
return "BOTTOM_HALF";
case LEFT_HALF:
return "LEFT_HALF";
case RIGHT_HALF:
return "RIGHT_HALF";
default:
case REMAINDER:
return "REMAINDER";
}
return "REMAINDER";
}
int CoverageRegion::_maxPolygonsUsed = 0;
int CoverageRegion::_totalPolygons = 0;
int CoverageRegion::_occlusionTests = 0;
int CoverageRegion::_outOfOrderPolygon = 0;
// 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) {
_totalPolygons++;
if (_polygonArraySize < _polygonCount + 1) {
growPolygonArray();
}
// This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to
// be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the
// insertion point in this array, and shift the array accordingly
const int IGNORED = NULL;
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
(void**)_polygons, _polygonDistances, IGNORED,
_polygonCount, _polygonArraySize);
// Debugging and Optimization Tuning code.
if (_polygonCount > _maxPolygonsUsed) {
_maxPolygonsUsed = _polygonCount;
//printLog("CoverageRegion new _maxPolygonsUsed reached=%d region=%s\n",_maxPolygonsUsed, getRegionName());
//_myBoundingBox.printDebugDetails("map._myBoundingBox");
} else {
//printLog("CoverageRegion::storeInArray() _polygonCount=%d region=%s\n",_polygonCount, getRegionName());
}
}
CoverageMapStorageResult CoverageRegion::checkRegion(VoxelProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt) {
CoverageMapStorageResult result = DOESNT_FIT;
if (_isRoot || _myBoundingBox.contains(polygonBox)) {
result = NOT_STORED; // if we got here, then we DO fit...
// 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;
} 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;
}
}
}
return result;
}

View file

@ -3,6 +3,7 @@
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef _COVERAGE_MAP_
@ -11,6 +12,45 @@
#include <glm/glm.hpp>
#include "VoxelProjectedPolygon.h"
typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} CoverageMapStorageResult;
typedef enum {TOP_HALF, BOTTOM_HALF, LEFT_HALF, RIGHT_HALF, REMAINDER} RegionName;
class CoverageRegion {
public:
CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons = true, RegionName regionName = REMAINDER);
~CoverageRegion();
CoverageMapStorageResult checkRegion(VoxelProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt);
void storeInArray(VoxelProjectedPolygon* polygon);
bool contains(const BoundingBox& box) const { return _myBoundingBox.contains(box); };
void erase(); // erase the coverage region
static int _maxPolygonsUsed;
static int _totalPolygons;
static int _occlusionTests;
static int _outOfOrderPolygon;
const char* getRegionName() const;
private:
void init();
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
BoundingBox _myBoundingBox;
bool _managePolygons; // will the coverage map delete the polygons on destruct
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;
float* _polygonDistances;
void growPolygonArray();
static const int DEFAULT_GROW_SIZE = 100;
};
class CoverageMap {
public:
@ -18,36 +58,37 @@ public:
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;
CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true);
~CoverageMap();
typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} StorageResult;
StorageResult checkMap(VoxelProjectedPolygon* polygon, bool storeIt = true);
CoverageMapStorageResult checkMap(VoxelProjectedPolygon* polygon, bool storeIt = true);
BoundingBox getChildBoundingBox(int childIndex);
void erase(); // erase the coverage map
static bool wantDebugging;
private:
void init();
void growPolygonArray();
void storeInArray(VoxelProjectedPolygon* polygon);
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
BoundingBox _myBoundingBox;
bool _managePolygons; // will the coverage map delete the polygons on destruct
int _polygonCount; // how many polygons at this level
int _polygonArraySize; // how much room is there to store polygons at this level
VoxelProjectedPolygon** _polygons;
float* _polygonDistances;
CoverageMap* _childMaps[NUMBER_OF_CHILDREN];
bool _managePolygons; // will the coverage map delete the polygons on destruct
// We divide the map into 5 regions representing each possible half of the map, and the whole map
// this allows us to keep the list of polygons shorter
CoverageRegion _topHalf;
CoverageRegion _bottomHalf;
CoverageRegion _leftHalf;
CoverageRegion _rightHalf;
CoverageRegion _remainder;
static const int DEFAULT_GROW_SIZE = 100;
static int _mapCount;
static int _maxPolygonsUsed;
static int _totalPolygons;
static int _mapCount;
static int _checkMapRootCalls;
};

View file

@ -115,6 +115,18 @@ void ViewFrustum::calculate() {
_planes[NEAR_PLANE ].set3Points(_nearBottomRight,_nearBottomLeft,_nearTopLeft);
_planes[FAR_PLANE ].set3Points(_farBottomLeft,_farBottomRight,_farTopRight);
// Also calculate our projection matrix in case people want to project points...
// Projection matrix : Field of View, ratio, display range : near to far
glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, _farClip);
glm::vec3 lookAt = _position + _direction;
glm::mat4 view = glm::lookAt(_position, lookAt, _up);
// Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it)
_ourModelViewProjectionMatrix = projection * view; // Remember, matrix multiplication is the other way around
// Set up our keyhole bounding box...
glm::vec3 corner = _position - _keyholeRadius;
_keyholeBoundingBox = AABox(corner,(_keyholeRadius * 2.0f));
}
//enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE };
@ -130,14 +142,14 @@ const char* ViewFrustum::debugPlaneName (int plane) const {
return "Unknown";
}
ViewFrustum::location ViewFrustum::pointInSphere(const glm::vec3& point, const glm::vec3& center, float radius ) const {
ViewFrustum::location ViewFrustum::pointInKeyhole(const glm::vec3& point) const {
ViewFrustum::location result = INTERSECT;
float distance = glm::distance(point, center);
if (distance > radius) {
float distance = glm::distance(point, _position);
if (distance > _keyholeRadius) {
result = OUTSIDE;
} else if (distance < radius) {
} else if (distance < _keyholeRadius) {
result = INSIDE;
}
@ -147,15 +159,13 @@ ViewFrustum::location ViewFrustum::pointInSphere(const glm::vec3& point, const g
// To determine if two spheres intersect, simply calculate the distance between the centers of the two spheres.
// If the distance is greater than the sum of the two sphere radii, they dont intersect. Otherwise they intersect.
// If the distance plus the radius of sphere A is less than the radius of sphere B then, sphere A is inside of sphere B
ViewFrustum::location ViewFrustum::sphereInSphere(const glm::vec3& centerA, float radiusA,
const glm::vec3& centerB, float radiusB ) const {
ViewFrustum::location ViewFrustum::sphereInKeyhole(const glm::vec3& center, float radius) const {
ViewFrustum::location result = INTERSECT;
float distanceFromAtoB = glm::distance(centerA, centerB);
if (distanceFromAtoB > (radiusA + radiusB)) {
float distance = glm::distance(center, _position);
if (distance > (radius + _keyholeRadius)) {
result = OUTSIDE;
} else if ((distanceFromAtoB + radiusA) < radiusB) {
} else if ((distance + radius) < _keyholeRadius) {
result = INSIDE;
}
@ -166,9 +176,16 @@ ViewFrustum::location ViewFrustum::sphereInSphere(const glm::vec3& centerA, floa
// A box is inside a sphere if all of its corners are inside the sphere
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
ViewFrustum::location ViewFrustum::boxInSphere(const AABox& box, const glm::vec3& center, float radius) const {
ViewFrustum::location ViewFrustum::boxInKeyhole(const AABox& box) const {
// First check to see if the box is in the bounding box for the sphere, if it's not, then we can short circuit
// this and not check with sphere penetration which is more expensive
if (!_keyholeBoundingBox.contains(box)) {
return OUTSIDE;
}
glm::vec3 penetration;
bool intersects = box.findSpherePenetration(center, radius, penetration);
bool intersects = box.findSpherePenetration(_position, _keyholeRadius, penetration);
ViewFrustum::location result = OUTSIDE;
@ -177,32 +194,18 @@ ViewFrustum::location ViewFrustum::boxInSphere(const AABox& box, const glm::vec3
result = INTERSECT;
// test all the corners, if they are all inside the sphere, the entire box is in the sphere
glm::vec3 testPoint = box.getCorner();
glm::vec3 size = box.getSize();
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(size.x, 0.0f, 0.0f);
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(0.0f, 0.0f, size.z);
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(size.x, 0.0f, size.z);
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(0.0f, size.y, 0.0f);
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(size.x, size.y, 0.0f);
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(0.0f, size.y, size.z);
if (pointInSphere(testPoint, center, radius)) {
testPoint = box.getCorner() + glm::vec3(size.x, size.y, size.z);
if (pointInSphere(testPoint, center, radius)) {
result = INSIDE;
}
}
}
}
}
}
bool allPointsInside = true; // assume the best
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
glm::vec3 vertex = box.getVertex((BoxVertex)v);
if (!pointInKeyhole(vertex)) {
allPointsInside = false;
break;
}
}
if (allPointsInside) {
result = INSIDE;
}
}
return result;
@ -214,7 +217,7 @@ ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point) const
// If we have a keyholeRadius, check that first, since it's cheaper
if (_keyholeRadius >= 0.0f) {
keyholeResult = pointInSphere(point, _position, _keyholeRadius);
keyholeResult = pointInKeyhole(point);
}
if (keyholeResult == INSIDE) {
return keyholeResult;
@ -237,7 +240,7 @@ ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, floa
// If we have a keyholeRadius, check that first, since it's cheaper
if (_keyholeRadius >= 0.0f) {
keyholeResult = sphereInSphere(center, radius, _position, _keyholeRadius);
keyholeResult = sphereInKeyhole(center, radius);
}
if (keyholeResult == INSIDE) {
return keyholeResult;
@ -264,7 +267,7 @@ ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
// If we have a keyholeRadius, check that first, since it's cheaper
if (_keyholeRadius >= 0.0f) {
keyholeResult = boxInSphere(box, _position, _keyholeRadius);
keyholeResult = boxInKeyhole(box);
}
if (keyholeResult == INSIDE) {
return keyholeResult;
@ -425,18 +428,10 @@ void ViewFrustum::printDebugDetails() const {
_eyeOffsetOrientation.w );
}
glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const {
// Projection matrix : Field of View, ratio, display range : near to far
glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, _farClip);
glm::vec3 lookAt = _position + _direction;
glm::mat4 view = glm::lookAt(_position, lookAt, _up);
// Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it)
glm::mat4 VP = projection * view; // Remember, matrix multiplication is the other way around
glm::vec4 pointVec4 = glm::vec4(point,1);
glm::vec4 projectedPointVec4 = VP * pointVec4;
glm::vec4 pointVec4 = glm::vec4(point,1) ;
glm::vec4 projectedPointVec4 = _ourModelViewProjectionMatrix * pointVec4;
pointInView = (projectedPointVec4.w > 0); // math! If the w result is negative then the point is behind the viewer
// what happens with w is 0???
@ -503,7 +498,7 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1]
{6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left
};
VoxelProjectedPolygon ViewFrustum::getProjectedShadow(const AABox& box) const {
VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const {
glm::vec3 bottomNearRight = box.getCorner();
glm::vec3 topFarLeft = box.getCorner() + box.getSize();
int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit

View file

@ -89,14 +89,14 @@ public:
void printDebugDetails() const;
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
VoxelProjectedPolygon getProjectedShadow(const AABox& box) const;
VoxelProjectedPolygon getProjectedPolygon(const AABox& box) const;
private:
// Used for keyhole calculations
ViewFrustum::location pointInSphere(const glm::vec3& point, const glm::vec3& center, float radius) const;
ViewFrustum::location sphereInSphere(const glm::vec3& centerA, float radiusA, const glm::vec3& centerB, float radiusB) const;
ViewFrustum::location boxInSphere(const AABox& box, const glm::vec3& center, float radius) const;
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
ViewFrustum::location sphereInKeyhole(const glm::vec3& center, float radius) const;
ViewFrustum::location boxInKeyhole(const AABox& box) const;
// camera location/orientation attributes
glm::vec3 _position;
@ -117,6 +117,7 @@ private:
// keyhole attributes
float _keyholeRadius;
AABox _keyholeBoundingBox;
// Calculated values
@ -137,6 +138,8 @@ private:
const char* debugPlaneName (int plane) const;
// Used to project points
glm::mat4 _ourModelViewProjectionMatrix;
};

View file

@ -10,6 +10,30 @@
#include "Log.h"
BoundingBox BoundingBox::topHalf() const {
float halfY = size.y/2.0f;
BoundingBox result(glm::vec2(corner.x,corner.y + halfY), glm::vec2(size.x, halfY));
return result;
}
BoundingBox BoundingBox::bottomHalf() const {
float halfY = size.y/2.0f;
BoundingBox result(corner, glm::vec2(size.x, halfY));
return result;
}
BoundingBox BoundingBox::leftHalf() const {
float halfX = size.x/2.0f;
BoundingBox result(corner, glm::vec2(halfX, size.y));
return result;
}
BoundingBox BoundingBox::rightHalf() const {
float halfX = size.x/2.0f;
BoundingBox result(glm::vec2(corner.x + halfX , corner.y), glm::vec2(halfX, size.y));
return result;
}
bool BoundingBox::contains(const BoundingBox& box) const {
return (
(box.corner.x >= corner.x) &&
@ -48,7 +72,7 @@ void VoxelProjectedPolygon::setVertex(int vertex, const glm::vec2& point) {
};
bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee) const {
bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView) const {
// if we are completely out of view, then we definitely don't occlude!
// if the occludee is completely out of view, then we also don't occlude it
@ -56,7 +80,7 @@ bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee) cons
// this is true, but unfortunately, we're not quite handling projects in the
// case when SOME points are in view and others are not. So, we will not consider
// occlusion for any shadows that are partially in view.
if (!getAllInView() || !occludee.getAllInView() ) {
if (checkAllInView && (!getAllInView() || !occludee.getAllInView())) {
return false;
}

View file

@ -20,6 +20,12 @@ public:
glm::vec2 corner;
glm::vec2 size;
bool contains(const BoundingBox& box) const;
float area() const { return size.x * size.y; };
BoundingBox topHalf() const;
BoundingBox bottomHalf() const;
BoundingBox leftHalf() const;
BoundingBox rightHalf() const;
void printDebugDetails(const char* label=NULL) const;
};
@ -48,7 +54,7 @@ public:
bool getAllInView() const { return _allInView; };
void setAllInView(bool allInView) { _allInView = allInView; };
bool occludes(const VoxelProjectedPolygon& occludee) const;
bool occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView = false) const;
bool pointInside(const glm::vec2& point) const;
float getMaxX() const { return _maxX; }

View file

@ -1125,16 +1125,16 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
//node->printDebugDetails("upper section, params.wantOcclusionCulling... node=");
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(params.viewFrustum->getProjectedShadow(voxelBox));
VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox));
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
// culling and proceed as normal
if (voxelShadow->getAllInView()) {
//node->printDebugDetails("upper section, voxelShadow->getAllInView() node=");
if (voxelPolygon->getAllInView()) {
//node->printDebugDetails("upper section, voxelPolygon->getAllInView() node=");
CoverageMap::StorageResult result = params.map->checkMap(voxelShadow, false);
delete voxelShadow; // cleanup
if (result == CoverageMap::OCCLUDED) {
CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false);
delete voxelPolygon; // cleanup
if (result == OCCLUDED) {
//node->printDebugDetails("upper section, non-Leaf is occluded!! node=");
//args->nonLeavesOccluded++;
@ -1147,7 +1147,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
//node->printDebugDetails("upper section, shadow Not in view node=");
// If this shadow wasn't "all in view" then we ignored it for occlusion culling, but
// we do need to clean up memory and proceed as normal...
delete voxelShadow;
delete voxelPolygon;
}
}
}
@ -1241,26 +1241,27 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
AABox voxelBox = childNode->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(params.viewFrustum->getProjectedShadow(voxelBox));
VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(
params.viewFrustum->getProjectedPolygon(voxelBox));
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
// culling and proceed as normal
if (voxelShadow->getAllInView()) {
CoverageMap::StorageResult result = params.map->checkMap(voxelShadow, true);
if (voxelPolygon->getAllInView()) {
CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true);
// In all cases where the shadow wasn't stored, we need to free our own memory.
// In the case where it is stored, the CoverageMap will free memory for us later.
if (result != CoverageMap::STORED) {
delete voxelShadow;
if (result != STORED) {
delete voxelPolygon;
}
// If while attempting to add this voxel's shadow, we determined it was occluded, then
// we don't need to process it further and we can exit early.
if (result == CoverageMap::OCCLUDED) {
if (result == OCCLUDED) {
childIsOccluded = true;
}
} else {
delete voxelShadow;
delete voxelPolygon;
}
} // wants occlusion culling & isLeaf()