mirror of
https://github.com/JulianGro/overte.git
synced 2025-08-05 11:40:36 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
e219cd8223
17 changed files with 195 additions and 107 deletions
|
@ -1124,7 +1124,6 @@ void Application::setWantsResIn(bool wantsResIn) {
|
||||||
_myAvatar.setWantResIn(wantsResIn);
|
_myAvatar.setWantResIn(wantsResIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Application::setWantsDelta(bool wantsDelta) {
|
void Application::setWantsDelta(bool wantsDelta) {
|
||||||
_myAvatar.setWantDelta(wantsDelta);
|
_myAvatar.setWantDelta(wantsDelta);
|
||||||
}
|
}
|
||||||
|
@ -1280,7 +1279,7 @@ void Application::initMenu() {
|
||||||
QMenu* debugMenu = menuBar->addMenu("Debug");
|
QMenu* debugMenu = menuBar->addMenu("Debug");
|
||||||
debugMenu->addAction("Show Render Pipeline Warnings", this, SLOT(setRenderWarnings(bool)))->setCheckable(true);
|
debugMenu->addAction("Show Render Pipeline Warnings", this, SLOT(setRenderWarnings(bool)))->setCheckable(true);
|
||||||
debugMenu->addAction("Kill Local Voxels", this, SLOT(doKillLocalVoxels()));
|
debugMenu->addAction("Kill Local Voxels", this, SLOT(doKillLocalVoxels()));
|
||||||
debugMenu->addAction("Randomize Voxel TRUE Colors", this, SLOT(doRandomizeVoxelColors()));
|
debugMenu->addAction("Randomize Voxel TRUE Colors", this, SLOT(doRandomizeVoxelColors()), Qt::CTRL | Qt::Key_R);
|
||||||
debugMenu->addAction("FALSE Color Voxels Randomly", this, SLOT(doFalseRandomizeVoxelColors()));
|
debugMenu->addAction("FALSE Color Voxels Randomly", this, SLOT(doFalseRandomizeVoxelColors()));
|
||||||
debugMenu->addAction("FALSE Color Voxel Every Other Randomly", this, SLOT(doFalseRandomizeEveryOtherVoxelColors()));
|
debugMenu->addAction("FALSE Color Voxel Every Other Randomly", this, SLOT(doFalseRandomizeEveryOtherVoxelColors()));
|
||||||
debugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance()));
|
debugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance()));
|
||||||
|
@ -2045,11 +2044,9 @@ void Application::maybeEditVoxelUnderCursor() {
|
||||||
|
|
||||||
void Application::deleteVoxelUnderCursor() {
|
void Application::deleteVoxelUnderCursor() {
|
||||||
if (_mouseVoxel.s != 0) {
|
if (_mouseVoxel.s != 0) {
|
||||||
|
// sending delete to the server is sufficient, server will send new version so we see updates soon enough
|
||||||
sendVoxelEditMessage(PACKET_HEADER_ERASE_VOXEL, _mouseVoxel);
|
sendVoxelEditMessage(PACKET_HEADER_ERASE_VOXEL, _mouseVoxel);
|
||||||
|
|
||||||
// delete the voxel locally so it disappears immediately
|
|
||||||
_voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
|
|
||||||
|
|
||||||
// remember the position for drag detection
|
// remember the position for drag detection
|
||||||
_justEditedVoxel = true;
|
_justEditedVoxel = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,20 +106,16 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
|
||||||
{
|
{
|
||||||
PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()");
|
PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()");
|
||||||
// ask the VoxelTree to read the bitstream into the tree
|
// ask the VoxelTree to read the bitstream into the tree
|
||||||
_tree->readBitstreamToTree(voxelData, numBytes - 1);
|
_tree->readBitstreamToTree(voxelData, numBytes - 1, WANT_COLOR, WANT_EXISTS_BITS);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PACKET_HEADER_VOXEL_DATA_MONOCHROME:
|
case PACKET_HEADER_VOXEL_DATA_MONOCHROME:
|
||||||
{
|
{
|
||||||
PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()");
|
PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()");
|
||||||
// ask the VoxelTree to read the MONOCHROME bitstream into the tree
|
// ask the VoxelTree to read the MONOCHROME bitstream into the tree
|
||||||
_tree->readBitstreamToTree(voxelData, numBytes - 1, false);
|
_tree->readBitstreamToTree(voxelData, numBytes - 1, NO_COLOR, WANT_EXISTS_BITS);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PACKET_HEADER_ERASE_VOXEL:
|
|
||||||
// ask the tree to read the "remove" bitstream
|
|
||||||
_tree->processRemoveVoxelBitstream(sourceBuffer, numBytes);
|
|
||||||
break;
|
|
||||||
case PACKET_HEADER_Z_COMMAND:
|
case PACKET_HEADER_Z_COMMAND:
|
||||||
|
|
||||||
// the Z command is a special command that allows the sender to send high level semantic
|
// the Z command is a special command that allows the sender to send high level semantic
|
||||||
|
@ -186,7 +182,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
||||||
if (_tree->isDirty()) {
|
if (_tree->isDirty()) {
|
||||||
static char buffer[64] = { 0 };
|
static char buffer[64] = { 0 };
|
||||||
if (_renderWarningsOn) {
|
if (_renderWarningsOn) {
|
||||||
sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", (_writeRenderFullVBO ? "yes" : "no"));
|
sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", debug::valueOf(_writeRenderFullVBO));
|
||||||
};
|
};
|
||||||
PerformanceWarning warn(_renderWarningsOn, buffer);
|
PerformanceWarning warn(_renderWarningsOn, buffer);
|
||||||
_callsToTreesToArrays++;
|
_callsToTreesToArrays++;
|
||||||
|
@ -614,7 +610,7 @@ void VoxelSystem::updatePartialVBOs() {
|
||||||
void VoxelSystem::updateVBOs() {
|
void VoxelSystem::updateVBOs() {
|
||||||
static char buffer[40] = { 0 };
|
static char buffer[40] = { 0 };
|
||||||
if (_renderWarningsOn) {
|
if (_renderWarningsOn) {
|
||||||
sprintf(buffer, "updateVBOs() _readRenderFullVBO=%s", (_readRenderFullVBO ? "yes" : "no"));
|
sprintf(buffer, "updateVBOs() _readRenderFullVBO=%s", debug::valueOf(_readRenderFullVBO));
|
||||||
};
|
};
|
||||||
PerformanceWarning warn(_renderWarningsOn, buffer); // would like to include _callsToTreesToArrays
|
PerformanceWarning warn(_renderWarningsOn, buffer); // would like to include _callsToTreesToArrays
|
||||||
if (_voxelsDirty) {
|
if (_voxelsDirty) {
|
||||||
|
@ -1030,7 +1026,7 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* ex
|
||||||
if (args->hasIndexFound[nodeIndex]) {
|
if (args->hasIndexFound[nodeIndex]) {
|
||||||
args->duplicateVBOIndex++;
|
args->duplicateVBOIndex++;
|
||||||
printLog("duplicateVBO found... index=%ld, isDirty=%s, shouldRender=%s \n", nodeIndex,
|
printLog("duplicateVBO found... index=%ld, isDirty=%s, shouldRender=%s \n", nodeIndex,
|
||||||
node->isDirty() ? "yes" : "no" , node->getShouldRender() ? "yes" : "no" );
|
debug::valueOf(node->isDirty()), debug::valueOf(node->getShouldRender()));
|
||||||
} else {
|
} else {
|
||||||
args->hasIndexFound[nodeIndex] = true;
|
args->hasIndexFound[nodeIndex] = true;
|
||||||
}
|
}
|
||||||
|
@ -1059,7 +1055,7 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
|
||||||
args.expectedMax = _voxelsInWriteArrays;
|
args.expectedMax = _voxelsInWriteArrays;
|
||||||
_tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);
|
_tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);
|
||||||
|
|
||||||
printLog("_voxelsDirty=%s _voxelsInWriteArrays=%ld minDirty=%ld maxDirty=%ld \n", (_voxelsDirty ? "yes" : "no"),
|
printLog("_voxelsDirty=%s _voxelsInWriteArrays=%ld minDirty=%ld maxDirty=%ld \n", debug::valueOf(_voxelsDirty),
|
||||||
_voxelsInWriteArrays, minDirty, maxDirty);
|
_voxelsInWriteArrays, minDirty, maxDirty);
|
||||||
|
|
||||||
printLog("stats: total %ld, leaves %ld, dirty %ld, colored %ld, shouldRender %ld, inVBO %ld\n",
|
printLog("stats: total %ld, leaves %ld, dirty %ld, colored %ld, shouldRender %ld, inVBO %ld\n",
|
||||||
|
|
|
@ -82,7 +82,6 @@ public:
|
||||||
void createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, rgbColor color, bool destructive = false);
|
void createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, rgbColor color, bool destructive = false);
|
||||||
void createSphere(float r,float xc, float yc, float zc, float s, bool solid,
|
void createSphere(float r,float xc, float yc, float zc, float s, bool solid,
|
||||||
creationMode mode, bool destructive = false, bool debug = false);
|
creationMode mode, bool destructive = false, bool debug = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// disallow copying of VoxelSystem objects
|
// disallow copying of VoxelSystem objects
|
||||||
VoxelSystem(const VoxelSystem&);
|
VoxelSystem(const VoxelSystem&);
|
||||||
|
@ -156,7 +155,7 @@ private:
|
||||||
void copyWrittenDataToReadArrays(bool fullVBOs);
|
void copyWrittenDataToReadArrays(bool fullVBOs);
|
||||||
|
|
||||||
bool _voxelsDirty;
|
bool _voxelsDirty;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void updateVBOs();
|
void updateVBOs();
|
||||||
void updateFullVBOs(); // all voxels in the VBO
|
void updateFullVBOs(); // all voxels in the VBO
|
||||||
|
|
|
@ -54,7 +54,6 @@ AvatarData::AvatarData() :
|
||||||
_wantDelta(false),
|
_wantDelta(false),
|
||||||
_headData(NULL)
|
_headData(NULL)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarData::~AvatarData() {
|
AvatarData::~AvatarData() {
|
||||||
|
@ -232,10 +231,9 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) {
|
||||||
// voxel sending features...
|
// voxel sending features...
|
||||||
unsigned char wantItems = 0;
|
unsigned char wantItems = 0;
|
||||||
wantItems = (unsigned char)*sourceBuffer++;
|
wantItems = (unsigned char)*sourceBuffer++;
|
||||||
|
|
||||||
_wantResIn = oneAtBit(wantItems,WANT_RESIN_AT_BIT);
|
_wantResIn = oneAtBit(wantItems,WANT_RESIN_AT_BIT);
|
||||||
_wantColor = oneAtBit(wantItems,WANT_COLOR_AT_BIT);
|
_wantColor = oneAtBit(wantItems,WANT_COLOR_AT_BIT);
|
||||||
_wantDelta = oneAtBit(wantItems,WANT_DELTA_AT_BIT);
|
_wantDelta = oneAtBit(wantItems,WANT_DELTA_AT_BIT);
|
||||||
|
|
||||||
return sourceBuffer - startPosition;
|
return sourceBuffer - startPosition;
|
||||||
}
|
}
|
|
@ -80,4 +80,9 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex,
|
||||||
void** valueArray, float* keyArray, int* originalIndexArray,
|
void** valueArray, float* keyArray, int* originalIndexArray,
|
||||||
int currentCount, int maxCount);
|
int currentCount, int maxCount);
|
||||||
|
|
||||||
|
// Helper Class for debugging
|
||||||
|
class debug {
|
||||||
|
public:
|
||||||
|
static const char* valueOf(bool checkValue) { return checkValue ? "yes" : "no"; };
|
||||||
|
};
|
||||||
#endif /* defined(__hifi__SharedUtil__) */
|
#endif /* defined(__hifi__SharedUtil__) */
|
||||||
|
|
|
@ -243,7 +243,7 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo) const {
|
||||||
compareTo._eyeOffsetOrientation == _eyeOffsetOrientation;
|
compareTo._eyeOffsetOrientation == _eyeOffsetOrientation;
|
||||||
|
|
||||||
if (!result && debug) {
|
if (!result && debug) {
|
||||||
printLog("ViewFrustum::matches()... result=%s\n", (result ? "yes" : "no"));
|
printLog("ViewFrustum::matches()... result=%s\n", debug::valueOf(result));
|
||||||
printLog("%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f\n",
|
printLog("%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f\n",
|
||||||
(compareTo._position == _position ? "MATCHES " : "NO MATCH"),
|
(compareTo._position == _position ? "MATCHES " : "NO MATCH"),
|
||||||
compareTo._position.x, compareTo._position.y, compareTo._position.z,
|
compareTo._position.x, compareTo._position.y, compareTo._position.z,
|
||||||
|
|
|
@ -119,6 +119,33 @@ void VoxelNode::addChildAtIndex(int childIndex) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handles staging or deletion of all deep children
|
||||||
|
void VoxelNode::safeDeepDeleteChildAtIndex(int childIndex, bool& stagedForDeletion) {
|
||||||
|
VoxelNode* childToDelete = getChildAtIndex(childIndex);
|
||||||
|
if (childToDelete) {
|
||||||
|
// If the child is not a leaf, then call ourselves recursively on all the children
|
||||||
|
if (!childToDelete->isLeaf()) {
|
||||||
|
// delete all it's children
|
||||||
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
|
childToDelete->safeDeepDeleteChildAtIndex(i, stagedForDeletion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if this node has a BufferIndex then we need to stage it for deletion
|
||||||
|
// instead of actually deleting it from the tree
|
||||||
|
if (childToDelete->isKnownBufferIndex()) {
|
||||||
|
stagedForDeletion = true;
|
||||||
|
}
|
||||||
|
if (stagedForDeletion) {
|
||||||
|
childToDelete->stageForDeletion();
|
||||||
|
_isDirty = true;
|
||||||
|
} else {
|
||||||
|
deleteChildAtIndex(childIndex);
|
||||||
|
_isDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// will average the child colors...
|
// will average the child colors...
|
||||||
void VoxelNode::setColorFromAverageOfChildren() {
|
void VoxelNode::setColorFromAverageOfChildren() {
|
||||||
int colorArray[4] = {0,0,0,0};
|
int colorArray[4] = {0,0,0,0};
|
||||||
|
@ -242,8 +269,20 @@ void VoxelNode::setRandomColor(int minimumBrightness) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelNode::printDebugDetails(const char* label) const {
|
void VoxelNode::printDebugDetails(const char* label) const {
|
||||||
printLog("%s - Voxel at corner=(%f,%f,%f) size=%f octcode=", label,
|
unsigned char childBits = 0;
|
||||||
_box.getCorner().x, _box.getCorner().y, _box.getCorner().z, _box.getSize().x);
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
|
if (_children[i]) {
|
||||||
|
setAtBit(childBits,i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printLog("%s - Voxel at corner=(%f,%f,%f) size=%f\n isLeaf=%s isColored=%s isDirty=%s shouldRender=%s\n children=", label,
|
||||||
|
_box.getCorner().x, _box.getCorner().y, _box.getCorner().z, _box.getSize().x,
|
||||||
|
debug::valueOf(isLeaf()), debug::valueOf(isColored()), debug::valueOf(isDirty()),
|
||||||
|
debug::valueOf(getShouldRender()));
|
||||||
|
|
||||||
|
outputBits(childBits, false);
|
||||||
|
printLog("\n octalCode=");
|
||||||
printOctalCode(_octalCode);
|
printOctalCode(_octalCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,10 +43,12 @@ public:
|
||||||
~VoxelNode();
|
~VoxelNode();
|
||||||
|
|
||||||
unsigned char* getOctalCode() const { return _octalCode; };
|
unsigned char* getOctalCode() const { return _octalCode; };
|
||||||
VoxelNode* getChildAtIndex(int i) const { return _children[i]; };
|
VoxelNode* getChildAtIndex(int childIndex) const { return _children[childIndex]; };
|
||||||
void deleteChildAtIndex(int childIndex);
|
void deleteChildAtIndex(int childIndex);
|
||||||
VoxelNode* removeChildAtIndex(int childIndex);
|
VoxelNode* removeChildAtIndex(int childIndex);
|
||||||
void addChildAtIndex(int childIndex);
|
void addChildAtIndex(int childIndex);
|
||||||
|
void safeDeepDeleteChildAtIndex(int childIndex, bool& stagedForDeletion); // handles staging or deletion of all descendents
|
||||||
|
|
||||||
void setColorFromAverageOfChildren();
|
void setColorFromAverageOfChildren();
|
||||||
void setRandomColor(int minimumBrightness);
|
void setRandomColor(int minimumBrightness);
|
||||||
bool collapseIdenticalLeaves();
|
bool collapseIdenticalLeaves();
|
||||||
|
@ -69,6 +71,7 @@ public:
|
||||||
float distanceToPoint(const glm::vec3& point) const;
|
float distanceToPoint(const glm::vec3& point) const;
|
||||||
|
|
||||||
bool isLeaf() const { return _childCount == 0; }
|
bool isLeaf() const { return _childCount == 0; }
|
||||||
|
int getChildCount() const { return _childCount; }
|
||||||
void printDebugDetails(const char* label) const;
|
void printDebugDetails(const char* label) const;
|
||||||
bool isDirty() const { return _isDirty; };
|
bool isDirty() const { return _isDirty; };
|
||||||
void clearDirtyBit() { _isDirty = false; };
|
void clearDirtyBit() { _isDirty = false; };
|
||||||
|
@ -81,7 +84,7 @@ public:
|
||||||
bool getShouldRender() const { return _shouldRender; }
|
bool getShouldRender() const { return _shouldRender; }
|
||||||
|
|
||||||
// Used by VoxelSystem to mark a node as to be deleted on next render pass
|
// Used by VoxelSystem to mark a node as to be deleted on next render pass
|
||||||
void stageForDeletion() { _isStagedForDeletion = true; };
|
void stageForDeletion() { _isStagedForDeletion = true; _isDirty = true; };
|
||||||
bool isStagedForDeletion() const { return _isStagedForDeletion; }
|
bool isStagedForDeletion() const { return _isStagedForDeletion; }
|
||||||
|
|
||||||
#ifndef NO_FALSE_COLOR // !NO_FALSE_COLOR means, does have false color
|
#ifndef NO_FALSE_COLOR // !NO_FALSE_COLOR means, does have false color
|
||||||
|
|
|
@ -99,10 +99,9 @@ VoxelNode * VoxelTree::nodeForOctalCode(VoxelNode *ancestorNode, unsigned char *
|
||||||
|
|
||||||
// returns the node created!
|
// returns the node created!
|
||||||
VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char* codeToReach) {
|
VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char* codeToReach) {
|
||||||
|
|
||||||
int indexOfNewChild = branchIndexWithDescendant(lastParentNode->getOctalCode(), codeToReach);
|
int indexOfNewChild = branchIndexWithDescendant(lastParentNode->getOctalCode(), codeToReach);
|
||||||
|
// If this parent node is a leaf, then you know the child path doesn't exist, so deal with
|
||||||
// we could be coming down a branch that was already created, so don't stomp on it.
|
// breaking up the leaf first, which will also create a child path
|
||||||
if (lastParentNode->isLeaf() && lastParentNode->isColored()) {
|
if (lastParentNode->isLeaf() && lastParentNode->isColored()) {
|
||||||
// for colored leaves, we must add *all* the children
|
// for colored leaves, we must add *all* the children
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
|
@ -110,6 +109,7 @@ VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char
|
||||||
lastParentNode->getChildAtIndex(i)->setColor(lastParentNode->getColor());
|
lastParentNode->getChildAtIndex(i)->setColor(lastParentNode->getColor());
|
||||||
}
|
}
|
||||||
} else if (!lastParentNode->getChildAtIndex(indexOfNewChild)) {
|
} else if (!lastParentNode->getChildAtIndex(indexOfNewChild)) {
|
||||||
|
// we could be coming down a branch that was already created, so don't stomp on it.
|
||||||
lastParentNode->addChildAtIndex(indexOfNewChild);
|
lastParentNode->addChildAtIndex(indexOfNewChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,12 +121,17 @@ VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, int bytesLeftToRead, bool includeColor) {
|
int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, int bytesLeftToRead,
|
||||||
|
bool includeColor, bool includeExistsBits) {
|
||||||
|
// give this destination node the child mask from the packet
|
||||||
|
const unsigned char ALL_CHILDREN_ASSUMED_TO_EXIST = 0xFF;
|
||||||
|
unsigned char colorInPacketMask = *nodeData;
|
||||||
|
|
||||||
// instantiate variable for bytes already read
|
// instantiate variable for bytes already read
|
||||||
int bytesRead = 1;
|
int bytesRead = sizeof(colorInPacketMask);
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
// check the colors mask to see if we have a child to color in
|
// check the colors mask to see if we have a child to color in
|
||||||
if (oneAtBit(*nodeData, i)) {
|
if (oneAtBit(colorInPacketMask, i)) {
|
||||||
// create the child if it doesn't exist
|
// create the child if it doesn't exist
|
||||||
if (!destinationNode->getChildAtIndex(i)) {
|
if (!destinationNode->getChildAtIndex(i)) {
|
||||||
destinationNode->addChildAtIndex(i);
|
destinationNode->addChildAtIndex(i);
|
||||||
|
@ -159,10 +164,11 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData,
|
||||||
}
|
}
|
||||||
|
|
||||||
// give this destination node the child mask from the packet
|
// give this destination node the child mask from the packet
|
||||||
unsigned char childMask = *(nodeData + bytesRead);
|
unsigned char childrenInTreeMask = includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST;
|
||||||
|
unsigned char childMask = *(nodeData + bytesRead + (includeExistsBits ? sizeof(childrenInTreeMask) : 0));
|
||||||
|
|
||||||
int childIndex = 0;
|
int childIndex = 0;
|
||||||
bytesRead++;
|
bytesRead += includeExistsBits ? sizeof(childrenInTreeMask) + sizeof(childMask) : sizeof(childMask);
|
||||||
|
|
||||||
while (bytesLeftToRead - bytesRead > 0 && childIndex < NUMBER_OF_CHILDREN) {
|
while (bytesLeftToRead - bytesRead > 0 && childIndex < NUMBER_OF_CHILDREN) {
|
||||||
// check the exists mask to see if we have a child to traverse into
|
// check the exists mask to see if we have a child to traverse into
|
||||||
|
@ -185,17 +191,28 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData,
|
||||||
|
|
||||||
// tell the child to read the subsequent data
|
// tell the child to read the subsequent data
|
||||||
bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex),
|
bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex),
|
||||||
nodeData + bytesRead,
|
nodeData + bytesRead, bytesLeftToRead - bytesRead, includeColor, includeExistsBits);
|
||||||
bytesLeftToRead - bytesRead, includeColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
childIndex++;
|
childIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (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/node, because it shouldn't actually exist in the tree.
|
||||||
|
if (!oneAtBit(childrenInTreeMask, i) && destinationNode->getChildAtIndex(i)) {
|
||||||
|
bool stagedForDeletion = false; // assume staging is not needed
|
||||||
|
destinationNode->safeDeepDeleteChildAtIndex(i, stagedForDeletion);
|
||||||
|
_isDirty = true; // by definition!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes, bool includeColor) {
|
void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes,
|
||||||
|
bool includeColor, bool includeExistsBits) {
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
unsigned char* bitstreamAt = bitstream;
|
unsigned char* bitstreamAt = bitstream;
|
||||||
|
|
||||||
|
@ -224,7 +241,7 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int
|
||||||
int theseBytesRead = 0;
|
int theseBytesRead = 0;
|
||||||
theseBytesRead += octalCodeBytes;
|
theseBytesRead += octalCodeBytes;
|
||||||
theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes,
|
theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes,
|
||||||
bufferSizeBytes - (bytesRead + octalCodeBytes), includeColor);
|
bufferSizeBytes - (bytesRead + octalCodeBytes), includeColor, includeExistsBits);
|
||||||
|
|
||||||
// skip bitstream to new startPoint
|
// skip bitstream to new startPoint
|
||||||
bitstreamAt += theseBytesRead;
|
bitstreamAt += theseBytesRead;
|
||||||
|
@ -244,23 +261,30 @@ void VoxelTree::deleteVoxelAt(float x, float y, float z, float s, bool stage) {
|
||||||
|
|
||||||
// Note: uses the codeColorBuffer format, but the color's are ignored, because
|
// Note: uses the codeColorBuffer format, but the color's are ignored, because
|
||||||
// this only finds and deletes the node from the tree.
|
// this only finds and deletes the node from the tree.
|
||||||
void VoxelTree::deleteVoxelCodeFromTree(unsigned char *codeBuffer, bool stage) {
|
void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool stage) {
|
||||||
VoxelNode* parentNode = NULL;
|
VoxelNode* parentNode = NULL;
|
||||||
VoxelNode* nodeToDelete = nodeForOctalCode(rootNode, codeBuffer, &parentNode);
|
VoxelNode* nodeToDelete = nodeForOctalCode(rootNode, codeBuffer, &parentNode);
|
||||||
|
|
||||||
// If the node exists...
|
// If the node exists...
|
||||||
int lengthInBytes = bytesRequiredForCodeLength(*codeBuffer); // includes octet count, not color!
|
int lengthInBytes = bytesRequiredForCodeLength(*codeBuffer); // includes octet count, not color!
|
||||||
|
|
||||||
if (0 == memcmp(nodeToDelete->getOctalCode(),codeBuffer,lengthInBytes)) {
|
// if the code we got back matches our target, then we know we can actually delete it
|
||||||
|
if (memcmp(nodeToDelete->getOctalCode(), codeBuffer, lengthInBytes) == 0) {
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
int childIndex = branchIndexWithDescendant(parentNode->getOctalCode(), codeBuffer);
|
int childIndex = branchIndexWithDescendant(parentNode->getOctalCode(), codeBuffer);
|
||||||
|
|
||||||
if (stage) {
|
if (stage) {
|
||||||
nodeToDelete->stageForDeletion();
|
nodeToDelete->stageForDeletion();
|
||||||
} else {
|
} else {
|
||||||
parentNode->deleteChildAtIndex(childIndex);
|
parentNode->deleteChildAtIndex(childIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're not a colored leaf, and we have no children, then delete ourselves
|
||||||
|
// This will collapse the empty tree above us.
|
||||||
|
if (parentNode->getChildCount() == 0 && !parentNode->isColored()) {
|
||||||
|
// Can't delete the root this way.
|
||||||
|
if (parentNode != rootNode) {
|
||||||
|
deleteVoxelCodeFromTree(parentNode->getOctalCode(),stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
reaverageVoxelColors(rootNode); // Fix our colors!! Need to call it on rootNode
|
reaverageVoxelColors(rootNode); // Fix our colors!! Need to call it on rootNode
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
}
|
}
|
||||||
|
@ -330,17 +354,12 @@ void VoxelTree::readCodeColorBufferToTree(unsigned char *codeColorBuffer, bool d
|
||||||
void VoxelTree::processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes) {
|
void VoxelTree::processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes) {
|
||||||
// XXXBHG: validate buffer is at least 4 bytes long? other guards??
|
// XXXBHG: validate buffer is at least 4 bytes long? other guards??
|
||||||
unsigned short int itemNumber = (*((unsigned short int*)&bitstream[1]));
|
unsigned short int itemNumber = (*((unsigned short int*)&bitstream[1]));
|
||||||
printLog("processRemoveVoxelBitstream() receivedBytes=%d itemNumber=%d\n",bufferSizeBytes,itemNumber);
|
|
||||||
int atByte = 3;
|
int atByte = 3;
|
||||||
unsigned char* pVoxelData = (unsigned char*)&bitstream[3];
|
unsigned char* pVoxelData = (unsigned char*)&bitstream[3];
|
||||||
while (atByte < bufferSizeBytes) {
|
while (atByte < bufferSizeBytes) {
|
||||||
unsigned char octets = (unsigned char)*pVoxelData;
|
unsigned char octets = (unsigned char)*pVoxelData;
|
||||||
int voxelDataSize = bytesRequiredForCodeLength(octets)+3; // 3 for color!
|
int voxelDataSize = bytesRequiredForCodeLength(octets)+3; // 3 for color!
|
||||||
|
|
||||||
float* vertices = firstVertexForCode(pVoxelData);
|
|
||||||
printLog("deleting voxel at: %f,%f,%f\n",vertices[0],vertices[1],vertices[2]);
|
|
||||||
delete []vertices;
|
|
||||||
|
|
||||||
deleteVoxelCodeFromTree(pVoxelData);
|
deleteVoxelCodeFromTree(pVoxelData);
|
||||||
|
|
||||||
pVoxelData+=voxelDataSize;
|
pVoxelData+=voxelDataSize;
|
||||||
|
@ -427,7 +446,6 @@ void VoxelTree::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) {
|
||||||
bool bail = false;
|
bool bail = false;
|
||||||
while (!file.eof() && !bail) {
|
while (!file.eof() && !bail) {
|
||||||
file.get(octets);
|
file.get(octets);
|
||||||
//printLog("octets=%d...\n",octets);
|
|
||||||
totalBytesRead++;
|
totalBytesRead++;
|
||||||
lengthInBytes = bytesRequiredForCodeLength(octets) - 1;
|
lengthInBytes = bytesRequiredForCodeLength(octets) - 1;
|
||||||
unsigned char * voxelData = new unsigned char[lengthInBytes + 1 + 3];
|
unsigned char * voxelData = new unsigned char[lengthInBytes + 1 + 3];
|
||||||
|
@ -851,7 +869,7 @@ int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSe
|
||||||
}
|
}
|
||||||
|
|
||||||
int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes,
|
int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes,
|
||||||
VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor,
|
VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits,
|
||||||
bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const {
|
bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const {
|
||||||
|
|
||||||
// How many bytes have we written so far at this level;
|
// How many bytes have we written so far at this level;
|
||||||
|
@ -871,8 +889,8 @@ int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned
|
||||||
availableBytes -= codeLength; // keep track or remaining space
|
availableBytes -= codeLength; // keep track or remaining space
|
||||||
|
|
||||||
int currentEncodeLevel = 0;
|
int currentEncodeLevel = 0;
|
||||||
int childBytesWritten = encodeTreeBitstreamRecursion(maxEncodeLevel, currentEncodeLevel,
|
int childBytesWritten = encodeTreeBitstreamRecursion(maxEncodeLevel, currentEncodeLevel, node, outputBuffer, availableBytes,
|
||||||
node, outputBuffer, availableBytes, bag, viewFrustum, includeColor,
|
bag, viewFrustum, includeColor, includeExistsBits,
|
||||||
deltaViewFrustum, lastViewFrustum);
|
deltaViewFrustum, lastViewFrustum);
|
||||||
|
|
||||||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||||
|
@ -894,10 +912,11 @@ int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel,
|
int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, VoxelNode* node,
|
||||||
VoxelNode* node, unsigned char* outputBuffer, int availableBytes,
|
unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||||
VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor,
|
const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits,
|
||||||
bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const {
|
bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const {
|
||||||
|
|
||||||
// How many bytes have we written so far at this level;
|
// How many bytes have we written so far at this level;
|
||||||
int bytesAtThisLevel = 0;
|
int bytesAtThisLevel = 0;
|
||||||
|
|
||||||
|
@ -942,7 +961,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
unsigned char thisLevelBuffer[MAX_LEVEL_BYTES];
|
unsigned char thisLevelBuffer[MAX_LEVEL_BYTES];
|
||||||
unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0];
|
unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0];
|
||||||
|
|
||||||
unsigned char childrenExistBits = 0;
|
unsigned char childrenExistInTreeBits = 0;
|
||||||
|
unsigned char childrenExistInPacketBits = 0;
|
||||||
unsigned char childrenColoredBits = 0;
|
unsigned char childrenColoredBits = 0;
|
||||||
int inViewCount = 0;
|
int inViewCount = 0;
|
||||||
int inViewNotLeafCount = 0;
|
int inViewNotLeafCount = 0;
|
||||||
|
@ -952,6 +972,12 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
// add them to our distance ordered array of children
|
// add them to our distance ordered array of children
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
VoxelNode* childNode = node->getChildAtIndex(i);
|
VoxelNode* childNode = node->getChildAtIndex(i);
|
||||||
|
|
||||||
|
// if the caller wants to include childExistsBits, then include them even if not in view
|
||||||
|
if (includeExistsBits && childNode) {
|
||||||
|
childrenExistInTreeBits += (1 << (7 - i));
|
||||||
|
}
|
||||||
|
|
||||||
bool childIsInView = (childNode && (!viewFrustum || childNode->isInView(*viewFrustum)));
|
bool childIsInView = (childNode && (!viewFrustum || childNode->isInView(*viewFrustum)));
|
||||||
|
|
||||||
if (childIsInView) {
|
if (childIsInView) {
|
||||||
|
@ -966,7 +992,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
// we don't care about recursing deeper on them, and we don't consider their
|
// we don't care about recursing deeper on them, and we don't consider their
|
||||||
// subtree to exist
|
// subtree to exist
|
||||||
if (!(childNode && childNode->isLeaf())) {
|
if (!(childNode && childNode->isLeaf())) {
|
||||||
childrenExistBits += (1 << (7 - i));
|
childrenExistInPacketBits += (1 << (7 - i));
|
||||||
inViewNotLeafCount++;
|
inViewNotLeafCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -984,7 +1010,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
*writeToThisLevelBuffer = childrenColoredBits;
|
*writeToThisLevelBuffer = childrenColoredBits;
|
||||||
writeToThisLevelBuffer += sizeof(childrenColoredBits); // move the pointer
|
writeToThisLevelBuffer += sizeof(childrenColoredBits); // move the pointer
|
||||||
bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count
|
bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count
|
||||||
|
|
||||||
// write the color data...
|
// write the color data...
|
||||||
if (includeColor) {
|
if (includeColor) {
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
|
@ -995,12 +1021,20 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// write the child exist bits
|
|
||||||
*writeToThisLevelBuffer = childrenExistBits;
|
|
||||||
|
|
||||||
writeToThisLevelBuffer += sizeof(childrenExistBits); // move the pointer
|
// if the caller wants to include childExistsBits, then include them even if not in view, put them before the
|
||||||
bytesAtThisLevel += sizeof(childrenExistBits); // keep track of byte count
|
// childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits
|
||||||
|
if (includeExistsBits) {
|
||||||
|
*writeToThisLevelBuffer = childrenExistInTreeBits;
|
||||||
|
writeToThisLevelBuffer += sizeof(childrenExistInTreeBits); // move the pointer
|
||||||
|
bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the child exist bits
|
||||||
|
*writeToThisLevelBuffer = childrenExistInPacketBits;
|
||||||
|
writeToThisLevelBuffer += sizeof(childrenExistInPacketBits); // move the pointer
|
||||||
|
bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count
|
||||||
|
|
||||||
// We only need to keep digging, if there is at least one child that is inView, and not a leaf.
|
// We only need to keep digging, if there is at least one child that is inView, and not a leaf.
|
||||||
keepDiggingDeeper = (inViewNotLeafCount > 0);
|
keepDiggingDeeper = (inViewNotLeafCount > 0);
|
||||||
|
|
||||||
|
@ -1020,23 +1054,23 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
// and we need to determine if there's a deeper tree below them that we care about.
|
// 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
|
// Since this recursive function assumes we're already writing, we know we've already written our
|
||||||
// childrenExistBits. But... we don't really know how big the child tree will be. And we don't know if
|
// 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
|
// 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 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.
|
// 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 outputBuffer was our childrenExistBits. Let's remember where that was!
|
// we know the last thing we wrote to the outputBuffer was our childrenExistInPacketBits. Let's remember where that was!
|
||||||
unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistBits);
|
unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistInPacketBits);
|
||||||
|
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
|
|
||||||
if (oneAtBit(childrenExistBits, i)) {
|
if (oneAtBit(childrenExistInPacketBits, i)) {
|
||||||
VoxelNode* childNode = node->getChildAtIndex(i);
|
VoxelNode* childNode = node->getChildAtIndex(i);
|
||||||
|
|
||||||
int thisLevel = currentEncodeLevel;
|
int thisLevel = currentEncodeLevel;
|
||||||
int childTreeBytesOut = encodeTreeBitstreamRecursion(maxEncodeLevel, thisLevel, childNode,
|
int childTreeBytesOut = encodeTreeBitstreamRecursion(maxEncodeLevel, thisLevel, childNode,
|
||||||
outputBuffer, availableBytes, bag,
|
outputBuffer, availableBytes, bag,
|
||||||
viewFrustum, includeColor,
|
viewFrustum, includeColor, includeExistsBits,
|
||||||
deltaViewFrustum, lastViewFrustum);
|
deltaViewFrustum, lastViewFrustum);
|
||||||
|
|
||||||
// if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space,
|
// if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space,
|
||||||
|
@ -1067,14 +1101,15 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco
|
||||||
// then we want to remove their bit from the childExistsPlaceHolder bitmask
|
// then we want to remove their bit from the childExistsPlaceHolder bitmask
|
||||||
if (childTreeBytesOut == 0) {
|
if (childTreeBytesOut == 0) {
|
||||||
// remove this child's bit...
|
// remove this child's bit...
|
||||||
childrenExistBits -= (1 << (7 - i));
|
childrenExistInPacketBits -= (1 << (7 - i));
|
||||||
// repair the child exists mask
|
// repair the child exists mask
|
||||||
*childExistsPlaceHolder = childrenExistBits;
|
*childExistsPlaceHolder = childrenExistInPacketBits;
|
||||||
// Note: no need to move the pointer, cause we already stored this
|
// Note: no need to move the pointer, cause we already stored this
|
||||||
} // end if (childTreeBytesOut == 0)
|
} // end if (childTreeBytesOut == 0)
|
||||||
} // end if (oneAtBit(childrenExistBits, i))
|
} // end if (oneAtBit(childrenExistInPacketBits, i))
|
||||||
} // end for
|
} // end for
|
||||||
} // end keepDiggingDeeper
|
} // end keepDiggingDeeper
|
||||||
|
|
||||||
return bytesAtThisLevel;
|
return bytesAtThisLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1090,7 +1125,7 @@ bool VoxelTree::readFromFileV2(const char* fileName) {
|
||||||
// read the entire file into a buffer, WHAT!? Why not.
|
// read the entire file into a buffer, WHAT!? Why not.
|
||||||
unsigned char* entireFile = new unsigned char[fileLength];
|
unsigned char* entireFile = new unsigned char[fileLength];
|
||||||
file.read((char*)entireFile, fileLength);
|
file.read((char*)entireFile, fileLength);
|
||||||
readBitstreamToTree(entireFile, fileLength, true);
|
readBitstreamToTree(entireFile, fileLength, WANT_COLOR, NO_EXISTS_BITS);
|
||||||
delete[] entireFile;
|
delete[] entireFile;
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
@ -1115,7 +1150,7 @@ void VoxelTree::writeToFileV2(const char* fileName) const {
|
||||||
while (!nodeBag.isEmpty()) {
|
while (!nodeBag.isEmpty()) {
|
||||||
VoxelNode* subTree = nodeBag.extract();
|
VoxelNode* subTree = nodeBag.extract();
|
||||||
bytesWritten = encodeTreeBitstream(INT_MAX, subTree, &outputBuffer[0],
|
bytesWritten = encodeTreeBitstream(INT_MAX, subTree, &outputBuffer[0],
|
||||||
MAX_VOXEL_PACKET_SIZE - 1, nodeBag, NULL, true);
|
MAX_VOXEL_PACKET_SIZE - 1, nodeBag, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
|
||||||
|
|
||||||
file.write((const char*)&outputBuffer[0], bytesWritten);
|
file.write((const char*)&outputBuffer[0], bytesWritten);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,12 @@
|
||||||
typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData);
|
typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData);
|
||||||
typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
|
typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
|
||||||
|
|
||||||
|
#define NO_EXISTS_BITS false
|
||||||
|
#define WANT_EXISTS_BITS true
|
||||||
|
#define NO_COLOR false
|
||||||
|
#define WANT_COLOR true
|
||||||
|
#define IGNORE_VIEW_FRUSTUM NULL
|
||||||
|
|
||||||
class VoxelTree {
|
class VoxelTree {
|
||||||
public:
|
public:
|
||||||
// when a voxel is created in the tree (object new'd)
|
// when a voxel is created in the tree (object new'd)
|
||||||
|
@ -40,7 +46,8 @@ public:
|
||||||
void eraseAllVoxels();
|
void eraseAllVoxels();
|
||||||
|
|
||||||
void processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes);
|
void processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes);
|
||||||
void readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes, bool includeColor = true);
|
void readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes,
|
||||||
|
bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS);
|
||||||
void readCodeColorBufferToTree(unsigned char *codeColorBuffer, bool destructive = false);
|
void readCodeColorBufferToTree(unsigned char *codeColorBuffer, bool destructive = false);
|
||||||
void deleteVoxelCodeFromTree(unsigned char *codeBuffer, bool stage = false);
|
void deleteVoxelCodeFromTree(unsigned char *codeBuffer, bool stage = false);
|
||||||
void printTreeForDebugging(VoxelNode *startNode);
|
void printTreeForDebugging(VoxelNode *startNode);
|
||||||
|
@ -57,7 +64,8 @@ public:
|
||||||
void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL);
|
void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL);
|
||||||
|
|
||||||
int encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes,
|
int encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes,
|
||||||
VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor = true,
|
VoxelNodeBag& bag, const ViewFrustum* viewFrustum,
|
||||||
|
bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS,
|
||||||
bool deltaViewFrustum = false, const ViewFrustum* lastViewFrustum = NULL) const;
|
bool deltaViewFrustum = false, const ViewFrustum* lastViewFrustum = NULL) const;
|
||||||
|
|
||||||
int searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag,
|
int searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag,
|
||||||
|
@ -85,8 +93,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel,
|
int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel,
|
||||||
VoxelNode* node, unsigned char* outputBuffer, int availableBytes,
|
VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||||
VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor,
|
const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits,
|
||||||
bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const;
|
bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const;
|
||||||
|
|
||||||
int searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel,
|
int searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel,
|
||||||
|
@ -98,7 +106,8 @@ private:
|
||||||
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
|
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
|
||||||
VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const;
|
VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const;
|
||||||
VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate);
|
VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate);
|
||||||
int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes, bool includeColor = true);
|
int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes,
|
||||||
|
bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS);
|
||||||
|
|
||||||
bool _isDirty;
|
bool _isDirty;
|
||||||
unsigned long int _nodesChangedFromBitstream;
|
unsigned long int _nodesChangedFromBitstream;
|
||||||
|
|
BIN
tools/samples/cube1.hio
Executable file
BIN
tools/samples/cube1.hio
Executable file
Binary file not shown.
BIN
tools/samples/oneRedVoxel.hio
Executable file
BIN
tools/samples/oneRedVoxel.hio
Executable file
Binary file not shown.
BIN
tools/samples/simple1.hio
Normal file
BIN
tools/samples/simple1.hio
Normal file
Binary file not shown.
BIN
tools/samples/single.hio
Executable file
BIN
tools/samples/single.hio
Executable file
Binary file not shown.
BIN
tools/samples/small.hio
Normal file
BIN
tools/samples/small.hio
Normal file
Binary file not shown.
|
@ -30,8 +30,8 @@ function send_voxels($inputFileName,$server,$port,$command) {
|
||||||
echo "sending adding octets=$octets size=$size to packet packetSize=$packetSize\n";
|
echo "sending adding octets=$octets size=$size to packet packetSize=$packetSize\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "sending packet server=$serverIP port=$serverSendPort $voxNum size=$packetSize result=$result\n";
|
|
||||||
$result = socket_sendto($socketHandle, $netData, $packetSize, 0, $serverIP, $serverSendPort);
|
$result = socket_sendto($socketHandle, $netData, $packetSize, 0, $serverIP, $serverSendPort);
|
||||||
|
echo "sent packet server=$serverIP port=$serverSendPort $voxNum size=$packetSize result=$result\n";
|
||||||
usleep(20000); // 1,000,000 per second
|
usleep(20000); // 1,000,000 per second
|
||||||
$voxNum++;
|
$voxNum++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,7 @@ bool wantLocalDomain = false;
|
||||||
bool wantColorRandomizer = false;
|
bool wantColorRandomizer = false;
|
||||||
bool debugVoxelSending = false;
|
bool debugVoxelSending = false;
|
||||||
bool shouldShowAnimationDebug = false;
|
bool shouldShowAnimationDebug = false;
|
||||||
|
bool wantSearchForColoredNodes = false;
|
||||||
|
|
||||||
|
|
||||||
EnvironmentData environmentData;
|
EnvironmentData environmentData;
|
||||||
|
|
||||||
|
@ -168,7 +167,7 @@ void resInVoxelDistributor(AgentList* agentList,
|
||||||
bytesWritten = randomTree.encodeTreeBitstream(agentData->getMaxSearchLevel(), subTree,
|
bytesWritten = randomTree.encodeTreeBitstream(agentData->getMaxSearchLevel(), subTree,
|
||||||
&tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
|
&tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
|
||||||
agentData->nodeBag, &viewFrustum,
|
agentData->nodeBag, &viewFrustum,
|
||||||
agentData->getWantColor());
|
agentData->getWantColor(), WANT_EXISTS_BITS);
|
||||||
|
|
||||||
if (agentData->getAvailable() >= bytesWritten) {
|
if (agentData->getAvailable() >= bytesWritten) {
|
||||||
agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
|
agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
|
||||||
|
@ -241,20 +240,32 @@ void deepestLevelVoxelDistributor(AgentList* agentList,
|
||||||
|
|
||||||
if (::debugVoxelSending) {
|
if (::debugVoxelSending) {
|
||||||
printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
|
printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
|
||||||
viewFrustumChanged ? "yes" : "no",
|
debug::valueOf(viewFrustumChanged), debug::valueOf(agentData->nodeBag.isEmpty()),
|
||||||
agentData->nodeBag.isEmpty() ? "yes" : "no",
|
debug::valueOf(agentData->getViewSent())
|
||||||
agentData->getViewSent() ? "yes" : "no"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current view frustum has changed OR we have nothing to send, then search against
|
// If the current view frustum has changed OR we have nothing to send, then search against
|
||||||
// the current view frustum for things to send.
|
// the current view frustum for things to send.
|
||||||
if (viewFrustumChanged || agentData->nodeBag.isEmpty()) {
|
if (viewFrustumChanged || agentData->nodeBag.isEmpty()) {
|
||||||
// If the bag was empty, then send everything in view, not just the delta
|
|
||||||
maxLevelReached = randomTree.searchForColoredNodes(INT_MAX, randomTree.rootNode, agentData->getCurrentViewFrustum(),
|
|
||||||
agentData->nodeBag, wantDelta, lastViewFrustum);
|
|
||||||
|
|
||||||
agentData->setViewSent(false);
|
// For now, we're going to disable the "search for colored nodes" because that strategy doesn't work when we support
|
||||||
|
// deletion of nodes. Instead if we just start at the root we get the correct behavior we want. We are keeping this
|
||||||
|
// code for now because we want to be able to go back to it and find a solution to support both. The search method
|
||||||
|
// helps improve overall bitrate performance.
|
||||||
|
if (::wantSearchForColoredNodes) {
|
||||||
|
// If the bag was empty, then send everything in view, not just the delta
|
||||||
|
maxLevelReached = randomTree.searchForColoredNodes(INT_MAX, randomTree.rootNode, agentData->getCurrentViewFrustum(),
|
||||||
|
agentData->nodeBag, wantDelta, lastViewFrustum);
|
||||||
|
|
||||||
|
// if nothing was found in view, send the root node.
|
||||||
|
if (agentData->nodeBag.isEmpty()){
|
||||||
|
agentData->nodeBag.insert(randomTree.rootNode);
|
||||||
|
}
|
||||||
|
agentData->setViewSent(false);
|
||||||
|
} else {
|
||||||
|
agentData->nodeBag.insert(randomTree.rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
double end = usecTimestampNow();
|
double end = usecTimestampNow();
|
||||||
|
@ -288,7 +299,8 @@ void deepestLevelVoxelDistributor(AgentList* agentList,
|
||||||
bytesWritten = randomTree.encodeTreeBitstream(INT_MAX, subTree,
|
bytesWritten = randomTree.encodeTreeBitstream(INT_MAX, subTree,
|
||||||
&tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
|
&tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
|
||||||
agentData->nodeBag, &agentData->getCurrentViewFrustum(),
|
agentData->nodeBag, &agentData->getCurrentViewFrustum(),
|
||||||
agentData->getWantColor(), wantDelta, lastViewFrustum);
|
agentData->getWantColor(), WANT_EXISTS_BITS,
|
||||||
|
wantDelta, lastViewFrustum);
|
||||||
|
|
||||||
if (agentData->getAvailable() >= bytesWritten) {
|
if (agentData->getAvailable() >= bytesWritten) {
|
||||||
agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
|
agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
|
||||||
|
@ -392,7 +404,7 @@ void *distributeVoxelsToListeners(void *args) {
|
||||||
if (agentData) {
|
if (agentData) {
|
||||||
bool viewFrustumChanged = agentData->updateCurrentViewFrustum();
|
bool viewFrustumChanged = agentData->updateCurrentViewFrustum();
|
||||||
if (::debugVoxelSending) {
|
if (::debugVoxelSending) {
|
||||||
printf("agentData->updateCurrentViewFrustum() changed=%s\n", (viewFrustumChanged ? "yes" : "no"));
|
printf("agentData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentData->getWantResIn()) {
|
if (agentData->getWantResIn()) {
|
||||||
|
@ -444,22 +456,26 @@ int main(int argc, const char * argv[])
|
||||||
|
|
||||||
const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending";
|
const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending";
|
||||||
::debugVoxelSending = cmdOptionExists(argc, argv, DEBUG_VOXEL_SENDING);
|
::debugVoxelSending = cmdOptionExists(argc, argv, DEBUG_VOXEL_SENDING);
|
||||||
printf("debugVoxelSending=%s\n", (::debugVoxelSending ? "yes" : "no"));
|
printf("debugVoxelSending=%s\n", debug::valueOf(::debugVoxelSending));
|
||||||
|
|
||||||
const char* WANT_ANIMATION_DEBUG = "--shouldShowAnimationDebug";
|
const char* WANT_ANIMATION_DEBUG = "--shouldShowAnimationDebug";
|
||||||
::shouldShowAnimationDebug = cmdOptionExists(argc, argv, WANT_ANIMATION_DEBUG);
|
::shouldShowAnimationDebug = cmdOptionExists(argc, argv, WANT_ANIMATION_DEBUG);
|
||||||
printf("shouldShowAnimationDebug=%s\n", (::shouldShowAnimationDebug ? "yes" : "no"));
|
printf("shouldShowAnimationDebug=%s\n", debug::valueOf(::shouldShowAnimationDebug));
|
||||||
|
|
||||||
const char* WANT_COLOR_RANDOMIZER = "--wantColorRandomizer";
|
const char* WANT_COLOR_RANDOMIZER = "--wantColorRandomizer";
|
||||||
::wantColorRandomizer = cmdOptionExists(argc, argv, WANT_COLOR_RANDOMIZER);
|
::wantColorRandomizer = cmdOptionExists(argc, argv, WANT_COLOR_RANDOMIZER);
|
||||||
printf("wantColorRandomizer=%s\n", (::wantColorRandomizer ? "yes" : "no"));
|
printf("wantColorRandomizer=%s\n", debug::valueOf(::wantColorRandomizer));
|
||||||
|
|
||||||
|
const char* WANT_SEARCH_FOR_NODES = "--wantSearchForColoredNodes";
|
||||||
|
::wantSearchForColoredNodes = cmdOptionExists(argc, argv, WANT_SEARCH_FOR_NODES);
|
||||||
|
printf("wantSearchForColoredNodes=%s\n", debug::valueOf(::wantSearchForColoredNodes));
|
||||||
|
|
||||||
// By default we will voxel persist, if you want to disable this, then pass in this parameter
|
// By default we will voxel persist, if you want to disable this, then pass in this parameter
|
||||||
const char* NO_VOXEL_PERSIST = "--NoVoxelPersist";
|
const char* NO_VOXEL_PERSIST = "--NoVoxelPersist";
|
||||||
if (cmdOptionExists(argc, argv, NO_VOXEL_PERSIST)) {
|
if (cmdOptionExists(argc, argv, NO_VOXEL_PERSIST)) {
|
||||||
::wantVoxelPersist = false;
|
::wantVoxelPersist = false;
|
||||||
}
|
}
|
||||||
printf("wantVoxelPersist=%s\n", (::wantVoxelPersist ? "yes" : "no"));
|
printf("wantVoxelPersist=%s\n", debug::valueOf(::wantVoxelPersist));
|
||||||
|
|
||||||
// if we want Voxel Persistance, load the local file now...
|
// if we want Voxel Persistance, load the local file now...
|
||||||
bool persistantFileRead = false;
|
bool persistantFileRead = false;
|
||||||
|
@ -467,7 +483,7 @@ int main(int argc, const char * argv[])
|
||||||
printf("loading voxels from file...\n");
|
printf("loading voxels from file...\n");
|
||||||
persistantFileRead = ::randomTree.readFromFileV2(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE);
|
persistantFileRead = ::randomTree.readFromFileV2(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE);
|
||||||
::randomTree.clearDirtyBit(); // the tree is clean since we just loaded it
|
::randomTree.clearDirtyBit(); // the tree is clean since we just loaded it
|
||||||
printf("DONE loading voxels from file... fileRead=%s\n", persistantFileRead ? "yes" : "no" );
|
printf("DONE loading voxels from file... fileRead=%s\n", debug::valueOf(persistantFileRead));
|
||||||
unsigned long nodeCount = ::randomTree.getVoxelCount();
|
unsigned long nodeCount = ::randomTree.getVoxelCount();
|
||||||
printf("Nodes after loading scene %ld nodes\n", nodeCount);
|
printf("Nodes after loading scene %ld nodes\n", nodeCount);
|
||||||
}
|
}
|
||||||
|
@ -532,16 +548,12 @@ int main(int argc, const char * argv[])
|
||||||
persistVoxelsWhenDirty();
|
persistVoxelsWhenDirty();
|
||||||
|
|
||||||
if (agentList->getAgentSocket()->receive(&agentPublicAddress, packetData, &receivedBytes)) {
|
if (agentList->getAgentSocket()->receive(&agentPublicAddress, packetData, &receivedBytes)) {
|
||||||
// XXXBHG: Hacked in support for 'S' SET command
|
|
||||||
if (packetData[0] == PACKET_HEADER_SET_VOXEL || packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) {
|
if (packetData[0] == PACKET_HEADER_SET_VOXEL || packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) {
|
||||||
bool destructive = (packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE);
|
bool destructive = (packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE);
|
||||||
|
|
||||||
PerformanceWarning warn(::shouldShowAnimationDebug,
|
PerformanceWarning warn(::shouldShowAnimationDebug,
|
||||||
destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL",
|
destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL",
|
||||||
::shouldShowAnimationDebug);
|
::shouldShowAnimationDebug);
|
||||||
|
|
||||||
unsigned short int itemNumber = (*((unsigned short int*)&packetData[1]));
|
unsigned short int itemNumber = (*((unsigned short int*)&packetData[1]));
|
||||||
|
|
||||||
if (::shouldShowAnimationDebug) {
|
if (::shouldShowAnimationDebug) {
|
||||||
printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n",
|
printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n",
|
||||||
destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL",
|
destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL",
|
||||||
|
@ -592,13 +604,8 @@ int main(int argc, const char * argv[])
|
||||||
if (packetData[0] == PACKET_HEADER_ERASE_VOXEL) {
|
if (packetData[0] == PACKET_HEADER_ERASE_VOXEL) {
|
||||||
|
|
||||||
// Send these bits off to the VoxelTree class to process them
|
// Send these bits off to the VoxelTree class to process them
|
||||||
printf("got Erase Voxels message, have voxel tree do the work... randomTree.processRemoveVoxelBitstream()\n");
|
//printf("got Erase Voxels message, have voxel tree do the work... randomTree.processRemoveVoxelBitstream()\n");
|
||||||
randomTree.processRemoveVoxelBitstream((unsigned char*)packetData,receivedBytes);
|
randomTree.processRemoveVoxelBitstream((unsigned char*)packetData,receivedBytes);
|
||||||
|
|
||||||
// Now send this to the connected agents so they know to delete
|
|
||||||
printf("rebroadcasting delete voxel message to connected agents... agentList.broadcastToAgents()\n");
|
|
||||||
agentList->broadcastToAgents(packetData,receivedBytes, &AGENT_TYPE_AVATAR, 1);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (packetData[0] == PACKET_HEADER_Z_COMMAND) {
|
if (packetData[0] == PACKET_HEADER_Z_COMMAND) {
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue