diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e9fcd62cbe..7ec70a9cca 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1124,7 +1124,6 @@ void Application::setWantsResIn(bool wantsResIn) { _myAvatar.setWantResIn(wantsResIn); } - void Application::setWantsDelta(bool wantsDelta) { _myAvatar.setWantDelta(wantsDelta); } @@ -1280,7 +1279,7 @@ void Application::initMenu() { QMenu* debugMenu = menuBar->addMenu("Debug"); debugMenu->addAction("Show Render Pipeline Warnings", this, SLOT(setRenderWarnings(bool)))->setCheckable(true); 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 Voxel Every Other Randomly", this, SLOT(doFalseRandomizeEveryOtherVoxelColors())); debugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance())); @@ -2045,11 +2044,9 @@ void Application::maybeEditVoxelUnderCursor() { void Application::deleteVoxelUnderCursor() { 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); - // delete the voxel locally so it disappears immediately - _voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); - // remember the position for drag detection _justEditedVoxel = true; } diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index a6f924dd40..85f43692b8 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -106,20 +106,16 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // 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; case PACKET_HEADER_VOXEL_DATA_MONOCHROME: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // 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; - case PACKET_HEADER_ERASE_VOXEL: - // ask the tree to read the "remove" bitstream - _tree->processRemoveVoxelBitstream(sourceBuffer, numBytes); - break; case PACKET_HEADER_Z_COMMAND: // 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()) { static char buffer[64] = { 0 }; if (_renderWarningsOn) { - sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", (_writeRenderFullVBO ? "yes" : "no")); + sprintf(buffer, "newTreeToArrays() _writeRenderFullVBO=%s", debug::valueOf(_writeRenderFullVBO)); }; PerformanceWarning warn(_renderWarningsOn, buffer); _callsToTreesToArrays++; @@ -614,7 +610,7 @@ void VoxelSystem::updatePartialVBOs() { void VoxelSystem::updateVBOs() { static char buffer[40] = { 0 }; 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 if (_voxelsDirty) { @@ -1030,7 +1026,7 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* ex if (args->hasIndexFound[nodeIndex]) { args->duplicateVBOIndex++; 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 { args->hasIndexFound[nodeIndex] = true; } @@ -1059,7 +1055,7 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { args.expectedMax = _voxelsInWriteArrays; _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); printLog("stats: total %ld, leaves %ld, dirty %ld, colored %ld, shouldRender %ld, inVBO %ld\n", diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index 3806a904ab..21c1ec38ee 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -82,7 +82,6 @@ public: 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, creationMode mode, bool destructive = false, bool debug = false); - private: // disallow copying of VoxelSystem objects VoxelSystem(const VoxelSystem&); @@ -156,7 +155,7 @@ private: void copyWrittenDataToReadArrays(bool fullVBOs); bool _voxelsDirty; - + public: void updateVBOs(); void updateFullVBOs(); // all voxels in the VBO diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5aaae9f3ad..820626d369 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -54,7 +54,6 @@ AvatarData::AvatarData() : _wantDelta(false), _headData(NULL) { - } AvatarData::~AvatarData() { @@ -232,10 +231,9 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { // voxel sending features... unsigned char wantItems = 0; wantItems = (unsigned char)*sourceBuffer++; - _wantResIn = oneAtBit(wantItems,WANT_RESIN_AT_BIT); _wantColor = oneAtBit(wantItems,WANT_COLOR_AT_BIT); _wantDelta = oneAtBit(wantItems,WANT_DELTA_AT_BIT); - + return sourceBuffer - startPosition; } \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index d0e71ec71e..2458e1e5a2 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -80,4 +80,9 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex, void** valueArray, float* keyArray, int* originalIndexArray, 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__) */ diff --git a/libraries/voxels/src/ViewFrustum.cpp b/libraries/voxels/src/ViewFrustum.cpp index cce3eac76f..d03678ce42 100644 --- a/libraries/voxels/src/ViewFrustum.cpp +++ b/libraries/voxels/src/ViewFrustum.cpp @@ -243,7 +243,7 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo) const { compareTo._eyeOffsetOrientation == _eyeOffsetOrientation; 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", (compareTo._position == _position ? "MATCHES " : "NO MATCH"), compareTo._position.x, compareTo._position.y, compareTo._position.z, diff --git a/libraries/voxels/src/VoxelNode.cpp b/libraries/voxels/src/VoxelNode.cpp index e79e06e3a3..5125e2d574 100644 --- a/libraries/voxels/src/VoxelNode.cpp +++ b/libraries/voxels/src/VoxelNode.cpp @@ -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... void VoxelNode::setColorFromAverageOfChildren() { int colorArray[4] = {0,0,0,0}; @@ -242,8 +269,20 @@ void VoxelNode::setRandomColor(int minimumBrightness) { } void VoxelNode::printDebugDetails(const char* label) const { - printLog("%s - Voxel at corner=(%f,%f,%f) size=%f octcode=", label, - _box.getCorner().x, _box.getCorner().y, _box.getCorner().z, _box.getSize().x); + unsigned char childBits = 0; + 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); } diff --git a/libraries/voxels/src/VoxelNode.h b/libraries/voxels/src/VoxelNode.h index 5ce12199c8..1b21962261 100644 --- a/libraries/voxels/src/VoxelNode.h +++ b/libraries/voxels/src/VoxelNode.h @@ -43,10 +43,12 @@ public: ~VoxelNode(); 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); VoxelNode* removeChildAtIndex(int childIndex); void addChildAtIndex(int childIndex); + void safeDeepDeleteChildAtIndex(int childIndex, bool& stagedForDeletion); // handles staging or deletion of all descendents + void setColorFromAverageOfChildren(); void setRandomColor(int minimumBrightness); bool collapseIdenticalLeaves(); @@ -69,6 +71,7 @@ public: float distanceToPoint(const glm::vec3& point) const; bool isLeaf() const { return _childCount == 0; } + int getChildCount() const { return _childCount; } void printDebugDetails(const char* label) const; bool isDirty() const { return _isDirty; }; void clearDirtyBit() { _isDirty = false; }; @@ -81,7 +84,7 @@ public: bool getShouldRender() const { return _shouldRender; } // 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; } #ifndef NO_FALSE_COLOR // !NO_FALSE_COLOR means, does have false color diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index cf8615bd3b..31d68ecc45 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -99,10 +99,9 @@ VoxelNode * VoxelTree::nodeForOctalCode(VoxelNode *ancestorNode, unsigned char * // returns the node created! VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char* codeToReach) { - int indexOfNewChild = branchIndexWithDescendant(lastParentNode->getOctalCode(), codeToReach); - - // we could be coming down a branch that was already created, so don't stomp on it. + // If this parent node 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 (lastParentNode->isLeaf() && lastParentNode->isColored()) { // for colored leaves, we must add *all* the children 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()); } } 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); } @@ -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 - int bytesRead = 1; + int bytesRead = 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(*nodeData, i)) { + if (oneAtBit(colorInPacketMask, i)) { // create the child if it doesn't exist if (!destinationNode->getChildAtIndex(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 - 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; - bytesRead++; + bytesRead += includeExistsBits ? sizeof(childrenInTreeMask) + sizeof(childMask) : sizeof(childMask); while (bytesLeftToRead - bytesRead > 0 && childIndex < NUMBER_OF_CHILDREN) { // 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 bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex), - nodeData + bytesRead, - bytesLeftToRead - bytesRead, includeColor); + nodeData + bytesRead, bytesLeftToRead - bytesRead, includeColor, includeExistsBits); } - 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; } -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; unsigned char* bitstreamAt = bitstream; @@ -224,7 +241,7 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int int theseBytesRead = 0; theseBytesRead += octalCodeBytes; theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes, - bufferSizeBytes - (bytesRead + octalCodeBytes), includeColor); + bufferSizeBytes - (bytesRead + octalCodeBytes), includeColor, includeExistsBits); // skip bitstream to new startPoint 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 // 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* nodeToDelete = nodeForOctalCode(rootNode, codeBuffer, &parentNode); - // If the node exists... 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) { int childIndex = branchIndexWithDescendant(parentNode->getOctalCode(), codeBuffer); - if (stage) { nodeToDelete->stageForDeletion(); } else { 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 _isDirty = true; } @@ -330,17 +354,12 @@ void VoxelTree::readCodeColorBufferToTree(unsigned char *codeColorBuffer, bool d void VoxelTree::processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes) { // XXXBHG: validate buffer is at least 4 bytes long? other guards?? unsigned short int itemNumber = (*((unsigned short int*)&bitstream[1])); - printLog("processRemoveVoxelBitstream() receivedBytes=%d itemNumber=%d\n",bufferSizeBytes,itemNumber); int atByte = 3; unsigned char* pVoxelData = (unsigned char*)&bitstream[3]; while (atByte < bufferSizeBytes) { unsigned char octets = (unsigned char)*pVoxelData; 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); pVoxelData+=voxelDataSize; @@ -427,7 +446,6 @@ void VoxelTree::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { bool bail = false; while (!file.eof() && !bail) { file.get(octets); - //printLog("octets=%d...\n",octets); totalBytesRead++; lengthInBytes = bytesRequiredForCodeLength(octets) - 1; 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, - VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, + VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const { // 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 int currentEncodeLevel = 0; - int childBytesWritten = encodeTreeBitstreamRecursion(maxEncodeLevel, currentEncodeLevel, - node, outputBuffer, availableBytes, bag, viewFrustum, includeColor, + int childBytesWritten = encodeTreeBitstreamRecursion(maxEncodeLevel, currentEncodeLevel, node, outputBuffer, availableBytes, + bag, viewFrustum, includeColor, includeExistsBits, deltaViewFrustum, lastViewFrustum); // 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; } -int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, - VoxelNode* node, unsigned char* outputBuffer, int availableBytes, - VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, +int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, VoxelNode* node, + unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, + const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const { + // How many bytes have we written so far at this level; int bytesAtThisLevel = 0; @@ -942,7 +961,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco unsigned char thisLevelBuffer[MAX_LEVEL_BYTES]; unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0]; - unsigned char childrenExistBits = 0; + unsigned char childrenExistInTreeBits = 0; + unsigned char childrenExistInPacketBits = 0; unsigned char childrenColoredBits = 0; int inViewCount = 0; int inViewNotLeafCount = 0; @@ -952,6 +972,12 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco // add them to our distance ordered array of children for (int i = 0; i < NUMBER_OF_CHILDREN; 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))); 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 // subtree to exist if (!(childNode && childNode->isLeaf())) { - childrenExistBits += (1 << (7 - i)); + childrenExistInPacketBits += (1 << (7 - i)); inViewNotLeafCount++; } @@ -984,7 +1010,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco *writeToThisLevelBuffer = childrenColoredBits; writeToThisLevelBuffer += sizeof(childrenColoredBits); // move the pointer bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count - + // write the color data... if (includeColor) { 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 - bytesAtThisLevel += sizeof(childrenExistBits); // keep track of byte count - + // if the caller wants to include childExistsBits, then include them even if not in view, put them before the + // childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits + if (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. 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. // // 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 // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they // write something, we keep them in the bits, if they don't, we take them out. // - // we know the last thing we wrote to the outputBuffer was our childrenExistBits. Let's remember where that was! - unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistBits); + // we know the last thing we wrote to the outputBuffer was our childrenExistInPacketBits. Let's remember where that was! + unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistInPacketBits); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenExistBits, i)) { + if (oneAtBit(childrenExistInPacketBits, i)) { VoxelNode* childNode = node->getChildAtIndex(i); int thisLevel = currentEncodeLevel; int childTreeBytesOut = encodeTreeBitstreamRecursion(maxEncodeLevel, thisLevel, childNode, outputBuffer, availableBytes, bag, - viewFrustum, includeColor, + viewFrustum, includeColor, includeExistsBits, deltaViewFrustum, lastViewFrustum); // 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 if (childTreeBytesOut == 0) { // remove this child's bit... - childrenExistBits -= (1 << (7 - i)); + childrenExistInPacketBits -= (1 << (7 - i)); // repair the child exists mask - *childExistsPlaceHolder = childrenExistBits; + *childExistsPlaceHolder = childrenExistInPacketBits; // Note: no need to move the pointer, cause we already stored this } // end if (childTreeBytesOut == 0) - } // end if (oneAtBit(childrenExistBits, i)) + } // end if (oneAtBit(childrenExistInPacketBits, i)) } // end for } // end keepDiggingDeeper + return bytesAtThisLevel; } @@ -1090,7 +1125,7 @@ bool VoxelTree::readFromFileV2(const char* fileName) { // read the entire file into a buffer, WHAT!? Why not. unsigned char* entireFile = new unsigned char[fileLength]; file.read((char*)entireFile, fileLength); - readBitstreamToTree(entireFile, fileLength, true); + readBitstreamToTree(entireFile, fileLength, WANT_COLOR, NO_EXISTS_BITS); delete[] entireFile; file.close(); @@ -1115,7 +1150,7 @@ void VoxelTree::writeToFileV2(const char* fileName) const { while (!nodeBag.isEmpty()) { VoxelNode* subTree = nodeBag.extract(); 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); } diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 190495c92e..4dde00c246 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -19,6 +19,12 @@ typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData); 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 { public: // when a voxel is created in the tree (object new'd) @@ -40,7 +46,8 @@ public: void eraseAllVoxels(); 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 deleteVoxelCodeFromTree(unsigned char *codeBuffer, bool stage = false); void printTreeForDebugging(VoxelNode *startNode); @@ -57,7 +64,8 @@ public: void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); 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; int searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag, @@ -85,8 +93,8 @@ public: private: int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, - VoxelNode* node, unsigned char* outputBuffer, int availableBytes, - VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, + VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, + const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const; int searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel, @@ -98,7 +106,8 @@ private: void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const; 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; unsigned long int _nodesChangedFromBitstream; diff --git a/tools/samples/cube1.hio b/tools/samples/cube1.hio new file mode 100755 index 0000000000..8c2b2c80b4 Binary files /dev/null and b/tools/samples/cube1.hio differ diff --git a/tools/samples/oneRedVoxel.hio b/tools/samples/oneRedVoxel.hio new file mode 100755 index 0000000000..4168d58ae2 Binary files /dev/null and b/tools/samples/oneRedVoxel.hio differ diff --git a/tools/samples/simple1.hio b/tools/samples/simple1.hio new file mode 100644 index 0000000000..30164b7cc7 Binary files /dev/null and b/tools/samples/simple1.hio differ diff --git a/tools/samples/single.hio b/tools/samples/single.hio new file mode 100755 index 0000000000..f2b541326a Binary files /dev/null and b/tools/samples/single.hio differ diff --git a/tools/samples/small.hio b/tools/samples/small.hio new file mode 100644 index 0000000000..c49f667491 Binary files /dev/null and b/tools/samples/small.hio differ diff --git a/tools/sendvoxels.php b/tools/sendvoxels.php index 438c9a1b8a..ed3b7b6095 100644 --- a/tools/sendvoxels.php +++ b/tools/sendvoxels.php @@ -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 packet server=$serverIP port=$serverSendPort $voxNum size=$packetSize result=$result\n"; $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 $voxNum++; } diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index c268031e74..c258ad052c 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -57,8 +57,7 @@ bool wantLocalDomain = false; bool wantColorRandomizer = false; bool debugVoxelSending = false; bool shouldShowAnimationDebug = false; - - +bool wantSearchForColoredNodes = false; EnvironmentData environmentData; @@ -168,7 +167,7 @@ void resInVoxelDistributor(AgentList* agentList, bytesWritten = randomTree.encodeTreeBitstream(agentData->getMaxSearchLevel(), subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, agentData->nodeBag, &viewFrustum, - agentData->getWantColor()); + agentData->getWantColor(), WANT_EXISTS_BITS); if (agentData->getAvailable() >= bytesWritten) { agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten); @@ -241,20 +240,32 @@ void deepestLevelVoxelDistributor(AgentList* agentList, if (::debugVoxelSending) { printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n", - viewFrustumChanged ? "yes" : "no", - agentData->nodeBag.isEmpty() ? "yes" : "no", - agentData->getViewSent() ? "yes" : "no" + debug::valueOf(viewFrustumChanged), debug::valueOf(agentData->nodeBag.isEmpty()), + debug::valueOf(agentData->getViewSent()) ); } // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. 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(); @@ -288,7 +299,8 @@ void deepestLevelVoxelDistributor(AgentList* agentList, bytesWritten = randomTree.encodeTreeBitstream(INT_MAX, subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, agentData->nodeBag, &agentData->getCurrentViewFrustum(), - agentData->getWantColor(), wantDelta, lastViewFrustum); + agentData->getWantColor(), WANT_EXISTS_BITS, + wantDelta, lastViewFrustum); if (agentData->getAvailable() >= bytesWritten) { agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten); @@ -392,7 +404,7 @@ void *distributeVoxelsToListeners(void *args) { if (agentData) { bool viewFrustumChanged = agentData->updateCurrentViewFrustum(); if (::debugVoxelSending) { - printf("agentData->updateCurrentViewFrustum() changed=%s\n", (viewFrustumChanged ? "yes" : "no")); + printf("agentData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged)); } if (agentData->getWantResIn()) { @@ -444,22 +456,26 @@ int main(int argc, const char * argv[]) const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending"; ::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"; ::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"; ::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 const char* NO_VOXEL_PERSIST = "--NoVoxelPersist"; if (cmdOptionExists(argc, argv, NO_VOXEL_PERSIST)) { ::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... bool persistantFileRead = false; @@ -467,7 +483,7 @@ int main(int argc, const char * argv[]) printf("loading voxels from file...\n"); persistantFileRead = ::randomTree.readFromFileV2(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); ::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(); printf("Nodes after loading scene %ld nodes\n", nodeCount); } @@ -532,16 +548,12 @@ int main(int argc, const char * argv[]) persistVoxelsWhenDirty(); 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) { bool destructive = (packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE); - PerformanceWarning warn(::shouldShowAnimationDebug, destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL", ::shouldShowAnimationDebug); - unsigned short int itemNumber = (*((unsigned short int*)&packetData[1])); - if (::shouldShowAnimationDebug) { printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n", 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) { // 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); - - // 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) {