Revert "Cruft removal"

This commit is contained in:
Brad Hefta-Gaub 2015-12-03 17:40:41 -08:00
parent 46556b7fa5
commit 5a75a17174
21 changed files with 1272 additions and 85 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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

View 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...
}

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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