mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-23 10:24:01 +02:00
941 lines
35 KiB
C++
941 lines
35 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
|
|
//
|
|
|
|
#include "Octree.h"
|
|
|
|
#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 <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QFileInfo>
|
|
#include <QSaveFile>
|
|
#include <QString>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
|
|
#include <GeometryUtil.h>
|
|
#include <Gzip.h>
|
|
#include <LogHandler.h>
|
|
#include <NetworkAccessManager.h>
|
|
#include <OctalCode.h>
|
|
#include <udt/PacketHeaders.h>
|
|
#include <ResourceManager.h>
|
|
#include <SharedUtil.h>
|
|
#include <PathUtils.h>
|
|
#include <ViewFrustum.h>
|
|
|
|
#include "OctreeConstants.h"
|
|
#include "OctreeLogging.h"
|
|
#include "OctreeQueryNode.h"
|
|
#include "OctreeUtils.h"
|
|
|
|
|
|
QVector<QString> PERSIST_EXTENSIONS = {"json", "json.gz"};
|
|
|
|
Octree::Octree(bool shouldReaverage) :
|
|
_rootElement(NULL),
|
|
_isDirty(true),
|
|
_shouldReaverage(shouldReaverage),
|
|
_isViewing(false),
|
|
_isServer(false)
|
|
{
|
|
}
|
|
|
|
Octree::~Octree() {
|
|
// This will delete all children, don't create a new root in this case.
|
|
eraseAllOctreeElements(false);
|
|
}
|
|
|
|
// Recurses voxel tree calling the RecurseOctreeOperation function for each element.
|
|
// stops recursion if operation function returns false.
|
|
void Octree::recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData) {
|
|
recurseElementWithOperation(_rootElement, operation, extraData);
|
|
}
|
|
|
|
// Recurses voxel element with an operation function
|
|
void Octree::recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData,
|
|
int recursionCount) {
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
HIFI_FCDEBUG(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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Octree::recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData) {
|
|
recurseElementWithOperationSorted(_rootElement, operation, sortingOperation, extraData);
|
|
}
|
|
|
|
// Recurses voxel element with an operation function, calling operation on its children in a specific order
|
|
bool Octree::recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
|
const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount) {
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
// If we go too deep, we want to keep searching other paths
|
|
return true;
|
|
}
|
|
|
|
bool keepSearching = operation(element, extraData);
|
|
|
|
std::vector<SortedChild> sortedChildren;
|
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
OctreeElementPointer child = element->getChildAtIndex(i);
|
|
if (child) {
|
|
float priority = sortingOperation(child, extraData);
|
|
if (priority < FLT_MAX) {
|
|
sortedChildren.emplace_back(priority, child);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sortedChildren.size() > 1) {
|
|
static auto comparator = [](const SortedChild& left, const SortedChild& right) { return left.first < right.first; };
|
|
std::sort(sortedChildren.begin(), sortedChildren.end(), comparator);
|
|
}
|
|
|
|
for (auto it = sortedChildren.begin(); it != sortedChildren.end(); ++it) {
|
|
const SortedChild& sortedChild = *it;
|
|
// Our children were sorted, so if one hits something, we don't need to check the others
|
|
if (!recurseElementWithOperationSorted(sortedChild.second, operation, sortingOperation, extraData, recursionCount + 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
// We checked all our children and didn't find anything.
|
|
// Stop if we hit something in this element. Continue if we didn't.
|
|
return keepSearching;
|
|
}
|
|
|
|
void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) {
|
|
recurseElementWithOperator(_rootElement, operatorObject);
|
|
}
|
|
|
|
bool Octree::recurseElementWithOperator(const OctreeElementPointer& element,
|
|
RecurseOctreeOperator* operatorObject, int recursionCount) {
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
HIFI_FCDEBUG(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(const 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(const OctreeElementPointer& lastParentElement,
|
|
const unsigned char* codeToReach, int recursionCount) {
|
|
|
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
HIFI_FCDEBUG(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(const 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)) {
|
|
auto childAt = destinationElement->getChildAtIndex(childIndex);
|
|
if (!childAt) {
|
|
// add a child at that index, if it doesn't exist
|
|
childAt = destinationElement->addChildAtIndex(childIndex);
|
|
bool nodeIsDirty = destinationElement->isDirty();
|
|
if (nodeIsDirty) {
|
|
_isDirty = true;
|
|
}
|
|
}
|
|
|
|
// tell the child to read the subsequent data
|
|
int lowerLevelBytes = readElementData(childAt, 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;
|
|
}
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
void Octree::readBitstreamToTree(const unsigned char * bitstream, uint64_t 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) {
|
|
HIFI_FCDEBUG(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;
|
|
}
|
|
}
|
|
|
|
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(const 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(const 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(const 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(const 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;
|
|
}
|
|
|
|
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);
|
|
uint64_t fileLength = fileInfo.size();
|
|
|
|
qCDebug(octree) << "Loading file" << qFileName << "...";
|
|
|
|
bool success = readFromStream(fileLength, fileInputStream);
|
|
|
|
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);
|
|
}
|
|
|
|
// hack to get the marketplace id into the entities. We will create a way to get this from a hash of
|
|
// the entity later, but this helps us move things along for now
|
|
QString getMarketplaceID(const QString& urlString) {
|
|
// the url should be http://mpassets.highfidelity.com/<uuid>-v1/<item name>.extension
|
|
// a regex for the this is a PITA as there are several valid versions of uuids, and so
|
|
// lets strip out the uuid (if any) and try to create a UUID from the string, relying on
|
|
// QT to parse it
|
|
static const QRegularExpression re("^http:\\/\\/mpassets.highfidelity.com\\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-v[\\d]+\\/.*");
|
|
QRegularExpressionMatch match = re.match(urlString);
|
|
if (match.hasMatch()) {
|
|
QString matched = match.captured(1);
|
|
if (QUuid(matched).isNull()) {
|
|
qDebug() << "invalid uuid for marketplaceID";
|
|
} else {
|
|
return matched;
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
bool Octree::readFromURL(const QString& urlString) {
|
|
QString trimmedUrl = urlString.trimmed();
|
|
QString marketplaceID = getMarketplaceID(trimmedUrl);
|
|
auto request =
|
|
std::unique_ptr<ResourceRequest>(DependencyManager::get<ResourceManager>()->createResourceRequest(this, trimmedUrl));
|
|
|
|
if (!request) {
|
|
return false;
|
|
}
|
|
|
|
QEventLoop loop;
|
|
connect(request.get(), &ResourceRequest::finished, &loop, &QEventLoop::quit);
|
|
request->send();
|
|
loop.exec();
|
|
|
|
if (request->getResult() != ResourceRequest::Success) {
|
|
return false;
|
|
}
|
|
|
|
auto data = request->getData();
|
|
|
|
QByteArray uncompressedJsonData;
|
|
bool wasCompressed = gunzip(data, uncompressedJsonData);
|
|
|
|
if (wasCompressed) {
|
|
QDataStream inputStream(uncompressedJsonData);
|
|
return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID);
|
|
}
|
|
|
|
QDataStream inputStream(data);
|
|
return readFromStream(data.size(), inputStream, marketplaceID);
|
|
}
|
|
|
|
|
|
bool Octree::readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID) {
|
|
// 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) {
|
|
qCWarning(octree) << "Reading from binary SVO no longer supported";
|
|
return false;
|
|
} else {
|
|
qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength;
|
|
return readJSONFromStream(streamLength, inputStream, marketplaceID);
|
|
}
|
|
}
|
|
|
|
|
|
// hack to get the marketplace id into the entities. We will create a way to get this from a hash of
|
|
// the entity later, but this helps us move things along for now
|
|
QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QString& marketplaceID) {
|
|
if (!marketplaceID.isEmpty()) {
|
|
QJsonDocument newDoc;
|
|
QJsonObject rootObj = doc.object();
|
|
QJsonArray newEntitiesArray;
|
|
|
|
// build a new entities array
|
|
auto entitiesArray = rootObj["Entities"].toArray();
|
|
for(auto it = entitiesArray.begin(); it != entitiesArray.end(); it++) {
|
|
auto entity = (*it).toObject();
|
|
entity["marketplaceID"] = marketplaceID;
|
|
newEntitiesArray.append(entity);
|
|
}
|
|
rootObj["Entities"] = newEntitiesArray;
|
|
newDoc.setObject(rootObj);
|
|
return newDoc;
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
const int READ_JSON_BUFFER_SIZE = 2048;
|
|
|
|
bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID /*=""*/) {
|
|
// 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);
|
|
if (!marketplaceID.isEmpty()) {
|
|
asDocument = addMarketplaceIDToDocumentEntities(asDocument, marketplaceID);
|
|
}
|
|
QVariant asVariant = asDocument.toVariant();
|
|
QVariantMap asMap = asVariant.toMap();
|
|
bool success = readFromMap(asMap);
|
|
delete[] rawData;
|
|
return success;
|
|
}
|
|
|
|
bool Octree::writeToFile(const char* fileName, const 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();
|
|
|
|
bool success = false;
|
|
if (persistAsFileType == "json") {
|
|
success = writeToJSONFile(cFileName, element);
|
|
} else if (persistAsFileType == "json.gz") {
|
|
success = writeToJSONFile(cFileName, element, true);
|
|
} else {
|
|
qCDebug(octree) << "unable to write octree to file of type" << persistAsFileType;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool Octree::toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& element) {
|
|
QVariantMap entityDescription;
|
|
|
|
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, true);
|
|
if (!entityDescriptionSuccess) {
|
|
qCritical("Failed to convert Entities to QVariantMap while saving to json.");
|
|
return false;
|
|
}
|
|
|
|
*doc = QJsonDocument::fromVariant(entityDescription);
|
|
return true;
|
|
}
|
|
|
|
bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool doGzip) {
|
|
QJsonDocument doc;
|
|
if (!toJSONDocument(&doc, element)) {
|
|
qCritical("Failed to convert Entities to QVariantMap while converting to json.");
|
|
return false;
|
|
}
|
|
|
|
if (doGzip) {
|
|
QByteArray jsonData = doc.toJson();
|
|
|
|
if (!gzip(jsonData, *data, -1)) {
|
|
qCritical("Unable to gzip data while saving to json.");
|
|
return false;
|
|
}
|
|
} else {
|
|
*data = doc.toJson();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& element, bool doGzip) {
|
|
qCDebug(octree, "Saving JSON SVO to file %s...", fileName);
|
|
|
|
QByteArray jsonDataForFile;
|
|
if (!toJSON(&jsonDataForFile, element, doGzip)) {
|
|
return false;
|
|
}
|
|
|
|
QSaveFile persistFile(fileName);
|
|
bool success = false;
|
|
if (persistFile.open(QIODevice::WriteOnly)) {
|
|
if (persistFile.write(jsonDataForFile) != -1) {
|
|
success = persistFile.commit();
|
|
if (!success) {
|
|
qCritical() << "Failed to commit to JSON save file:" << persistFile.errorString();
|
|
}
|
|
} else {
|
|
qCritical("Failed to write to JSON file.");
|
|
}
|
|
} else {
|
|
qCritical("Failed to open JSON file for writing.");
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
uint64_t Octree::getOctreeElementsCount() {
|
|
uint64_t nodeCount = 0;
|
|
recurseTreeWithOperation(countOctreeElementsOperation, &nodeCount);
|
|
return nodeCount;
|
|
}
|
|
|
|
bool Octree::countOctreeElementsOperation(const OctreeElementPointer& element, void* extraData) {
|
|
(*(uint64_t*)extraData)++;
|
|
return true; // keep going
|
|
}
|