mirror of
https://github.com/lubosz/overte.git
synced 2025-04-15 15:19:36 +02:00
Revert "Cruft removal"
This commit is contained in:
parent
46556b7fa5
commit
5a75a17174
21 changed files with 1272 additions and 85 deletions
|
@ -179,9 +179,15 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
_currentPacketIsColor = getWantColor();
|
||||
_currentPacketIsCompressed = getWantCompression();
|
||||
OCTREE_PACKET_FLAGS flags = 0;
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
if (_currentPacketIsColor) {
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
}
|
||||
if (_currentPacketIsCompressed) {
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
}
|
||||
|
||||
_octreePacket->reset();
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#include <CoverageMap.h>
|
||||
#include <NodeData.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <OctreeElementBag.h>
|
||||
|
@ -54,6 +55,7 @@ public:
|
|||
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
|
||||
|
||||
OctreeElementBag elementBag;
|
||||
CoverageMap map;
|
||||
OctreeElementExtraEncodeData extraEncodeData;
|
||||
|
||||
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }
|
||||
|
@ -77,7 +79,9 @@ public:
|
|||
|
||||
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }
|
||||
bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; }
|
||||
bool getCurrentPacketFormatMatches() { return (getCurrentPacketIsCompressed() == true); } // FIXME
|
||||
bool getCurrentPacketFormatMatches() {
|
||||
return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression());
|
||||
}
|
||||
|
||||
bool hasLodChanged() const { return _lodChanged; }
|
||||
|
||||
|
|
|
@ -321,6 +321,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// If we're starting a fresh packet, then...
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
bool wantColor = nodeData->getWantColor();
|
||||
bool wantCompression = nodeData->getWantCompression();
|
||||
|
||||
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
|
||||
// then let's just send that waiting packet.
|
||||
|
@ -331,8 +333,10 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
nodeData->resetOctreePacket();
|
||||
}
|
||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
_packetData.changeSettings(targetSize);
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
_packetData.changeSettings(wantCompression, targetSize);
|
||||
}
|
||||
|
||||
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||
|
@ -346,6 +350,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
|
||||
nodeData->dumpOutOfView();
|
||||
}
|
||||
nodeData->map.erase();
|
||||
}
|
||||
|
||||
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
|
||||
|
@ -446,15 +451,18 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
}
|
||||
*/
|
||||
|
||||
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
|
||||
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
|
||||
|
||||
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
||||
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(),
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
||||
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
|
||||
boundaryLevelAdjust, octreeSizeScale,
|
||||
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale,
|
||||
nodeData->getLastTimeBagEmpty(),
|
||||
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
|
||||
&nodeData->extraEncodeData);
|
||||
|
@ -548,7 +556,10 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
|
||||
quint64 packetSendingEnd = usecTimestampNow();
|
||||
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
} else {
|
||||
// If we're in compressed mode, then we want to see if we have room for more in this wire packet.
|
||||
// but we've finalized the _packetData, so we want to start a new section, we will do that by
|
||||
|
@ -558,7 +569,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// a larger compressed size then uncompressed size
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||
}
|
||||
_packetData.changeSettings(targetSize); // will do reset
|
||||
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
||||
|
||||
}
|
||||
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
||||
|
@ -623,6 +634,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
if (nodeData->elementBag.isEmpty()) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
nodeData->setViewSent(true);
|
||||
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
|
||||
}
|
||||
|
||||
} // end if bag wasn't empty, and so we sent stuff...
|
||||
|
|
|
@ -3074,7 +3074,10 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
|
||||
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
|
||||
_octreeQuery.setWantLowResMoving(true);
|
||||
_octreeQuery.setWantColor(true);
|
||||
_octreeQuery.setWantDelta(true);
|
||||
_octreeQuery.setWantOcclusionCulling(false);
|
||||
_octreeQuery.setWantCompression(true);
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
|
|
|
@ -790,9 +790,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
//
|
||||
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||
QByteArray& buffer) {
|
||||
|
||||
// FIXME - remove non-compressed OctreePacketData and handle compressed edit packets
|
||||
OctreePacketData ourDataPacket(buffer.size(), false); // create a packetData object to add out packet details too.
|
||||
OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
|
||||
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
|
||||
|
||||
bool success = true; // assume the best
|
||||
|
|
542
libraries/octree/src/CoverageMap.cpp
Normal file
542
libraries/octree/src/CoverageMap.cpp
Normal file
|
@ -0,0 +1,542 @@
|
|||
//
|
||||
// CoverageMap.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreeLogging.h"
|
||||
#include "CoverageMap.h"
|
||||
|
||||
int CoverageMap::_mapCount = 0;
|
||||
int CoverageMap::_checkMapRootCalls = 0;
|
||||
int CoverageMap::_notAllInView = 0;
|
||||
bool CoverageMap::wantDebugging = false;
|
||||
|
||||
const int MAX_POLYGONS_PER_REGION = 50;
|
||||
|
||||
const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.0f,-1.0f), glm::vec2(2.0f,2.0f));
|
||||
|
||||
// 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),
|
||||
_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();
|
||||
};
|
||||
|
||||
CoverageMap::~CoverageMap() {
|
||||
erase();
|
||||
};
|
||||
|
||||
void CoverageMap::printStats() {
|
||||
qCDebug(octree, "CoverageMap::printStats()...");
|
||||
qCDebug(octree, "MINIMUM_POLYGON_AREA_TO_STORE=%f", (double)MINIMUM_POLYGON_AREA_TO_STORE);
|
||||
qCDebug(octree, "_mapCount=%d",_mapCount);
|
||||
qCDebug(octree, "_checkMapRootCalls=%d",_checkMapRootCalls);
|
||||
qCDebug(octree, "_notAllInView=%d",_notAllInView);
|
||||
qCDebug(octree, "_maxPolygonsUsed=%d",CoverageRegion::_maxPolygonsUsed);
|
||||
qCDebug(octree, "_totalPolygons=%d",CoverageRegion::_totalPolygons);
|
||||
qCDebug(octree, "_occlusionTests=%d",CoverageRegion::_occlusionTests);
|
||||
qCDebug(octree, "_regionSkips=%d",CoverageRegion::_regionSkips);
|
||||
qCDebug(octree, "_tooSmallSkips=%d",CoverageRegion::_tooSmallSkips);
|
||||
qCDebug(octree, "_regionFullSkips=%d",CoverageRegion::_regionFullSkips);
|
||||
qCDebug(octree, "_outOfOrderPolygon=%d",CoverageRegion::_outOfOrderPolygon);
|
||||
qCDebug(octree, "_clippedPolygons=%d",CoverageRegion::_clippedPolygons);
|
||||
}
|
||||
|
||||
void CoverageMap::erase() {
|
||||
// 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];
|
||||
_childMaps[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isRoot && wantDebugging) {
|
||||
qCDebug(octree, "CoverageMap last to be deleted...");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void CoverageMap::init() {
|
||||
memset(_childMaps,0,sizeof(_childMaps));
|
||||
}
|
||||
|
||||
// 0 = bottom, right
|
||||
// 1 = bottom, left
|
||||
// 2 = top, right
|
||||
// 3 = top, left
|
||||
BoundingBox CoverageMap::getChildBoundingBox(int childIndex) {
|
||||
const int LEFT_BIT = 1;
|
||||
const int TOP_BIT = 2;
|
||||
// initialize to our corner, and half our size
|
||||
BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f);
|
||||
// if our "left" bit is set, then add size.x to the corner
|
||||
if ((childIndex & LEFT_BIT) == LEFT_BIT) {
|
||||
result.corner.x += result.size.x;
|
||||
}
|
||||
// if our "top" bit is set, then add size.y to the corner
|
||||
if ((childIndex & TOP_BIT) == TOP_BIT) {
|
||||
result.corner.y += result.size.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int CoverageMap::getPolygonCount() const {
|
||||
return (_topHalf.getPolygonCount() +
|
||||
_bottomHalf.getPolygonCount() +
|
||||
_leftHalf.getPolygonCount() +
|
||||
_rightHalf.getPolygonCount() +
|
||||
_remainder.getPolygonCount());
|
||||
}
|
||||
|
||||
OctreeProjectedPolygon* 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(OctreeProjectedPolygon* 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()) {
|
||||
_notAllInView++;
|
||||
return DOESNT_FIT;
|
||||
}
|
||||
|
||||
BoundingBox polygonBox(polygon->getBoundingBox());
|
||||
if (_isRoot || _myBoundingBox.contains(polygonBox)) {
|
||||
|
||||
CoverageMapStorageResult result = NOT_STORED;
|
||||
CoverageRegion* storeIn = &_remainder;
|
||||
|
||||
// Check each half of the box independently
|
||||
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;
|
||||
} else if (_bottomHalf.contains(polygonBox)) {
|
||||
result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_bottomHalf;
|
||||
} else if (_leftHalf.contains(polygonBox)) {
|
||||
result = _leftHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_leftHalf;
|
||||
} else if (_rightHalf.contains(polygonBox)) {
|
||||
result = _rightHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_rightHalf;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
/*
|
||||
if (result == STORED)
|
||||
qCDebug(octree, "CoverageMap2::checkMap()... STORED\n");
|
||||
else
|
||||
qCDebug(octree, "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
|
||||
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:
|
||||
qCDebug(octree, "checkMap() = STORED\n");
|
||||
break;
|
||||
case NOT_STORED:
|
||||
qCDebug(octree, "checkMap() = NOT_STORED\n");
|
||||
break;
|
||||
case OCCLUDED:
|
||||
qCDebug(octree, "checkMap() = OCCLUDED\n");
|
||||
break;
|
||||
default:
|
||||
qCDebug(octree, "checkMap() = ????? \n");
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
if (storeIn->getPolygonCount() < MAX_POLYGONS_PER_REGION) {
|
||||
storeIn->storeInArray(polygon);
|
||||
return STORED;
|
||||
} else {
|
||||
CoverageRegion::_regionFullSkips++;
|
||||
return NOT_STORED;
|
||||
}
|
||||
} else {
|
||||
CoverageRegion::_tooSmallSkips++;
|
||||
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;
|
||||
_polygonSizes = NULL;
|
||||
}
|
||||
|
||||
|
||||
void CoverageRegion::erase() {
|
||||
|
||||
/**
|
||||
if (_polygonCount) {
|
||||
qCDebug(octree, "CoverageRegion::erase()...\n");
|
||||
qCDebug(octree, "_polygonCount=%d\n",_polygonCount);
|
||||
_myBoundingBox.printDebugDetails(getRegionName());
|
||||
//for (int i = 0; i < _polygonCount; i++) {
|
||||
// qCDebug(octree, "_polygons[%d]=",i);
|
||||
// _polygons[i]->getBoundingBox().printDebugDetails();
|
||||
//}
|
||||
}
|
||||
**/
|
||||
// If we're in charge of managing the polygons, then clean them up first
|
||||
if (_polygons && _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;
|
||||
}
|
||||
if (_polygonSizes) {
|
||||
delete[] _polygonSizes;
|
||||
_polygonSizes = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoverageRegion::growPolygonArray() {
|
||||
OctreeProjectedPolygon** newPolygons = new OctreeProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE];
|
||||
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
|
||||
float* newSizes = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
|
||||
|
||||
|
||||
if (_polygons) {
|
||||
memcpy(newPolygons, _polygons, sizeof(OctreeProjectedPolygon*) * _polygonCount);
|
||||
delete[] _polygons;
|
||||
memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount);
|
||||
delete[] _polygonDistances;
|
||||
memcpy(newSizes, _polygonSizes, sizeof(float) * _polygonCount);
|
||||
delete[] _polygonSizes;
|
||||
}
|
||||
_polygons = newPolygons;
|
||||
_polygonDistances = newDistances;
|
||||
_polygonSizes = newSizes;
|
||||
_polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE;
|
||||
}
|
||||
|
||||
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::_regionSkips = 0;
|
||||
int CoverageRegion::_tooSmallSkips = 0;
|
||||
int CoverageRegion::_regionFullSkips = 0;
|
||||
int CoverageRegion::_outOfOrderPolygon = 0;
|
||||
int CoverageRegion::_clippedPolygons = 0;
|
||||
|
||||
|
||||
bool CoverageRegion::mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray) {
|
||||
for (int i = 0; i < _polygonCount; i++) {
|
||||
OctreeProjectedPolygon* otherPolygon = _polygons[i];
|
||||
if (otherPolygon->canMerge(*seed)) {
|
||||
otherPolygon->merge(*seed);
|
||||
|
||||
if (seedInArray) {
|
||||
int* IGNORED_ADDRESS = NULL;
|
||||
// remove this otherOtherPolygon for our polygon array
|
||||
_polygonCount = removeFromSortedArrays((void*)seed,
|
||||
(void**)_polygons, _polygonDistances, IGNORED_ADDRESS,
|
||||
_polygonCount, _polygonArraySize);
|
||||
_totalPolygons--;
|
||||
}
|
||||
|
||||
// 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(OctreeProjectedPolygon* 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();
|
||||
}
|
||||
|
||||
// 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 = 0;
|
||||
int* IGNORED_ADDRESS = NULL;
|
||||
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;
|
||||
_polygonCount = insertIntoSortedArrays((void*)polygon, reverseArea, IGNORED,
|
||||
(void**)_polygons, _polygonSizes, IGNORED_ADDRESS,
|
||||
_polygonCount, _polygonArraySize);
|
||||
} else {
|
||||
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
|
||||
(void**)_polygons, _polygonDistances, IGNORED_ADDRESS,
|
||||
_polygonCount, _polygonArraySize);
|
||||
}
|
||||
|
||||
// Debugging and Optimization Tuning code.
|
||||
if (_polygonCount > _maxPolygonsUsed) {
|
||||
_maxPolygonsUsed = _polygonCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
CoverageMapStorageResult CoverageRegion::checkRegion(OctreeProjectedPolygon* 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...
|
||||
|
||||
// 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++) {
|
||||
OctreeProjectedPolygon* 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// this polygon is occluded by a closer polygon, so don't store it, and let the caller know
|
||||
return OCCLUDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
120
libraries/octree/src/CoverageMap.h
Normal file
120
libraries/octree/src/CoverageMap.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// CoverageMap.h
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// 2D CoverageMap Quad tree for storage of OctreeProjectedPolygons
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CoverageMap_h
|
||||
#define hifi_CoverageMap_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include "OctreeProjectedPolygon.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(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt);
|
||||
void storeInArray(OctreeProjectedPolygon* 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 _regionSkips;
|
||||
static int _tooSmallSkips;
|
||||
static int _regionFullSkips;
|
||||
static int _outOfOrderPolygon;
|
||||
static int _clippedPolygons;
|
||||
|
||||
|
||||
const char* getRegionName() const;
|
||||
|
||||
int getPolygonCount() const { return _polygonCount; };
|
||||
OctreeProjectedPolygon* 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
|
||||
OctreeProjectedPolygon** _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(OctreeProjectedPolygon* seed, bool seedInArray);
|
||||
|
||||
};
|
||||
|
||||
class CoverageMap {
|
||||
|
||||
public:
|
||||
static const int NUMBER_OF_CHILDREN = 4;
|
||||
static const bool NOT_ROOT=false;
|
||||
static const bool IS_ROOT=true;
|
||||
static const BoundingBox ROOT_BOUNDING_BOX;
|
||||
static const float MINIMUM_POLYGON_AREA_TO_STORE;
|
||||
|
||||
CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true);
|
||||
~CoverageMap();
|
||||
|
||||
CoverageMapStorageResult checkMap(OctreeProjectedPolygon* polygon, bool storeIt = true);
|
||||
|
||||
BoundingBox getChildBoundingBox(int childIndex);
|
||||
|
||||
void erase(); // erase the coverage map
|
||||
void printStats();
|
||||
|
||||
static bool wantDebugging;
|
||||
|
||||
int getPolygonCount() const;
|
||||
OctreeProjectedPolygon* getPolygon(int index) const;
|
||||
CoverageMap* getChild(int childIndex) const { return _childMaps[childIndex]; };
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
|
||||
BoundingBox _myBoundingBox;
|
||||
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 int _mapCount;
|
||||
static int _checkMapRootCalls;
|
||||
static int _notAllInView;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_CoverageMap_h
|
251
libraries/octree/src/CoverageMapV2.cpp
Normal file
251
libraries/octree/src/CoverageMapV2.cpp
Normal file
|
@ -0,0 +1,251 @@
|
|||
//
|
||||
// CoverageMapV2.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreeLogging.h"
|
||||
#include "CoverageMapV2.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.0f,-1.0f), glm::vec2(2.0f,2.0f));
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
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) {
|
||||
qCDebug(octree, "CoverageMapV2 last to be deleted...");
|
||||
qCDebug(octree, "MINIMUM_POLYGON_AREA_TO_STORE=%f", (double)MINIMUM_POLYGON_AREA_TO_STORE);
|
||||
qCDebug(octree, "_mapCount=%d",_mapCount);
|
||||
qCDebug(octree, "_checkMapRootCalls=%d",_checkMapRootCalls);
|
||||
qCDebug(octree, "_notAllInView=%d",_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 OctreeProjectedPolygon* 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 OctreeProjectedPolygon* 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...
|
||||
}
|
72
libraries/octree/src/CoverageMapV2.h
Normal file
72
libraries/octree/src/CoverageMapV2.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// CoverageMapV2.h
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CoverageMapV2_h
|
||||
#define hifi_CoverageMapV2_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "OctreeProjectedPolygon.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 OctreeProjectedPolygon* 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 OctreeProjectedPolygon* 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 // hifi_CoverageMapV2_h
|
|
@ -41,6 +41,7 @@
|
|||
#include <PathUtils.h>
|
||||
#include <Gzip.h>
|
||||
|
||||
#include "CoverageMap.h"
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeElementBag.h"
|
||||
#include "Octree.h"
|
||||
|
@ -950,9 +951,9 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||
assert(childBytesWritten != 1);
|
||||
|
||||
// if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// reason couldn't be written... so reset them here... This isn't true for the non-color included case
|
||||
if (suppressEmptySubtrees() && childBytesWritten == 2) {
|
||||
if (suppressEmptySubtrees() && params.includeColor && childBytesWritten == 2) {
|
||||
childBytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT...
|
||||
}
|
||||
|
@ -1102,6 +1103,31 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// If the user also asked for occlusion culling, check if this element is occluded, but only if it's not a leaf.
|
||||
// leaf occlusion is handled down below when we check child nodes
|
||||
if (params.wantOcclusionCulling && !element->isLeaf()) {
|
||||
OctreeProjectedPolygon* voxelPolygon =
|
||||
new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(element->getAACube()));
|
||||
|
||||
// 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 (voxelPolygon->getAllInView()) {
|
||||
CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false);
|
||||
delete voxelPolygon; // cleanup
|
||||
if (result == OCCLUDED) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedOccluded(element);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::OCCLUDED;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
} else {
|
||||
// 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 voxelPolygon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
|
||||
|
@ -1164,10 +1190,20 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
}
|
||||
|
||||
sortedChildren[i] = childElement;
|
||||
indexOfChildren[i] = i;
|
||||
distancesToChildren[i] = 0.0f;
|
||||
currentCount++;
|
||||
if (params.wantOcclusionCulling) {
|
||||
if (childElement) {
|
||||
float distance = params.viewFrustum ? childElement->distanceToCamera(*params.viewFrustum) : 0;
|
||||
|
||||
currentCount = insertOctreeElementIntoSortedArrays(childElement, distance, i,
|
||||
sortedChildren, (float*)&distancesToChildren,
|
||||
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
|
||||
}
|
||||
} else {
|
||||
sortedChildren[i] = childElement;
|
||||
indexOfChildren[i] = i;
|
||||
distancesToChildren[i] = 0.0f;
|
||||
currentCount++;
|
||||
}
|
||||
|
||||
// track stats
|
||||
// must check childElement here, because it could be we got here with no childElement
|
||||
|
@ -1219,6 +1255,36 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
|
||||
bool childIsOccluded = false; // assume it's not occluded
|
||||
|
||||
// If the user also asked for occlusion culling, check if this element is occluded
|
||||
if (params.wantOcclusionCulling && childElement->isLeaf()) {
|
||||
// Don't check occlusion here, just add them to our distance ordered array...
|
||||
|
||||
// FIXME params.ViewFrustum is used here, but later it is checked against nullptr.
|
||||
OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(
|
||||
params.viewFrustum->getProjectedPolygon(childElement->getAACube()));
|
||||
|
||||
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we ignore occlusion
|
||||
// culling and proceed as normal
|
||||
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 != 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 == OCCLUDED) {
|
||||
childIsOccluded = true;
|
||||
}
|
||||
} else {
|
||||
delete voxelPolygon;
|
||||
}
|
||||
} // wants occlusion culling & isLeaf()
|
||||
|
||||
|
||||
bool shouldRender = !params.viewFrustum
|
||||
? true
|
||||
: childElement->calculateShouldRender(params.viewFrustum,
|
||||
|
@ -1293,7 +1359,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data
|
||||
element->initializeExtraEncodeData(params);
|
||||
|
||||
// write the child element data...
|
||||
// write the child element data... NOTE: includeColor means include element data
|
||||
// NOTE: the format of the bitstream is generally this:
|
||||
// [octalcode]
|
||||
// [bitmask for existence of child data]
|
||||
|
@ -1303,63 +1369,65 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// N x [ ... tree for children ...]
|
||||
//
|
||||
// This section of the code, is writing the "N x [child data]" portion of this bitstream
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (oneAtBit(childrenDataBits, i)) {
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
||||
if (params.includeColor) {
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (oneAtBit(childrenDataBits, i)) {
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
||||
|
||||
// the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already
|
||||
// processed and sent the data bits for. Let our tree subclass determine if it really wants to send the
|
||||
// data for this child at this point
|
||||
if (childElement && element->shouldIncludeChildData(i, params)) {
|
||||
// the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already
|
||||
// processed and sent the data bits for. Let our tree subclass determine if it really wants to send the
|
||||
// data for this child at this point
|
||||
if (childElement && element->shouldIncludeChildData(i, params)) {
|
||||
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
|
||||
// a childElement may "partially" write it's data. for example, the model server where the entire
|
||||
// contents of the element may be larger than can fit in a single MTU/packetData. In this case,
|
||||
// we want to allow the appendElementData() to respond that it produced partial data, which should be
|
||||
// written, but that the childElement needs to be reprocessed in an additional pass or passes
|
||||
// to be completed.
|
||||
LevelDetails childDataLevelKey = packetData->startLevel();
|
||||
// a childElement may "partially" write it's data. for example, the model server where the entire
|
||||
// contents of the element may be larger than can fit in a single MTU/packetData. In this case,
|
||||
// we want to allow the appendElementData() to respond that it produced partial data, which should be
|
||||
// written, but that the childElement needs to be reprocessed in an additional pass or passes
|
||||
// to be completed.
|
||||
LevelDetails childDataLevelKey = packetData->startLevel();
|
||||
|
||||
OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params);
|
||||
OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params);
|
||||
|
||||
// allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state
|
||||
element->updateEncodedData(i, childAppendState, params);
|
||||
// allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state
|
||||
element->updateEncodedData(i, childAppendState, params);
|
||||
|
||||
// Continue this level so long as some part of this child element was appended.
|
||||
bool childFit = (childAppendState != OctreeElement::NONE);
|
||||
// Continue this level so long as some part of this child element was appended.
|
||||
bool childFit = (childAppendState != OctreeElement::NONE);
|
||||
|
||||
// some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit
|
||||
// the data type wants to bail on this element level completely
|
||||
if (!childFit && mustIncludeAllChildData()) {
|
||||
continueThisLevel = false;
|
||||
break;
|
||||
}
|
||||
// some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit
|
||||
// the data type wants to bail on this element level completely
|
||||
if (!childFit && mustIncludeAllChildData()) {
|
||||
continueThisLevel = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the child was partially or fully appended, then mark the actualChildrenDataBits as including
|
||||
// this child data
|
||||
if (childFit) {
|
||||
actualChildrenDataBits += (1 << (7 - i));
|
||||
continueThisLevel = packetData->endLevel(childDataLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(childDataLevelKey);
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
// If the child was partially or fully appended, then mark the actualChildrenDataBits as including
|
||||
// this child data
|
||||
if (childFit) {
|
||||
actualChildrenDataBits += (1 << (7 - i));
|
||||
continueThisLevel = packetData->endLevel(childDataLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(childDataLevelKey);
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
// If this child was partially appended, then consider this element to be partially appended
|
||||
if (childAppendState == OctreeElement::PARTIAL) {
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
// If this child was partially appended, then consider this element to be partially appended
|
||||
if (childAppendState == OctreeElement::PARTIAL) {
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (params.stats && (childAppendState != OctreeElement::NONE)) {
|
||||
params.stats->colorSent(childElement);
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (params.stats && (childAppendState != OctreeElement::NONE)) {
|
||||
params.stats->colorSent(childElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1438,6 +1506,15 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was!
|
||||
int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits));
|
||||
|
||||
// we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the
|
||||
// final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes,
|
||||
// and then later reshuffle these sections of our output buffer back into normal order. This allows us to make
|
||||
// a single recursive pass in distance sorted order, but retain standard order in our encoded packet
|
||||
int recursiveSliceSizes[NUMBER_OF_CHILDREN];
|
||||
const unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN];
|
||||
int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset();
|
||||
int allSlicesSize = 0;
|
||||
|
||||
// for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
|
||||
// add them to our distance ordered array of children
|
||||
for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) {
|
||||
|
@ -1447,6 +1524,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
||||
|
||||
int thisLevel = currentEncodeLevel;
|
||||
// remember this for reshuffling
|
||||
recursiveSliceStarts[originalIndex] = packetData->getUncompressedData() + packetData->getUncompressedSize();
|
||||
|
||||
int childTreeBytesOut = 0;
|
||||
|
||||
// NOTE: some octree styles (like models and particles) will store content in parent elements, and child
|
||||
|
@ -1466,6 +1546,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
}
|
||||
|
||||
// remember this for reshuffling
|
||||
recursiveSliceSizes[originalIndex] = childTreeBytesOut;
|
||||
allSlicesSize += childTreeBytesOut;
|
||||
|
||||
// if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space,
|
||||
// basically, the children below don't contain any info.
|
||||
|
||||
|
@ -1482,10 +1566,17 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// so, if the child returns 2 bytes out, we can actually consider that an empty tree also!!
|
||||
//
|
||||
// we can make this act like no bytes out, by just resetting the bytes out in this case
|
||||
if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) {
|
||||
if (suppressEmptySubtrees() && params.includeColor && !params.includeExistsBits && childTreeBytesOut == 2) {
|
||||
childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees
|
||||
|
||||
}
|
||||
// We used to try to collapse trees that didn't contain any data, but this does appear to create a problem
|
||||
// in detecting element deletion. So, I've commented this out but left it in here as a warning to anyone else
|
||||
// about not attempting to add this optimization back in, without solving the element deletion case.
|
||||
// We need to send these bitMasks in case the exists in tree bitmask is indicating the deletion of a tree
|
||||
//if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) {
|
||||
// childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees
|
||||
//}
|
||||
|
||||
bytesAtThisLevel += childTreeBytesOut;
|
||||
|
||||
|
@ -1505,7 +1596,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
|
||||
// If this is the last of the child exists bits, then we're actually be rolling out the entire tree
|
||||
if (params.stats && childrenExistInPacketBits == 0) {
|
||||
params.stats->childBitsRemoved(params.includeExistsBits);
|
||||
params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor);
|
||||
}
|
||||
|
||||
if (!continueThisLevel) {
|
||||
|
@ -1522,6 +1613,33 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
} // end if (childTreeBytesOut == 0)
|
||||
} // end if (oneAtBit(childrenExistInPacketBits, originalIndex))
|
||||
} // end for
|
||||
|
||||
// reshuffle here...
|
||||
if (continueThisLevel && params.wantOcclusionCulling) {
|
||||
unsigned char tempReshuffleBuffer[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE];
|
||||
|
||||
unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination
|
||||
|
||||
// iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree
|
||||
// details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them
|
||||
// back into original distance order
|
||||
for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) {
|
||||
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
||||
int thisSliceSize = recursiveSliceSizes[originalIndex];
|
||||
const unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex];
|
||||
|
||||
memcpy(tempBufferTo, thisSliceStarts, thisSliceSize);
|
||||
tempBufferTo += thisSliceSize;
|
||||
}
|
||||
}
|
||||
|
||||
// now that all slices are back in the correct order, copy them to the correct output buffer
|
||||
continueThisLevel = packetData->updatePriorBytes(firstRecursiveSliceOffset, &tempReshuffleBuffer[0], allSlicesSize);
|
||||
if (!continueThisLevel) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update recursive slice!!!";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
} // end keepDiggingDeeper
|
||||
|
||||
// If we made it this far, then we've written all of our child data... if this element is the root
|
||||
|
@ -1800,7 +1918,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr
|
|||
|
||||
unsigned char* dataAt = entireFileDataSection;
|
||||
|
||||
ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0,
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
|
||||
SharedNodePointer(), wantImportProgress, gotVersion);
|
||||
|
||||
readBitstreamToTree(dataAt, dataLength, args);
|
||||
|
@ -1839,7 +1957,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr
|
|||
unsigned char* dataAt = fileChunk;
|
||||
unsigned long dataLength = chunkLength;
|
||||
|
||||
ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0,
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
|
||||
SharedNodePointer(), wantImportProgress, gotVersion);
|
||||
|
||||
readBitstreamToTree(dataAt, dataLength, args);
|
||||
|
@ -1987,7 +2105,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element)
|
|||
bool lastPacketWritten = false;
|
||||
|
||||
while (OctreeElementPointer subTree = elementBag.extract()) {
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, NO_EXISTS_BITS);
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
|
||||
withReadLock([&] {
|
||||
params.extraEncodeData = &extraEncodeData;
|
||||
bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params);
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "OctreePacketData.h"
|
||||
#include "OctreeSceneStats.h"
|
||||
|
||||
class CoverageMap;
|
||||
class ReadBitstreamToTreeParams;
|
||||
class Octree;
|
||||
class OctreeElement;
|
||||
|
@ -52,8 +53,12 @@ typedef QHash<uint, AACube> CubeList;
|
|||
|
||||
const bool NO_EXISTS_BITS = false;
|
||||
const bool WANT_EXISTS_BITS = true;
|
||||
const bool NO_COLOR = false;
|
||||
const bool WANT_COLOR = true;
|
||||
const bool COLLAPSE_EMPTY_TREE = true;
|
||||
const bool DONT_COLLAPSE = false;
|
||||
const bool NO_OCCLUSION_CULLING = false;
|
||||
const bool WANT_OCCLUSION_CULLING = true;
|
||||
|
||||
const int DONT_CHOP = 0;
|
||||
const int NO_BOUNDARY_ADJUST = 0;
|
||||
|
@ -70,15 +75,18 @@ public:
|
|||
int maxEncodeLevel;
|
||||
int maxLevelReached;
|
||||
const ViewFrustum* viewFrustum;
|
||||
bool includeColor;
|
||||
bool includeExistsBits;
|
||||
int chopLevels;
|
||||
bool deltaViewFrustum;
|
||||
const ViewFrustum* lastViewFrustum;
|
||||
bool wantOcclusionCulling;
|
||||
int boundaryLevelAdjust;
|
||||
float octreeElementSizeScale;
|
||||
quint64 lastViewFrustumSent;
|
||||
bool forceSendScene;
|
||||
OctreeSceneStats* stats;
|
||||
CoverageMap* map;
|
||||
JurisdictionMap* jurisdictionMap;
|
||||
OctreeElementExtraEncodeData* extraEncodeData;
|
||||
|
||||
|
@ -100,10 +108,13 @@ public:
|
|||
EncodeBitstreamParams(
|
||||
int maxEncodeLevel = INT_MAX,
|
||||
const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM,
|
||||
bool includeColor = WANT_COLOR,
|
||||
bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
int chopLevels = 0,
|
||||
bool deltaViewFrustum = false,
|
||||
const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM,
|
||||
bool wantOcclusionCulling = NO_OCCLUSION_CULLING,
|
||||
CoverageMap* map = IGNORE_COVERAGE_MAP,
|
||||
int boundaryLevelAdjust = NO_BOUNDARY_ADJUST,
|
||||
float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
quint64 lastViewFrustumSent = IGNORE_LAST_SENT,
|
||||
|
@ -114,15 +125,18 @@ public:
|
|||
maxEncodeLevel(maxEncodeLevel),
|
||||
maxLevelReached(0),
|
||||
viewFrustum(viewFrustum),
|
||||
includeColor(includeColor),
|
||||
includeExistsBits(includeExistsBits),
|
||||
chopLevels(chopLevels),
|
||||
deltaViewFrustum(deltaViewFrustum),
|
||||
lastViewFrustum(lastViewFrustum),
|
||||
wantOcclusionCulling(wantOcclusionCulling),
|
||||
boundaryLevelAdjust(boundaryLevelAdjust),
|
||||
octreeElementSizeScale(octreeElementSizeScale),
|
||||
lastViewFrustumSent(lastViewFrustumSent),
|
||||
forceSendScene(forceSendScene),
|
||||
stats(stats),
|
||||
map(map),
|
||||
jurisdictionMap(jurisdictionMap),
|
||||
extraEncodeData(extraEncodeData),
|
||||
stopReason(UNKNOWN)
|
||||
|
@ -176,6 +190,7 @@ public:
|
|||
|
||||
class ReadBitstreamToTreeParams {
|
||||
public:
|
||||
bool includeColor;
|
||||
bool includeExistsBits;
|
||||
OctreeElementPointer destinationElement;
|
||||
QUuid sourceUUID;
|
||||
|
@ -186,12 +201,14 @@ public:
|
|||
int entitiesPerPacket = 0;
|
||||
|
||||
ReadBitstreamToTreeParams(
|
||||
bool includeColor = WANT_COLOR,
|
||||
bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
OctreeElementPointer destinationElement = NULL,
|
||||
QUuid sourceUUID = QUuid(),
|
||||
SharedNodePointer sourceNode = SharedNodePointer(),
|
||||
bool wantImportProgress = false,
|
||||
PacketVersion bitstreamVersion = 0) :
|
||||
includeColor(includeColor),
|
||||
includeExistsBits(includeExistsBits),
|
||||
destinationElement(destinationElement),
|
||||
sourceUUID(sourceUUID),
|
||||
|
@ -298,7 +315,7 @@ public:
|
|||
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
|
||||
|
||||
// Note: this assumes the fileFormat is the HIO individual voxels code files
|
||||
void loadOctreeFile(const char* fileName);
|
||||
void loadOctreeFile(const char* fileName, bool wantColorRandomizer);
|
||||
|
||||
// Octree exporters
|
||||
void writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo");
|
||||
|
|
|
@ -51,7 +51,10 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
|
||||
_octreeQuery.setWantLowResMoving(true);
|
||||
_octreeQuery.setWantColor(true);
|
||||
_octreeQuery.setWantDelta(true);
|
||||
_octreeQuery.setWantOcclusionCulling(false);
|
||||
_octreeQuery.setWantCompression(true); // TODO: should be on by default
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
|
|
|
@ -23,13 +23,12 @@ AtomicUIntStat OctreePacketData::_totalBytesOfValues { 0 };
|
|||
AtomicUIntStat OctreePacketData::_totalBytesOfPositions { 0 };
|
||||
AtomicUIntStat OctreePacketData::_totalBytesOfRawData { 0 };
|
||||
|
||||
OctreePacketData::OctreePacketData(int targetSize, bool enableCompression) {
|
||||
changeSettings(targetSize); // does reset...
|
||||
_enableCompression = enableCompression; // FIXME
|
||||
OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) {
|
||||
changeSettings(enableCompression, targetSize); // does reset...
|
||||
}
|
||||
|
||||
void OctreePacketData::changeSettings(unsigned int targetSize) {
|
||||
_enableCompression = true; // FIXME
|
||||
void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) {
|
||||
_enableCompression = enableCompression;
|
||||
_targetSize = std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize);
|
||||
reset();
|
||||
}
|
||||
|
|
|
@ -83,11 +83,11 @@ private:
|
|||
/// Handles packing of the data portion of PacketType_OCTREE_DATA messages.
|
||||
class OctreePacketData {
|
||||
public:
|
||||
OctreePacketData(int maxFinalizedSize = MAX_OCTREE_PACKET_DATA_SIZE, bool enableCompression = true);
|
||||
OctreePacketData(bool enableCompression = false, int maxFinalizedSize = MAX_OCTREE_PACKET_DATA_SIZE);
|
||||
~OctreePacketData();
|
||||
|
||||
/// change compression and target size settings
|
||||
void changeSettings(unsigned int targetSize = MAX_OCTREE_PACKET_DATA_SIZE);
|
||||
void changeSettings(bool enableCompression = false, unsigned int targetSize = MAX_OCTREE_PACKET_DATA_SIZE);
|
||||
|
||||
/// reset completely, all data is discarded
|
||||
void reset();
|
||||
|
@ -262,7 +262,7 @@ private:
|
|||
bool append(unsigned char byte);
|
||||
|
||||
unsigned int _targetSize;
|
||||
bool _enableCompression { true }; // FIXME - these will always be compressed, so remove this option
|
||||
bool _enableCompression;
|
||||
|
||||
unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE];
|
||||
int _bytesInUse;
|
||||
|
|
|
@ -41,7 +41,10 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
// bitMask of less than byte wide items
|
||||
unsigned char bitItems = 0;
|
||||
if (_wantLowResMoving) { setAtBit(bitItems, WANT_LOW_RES_MOVING_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); }
|
||||
if (_wantCompression) { setAtBit(bitItems, WANT_COMPRESSION); }
|
||||
|
||||
*destinationBuffer++ = bitItems;
|
||||
|
||||
|
@ -81,7 +84,10 @@ int OctreeQuery::parseData(NLPacket& packet) {
|
|||
unsigned char bitItems = 0;
|
||||
bitItems = (unsigned char)*sourceBuffer++;
|
||||
_wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT);
|
||||
_wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT);
|
||||
_wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT);
|
||||
_wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT);
|
||||
_wantCompression = oneAtBit(bitItems, WANT_COMPRESSION);
|
||||
|
||||
// desired Max Octree PPS
|
||||
memcpy(&_maxQueryPPS, sourceBuffer, sizeof(_maxQueryPPS));
|
||||
|
|
|
@ -35,10 +35,10 @@ typedef unsigned long long quint64;
|
|||
|
||||
// First bitset
|
||||
const int WANT_LOW_RES_MOVING_BIT = 0;
|
||||
const int UNUSED_BIT_1 = 1; // unused... available for new feature
|
||||
const int WANT_COLOR_AT_BIT = 1;
|
||||
const int WANT_DELTA_AT_BIT = 2;
|
||||
const int UNUSED_BIT_3 = 3; // unused... available for new feature
|
||||
const int UNUSED_BIT_4 = 4; // 5th bit, unused... available for new feature
|
||||
const int WANT_OCCLUSION_CULLING_BIT = 3;
|
||||
const int WANT_COMPRESSION = 4; // 5th bit
|
||||
|
||||
class OctreeQuery : public NodeData {
|
||||
Q_OBJECT
|
||||
|
@ -71,15 +71,21 @@ public:
|
|||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
|
||||
// related to Octree Sending strategies
|
||||
bool getWantColor() const { return _wantColor; }
|
||||
bool getWantDelta() const { return _wantDelta; }
|
||||
bool getWantLowResMoving() const { return _wantLowResMoving; }
|
||||
bool getWantOcclusionCulling() const { return _wantOcclusionCulling; }
|
||||
bool getWantCompression() const { return _wantCompression; }
|
||||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
float getOctreeSizeScale() const { return _octreeElementSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
public slots:
|
||||
void setWantLowResMoving(bool wantLowResMoving) { _wantLowResMoving = wantLowResMoving; }
|
||||
void setWantColor(bool wantColor) { _wantColor = wantColor; }
|
||||
void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; }
|
||||
void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; }
|
||||
void setWantCompression(bool wantCompression) { _wantCompression = wantCompression; }
|
||||
void setMaxQueryPacketsPerSecond(int maxQueryPPS) { _maxQueryPPS = maxQueryPPS; }
|
||||
void setOctreeSizeScale(float octreeSizeScale) { _octreeElementSizeScale = octreeSizeScale; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
|
@ -95,8 +101,11 @@ protected:
|
|||
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
|
||||
|
||||
// octree server sending items
|
||||
bool _wantColor = true;
|
||||
bool _wantDelta = true;
|
||||
bool _wantLowResMoving = true;
|
||||
bool _wantOcclusionCulling = false;
|
||||
bool _wantCompression = false;
|
||||
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
|
||||
int _boundaryLevelAdjust = 0; /// used for LOD calculations
|
||||
|
|
|
@ -115,7 +115,7 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN
|
|||
|
||||
if (sectionLength) {
|
||||
// ask the VoxelTree to read the bitstream into the tree
|
||||
ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL,
|
||||
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
|
||||
sourceUUID, sourceNode, false, packet.getVersion());
|
||||
quint64 startUncompress, startLock = usecTimestampNow();
|
||||
quint64 startReadBitsteam, endReadBitsteam;
|
||||
|
|
|
@ -371,12 +371,14 @@ void OctreeSceneStats::existsInPacketBitsWritten() {
|
|||
_existsInPacketBitsWritten++;
|
||||
}
|
||||
|
||||
void OctreeSceneStats::childBitsRemoved(bool includesExistsBits) {
|
||||
void OctreeSceneStats::childBitsRemoved(bool includesExistsBits, bool includesColors) {
|
||||
_existsInPacketBitsWritten--;
|
||||
if (includesExistsBits) {
|
||||
_existsBitsWritten--;
|
||||
}
|
||||
_colorBitsWritten--;
|
||||
if (includesColors) {
|
||||
_colorBitsWritten--;
|
||||
}
|
||||
_treesRemoved++;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ public:
|
|||
void existsInPacketBitsWritten();
|
||||
|
||||
/// Fix up tracking statistics in case where bitmasks were removed for some reason
|
||||
void childBitsRemoved(bool includesExistsBits);
|
||||
void childBitsRemoved(bool includesExistsBits, bool includesColors);
|
||||
|
||||
/// Pack the details of the statistics into a buffer for sending as a network packet
|
||||
int packIntoPacket();
|
||||
|
|
|
@ -264,6 +264,29 @@ unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLev
|
|||
return newCode;
|
||||
}
|
||||
|
||||
unsigned char* rebaseOctalCode(const unsigned char* originalOctalCode, const unsigned char* newParentOctalCode,
|
||||
bool includeColorSpace) {
|
||||
|
||||
int oldCodeLength = numberOfThreeBitSectionsInCode(originalOctalCode);
|
||||
int newParentCodeLength = numberOfThreeBitSectionsInCode(newParentOctalCode);
|
||||
int newCodeLength = newParentCodeLength + oldCodeLength;
|
||||
int bufferLength = newCodeLength + (includeColorSpace ? SIZE_OF_COLOR_DATA : 0);
|
||||
unsigned char* newCode = new unsigned char[bufferLength];
|
||||
*newCode = newCodeLength; // set the length byte
|
||||
|
||||
// copy parent code section first
|
||||
for (int sectionFromParent = 0; sectionFromParent < newParentCodeLength; sectionFromParent++) {
|
||||
char sectionValue = getOctalCodeSectionValue(newParentOctalCode, sectionFromParent);
|
||||
setOctalCodeSectionValue(newCode, sectionFromParent, sectionValue);
|
||||
}
|
||||
// copy original code section next
|
||||
for (int sectionFromOriginal = 0; sectionFromOriginal < oldCodeLength; sectionFromOriginal++) {
|
||||
char sectionValue = getOctalCodeSectionValue(originalOctalCode, sectionFromOriginal);
|
||||
setOctalCodeSectionValue(newCode, sectionFromOriginal + newParentCodeLength, sectionValue);
|
||||
}
|
||||
return newCode;
|
||||
}
|
||||
|
||||
bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) {
|
||||
if (!possibleAncestor || !possibleDescendent) {
|
||||
return false;
|
||||
|
|
|
@ -36,6 +36,8 @@ const int UNKNOWN_OCTCODE_LENGTH = -2;
|
|||
int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH);
|
||||
|
||||
unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels);
|
||||
unsigned char* rebaseOctalCode(const unsigned char* originalOctalCode, const unsigned char* newParentOctalCode,
|
||||
bool includeColorSpace = false);
|
||||
|
||||
const int CHECK_NODE_ONLY = -1;
|
||||
bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent,
|
||||
|
|
Loading…
Reference in a new issue