mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-05-15 06:09:38 +02:00
2049 lines
87 KiB
C++
2049 lines
87 KiB
C++
//
|
|
// Octree.cpp
|
|
// libraries/octree/src
|
|
//
|
|
// Created by Stephen Birarda on 3/13/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
|
|
//
|
|
|
|
#ifdef _WIN32
|
|
#define _USE_MATH_DEFINES
|
|
#endif
|
|
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <fstream> // to load voxels from file
|
|
|
|
#include <QDataStream>
|
|
#include <QDebug>
|
|
#include <QEventLoop>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkAccessManager>
|
|
#include <QVector>
|
|
#include <QFile>
|
|
#include <QJsonDocument>
|
|
#include <QFileInfo>
|
|
#include <QString>
|
|
|
|
#include <GeometryUtil.h>
|
|
#include <LogHandler.h>
|
|
#include <NetworkAccessManager.h>
|
|
#include <OctalCode.h>
|
|
#include <udt/PacketHeaders.h>
|
|
#include <SharedUtil.h>
|
|
#include <PathUtils.h>
|
|
#include <Gzip.h>
|
|
|
|
#include "OctreeConstants.h"
|
|
#include "OctreeElementBag.h"
|
|
#include "Octree.h"
|
|
#include "ViewFrustum.h"
|
|
#include "OctreeLogging.h"
|
|
|
|
|
|
QVector<QString> PERSIST_EXTENSIONS = {"svo", "json", "json.gz"};
|
|
|
|
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) {
|
|
return voxelSizeScale / powf(2, renderLevel);
|
|
}
|
|
|
|
Octree::Octree(bool shouldReaverage) :
|
|
_rootElement(NULL),
|
|
_isDirty(true),
|
|
_shouldReaverage(shouldReaverage),
|
|
_stopImport(false),
|
|
_isViewing(false),
|
|
_isServer(false)
|
|
{
|
|
}
|
|
|
|
Octree::~Octree() {
|
|
// This will delete all children, don't create a new root in this case.
|
|
eraseAllOctreeElements(false);
|
|
}
|
|
|
|
|
|
// Inserts the value and key into three arrays sorted by the key array, the first array is the value,
|
|
// the second array is a sorted key for the value, the third array is the index for the value in it original
|
|
// non-sorted array
|
|
// returns -1 if size exceeded
|
|
// originalIndexArray is optional
|
|
int insertOctreeElementIntoSortedArrays(OctreeElementPointer value, float key, int originalIndex,
|
|
OctreeElementPointer* valueArray, float* keyArray, int* originalIndexArray,
|
|
int currentCount, int maxCount) {
|
|
|
|
if (currentCount < maxCount) {
|
|
int i = 0;
|
|
if (currentCount > 0) {
|
|
while (i < currentCount && key > keyArray[i]) {
|
|
i++;
|
|
}
|
|
// i is our desired location
|
|
// shift array elements to the right
|
|
if (i < currentCount && i+1 < maxCount) {
|
|
for (int j = currentCount - 1; j > i; j--) {
|
|
valueArray[j] = valueArray[j - 1];
|
|
keyArray[j] = keyArray[j - 1];
|
|
}
|
|
}
|
|
}
|
|
// place new element at i
|
|
valueArray[i] = value;
|
|
keyArray[i] = key;
|
|
if (originalIndexArray) {
|
|
originalIndexArray[i] = originalIndex;
|
|
}
|
|
return currentCount + 1;
|
|
}
|
|
return -1; // error case
|
|
}
|
|
|
|
|
|
|
|
// Recurses voxel tree calling the RecurseOctreeOperation function for each element.
|
|
// stops recursion if operation function returns false.
|
|
void Octree::recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData) {
|
|
recurseElementWithOperation(_rootElement, operation, extraData);
|
|
}
|
|
|
|
// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each element in post-fix order.
|
|
void Octree::recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData) {
|
|
recurseElementWithPostOperation(_rootElement, operation, extraData);
|
|
}
|
|
|
|
// Recurses voxel element with an operation function
|
|
void Octree::recurseElementWithOperation(OctreeElementPointer element, RecurseOctreeOperation operation, void* extraData,
|
|
int recursionCount) {
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
static QString repeatedMessage
|
|
= LogHandler::getInstance().addRepeatedMessageRegex(
|
|
"Octree::recurseElementWithOperation\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
|
|
qCDebug(octree) << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!";
|
|
return;
|
|
}
|
|
|
|
if (operation(element, extraData)) {
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
OctreeElementPointer child = element->getChildAtIndex(i);
|
|
if (child) {
|
|
recurseElementWithOperation(child, operation, extraData, recursionCount+1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recurses voxel element with an operation function
|
|
void Octree::recurseElementWithPostOperation(OctreeElementPointer element, RecurseOctreeOperation operation,
|
|
void* extraData, int recursionCount) {
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
static QString repeatedMessage
|
|
= LogHandler::getInstance().addRepeatedMessageRegex(
|
|
"Octree::recurseElementWithPostOperation\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
|
|
qCDebug(octree) << "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!";
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
OctreeElementPointer child = element->getChildAtIndex(i);
|
|
if (child) {
|
|
recurseElementWithPostOperation(child, operation, extraData, recursionCount+1);
|
|
}
|
|
}
|
|
operation(element, extraData);
|
|
}
|
|
|
|
// Recurses voxel tree calling the RecurseOctreeOperation function for each element.
|
|
// stops recursion if operation function returns false.
|
|
void Octree::recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation,
|
|
const glm::vec3& point, void* extraData) {
|
|
|
|
recurseElementWithOperationDistanceSorted(_rootElement, operation, point, extraData);
|
|
}
|
|
|
|
// Recurses voxel element with an operation function
|
|
void Octree::recurseElementWithOperationDistanceSorted(OctreeElementPointer element, RecurseOctreeOperation operation,
|
|
const glm::vec3& point, void* extraData, int recursionCount) {
|
|
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
static QString repeatedMessage
|
|
= LogHandler::getInstance().addRepeatedMessageRegex(
|
|
"Octree::recurseElementWithOperationDistanceSorted\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
|
|
qCDebug(octree) << "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!";
|
|
return;
|
|
}
|
|
|
|
if (operation(element, extraData)) {
|
|
// determine the distance sorted order of our children
|
|
OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
int currentCount = 0;
|
|
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
|
if (childElement) {
|
|
// chance to optimize, doesn't need to be actual distance!! Could be distance squared
|
|
float distanceSquared = childElement->distanceSquareToPoint(point);
|
|
currentCount = insertOctreeElementIntoSortedArrays(childElement, distanceSquared, i,
|
|
sortedChildren, (float*)&distancesToChildren,
|
|
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < currentCount; i++) {
|
|
OctreeElementPointer childElement = sortedChildren[i];
|
|
if (childElement) {
|
|
recurseElementWithOperationDistanceSorted(childElement, operation, point, extraData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) {
|
|
recurseElementWithOperator(_rootElement, operatorObject);
|
|
}
|
|
|
|
bool Octree::recurseElementWithOperator(OctreeElementPointer element,
|
|
RecurseOctreeOperator* operatorObject, int recursionCount) {
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
static QString repeatedMessage
|
|
= LogHandler::getInstance().addRepeatedMessageRegex(
|
|
"Octree::recurseElementWithOperator\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
|
|
qCDebug(octree) << "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!";
|
|
return false;
|
|
}
|
|
|
|
if (operatorObject->preRecursion(element)) {
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
OctreeElementPointer child = element->getChildAtIndex(i);
|
|
|
|
// If there is no child at that location, the Operator may want to create a child at that location.
|
|
// So give the operator a chance to do so....
|
|
if (!child) {
|
|
child = operatorObject->possiblyCreateChildAt(element, i);
|
|
}
|
|
|
|
if (child) {
|
|
if (!recurseElementWithOperator(child, operatorObject, recursionCount + 1)) {
|
|
break; // stop recursing if operator returns false...
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return operatorObject->postRecursion(element);
|
|
}
|
|
|
|
|
|
OctreeElementPointer Octree::nodeForOctalCode(OctreeElementPointer ancestorElement, const unsigned char* needleCode,
|
|
OctreeElementPointer* parentOfFoundElement) const {
|
|
// special case for NULL octcode
|
|
if (!needleCode) {
|
|
return _rootElement;
|
|
}
|
|
|
|
// find the appropriate branch index based on this ancestorElement
|
|
if (*needleCode > 0) {
|
|
int branchForNeedle = branchIndexWithDescendant(ancestorElement->getOctalCode(), needleCode);
|
|
OctreeElementPointer childElement = ancestorElement->getChildAtIndex(branchForNeedle);
|
|
|
|
if (childElement) {
|
|
if (*childElement->getOctalCode() == *needleCode) {
|
|
|
|
// If the caller asked for the parent, then give them that too...
|
|
if (parentOfFoundElement) {
|
|
*parentOfFoundElement = ancestorElement;
|
|
}
|
|
// the fact that the number of sections is equivalent does not always guarantee
|
|
// that this is the same element, however due to the recursive traversal
|
|
// we know that this is our element
|
|
return childElement;
|
|
} else {
|
|
// we need to go deeper
|
|
return nodeForOctalCode(childElement, needleCode, parentOfFoundElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
// we've been given a code we don't have a element for
|
|
// return this element as the last created parent
|
|
return ancestorElement;
|
|
}
|
|
|
|
// returns the element created!
|
|
OctreeElementPointer Octree::createMissingElement(OctreeElementPointer lastParentElement,
|
|
const unsigned char* codeToReach, int recursionCount) {
|
|
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
static QString repeatedMessage
|
|
= LogHandler::getInstance().addRepeatedMessageRegex(
|
|
"Octree::createMissingElement\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
|
|
qCDebug(octree) << "Octree::createMissingElement() reached DANGEROUSLY_DEEP_RECURSION, bailing!";
|
|
return lastParentElement;
|
|
}
|
|
int indexOfNewChild = branchIndexWithDescendant(lastParentElement->getOctalCode(), codeToReach);
|
|
|
|
// If this parent element is a leaf, then you know the child path doesn't exist, so deal with
|
|
// breaking up the leaf first, which will also create a child path
|
|
if (lastParentElement->requiresSplit()) {
|
|
lastParentElement->splitChildren();
|
|
} else if (!lastParentElement->getChildAtIndex(indexOfNewChild)) {
|
|
// we could be coming down a branch that was already created, so don't stomp on it.
|
|
lastParentElement->addChildAtIndex(indexOfNewChild);
|
|
}
|
|
|
|
// This works because we know we traversed down the same tree so if the length is the same, then the whole code is the same
|
|
if (*lastParentElement->getChildAtIndex(indexOfNewChild)->getOctalCode() == *codeToReach) {
|
|
return lastParentElement->getChildAtIndex(indexOfNewChild);
|
|
} else {
|
|
return createMissingElement(lastParentElement->getChildAtIndex(indexOfNewChild), codeToReach, recursionCount + 1);
|
|
}
|
|
}
|
|
|
|
int Octree::readElementData(OctreeElementPointer destinationElement, const unsigned char* nodeData, int bytesAvailable,
|
|
ReadBitstreamToTreeParams& args) {
|
|
|
|
int bytesLeftToRead = bytesAvailable;
|
|
int bytesRead = 0;
|
|
|
|
// give this destination element the child mask from the packet
|
|
const unsigned char ALL_CHILDREN_ASSUMED_TO_EXIST = 0xFF;
|
|
|
|
if ((size_t)bytesLeftToRead < sizeof(unsigned char)) {
|
|
qCDebug(octree) << "UNEXPECTED: readElementData() only had " << bytesLeftToRead << " bytes. "
|
|
"Not enough for meaningful data.";
|
|
return bytesAvailable; // assume we read the entire buffer...
|
|
}
|
|
|
|
if (destinationElement->getScale() < SCALE_AT_DANGEROUSLY_DEEP_RECURSION) {
|
|
qCDebug(octree) << "UNEXPECTED: readElementData() destination element is unreasonably small ["
|
|
<< destinationElement->getScale() << " meters] "
|
|
<< " Discarding " << bytesAvailable << " remaining bytes.";
|
|
return bytesAvailable; // assume we read the entire buffer...
|
|
}
|
|
|
|
unsigned char colorInPacketMask = *nodeData;
|
|
bytesRead += sizeof(colorInPacketMask);
|
|
bytesLeftToRead -= sizeof(colorInPacketMask);
|
|
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
// check the colors mask to see if we have a child to color in
|
|
if (oneAtBit(colorInPacketMask, i)) {
|
|
// addChildAtIndex() should actually be called getOrAddChildAtIndex().
|
|
// When it adds the child it automatically sets the detinationElement dirty.
|
|
OctreeElementPointer childElementAt = destinationElement->addChildAtIndex(i);
|
|
|
|
int childElementDataRead = childElementAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args);
|
|
childElementAt->setSourceUUID(args.sourceUUID);
|
|
|
|
bytesRead += childElementDataRead;
|
|
bytesLeftToRead -= childElementDataRead;
|
|
|
|
// It's possible that we already had this version of element data, in which case the unpacking of the data
|
|
// wouldn't flag childElementAt as dirty, so we manually flag it here... if the element is to be rendered.
|
|
if (childElementAt->getShouldRender() && !childElementAt->isRendered()) {
|
|
childElementAt->setDirtyBit(); // force dirty!
|
|
_isDirty = true;
|
|
}
|
|
}
|
|
if (destinationElement->isDirty()) {
|
|
_isDirty = true;
|
|
}
|
|
}
|
|
|
|
unsigned char childrenInTreeMask = ALL_CHILDREN_ASSUMED_TO_EXIST;
|
|
unsigned char childInBufferMask = 0;
|
|
int bytesForMasks = args.includeExistsBits ? sizeof(childrenInTreeMask) + sizeof(childInBufferMask)
|
|
: sizeof(childInBufferMask);
|
|
|
|
if (bytesLeftToRead < bytesForMasks) {
|
|
if (bytesLeftToRead > 0) {
|
|
qCDebug(octree) << "UNEXPECTED: readElementDataFromBuffer() only had " << bytesLeftToRead << " bytes before masks. "
|
|
"Not enough for meaningful data.";
|
|
}
|
|
return bytesAvailable; // assume we read the entire buffer...
|
|
}
|
|
|
|
childrenInTreeMask = args.includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST;
|
|
childInBufferMask = *(nodeData + bytesRead + (args.includeExistsBits ? sizeof(childrenInTreeMask) : 0));
|
|
|
|
int childIndex = 0;
|
|
bytesRead += bytesForMasks;
|
|
bytesLeftToRead -= bytesForMasks;
|
|
|
|
while (bytesLeftToRead > 0 && childIndex < NUMBER_OF_CHILDREN) {
|
|
// check the exists mask to see if we have a child to traverse into
|
|
|
|
if (oneAtBit(childInBufferMask, childIndex)) {
|
|
if (!destinationElement->getChildAtIndex(childIndex)) {
|
|
// add a child at that index, if it doesn't exist
|
|
destinationElement->addChildAtIndex(childIndex);
|
|
bool nodeIsDirty = destinationElement->isDirty();
|
|
if (nodeIsDirty) {
|
|
_isDirty = true;
|
|
}
|
|
}
|
|
|
|
// tell the child to read the subsequent data
|
|
int lowerLevelBytes = readElementData(destinationElement->getChildAtIndex(childIndex),
|
|
nodeData + bytesRead, bytesLeftToRead, args);
|
|
|
|
bytesRead += lowerLevelBytes;
|
|
bytesLeftToRead -= lowerLevelBytes;
|
|
}
|
|
childIndex++;
|
|
}
|
|
|
|
if (args.includeExistsBits) {
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
// now also check the childrenInTreeMask, if the mask is missing the bit, then it means we need to delete this child
|
|
// subtree/element, because it shouldn't actually exist in the tree.
|
|
if (!oneAtBit(childrenInTreeMask, i) && destinationElement->getChildAtIndex(i)) {
|
|
destinationElement->safeDeepDeleteChildAtIndex(i);
|
|
_isDirty = true; // by definition!
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this is the root, and there is more data to read, allow it to read it's element data...
|
|
if (destinationElement == _rootElement && rootElementHasData() && bytesLeftToRead > 0) {
|
|
// tell the element to read the subsequent data
|
|
int rootDataSize = _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args);
|
|
bytesRead += rootDataSize;
|
|
bytesLeftToRead -= rootDataSize;
|
|
}
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int bufferSizeBytes,
|
|
ReadBitstreamToTreeParams& args) {
|
|
int bytesRead = 0;
|
|
const unsigned char* bitstreamAt = bitstream;
|
|
|
|
// If destination element is not included, set it to root
|
|
if (!args.destinationElement) {
|
|
args.destinationElement = _rootElement;
|
|
}
|
|
|
|
// Keep looping through the buffer calling readElementData() this allows us to pack multiple root-relative Octal codes
|
|
// into a single network packet. readElementData() basically goes down a tree from the root, and fills things in from there
|
|
// if there are more bytes after that, it's assumed to be another root relative tree
|
|
|
|
while (bitstreamAt < bitstream + bufferSizeBytes) {
|
|
OctreeElementPointer bitstreamRootElement = nodeForOctalCode(args.destinationElement,
|
|
(unsigned char *)bitstreamAt, NULL);
|
|
int numberOfThreeBitSectionsInStream = numberOfThreeBitSectionsInCode(bitstreamAt, bufferSizeBytes);
|
|
if (numberOfThreeBitSectionsInStream > UNREASONABLY_DEEP_RECURSION) {
|
|
static QString repeatedMessage
|
|
= LogHandler::getInstance().addRepeatedMessageRegex(
|
|
"UNEXPECTED: parsing of the octal code would make UNREASONABLY_DEEP_RECURSION... "
|
|
"numberOfThreeBitSectionsInStream: \\d+ This buffer is corrupt. Returning."
|
|
);
|
|
|
|
|
|
qCDebug(octree) << "UNEXPECTED: parsing of the octal code would make UNREASONABLY_DEEP_RECURSION... "
|
|
"numberOfThreeBitSectionsInStream:" << numberOfThreeBitSectionsInStream <<
|
|
"This buffer is corrupt. Returning.";
|
|
return;
|
|
}
|
|
|
|
if (numberOfThreeBitSectionsInStream == OVERFLOWED_OCTCODE_BUFFER) {
|
|
qCDebug(octree) << "UNEXPECTED: parsing of the octal code would overflow the buffer. "
|
|
"This buffer is corrupt. Returning.";
|
|
return;
|
|
}
|
|
|
|
int numberOfThreeBitSectionsFromNode = numberOfThreeBitSectionsInCode(bitstreamRootElement->getOctalCode());
|
|
|
|
// if the octal code returned is not on the same level as the code being searched for, we have OctreeElements to create
|
|
if (numberOfThreeBitSectionsInStream != numberOfThreeBitSectionsFromNode) {
|
|
|
|
// Note: we need to create this element relative to root, because we're assuming that the bitstream for the initial
|
|
// octal code is always relative to root!
|
|
bitstreamRootElement = createMissingElement(args.destinationElement, (unsigned char*) bitstreamAt);
|
|
if (bitstreamRootElement->isDirty()) {
|
|
_isDirty = true;
|
|
}
|
|
}
|
|
|
|
auto octalCodeBytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInStream);
|
|
|
|
int theseBytesRead = 0;
|
|
theseBytesRead += (int)octalCodeBytes;
|
|
int lowerLevelBytes = readElementData(bitstreamRootElement, bitstreamAt + octalCodeBytes,
|
|
bufferSizeBytes - (bytesRead + (int)octalCodeBytes), args);
|
|
|
|
theseBytesRead += lowerLevelBytes;
|
|
|
|
// skip bitstream to new startPoint
|
|
bitstreamAt += theseBytesRead;
|
|
bytesRead += theseBytesRead;
|
|
|
|
if (args.wantImportProgress) {
|
|
emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Octree::deleteOctreeElementAt(float x, float y, float z, float s) {
|
|
unsigned char* octalCode = pointToOctalCode(x,y,z,s);
|
|
deleteOctalCodeFromTree(octalCode);
|
|
delete[] octalCode; // cleanup memory
|
|
}
|
|
|
|
class DeleteOctalCodeFromTreeArgs {
|
|
public:
|
|
bool collapseEmptyTrees;
|
|
const unsigned char* codeBuffer;
|
|
int lengthOfCode;
|
|
bool deleteLastChild;
|
|
bool pathChanged;
|
|
};
|
|
|
|
// Note: uses the codeColorBuffer format, but the color's are ignored, because
|
|
// this only finds and deletes the element from the tree.
|
|
void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) {
|
|
// recurse the tree while decoding the codeBuffer, once you find the element in question, recurse
|
|
// back and implement color reaveraging, and marking of lastChanged
|
|
DeleteOctalCodeFromTreeArgs args;
|
|
args.collapseEmptyTrees = collapseEmptyTrees;
|
|
args.codeBuffer = codeBuffer;
|
|
args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer);
|
|
args.deleteLastChild = false;
|
|
args.pathChanged = false;
|
|
|
|
withWriteLock([&] {
|
|
deleteOctalCodeFromTreeRecursion(_rootElement, &args);
|
|
});
|
|
}
|
|
|
|
void Octree::deleteOctalCodeFromTreeRecursion(OctreeElementPointer element, void* extraData) {
|
|
DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData;
|
|
|
|
int lengthOfElementCode = numberOfThreeBitSectionsInCode(element->getOctalCode());
|
|
|
|
// Since we traverse the tree in code order, we know that if our code
|
|
// matches, then we've reached our target element.
|
|
if (lengthOfElementCode == args->lengthOfCode) {
|
|
// we've reached our target, depending on how we're called we may be able to operate on it
|
|
// it here, we need to recurse up, and delete it there. So we handle these cases the same to keep
|
|
// the logic consistent.
|
|
args->deleteLastChild = true;
|
|
return;
|
|
}
|
|
|
|
// Ok, we know we haven't reached our target element yet, so keep looking
|
|
int childIndex = branchIndexWithDescendant(element->getOctalCode(), args->codeBuffer);
|
|
OctreeElementPointer childElement = element->getChildAtIndex(childIndex);
|
|
|
|
// If there is no child at the target location, and the current parent element is a colored leaf,
|
|
// then it means we were asked to delete a child out of a larger leaf voxel.
|
|
// We support this by breaking up the parent voxel into smaller pieces.
|
|
if (!childElement && element->requiresSplit()) {
|
|
// we need to break up ancestors until we get to the right level
|
|
OctreeElementPointer ancestorElement = element;
|
|
while (true) {
|
|
int index = branchIndexWithDescendant(ancestorElement->getOctalCode(), args->codeBuffer);
|
|
|
|
// we end up with all the children, even the one we want to delete
|
|
ancestorElement->splitChildren();
|
|
|
|
int lengthOfAncestorElement = numberOfThreeBitSectionsInCode(ancestorElement->getOctalCode());
|
|
|
|
// If we've reached the parent of the target, then stop breaking up children
|
|
if (lengthOfAncestorElement == (args->lengthOfCode - 1)) {
|
|
|
|
// since we created all the children when we split, we need to delete this target one
|
|
ancestorElement->deleteChildAtIndex(index);
|
|
break;
|
|
}
|
|
ancestorElement = ancestorElement->getChildAtIndex(index);
|
|
}
|
|
_isDirty = true;
|
|
args->pathChanged = true;
|
|
|
|
// ends recursion, unwinds up stack
|
|
return;
|
|
}
|
|
|
|
// if we don't have a child and we reach this point, then we actually know that the parent
|
|
// isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and
|
|
// we can safely return, ending the recursion and unwinding
|
|
if (!childElement) {
|
|
return;
|
|
}
|
|
|
|
// If we got this far then we have a child for the branch we're looking for, but we're not there yet
|
|
// recurse till we get there
|
|
deleteOctalCodeFromTreeRecursion(childElement, args);
|
|
|
|
// If the lower level determined it needs to be deleted, then we should delete now.
|
|
if (args->deleteLastChild) {
|
|
element->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this element
|
|
|
|
// track our tree dirtiness
|
|
_isDirty = true;
|
|
|
|
// track that path has changed
|
|
args->pathChanged = true;
|
|
|
|
// If we're in collapseEmptyTrees mode, and this was the last child of this element, then we also want
|
|
// to delete this element. This will collapse the empty tree above us.
|
|
if (args->collapseEmptyTrees && element->getChildCount() == 0) {
|
|
// Can't delete the root this way.
|
|
if (element == _rootElement) {
|
|
args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything
|
|
}
|
|
} else {
|
|
args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything
|
|
}
|
|
}
|
|
|
|
// If the lower level did some work, then we need to let this element know, so it can
|
|
// do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc
|
|
if (args->pathChanged) {
|
|
element->handleSubtreeChanged(shared_from_this());
|
|
}
|
|
}
|
|
|
|
void Octree::eraseAllOctreeElements(bool createNewRoot) {
|
|
if (createNewRoot) {
|
|
_rootElement = createNewElement();
|
|
} else {
|
|
_rootElement.reset(); // this will recurse and delete all children
|
|
}
|
|
|
|
_isDirty = true;
|
|
}
|
|
|
|
// Note: this is an expensive call. Don't call it unless you really need to reaverage the entire tree (from startElement)
|
|
void Octree::reaverageOctreeElements(OctreeElementPointer startElement) {
|
|
if (!startElement) {
|
|
startElement = getRoot();
|
|
}
|
|
// if our tree is a reaveraging tree, then we do this, otherwise we don't do anything
|
|
if (_shouldReaverage) {
|
|
static int recursionCount;
|
|
if (startElement == _rootElement) {
|
|
recursionCount = 0;
|
|
} else {
|
|
recursionCount++;
|
|
}
|
|
if (recursionCount > UNREASONABLY_DEEP_RECURSION) {
|
|
qCDebug(octree, "Octree::reaverageOctreeElements()... bailing out of UNREASONABLY_DEEP_RECURSION");
|
|
recursionCount--;
|
|
return;
|
|
}
|
|
|
|
bool hasChildren = false;
|
|
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
if (startElement->getChildAtIndex(i)) {
|
|
reaverageOctreeElements(startElement->getChildAtIndex(i));
|
|
hasChildren = true;
|
|
}
|
|
}
|
|
|
|
// collapseIdenticalLeaves() returns true if it collapses the leaves
|
|
// in which case we don't need to set the average color
|
|
if (hasChildren && !startElement->collapseChildren()) {
|
|
startElement->calculateAverageFromChildren();
|
|
}
|
|
recursionCount--;
|
|
}
|
|
}
|
|
|
|
OctreeElementPointer Octree::getOctreeElementAt(float x, float y, float z, float s) const {
|
|
unsigned char* octalCode = pointToOctalCode(x,y,z,s);
|
|
OctreeElementPointer element = nodeForOctalCode(_rootElement, octalCode, NULL);
|
|
if (*element->getOctalCode() != *octalCode) {
|
|
element = NULL;
|
|
}
|
|
delete[] octalCode; // cleanup memory
|
|
return element;
|
|
}
|
|
|
|
OctreeElementPointer Octree::getOctreeEnclosingElementAt(float x, float y, float z, float s) const {
|
|
unsigned char* octalCode = pointToOctalCode(x,y,z,s);
|
|
OctreeElementPointer element = nodeForOctalCode(_rootElement, octalCode, NULL);
|
|
|
|
delete[] octalCode; // cleanup memory
|
|
return element;
|
|
}
|
|
|
|
|
|
OctreeElementPointer Octree::getOrCreateChildElementAt(float x, float y, float z, float s) {
|
|
return getRoot()->getOrCreateChildElementAt(x, y, z, s);
|
|
}
|
|
|
|
OctreeElementPointer Octree::getOrCreateChildElementContaining(const AACube& box) {
|
|
return getRoot()->getOrCreateChildElementContaining(box);
|
|
}
|
|
|
|
|
|
|
|
class SphereArgs {
|
|
public:
|
|
glm::vec3 center;
|
|
float radius;
|
|
glm::vec3& penetration;
|
|
bool found;
|
|
void* penetratedObject; /// the type is defined by the type of Octree, the caller is assumed to know the type
|
|
};
|
|
|
|
bool findSpherePenetrationOp(OctreeElementPointer element, void* extraData) {
|
|
SphereArgs* args = static_cast<SphereArgs*>(extraData);
|
|
|
|
// coarse check against bounds
|
|
if (!element->getAACube().expandedContains(args->center, args->radius)) {
|
|
return false;
|
|
}
|
|
if (element->hasContent()) {
|
|
glm::vec3 elementPenetration;
|
|
if (element->findSpherePenetration(args->center, args->radius, elementPenetration, &args->penetratedObject)) {
|
|
// NOTE: it is possible for this penetration accumulation algorithm to produce a
|
|
// final penetration vector with zero length.
|
|
args->penetration = addPenetrations(args->penetration, elementPenetration);
|
|
args->found = true;
|
|
}
|
|
}
|
|
if (!element->isLeaf()) {
|
|
return true; // recurse on children
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration,
|
|
void** penetratedObject, Octree::lockType lockType, bool* accurateResult) {
|
|
|
|
SphereArgs args = {
|
|
center,
|
|
radius,
|
|
penetration,
|
|
false,
|
|
NULL };
|
|
penetration = glm::vec3(0.0f, 0.0f, 0.0f);
|
|
|
|
bool requireLock = lockType == Octree::Lock;
|
|
bool lockResult = withReadLock([&]{
|
|
recurseTreeWithOperation(findSpherePenetrationOp, &args);
|
|
if (penetratedObject) {
|
|
*penetratedObject = args.penetratedObject;
|
|
}
|
|
}, requireLock);
|
|
|
|
if (accurateResult) {
|
|
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
|
|
}
|
|
return args.found;
|
|
}
|
|
|
|
class CapsuleArgs {
|
|
public:
|
|
glm::vec3 start;
|
|
glm::vec3 end;
|
|
float radius;
|
|
glm::vec3& penetration;
|
|
bool found;
|
|
};
|
|
|
|
class ContentArgs {
|
|
public:
|
|
AACube cube;
|
|
CubeList* cubes;
|
|
};
|
|
|
|
bool findCapsulePenetrationOp(OctreeElementPointer element, void* extraData) {
|
|
CapsuleArgs* args = static_cast<CapsuleArgs*>(extraData);
|
|
|
|
// coarse check against bounds
|
|
if (!element->getAACube().expandedIntersectsSegment(args->start, args->end, args->radius)) {
|
|
return false;
|
|
}
|
|
if (element->hasContent()) {
|
|
glm::vec3 nodePenetration;
|
|
if (element->getAACube().findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) {
|
|
args->penetration = addPenetrations(args->penetration, nodePenetration);
|
|
args->found = true;
|
|
}
|
|
}
|
|
if (!element->isLeaf()) {
|
|
return true; // recurse on children
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint qHash(const glm::vec3& point) {
|
|
// NOTE: TREE_SCALE = 16384 (15 bits) and multiplier is 1024 (11 bits),
|
|
// so each component (26 bits) uses more than its alloted 21 bits.
|
|
// however we don't expect to span huge cubes so it is ok if we wrap
|
|
// (every 2^21 / 2^10 = 2048 meters).
|
|
const uint BITS_PER_COMPONENT = 21;
|
|
const quint64 MAX_SCALED_COMPONENT = 2097152; // 2^21
|
|
const float RESOLUTION_PER_METER = 1024.0f; // 2^10
|
|
return qHash((quint64)(point.x * RESOLUTION_PER_METER) % MAX_SCALED_COMPONENT +
|
|
(((quint64)(point.y * RESOLUTION_PER_METER)) % MAX_SCALED_COMPONENT << BITS_PER_COMPONENT) +
|
|
(((quint64)(point.z * RESOLUTION_PER_METER)) % MAX_SCALED_COMPONENT << 2 * BITS_PER_COMPONENT));
|
|
}
|
|
|
|
bool findContentInCubeOp(OctreeElementPointer element, void* extraData) {
|
|
ContentArgs* args = static_cast<ContentArgs*>(extraData);
|
|
|
|
// coarse check against bounds
|
|
const AACube& cube = element->getAACube();
|
|
if (!cube.touches(args->cube)) {
|
|
return false;
|
|
}
|
|
if (!element->isLeaf()) {
|
|
return true; // recurse on children
|
|
}
|
|
if (element->hasContent()) {
|
|
// NOTE: the voxel's center is unique so we use it as the input for the key.
|
|
// We use the qHash(glm::vec()) as the key as an optimization for the code that uses CubeLists.
|
|
args->cubes->insert(qHash(cube.calcCenter()), cube);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius,
|
|
glm::vec3& penetration, Octree::lockType lockType, bool* accurateResult) {
|
|
|
|
CapsuleArgs args = { start, end, radius, penetration, false };
|
|
penetration = glm::vec3(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
bool requireLock = lockType == Octree::Lock;
|
|
bool lockResult = withReadLock([&]{
|
|
recurseTreeWithOperation(findCapsulePenetrationOp, &args);
|
|
}, requireLock);
|
|
|
|
if (accurateResult) {
|
|
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
|
|
}
|
|
|
|
return args.found;
|
|
}
|
|
|
|
bool Octree::findContentInCube(const AACube& cube, CubeList& cubes) {
|
|
return withTryReadLock([&]{
|
|
ContentArgs args = { cube, &cubes };
|
|
recurseTreeWithOperation(findContentInCubeOp, &args);
|
|
});
|
|
}
|
|
|
|
class GetElementEnclosingArgs {
|
|
public:
|
|
OctreeElementPointer element;
|
|
glm::vec3 point;
|
|
};
|
|
|
|
// Find the smallest colored voxel enclosing a point (if there is one)
|
|
bool getElementEnclosingOperation(OctreeElementPointer element, void* extraData) {
|
|
GetElementEnclosingArgs* args = static_cast<GetElementEnclosingArgs*>(extraData);
|
|
if (element->getAACube().contains(args->point)) {
|
|
if (element->hasContent() && element->isLeaf()) {
|
|
// we've reached a solid leaf containing the point, return the element.
|
|
args->element = element;
|
|
return false;
|
|
}
|
|
} else {
|
|
// The point is not inside this voxel, so stop recursing.
|
|
return false;
|
|
}
|
|
return true; // keep looking
|
|
}
|
|
|
|
OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Octree::lockType lockType, bool* accurateResult) {
|
|
GetElementEnclosingArgs args;
|
|
args.point = point;
|
|
args.element = NULL;
|
|
|
|
bool requireLock = lockType == Octree::Lock;
|
|
bool lockResult = withReadLock([&]{
|
|
recurseTreeWithOperation(getElementEnclosingOperation, (void*)&args);
|
|
}, requireLock);
|
|
|
|
if (accurateResult) {
|
|
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
|
|
}
|
|
|
|
return args.element;
|
|
}
|
|
|
|
|
|
|
|
int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|
OctreePacketData* packetData, OctreeElementBag& bag,
|
|
EncodeBitstreamParams& params) {
|
|
|
|
// How many bytes have we written so far at this level;
|
|
int bytesWritten = 0;
|
|
|
|
// you can't call this without a valid element
|
|
if (!element) {
|
|
qCDebug(octree, "WARNING! encodeTreeBitstream() called with element=NULL");
|
|
params.stopReason = EncodeBitstreamParams::NULL_NODE;
|
|
return bytesWritten;
|
|
}
|
|
|
|
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
|
if (params.viewFrustum && !element->isInView(*params.viewFrustum)) {
|
|
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
|
return bytesWritten;
|
|
}
|
|
|
|
// write the octal code
|
|
bool roomForOctalCode = false; // assume the worst
|
|
int codeLength = 1; // assume root
|
|
if (params.chopLevels) {
|
|
unsigned char* newCode = chopOctalCode(element->getOctalCode(), params.chopLevels);
|
|
roomForOctalCode = packetData->startSubTree(newCode);
|
|
|
|
if (newCode) {
|
|
delete[] newCode;
|
|
codeLength = numberOfThreeBitSectionsInCode(newCode);
|
|
} else {
|
|
codeLength = 1;
|
|
}
|
|
} else {
|
|
roomForOctalCode = packetData->startSubTree(element->getOctalCode());
|
|
codeLength = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(element->getOctalCode()));
|
|
}
|
|
|
|
// If the octalcode couldn't fit, then we can return, because no nodes below us will fit...
|
|
if (!roomForOctalCode) {
|
|
bag.insert(element);
|
|
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
|
return bytesWritten;
|
|
}
|
|
|
|
bytesWritten += codeLength; // keep track of byte count
|
|
|
|
int currentEncodeLevel = 0;
|
|
|
|
// record some stats, this is the one element that we won't record below in the recursion function, so we need to
|
|
// track it here
|
|
if (params.stats) {
|
|
params.stats->traversed(element);
|
|
}
|
|
|
|
ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
|
|
|
int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params,
|
|
currentEncodeLevel, parentLocationThisView);
|
|
|
|
|
|
// 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
|
|
// reason couldn't be written... so reset them here... This isn't true for the non-color included case
|
|
if (suppressEmptySubtrees() && childBytesWritten == 2) {
|
|
childBytesWritten = 0;
|
|
//params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT...
|
|
}
|
|
|
|
// if we wrote child bytes, then return our result of all bytes written
|
|
if (childBytesWritten) {
|
|
bytesWritten += childBytesWritten;
|
|
} else {
|
|
// otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code
|
|
bytesWritten = 0;
|
|
//params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
|
}
|
|
|
|
if (bytesWritten == 0) {
|
|
packetData->discardSubTree();
|
|
} else {
|
|
packetData->endSubTree();
|
|
}
|
|
|
|
return bytesWritten;
|
|
}
|
|
|
|
int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|
OctreePacketData* packetData, OctreeElementBag& bag,
|
|
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
|
const ViewFrustum::location& parentLocationThisView) const {
|
|
|
|
|
|
const bool wantDebug = false;
|
|
|
|
// The append state of this level/element.
|
|
OctreeElement::AppendState elementAppendState = OctreeElement::COMPLETED; // assume the best
|
|
|
|
// How many bytes have we written so far at this level;
|
|
int bytesAtThisLevel = 0;
|
|
|
|
// you can't call this without a valid element
|
|
if (!element) {
|
|
qCDebug(octree, "WARNING! encodeTreeBitstreamRecursion() called with element=NULL");
|
|
params.stopReason = EncodeBitstreamParams::NULL_NODE;
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
// Keep track of how deep we've encoded.
|
|
currentEncodeLevel++;
|
|
|
|
params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached);
|
|
|
|
// If we've reached our max Search Level, then stop searching.
|
|
if (currentEncodeLevel >= params.maxEncodeLevel) {
|
|
params.stopReason = EncodeBitstreamParams::TOO_DEEP;
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
// If we've been provided a jurisdiction map, then we need to honor it.
|
|
if (params.jurisdictionMap) {
|
|
// here's how it works... if we're currently above our root jurisdiction, then we proceed normally.
|
|
// but once we're in our own jurisdiction, then we need to make sure we're not below it.
|
|
if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(element->getOctalCode(), CHECK_NODE_ONLY)) {
|
|
params.stopReason = EncodeBitstreamParams::OUT_OF_JURISDICTION;
|
|
return bytesAtThisLevel;
|
|
}
|
|
}
|
|
|
|
ViewFrustum::location nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
|
|
|
// caller can pass NULL as viewFrustum if they want everything
|
|
if (params.viewFrustum) {
|
|
float distance = element->distanceToCamera(*params.viewFrustum);
|
|
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
|
|
params.octreeElementSizeScale);
|
|
|
|
// If we're too far away for our render level, then just return
|
|
if (distance >= boundaryDistance) {
|
|
if (params.stats) {
|
|
params.stats->skippedDistance(element);
|
|
}
|
|
params.stopReason = EncodeBitstreamParams::LOD_SKIP;
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
// if the parent isn't known to be INSIDE, then it must be INTERSECT, and we should double check to see
|
|
// if we are INSIDE, INTERSECT, or OUTSIDE
|
|
if (parentLocationThisView != ViewFrustum::INSIDE) {
|
|
assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE!
|
|
nodeLocationThisView = element->inFrustum(*params.viewFrustum);
|
|
}
|
|
|
|
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
|
// although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if
|
|
// we're out of view
|
|
if (nodeLocationThisView == ViewFrustum::OUTSIDE) {
|
|
if (params.stats) {
|
|
params.stats->skippedOutOfView(element);
|
|
}
|
|
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
// Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view
|
|
// because we don't send nodes from the previously know in view frustum.
|
|
bool wasInView = false;
|
|
|
|
if (params.deltaViewFrustum && params.lastViewFrustum) {
|
|
ViewFrustum::location location = element->inFrustum(*params.lastViewFrustum);
|
|
|
|
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
|
if (element->isLeaf()) {
|
|
wasInView = location != ViewFrustum::OUTSIDE;
|
|
} else {
|
|
wasInView = location == ViewFrustum::INSIDE;
|
|
}
|
|
|
|
// If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't
|
|
// tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer
|
|
// to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it
|
|
// as "was in view"...
|
|
if (wasInView) {
|
|
float distance = element->distanceToCamera(*params.lastViewFrustum);
|
|
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
|
|
params.octreeElementSizeScale);
|
|
if (distance >= boundaryDistance) {
|
|
// This would have been invisible... but now should be visible (we wouldn't be here otherwise)...
|
|
wasInView = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we were previously in the view, then we normally will return out of here and stop recursing. But
|
|
// if we're in deltaViewFrustum mode, and this element has changed since it was last sent, then we do
|
|
// need to send it.
|
|
if (wasInView && !(params.deltaViewFrustum && element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) {
|
|
if (params.stats) {
|
|
params.stats->skippedWasInView(element);
|
|
}
|
|
params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW;
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
|
|
// then we can also bail early and save bits
|
|
if (!params.forceSendScene && !params.deltaViewFrustum &&
|
|
!element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) {
|
|
if (params.stats) {
|
|
params.stats->skippedNoChange(element);
|
|
}
|
|
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
|
return bytesAtThisLevel;
|
|
}
|
|
}
|
|
|
|
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
|
|
|
|
// At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level
|
|
// is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees.
|
|
// There could be sub trees below this point, which might take many more bytes, but that's ok, because we can
|
|
// always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet
|
|
// for the remaining sub trees.
|
|
unsigned char childrenExistInTreeBits = 0;
|
|
unsigned char childrenExistInPacketBits = 0;
|
|
unsigned char childrenDataBits = 0;
|
|
|
|
// Make our local buffer large enough to handle writing at this level in case we need to.
|
|
LevelDetails thisLevelKey = packetData->startLevel();
|
|
int requiredBytes = sizeof(childrenDataBits) + sizeof(childrenExistInPacketBits);
|
|
if (params.includeExistsBits) {
|
|
requiredBytes += sizeof(childrenExistInTreeBits);
|
|
}
|
|
|
|
// If this datatype allows root elements to include data, and this is the root, then ask the tree for the
|
|
// minimum bytes needed for root data and reserve those also
|
|
if (element == _rootElement && rootElementHasData()) {
|
|
requiredBytes += minimumRequiredRootDataBytes();
|
|
}
|
|
|
|
bool continueThisLevel = packetData->reserveBytes(requiredBytes);
|
|
|
|
// If we can't reserve our minimum bytes then we can discard this level and return as if none of this level fits
|
|
if (!continueThisLevel) {
|
|
packetData->discardLevel(thisLevelKey);
|
|
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
|
bag.insert(element);
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
int inViewCount = 0;
|
|
int inViewNotLeafCount = 0;
|
|
int inViewWithColorCount = 0;
|
|
|
|
OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
int currentCount = 0;
|
|
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
|
|
|
// if the caller wants to include childExistsBits, then include them even if not in view, if however,
|
|
// we're in a portion of the tree that's not our responsibility, then we assume the child nodes exist
|
|
// even if they don't in our local tree
|
|
bool notMyJurisdiction = false;
|
|
if (params.jurisdictionMap) {
|
|
notMyJurisdiction = JurisdictionMap::WITHIN != params.jurisdictionMap->isMyJurisdiction(element->getOctalCode(), i);
|
|
}
|
|
if (params.includeExistsBits) {
|
|
// If the child is known to exist, OR, it's not my jurisdiction, then we mark the bit as existing
|
|
if (childElement || notMyJurisdiction) {
|
|
childrenExistInTreeBits += (1 << (7 - i));
|
|
}
|
|
}
|
|
|
|
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
|
|
if (params.stats && childElement) {
|
|
params.stats->traversed(childElement);
|
|
}
|
|
}
|
|
|
|
// 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 i = 0; i < currentCount; i++) {
|
|
OctreeElementPointer childElement = sortedChildren[i];
|
|
int originalIndex = indexOfChildren[i];
|
|
|
|
bool childIsInView = (childElement &&
|
|
( !params.viewFrustum || // no view frustum was given, everything is assumed in view
|
|
(nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are
|
|
(nodeLocationThisView == ViewFrustum::INTERSECT &&
|
|
childElement->isInView(*params.viewFrustum)) // the parent intersects and the child is in view
|
|
));
|
|
|
|
if (!childIsInView) {
|
|
// must check childElement here, because it could be we got here because there was no childElement
|
|
if (params.stats && childElement) {
|
|
params.stats->skippedOutOfView(childElement);
|
|
}
|
|
} else {
|
|
// Before we consider this further, let's see if it's in our LOD scope...
|
|
float distance = distancesToChildren[i];
|
|
float boundaryDistance = !params.viewFrustum ? 1 :
|
|
boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust,
|
|
params.octreeElementSizeScale);
|
|
|
|
if (!(distance < boundaryDistance)) {
|
|
// don't need to check childElement here, because we can't get here with no childElement
|
|
if (params.stats) {
|
|
params.stats->skippedDistance(childElement);
|
|
}
|
|
} else {
|
|
inViewCount++;
|
|
|
|
// track children in view as existing and not a leaf, if they're a leaf,
|
|
// we don't care about recursing deeper on them, and we don't consider their
|
|
// subtree to exist
|
|
if (!(childElement && childElement->isLeaf())) {
|
|
childrenExistInPacketBits += (1 << (7 - originalIndex));
|
|
inViewNotLeafCount++;
|
|
}
|
|
|
|
bool childIsOccluded = false; // assume it's not occluded
|
|
|
|
bool shouldRender = !params.viewFrustum
|
|
? true
|
|
: childElement->calculateShouldRender(params.viewFrustum,
|
|
params.octreeElementSizeScale, params.boundaryLevelAdjust);
|
|
|
|
// track some stats
|
|
if (params.stats) {
|
|
// don't need to check childElement here, because we can't get here with no childElement
|
|
if (!shouldRender && childElement->isLeaf()) {
|
|
params.stats->skippedDistance(childElement);
|
|
}
|
|
// don't need to check childElement here, because we can't get here with no childElement
|
|
if (childIsOccluded) {
|
|
params.stats->skippedOccluded(childElement);
|
|
}
|
|
}
|
|
|
|
// track children with actual color, only if the child wasn't previously in view!
|
|
if (shouldRender && !childIsOccluded) {
|
|
bool childWasInView = false;
|
|
|
|
if (childElement && params.deltaViewFrustum && params.lastViewFrustum) {
|
|
ViewFrustum::location location = childElement->inFrustum(*params.lastViewFrustum);
|
|
|
|
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
|
if (childElement->isLeaf()) {
|
|
childWasInView = location != ViewFrustum::OUTSIDE;
|
|
} else {
|
|
childWasInView = location == ViewFrustum::INSIDE;
|
|
}
|
|
}
|
|
|
|
// If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items.
|
|
// Or if we were previously in the view, but this element has changed since it was last sent, then we do
|
|
// need to send it.
|
|
if (!childWasInView ||
|
|
(params.deltaViewFrustum &&
|
|
childElement->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){
|
|
|
|
childrenDataBits += (1 << (7 - originalIndex));
|
|
inViewWithColorCount++;
|
|
} else {
|
|
// otherwise just track stats of the items we discarded
|
|
// don't need to check childElement here, because we can't get here with no childElement
|
|
if (params.stats) {
|
|
if (childWasInView) {
|
|
params.stats->skippedWasInView(childElement);
|
|
} else {
|
|
params.stats->skippedNoChange(childElement);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: the childrenDataBits indicates that there is an array of child element data included in this packet.
|
|
// We wil write this bit mask but we may come back later and update the bits that are actually included
|
|
packetData->releaseReservedBytes(sizeof(childrenDataBits));
|
|
continueThisLevel = packetData->appendBitMask(childrenDataBits);
|
|
|
|
int childDataBitsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenDataBits));
|
|
unsigned char actualChildrenDataBits = 0;
|
|
|
|
assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail
|
|
bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count
|
|
if (params.stats) {
|
|
params.stats->colorBitsWritten(); // really data bits not just color bits
|
|
}
|
|
|
|
// 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...
|
|
// NOTE: the format of the bitstream is generally this:
|
|
// [octalcode]
|
|
// [bitmask for existence of child data]
|
|
// N x [child data]
|
|
// [bitmask for existence of child elements in tree]
|
|
// [bitmask for existence of child elements in buffer]
|
|
// 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);
|
|
|
|
// 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();
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// 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 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();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mustIncludeAllChildData() && !continueThisLevel) {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: reached end of child element data loop with continueThisLevel=FALSE";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
|
|
if (continueThisLevel && actualChildrenDataBits != childrenDataBits) {
|
|
// repair the child data mask
|
|
continueThisLevel = packetData->updatePriorBitMask(childDataBitsPlaceHolder, actualChildrenDataBits);
|
|
if (!continueThisLevel) {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childDataBitsPlaceHolder";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
}
|
|
|
|
// if the caller wants to include childExistsBits, then include them even if not in view, put them before the
|
|
// childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits
|
|
if (continueThisLevel && params.includeExistsBits) {
|
|
packetData->releaseReservedBytes(sizeof(childrenExistInTreeBits));
|
|
continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits);
|
|
if (continueThisLevel) {
|
|
bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count
|
|
if (params.stats) {
|
|
params.stats->existsBitsWritten();
|
|
}
|
|
} else {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
}
|
|
|
|
// write the child exist bits
|
|
if (continueThisLevel) {
|
|
packetData->releaseReservedBytes(sizeof(childrenExistInPacketBits));
|
|
continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits);
|
|
if (continueThisLevel) {
|
|
bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count
|
|
if (params.stats) {
|
|
params.stats->existsInPacketBitsWritten();
|
|
}
|
|
} else {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
}
|
|
|
|
// We only need to keep digging, if there is at least one child that is inView, and not a leaf.
|
|
keepDiggingDeeper = (inViewNotLeafCount > 0);
|
|
|
|
//
|
|
// NOTE: the format of the bitstream is generally this:
|
|
// [octalcode]
|
|
// [bitmask for existence of child data]
|
|
// N x [child data]
|
|
// [bitmask for existence of child elements in tree]
|
|
// [bitmask for existence of child elements in buffer]
|
|
// N x [ ... tree for children ...]
|
|
//
|
|
// This section of the code, is writing the "N x [ ... tree for children ...]" portion of this bitstream
|
|
//
|
|
if (continueThisLevel && keepDiggingDeeper) {
|
|
|
|
// at this point, we need to iterate the children who are in view, even if not colored
|
|
// and we need to determine if there's a deeper tree below them that we care about.
|
|
//
|
|
// Since this recursive function assumes we're already writing, we know we've already written our
|
|
// childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if
|
|
// we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is
|
|
// write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they
|
|
// write something, we keep them in the bits, if they don't, we take them out.
|
|
//
|
|
// 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
|
|
|
|
// 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++) {
|
|
OctreeElementPointer childElement = sortedChildren[indexByDistance];
|
|
int originalIndex = indexOfChildren[indexByDistance];
|
|
|
|
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
|
|
|
int thisLevel = currentEncodeLevel;
|
|
|
|
int childTreeBytesOut = 0;
|
|
|
|
// NOTE: some octree styles (like models and particles) will store content in parent elements, and child
|
|
// elements. In this case, if we stop recursion when we include any data (the colorbits should really be
|
|
// called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep
|
|
// recursing, by returning TRUE in recurseChildrenWithData().
|
|
|
|
if (recurseChildrenWithData() || !params.viewFrustum || !oneAtBit(childrenDataBits, originalIndex)) {
|
|
|
|
// Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this
|
|
// will be true. But if the tree has already been encoded, we will skip this.
|
|
if (element->shouldRecurseChildTree(originalIndex, params)) {
|
|
childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params,
|
|
thisLevel, nodeLocationThisView);
|
|
} else {
|
|
childTreeBytesOut = 0;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
|
|
// if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color
|
|
// byte and the child exist byte.
|
|
//
|
|
assert(childTreeBytesOut != 1);
|
|
|
|
// if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because...
|
|
// if it had colors it would write 1 byte for the color mask,
|
|
// and at least a color's worth of bytes for the element of colors.
|
|
// if it had child trees (with something in them) then it would have the 1 byte for child mask
|
|
// and some number of bytes of lower children...
|
|
// 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) {
|
|
childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees
|
|
|
|
}
|
|
|
|
bytesAtThisLevel += childTreeBytesOut;
|
|
|
|
// If we had previously started writing, and if the child DIDN'T write any bytes,
|
|
// then we want to remove their bit from the childExistsPlaceHolder bitmask
|
|
if (childTreeBytesOut == 0) {
|
|
|
|
// remove this child's bit...
|
|
childrenExistInPacketBits -= (1 << (7 - originalIndex));
|
|
|
|
// repair the child exists mask
|
|
continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits);
|
|
if (!continueThisLevel) {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childExistsPlaceHolder";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (!continueThisLevel) {
|
|
if (wantDebug) {
|
|
qCDebug(octree) << " WARNING line:" << __LINE__;
|
|
qCDebug(octree) << " breaking the child recursion loop with continueThisLevel=false!!!";
|
|
qCDebug(octree) << " AFTER attempting to updatePriorBitMask() for empty sub tree....";
|
|
qCDebug(octree) << " IS THIS ACCEPTABLE!!!!";
|
|
}
|
|
break; // can't continue...
|
|
}
|
|
|
|
// Note: no need to move the pointer, cause we already stored this
|
|
} // end if (childTreeBytesOut == 0)
|
|
} // end if (oneAtBit(childrenExistInPacketBits, originalIndex))
|
|
} // end for
|
|
} // end keepDiggingDeeper
|
|
|
|
// If we made it this far, then we've written all of our child data... if this element is the root
|
|
// element, then we also allow the root element to write out it's data...
|
|
if (continueThisLevel && element == _rootElement && rootElementHasData()) {
|
|
|
|
int bytesBeforeChild = packetData->getUncompressedSize();
|
|
|
|
// release the bytes we reserved...
|
|
packetData->releaseReservedBytes(minimumRequiredRootDataBytes());
|
|
|
|
LevelDetails rootDataLevelKey = packetData->startLevel();
|
|
OctreeElement::AppendState rootAppendState = element->appendElementData(packetData, params);
|
|
bool partOfRootFit = (rootAppendState != OctreeElement::NONE);
|
|
bool allOfRootFit = (rootAppendState == OctreeElement::COMPLETED);
|
|
|
|
if (partOfRootFit) {
|
|
continueThisLevel = packetData->endLevel(rootDataLevelKey);
|
|
if (!continueThisLevel) {
|
|
qCDebug(octree) << " UNEXPECTED ROOT ELEMENT -- could not packetData->endLevel(rootDataLevelKey) -- line:" << __LINE__;
|
|
}
|
|
} else {
|
|
packetData->discardLevel(rootDataLevelKey);
|
|
}
|
|
|
|
if (!allOfRootFit) {
|
|
elementAppendState = OctreeElement::PARTIAL;
|
|
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
|
}
|
|
|
|
// do we really ever NOT want to continue this level???
|
|
//continueThisLevel = (rootAppendState == OctreeElement::COMPLETED);
|
|
|
|
int bytesAfterChild = packetData->getUncompressedSize();
|
|
|
|
if (continueThisLevel) {
|
|
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
|
|
|
if (params.stats) {
|
|
params.stats->colorSent(element);
|
|
}
|
|
}
|
|
|
|
if (!continueThisLevel) {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in packing ROOT data";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
|
|
}
|
|
|
|
// if we were unable to fit this level in our packet, then rewind and add it to the element bag for
|
|
// sending later...
|
|
if (continueThisLevel) {
|
|
continueThisLevel = packetData->endLevel(thisLevelKey);
|
|
} else {
|
|
packetData->discardLevel(thisLevelKey);
|
|
|
|
if (!mustIncludeAllChildData()) {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element";
|
|
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
|
}
|
|
}
|
|
|
|
// This happens if the element could not be written at all. In the case of Octree's that support partial
|
|
// element data, continueThisLevel will be true. So this only happens if the full element needs to be
|
|
// added back to the element bag.
|
|
if (!continueThisLevel) {
|
|
if (!mustIncludeAllChildData()) {
|
|
qCDebug(octree) << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element.";
|
|
qCDebug(octree) << " If the datatype requires all child data, then this might happen. Otherwise" ;
|
|
qCDebug(octree) << " this is an unexpected case and we should research a potential logic error." ;
|
|
}
|
|
|
|
bag.insert(element);
|
|
|
|
// don't need to check element here, because we can't get here with no element
|
|
if (params.stats) {
|
|
params.stats->didntFit(element);
|
|
}
|
|
|
|
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
|
bytesAtThisLevel = 0; // didn't fit
|
|
} else {
|
|
|
|
// assuming we made it here with continueThisLevel == true, we STILL might want
|
|
// to add our element back to the bag for additional encoding, specifically if
|
|
// the appendState is PARTIAL, in this case, we re-add our element to the bag
|
|
// and assume that the appendElementData() has stored any required state data
|
|
// in the params extraEncodeData
|
|
if (elementAppendState == OctreeElement::PARTIAL) {
|
|
bag.insert(element);
|
|
}
|
|
}
|
|
|
|
// If our element is completed let the element know so it can do any cleanup it of extra wants
|
|
if (elementAppendState == OctreeElement::COMPLETED) {
|
|
element->elementEncodeComplete(params);
|
|
}
|
|
|
|
return bytesAtThisLevel;
|
|
}
|
|
|
|
bool Octree::readFromFile(const char* fileName) {
|
|
QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS);
|
|
|
|
if (qFileName.endsWith(".json.gz")) {
|
|
return readJSONFromGzippedFile(qFileName);
|
|
}
|
|
|
|
QFile file(qFileName);
|
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qCritical() << "unable to open for reading: " << fileName;
|
|
return false;
|
|
}
|
|
|
|
QDataStream fileInputStream(&file);
|
|
QFileInfo fileInfo(qFileName);
|
|
unsigned long fileLength = fileInfo.size();
|
|
|
|
emit importSize(1.0f, 1.0f, 1.0f);
|
|
emit importProgress(0);
|
|
|
|
qCDebug(octree) << "Loading file" << qFileName << "...";
|
|
|
|
bool success = readFromStream(fileLength, fileInputStream);
|
|
|
|
emit importProgress(100);
|
|
file.close();
|
|
|
|
return success;
|
|
}
|
|
|
|
bool Octree::readJSONFromGzippedFile(QString qFileName) {
|
|
QFile file(qFileName);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qCritical() << "Cannot open gzipped json file for reading: " << qFileName;
|
|
return false;
|
|
}
|
|
QByteArray compressedJsonData = file.readAll();
|
|
QByteArray jsonData;
|
|
|
|
if (!gunzip(compressedJsonData, jsonData)) {
|
|
qCritical() << "json File not in gzip format: " << qFileName;
|
|
return false;
|
|
}
|
|
|
|
QDataStream jsonStream(jsonData);
|
|
return readJSONFromStream(-1, jsonStream);
|
|
}
|
|
|
|
bool Octree::readFromURL(const QString& urlString) {
|
|
bool readOk = false;
|
|
|
|
// determine if this is a local file or a network resource
|
|
QUrl url(urlString);
|
|
|
|
if (url.isLocalFile()) {
|
|
readOk = readFromFile(qPrintable(url.toLocalFile()));
|
|
} else {
|
|
QNetworkRequest request;
|
|
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
|
request.setUrl(url);
|
|
|
|
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
|
QNetworkReply* reply = networkAccessManager.get(request);
|
|
|
|
qCDebug(octree) << "Downloading svo at" << qPrintable(urlString);
|
|
|
|
QEventLoop loop;
|
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
|
loop.exec();
|
|
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
int resourceSize = reply->bytesAvailable();
|
|
QDataStream inputStream(reply);
|
|
readOk = readFromStream(resourceSize, inputStream);
|
|
}
|
|
}
|
|
return readOk;
|
|
}
|
|
|
|
|
|
bool Octree::readFromStream(unsigned long streamLength, QDataStream& inputStream) {
|
|
// decide if this is binary SVO or JSON-formatted SVO
|
|
QIODevice *device = inputStream.device();
|
|
char firstChar;
|
|
device->getChar(&firstChar);
|
|
device->ungetChar(firstChar);
|
|
|
|
if (firstChar == (char) PacketType::EntityData) {
|
|
qCDebug(octree) << "Reading from binary SVO Stream length:" << streamLength;
|
|
return readSVOFromStream(streamLength, inputStream);
|
|
} else {
|
|
qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength;
|
|
return readJSONFromStream(streamLength, inputStream);
|
|
}
|
|
}
|
|
|
|
|
|
bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStream) {
|
|
qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon.";
|
|
|
|
bool fileOk = false;
|
|
|
|
PacketVersion gotVersion = 0;
|
|
|
|
unsigned long headerLength = 0; // bytes in the header
|
|
|
|
bool wantImportProgress = true;
|
|
|
|
PacketType expectedType = expectedDataPacketType();
|
|
PacketVersion expectedVersion = versionForPacketType(expectedType);
|
|
bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
|
|
|
|
// before reading the file, check to see if this version of the Octree supports file versions
|
|
if (getWantSVOfileVersions()) {
|
|
|
|
// read just enough of the file to parse the header...
|
|
const unsigned long HEADER_LENGTH = sizeof(int) + sizeof(PacketVersion);
|
|
unsigned char fileHeader[HEADER_LENGTH];
|
|
inputStream.readRawData((char*)&fileHeader, HEADER_LENGTH);
|
|
|
|
headerLength = HEADER_LENGTH; // we need this later to skip to the data
|
|
|
|
unsigned char* dataAt = (unsigned char*)&fileHeader;
|
|
unsigned long dataLength = HEADER_LENGTH;
|
|
|
|
// if so, read the first byte of the file and see if it matches the expected version code
|
|
int intPacketType;
|
|
memcpy(&intPacketType, dataAt, sizeof(intPacketType));
|
|
PacketType gotType = (PacketType) intPacketType;
|
|
|
|
dataAt += sizeof(expectedType);
|
|
dataLength -= sizeof(expectedType);
|
|
gotVersion = *dataAt;
|
|
|
|
if (gotType == expectedType) {
|
|
if (canProcessVersion(gotVersion)) {
|
|
dataAt += sizeof(gotVersion);
|
|
dataLength -= sizeof(gotVersion);
|
|
fileOk = true;
|
|
qCDebug(octree, "SVO file version match. Expected: %d Got: %d",
|
|
versionForPacketType(expectedDataPacketType()), gotVersion);
|
|
|
|
hasBufferBreaks = versionHasSVOfileBreaks(gotVersion);
|
|
} else {
|
|
qCDebug(octree, "SVO file version mismatch. Expected: %d Got: %d",
|
|
versionForPacketType(expectedDataPacketType()), gotVersion);
|
|
}
|
|
} else {
|
|
qCDebug(octree) << "SVO file type mismatch. Expected: " << expectedType
|
|
<< " Got: " << gotType;
|
|
}
|
|
|
|
} else {
|
|
qCDebug(octree) << " NOTE: this file type does not include type and version information.";
|
|
fileOk = true; // assume the file is ok
|
|
}
|
|
|
|
if (hasBufferBreaks) {
|
|
qCDebug(octree) << " this version includes buffer breaks";
|
|
} else {
|
|
qCDebug(octree) << " this version does not include buffer breaks";
|
|
}
|
|
|
|
if (fileOk) {
|
|
|
|
// if this version of the file does not include buffer breaks, then we need to load the entire file at once
|
|
if (!hasBufferBreaks) {
|
|
|
|
// read the entire file into a buffer, WHAT!? Why not.
|
|
unsigned long dataLength = streamLength - headerLength;
|
|
unsigned char* entireFileDataSection = new unsigned char[dataLength];
|
|
inputStream.readRawData((char*)entireFileDataSection, dataLength);
|
|
|
|
unsigned char* dataAt = entireFileDataSection;
|
|
|
|
ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0,
|
|
SharedNodePointer(), wantImportProgress, gotVersion);
|
|
|
|
readBitstreamToTree(dataAt, dataLength, args);
|
|
delete[] entireFileDataSection;
|
|
|
|
} else {
|
|
|
|
|
|
unsigned long dataLength = streamLength - headerLength;
|
|
unsigned long remainingLength = dataLength;
|
|
const unsigned long MAX_CHUNK_LENGTH = MAX_OCTREE_PACKET_SIZE * 2;
|
|
unsigned char* fileChunk = new unsigned char[MAX_CHUNK_LENGTH];
|
|
|
|
while (remainingLength > 0) {
|
|
quint16 chunkLength = 0;
|
|
|
|
inputStream.readRawData((char*)&chunkLength, sizeof(chunkLength));
|
|
remainingLength -= sizeof(chunkLength);
|
|
|
|
if (chunkLength > remainingLength) {
|
|
qCDebug(octree) << "UNEXPECTED chunk size of:" << chunkLength
|
|
<< "greater than remaining length:" << remainingLength;
|
|
break;
|
|
}
|
|
|
|
if (chunkLength > MAX_CHUNK_LENGTH) {
|
|
qCDebug(octree) << "UNEXPECTED chunk size of:" << chunkLength
|
|
<< "greater than MAX_CHUNK_LENGTH:" << MAX_CHUNK_LENGTH;
|
|
break;
|
|
}
|
|
|
|
inputStream.readRawData((char*)fileChunk, chunkLength);
|
|
|
|
remainingLength -= chunkLength;
|
|
|
|
unsigned char* dataAt = fileChunk;
|
|
unsigned long dataLength = chunkLength;
|
|
|
|
ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0,
|
|
SharedNodePointer(), wantImportProgress, gotVersion);
|
|
|
|
readBitstreamToTree(dataAt, dataLength, args);
|
|
}
|
|
|
|
delete[] fileChunk;
|
|
}
|
|
}
|
|
|
|
|
|
return fileOk;
|
|
}
|
|
|
|
const int READ_JSON_BUFFER_SIZE = 2048;
|
|
|
|
bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputStream) {
|
|
// if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until
|
|
// we get an eof. Leave streamLength parameter for consistency.
|
|
|
|
QByteArray jsonBuffer;
|
|
char* rawData = new char[READ_JSON_BUFFER_SIZE];
|
|
while (!inputStream.atEnd()) {
|
|
int got = inputStream.readRawData(rawData, READ_JSON_BUFFER_SIZE - 1);
|
|
if (got < 0) {
|
|
qCritical() << "error while reading from json stream";
|
|
delete[] rawData;
|
|
return false;
|
|
}
|
|
if (got == 0) {
|
|
break;
|
|
}
|
|
jsonBuffer += QByteArray(rawData, got);
|
|
}
|
|
|
|
QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer);
|
|
QVariant asVariant = asDocument.toVariant();
|
|
QVariantMap asMap = asVariant.toMap();
|
|
readFromMap(asMap);
|
|
delete[] rawData;
|
|
return true;
|
|
}
|
|
|
|
void Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) {
|
|
// make the sure file extension makes sense
|
|
QString qFileName = fileNameWithoutExtension(QString(fileName), PERSIST_EXTENSIONS) + "." + persistAsFileType;
|
|
QByteArray byteArray = qFileName.toUtf8();
|
|
const char* cFileName = byteArray.constData();
|
|
|
|
if (persistAsFileType == "svo") {
|
|
writeToSVOFile(fileName, element);
|
|
} else if (persistAsFileType == "json") {
|
|
writeToJSONFile(cFileName, element);
|
|
} else if (persistAsFileType == "json.gz") {
|
|
writeToJSONFile(cFileName, element, true);
|
|
} else {
|
|
qCDebug(octree) << "unable to write octree to file of type" << persistAsFileType;
|
|
}
|
|
}
|
|
|
|
void Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, bool doGzip) {
|
|
QVariantMap entityDescription;
|
|
|
|
qCDebug(octree, "Saving JSON SVO to file %s...", fileName);
|
|
|
|
OctreeElementPointer top;
|
|
if (element) {
|
|
top = element;
|
|
} else {
|
|
top = _rootElement;
|
|
}
|
|
|
|
// include the "bitstream" version
|
|
PacketType expectedType = expectedDataPacketType();
|
|
PacketVersion expectedVersion = versionForPacketType(expectedType);
|
|
entityDescription["Version"] = (int) expectedVersion;
|
|
|
|
// store the entity data
|
|
bool entityDescriptionSuccess = writeToMap(entityDescription, top, true);
|
|
if (!entityDescriptionSuccess) {
|
|
qCritical("Failed to convert Entities to QVariantMap while saving to json.");
|
|
return;
|
|
}
|
|
|
|
// convert the QVariantMap to JSON
|
|
QByteArray jsonData = QJsonDocument::fromVariant(entityDescription).toJson();
|
|
QByteArray jsonDataForFile;
|
|
|
|
if (doGzip) {
|
|
if (!gzip(jsonData, jsonDataForFile, -1)) {
|
|
qCritical("unable to gzip data while saving to json.");
|
|
return;
|
|
}
|
|
} else {
|
|
jsonDataForFile = jsonData;
|
|
}
|
|
|
|
QFile persistFile(fileName);
|
|
if (persistFile.open(QIODevice::WriteOnly)) {
|
|
persistFile.write(jsonDataForFile);
|
|
} else {
|
|
qCritical("Could not write to JSON description of entities.");
|
|
}
|
|
}
|
|
|
|
void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) {
|
|
qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon.";
|
|
|
|
std::ofstream file(fileName, std::ios::out|std::ios::binary);
|
|
|
|
if(file.is_open()) {
|
|
qCDebug(octree, "Saving binary SVO to file %s...", fileName);
|
|
|
|
PacketType expectedPacketType = expectedDataPacketType();
|
|
int expectedIntType = (int) expectedPacketType;
|
|
PacketVersion expectedVersion = versionForPacketType(expectedPacketType);
|
|
bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
|
|
|
|
// before reading the file, check to see if this version of the Octree supports file versions
|
|
if (getWantSVOfileVersions()) {
|
|
// if so, read the first byte of the file and see if it matches the expected version code
|
|
file.write(reinterpret_cast<char*>(&expectedIntType), sizeof(expectedIntType));
|
|
file.write(&expectedVersion, sizeof(expectedVersion));
|
|
qCDebug(octree) << "SVO file type: " << expectedPacketType << " version: " << (int)expectedVersion;
|
|
|
|
hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
|
|
}
|
|
if (hasBufferBreaks) {
|
|
qCDebug(octree) << " this version includes buffer breaks";
|
|
} else {
|
|
qCDebug(octree) << " this version does not include buffer breaks";
|
|
}
|
|
|
|
|
|
OctreeElementBag elementBag;
|
|
OctreeElementExtraEncodeData extraEncodeData;
|
|
// If we were given a specific element, start from there, otherwise start from root
|
|
if (element) {
|
|
elementBag.insert(element);
|
|
} else {
|
|
elementBag.insert(_rootElement);
|
|
}
|
|
|
|
OctreePacketData packetData;
|
|
int bytesWritten = 0;
|
|
bool lastPacketWritten = false;
|
|
|
|
while (OctreeElementPointer subTree = elementBag.extract()) {
|
|
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, NO_EXISTS_BITS);
|
|
withReadLock([&] {
|
|
params.extraEncodeData = &extraEncodeData;
|
|
bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params);
|
|
});
|
|
|
|
// if the subTree couldn't fit, and so we should reset the packet and reinsert the element in our bag and try again
|
|
if (bytesWritten == 0 && (params.stopReason == EncodeBitstreamParams::DIDNT_FIT)) {
|
|
if (packetData.hasContent()) {
|
|
// if this type of SVO file should have buffer breaks, then we will write a buffer size before each
|
|
// buffer to allow the reader to read this file in chunks.
|
|
if (hasBufferBreaks) {
|
|
quint16 bufferSize = packetData.getFinalizedSize();
|
|
file.write((const char*)&bufferSize, sizeof(bufferSize));
|
|
}
|
|
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
|
|
lastPacketWritten = true;
|
|
}
|
|
packetData.reset(); // is there a better way to do this? could we fit more?
|
|
elementBag.insert(subTree);
|
|
} else {
|
|
lastPacketWritten = false;
|
|
}
|
|
}
|
|
|
|
if (!lastPacketWritten) {
|
|
// if this type of SVO file should have buffer breaks, then we will write a buffer size before each
|
|
// buffer to allow the reader to read this file in chunks.
|
|
if (hasBufferBreaks) {
|
|
quint16 bufferSize = packetData.getFinalizedSize();
|
|
file.write((const char*)&bufferSize, sizeof(bufferSize));
|
|
}
|
|
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
|
|
}
|
|
|
|
releaseSceneEncodeData(&extraEncodeData);
|
|
}
|
|
file.close();
|
|
}
|
|
|
|
unsigned long Octree::getOctreeElementsCount() {
|
|
unsigned long nodeCount = 0;
|
|
recurseTreeWithOperation(countOctreeElementsOperation, &nodeCount);
|
|
return nodeCount;
|
|
}
|
|
|
|
bool Octree::countOctreeElementsOperation(OctreeElementPointer element, void* extraData) {
|
|
(*(unsigned long*)extraData)++;
|
|
return true; // keep going
|
|
}
|
|
|
|
void Octree::cancelImport() {
|
|
_stopImport = true;
|
|
}
|