diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index cafba8c083..cff2c7ee2e 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -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(); diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 67298296e9..0c691a06a2 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -14,6 +14,7 @@ #include +#include #include #include #include @@ -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; } diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index d01117dff6..0a32f574de 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -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... diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 199fdecb8e..e6678c8758 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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()); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 30bee83482..78a4f3e8b6 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -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 diff --git a/libraries/octree/src/CoverageMap.cpp b/libraries/octree/src/CoverageMap.cpp new file mode 100644 index 0000000000..626d4bcf1a --- /dev/null +++ b/libraries/octree/src/CoverageMap.cpp @@ -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 + +#include + +#include + +#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; +} diff --git a/libraries/octree/src/CoverageMap.h b/libraries/octree/src/CoverageMap.h new file mode 100644 index 0000000000..bff6bb1078 --- /dev/null +++ b/libraries/octree/src/CoverageMap.h @@ -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 +#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 diff --git a/libraries/octree/src/CoverageMapV2.cpp b/libraries/octree/src/CoverageMapV2.cpp new file mode 100644 index 0000000000..8467ea1ee9 --- /dev/null +++ b/libraries/octree/src/CoverageMapV2.cpp @@ -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 +#include + +#include + +#include + +#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... +} diff --git a/libraries/octree/src/CoverageMapV2.h b/libraries/octree/src/CoverageMapV2.h new file mode 100644 index 0000000000..fc9a3ea70e --- /dev/null +++ b/libraries/octree/src/CoverageMapV2.h @@ -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 + +#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 diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 6da59f00a3..c02a034778 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -41,6 +41,7 @@ #include #include +#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); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 3ca8528a2f..514a9b391b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -28,6 +28,7 @@ #include "OctreePacketData.h" #include "OctreeSceneStats.h" +class CoverageMap; class ReadBitstreamToTreeParams; class Octree; class OctreeElement; @@ -52,8 +53,12 @@ typedef QHash 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"); diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 7b80d315f1..88a77a4c53 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -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()); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index f4fbf9bd97..811e96fcf4 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -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(); } diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index fb53b3472f..2c86d518ad 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -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; diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 8449e3083a..e8beb0404c 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -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)); diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 962a8e1425..86474ffc02 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -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 diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index c65359f12f..b7be4cf3e7 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -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; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index c70e0e4935..22352fbe3b 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -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++; } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index f8ecf93106..bdb4ef206a 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -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(); diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 1fa18903cb..0b77683d4c 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -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; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 09766b685a..9229157c3d 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -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,